aboutsummaryrefslogtreecommitdiff
path: root/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'src/components')
-rw-r--r--src/components/account_actions/account_actions.vue21
-rw-r--r--src/components/alert.style.js51
-rw-r--r--src/components/announcement/announcement.vue8
-rw-r--r--src/components/announcement_editor/announcement_editor.vue4
-rw-r--r--src/components/announcements_page/announcements_page.vue6
-rw-r--r--src/components/attachment/attachment.scss31
-rw-r--r--src/components/attachment/attachment.style.js24
-rw-r--r--src/components/attachment/attachment.vue5
-rw-r--r--src/components/autosuggest/autosuggest.vue18
-rw-r--r--src/components/avatar_list/avatar_list.vue5
-rw-r--r--src/components/badge.style.js30
-rw-r--r--src/components/basic_user_card/basic_user_card.vue1
-rw-r--r--src/components/border.style.js13
-rw-r--r--src/components/button.style.js101
-rw-r--r--src/components/button_unstyled.style.js96
-rw-r--r--src/components/chat/chat.scss16
-rw-r--r--src/components/chat/chat.style.js19
-rw-r--r--src/components/chat/chat.vue5
-rw-r--r--src/components/chat_list/chat_list.vue5
-rw-r--r--src/components/chat_list_item/chat_list_item.scss21
-rw-r--r--src/components/chat_list_item/chat_list_item.vue3
-rw-r--r--src/components/chat_message/chat_message.scss46
-rw-r--r--src/components/chat_message/chat_message.style.js30
-rw-r--r--src/components/chat_message/chat_message.vue2
-rw-r--r--src/components/chat_new/chat_new.scss5
-rw-r--r--src/components/chat_new/chat_new.vue43
-rw-r--r--src/components/chat_title/chat_title.vue5
-rw-r--r--src/components/checkbox/checkbox.vue57
-rw-r--r--src/components/color_input/color_input.scss36
-rw-r--r--src/components/color_input/color_input.vue50
-rw-r--r--src/components/conversation/conversation.js7
-rw-r--r--src/components/conversation/conversation.vue91
-rw-r--r--src/components/desktop_nav/desktop_nav.js5
-rw-r--r--src/components/desktop_nav/desktop_nav.scss32
-rw-r--r--src/components/desktop_nav/desktop_nav.vue9
-rw-r--r--src/components/dialog_modal/dialog_modal.vue13
-rw-r--r--src/components/emoji_input/emoji_input.js3
-rw-r--r--src/components/emoji_input/emoji_input.vue57
-rw-r--r--src/components/emoji_picker/emoji_picker.js7
-rw-r--r--src/components/emoji_picker/emoji_picker.scss28
-rw-r--r--src/components/emoji_picker/emoji_picker.vue34
-rw-r--r--src/components/emoji_reactions/emoji_reactions.js33
-rw-r--r--src/components/emoji_reactions/emoji_reactions.vue126
-rw-r--r--src/components/extra_buttons/extra_buttons.js13
-rw-r--r--src/components/extra_buttons/extra_buttons.vue47
-rw-r--r--src/components/extra_notifications/extra_notifications.js48
-rw-r--r--src/components/extra_notifications/extra_notifications.vue110
-rw-r--r--src/components/favorite_button/favorite_button.vue4
-rw-r--r--src/components/flash/flash.vue2
-rw-r--r--src/components/font_control/font_control.vue6
-rw-r--r--src/components/fun_text.style.js40
-rw-r--r--src/components/gallery/gallery.vue2
-rw-r--r--src/components/global_notice_list/global_notice_list.vue46
-rw-r--r--src/components/icon.style.js14
-rw-r--r--src/components/image_cropper/image_cropper.vue2
-rw-r--r--src/components/importer/importer.vue1
-rw-r--r--src/components/input.style.js60
-rw-r--r--src/components/interactions/interactions.js1
-rw-r--r--src/components/interactions/interactions.vue7
-rw-r--r--src/components/interface_language_switcher/interface_language_switcher.vue6
-rw-r--r--src/components/link-preview/link-preview.vue14
-rw-r--r--src/components/link.style.js24
-rw-r--r--src/components/list/list.vue32
-rw-r--r--src/components/list/list_item.style.js48
-rw-r--r--src/components/lists_card/lists_card.vue16
-rw-r--r--src/components/lists_edit/lists_edit.vue3
-rw-r--r--src/components/lists_user_search/lists_user_search.vue3
-rw-r--r--src/components/login_form/login_form.vue6
-rw-r--r--src/components/media_upload/media_upload.js18
-rw-r--r--src/components/media_upload/media_upload.vue23
-rw-r--r--src/components/mention_link/mention_link.scss13
-rw-r--r--src/components/mention_link/mention_link.vue2
-rw-r--r--src/components/mentions_line/mentions_line.vue4
-rw-r--r--src/components/menu_item.style.js90
-rw-r--r--src/components/mfa_form/recovery_form.vue2
-rw-r--r--src/components/mfa_form/totp_form.vue2
-rw-r--r--src/components/mobile_drawer.style.js41
-rw-r--r--src/components/mobile_nav/mobile_nav.js23
-rw-r--r--src/components/mobile_nav/mobile_nav.vue58
-rw-r--r--src/components/mobile_post_status_button/mobile_post_status_button.vue7
-rw-r--r--src/components/modal/modals.style.js9
-rw-r--r--src/components/moderation_tools/moderation_tools.vue38
-rw-r--r--src/components/mrf_transparency_panel/mrf_transparency_panel.vue1
-rw-r--r--src/components/nav_panel/nav_panel.vue50
-rw-r--r--src/components/navigation/navigation_entry.vue79
-rw-r--r--src/components/navigation/navigation_pins.js3
-rw-r--r--src/components/navigation/navigation_pins.vue27
-rw-r--r--src/components/notification/notification.js6
-rw-r--r--src/components/notification/notification.scss26
-rw-r--r--src/components/notification/notification.style.js17
-rw-r--r--src/components/notification/notification.vue13
-rw-r--r--src/components/notifications/notification_filters.vue37
-rw-r--r--src/components/notifications/notifications.js60
-rw-r--r--src/components/notifications/notifications.scss29
-rw-r--r--src/components/notifications/notifications.vue23
-rw-r--r--src/components/opacity_input/opacity_input.vue2
-rw-r--r--src/components/panel.style.js41
-rw-r--r--src/components/panel_header.style.js24
-rw-r--r--src/components/panel_loading/panel_loading.vue8
-rw-r--r--src/components/password_reset/password_reset.vue11
-rw-r--r--src/components/poll/poll.js5
-rw-r--r--src/components/poll/poll.vue30
-rw-r--r--src/components/poll/poll_form.vue6
-rw-r--r--src/components/poll/poll_graph.style.js12
-rw-r--r--src/components/popover.style.js36
-rw-r--r--src/components/popover/popover.js3
-rw-r--r--src/components/popover/popover.vue127
-rw-r--r--src/components/post_status_form/post_status_form.js44
-rw-r--r--src/components/post_status_form/post_status_form.vue101
-rw-r--r--src/components/post_status_modal/post_status_modal.js4
-rw-r--r--src/components/post_status_modal/post_status_modal.vue2
-rw-r--r--src/components/quick_filter_settings/quick_filter_settings.js7
-rw-r--r--src/components/quick_filter_settings/quick_filter_settings.vue38
-rw-r--r--src/components/quick_view_settings/quick_view_settings.js8
-rw-r--r--src/components/quick_view_settings/quick_view_settings.vue22
-rw-r--r--src/components/quotes_timeline/quotes_timeline.js26
-rw-r--r--src/components/quotes_timeline/quotes_timeline.vue10
-rw-r--r--src/components/range_input/range_input.vue6
-rw-r--r--src/components/react_button/react_button.js2
-rw-r--r--src/components/react_button/react_button.vue10
-rw-r--r--src/components/registration/registration.js10
-rw-r--r--src/components/registration/registration.vue40
-rw-r--r--src/components/reply_button/reply_button.vue4
-rw-r--r--src/components/report/report.js4
-rw-r--r--src/components/report/report.scss10
-rw-r--r--src/components/report/report.vue2
-rw-r--r--src/components/retweet_button/retweet_button.vue4
-rw-r--r--src/components/rich_content/rich_content.jsx66
-rw-r--r--src/components/rich_content/rich_content.scss30
-rw-r--r--src/components/rich_content/rich_content.style.js18
-rw-r--r--src/components/root.style.js44
-rw-r--r--src/components/scope_selector/scope_selector.js8
-rw-r--r--src/components/scope_selector/scope_selector.vue7
-rw-r--r--src/components/screen_reader_notice/screen_reader_notice.js2
-rw-r--r--src/components/scrollbar.style.js11
-rw-r--r--src/components/scrollbar_element.style.js101
-rw-r--r--src/components/search/search.vue34
-rw-r--r--src/components/search_bar/search_bar.vue7
-rw-r--r--src/components/select/select.vue11
-rw-r--r--src/components/selectable_list/selectable_list.vue29
-rw-r--r--src/components/settings_modal/admin_tabs/emoji_tab.js257
-rw-r--r--src/components/settings_modal/admin_tabs/emoji_tab.scss59
-rw-r--r--src/components/settings_modal/admin_tabs/emoji_tab.vue358
-rw-r--r--src/components/settings_modal/admin_tabs/frontends_tab.js113
-rw-r--r--src/components/settings_modal/admin_tabs/frontends_tab.scss29
-rw-r--r--src/components/settings_modal/admin_tabs/frontends_tab.vue209
-rw-r--r--src/components/settings_modal/admin_tabs/instance_tab.js38
-rw-r--r--src/components/settings_modal/admin_tabs/instance_tab.vue206
-rw-r--r--src/components/settings_modal/admin_tabs/limits_tab.js29
-rw-r--r--src/components/settings_modal/admin_tabs/limits_tab.vue136
-rw-r--r--src/components/settings_modal/helpers/attachment_setting.js44
-rw-r--r--src/components/settings_modal/helpers/attachment_setting.vue126
-rw-r--r--src/components/settings_modal/helpers/boolean_setting.js63
-rw-r--r--src/components/settings_modal/helpers/boolean_setting.vue34
-rw-r--r--src/components/settings_modal/helpers/choice_setting.js68
-rw-r--r--src/components/settings_modal/helpers/choice_setting.vue20
-rw-r--r--src/components/settings_modal/helpers/draft_buttons.vue88
-rw-r--r--src/components/settings_modal/helpers/emoji_editing_popover.vue227
-rw-r--r--src/components/settings_modal/helpers/group_setting.js13
-rw-r--r--src/components/settings_modal/helpers/group_setting.vue15
-rw-r--r--src/components/settings_modal/helpers/modified_indicator.vue10
-rw-r--r--src/components/settings_modal/helpers/number_setting.js62
-rw-r--r--src/components/settings_modal/helpers/number_setting.vue28
-rw-r--r--src/components/settings_modal/helpers/profile_setting_indicator.vue (renamed from src/components/settings_modal/helpers/server_side_indicator.vue)12
-rw-r--r--src/components/settings_modal/helpers/setting.js238
-rw-r--r--src/components/settings_modal/helpers/shared_computed_object.js56
-rw-r--r--src/components/settings_modal/helpers/size_setting.js67
-rw-r--r--src/components/settings_modal/helpers/string_setting.js5
-rw-r--r--src/components/settings_modal/helpers/string_setting.vue42
-rw-r--r--src/components/settings_modal/helpers/unit_setting.js48
-rw-r--r--src/components/settings_modal/helpers/unit_setting.vue (renamed from src/components/settings_modal/helpers/size_setting.vue)28
-rw-r--r--src/components/settings_modal/settings_modal.js37
-rw-r--r--src/components/settings_modal/settings_modal.scss26
-rw-r--r--src/components/settings_modal/settings_modal.vue48
-rw-r--r--src/components/settings_modal/settings_modal_admin_content.js95
-rw-r--r--src/components/settings_modal/settings_modal_admin_content.scss (renamed from src/components/settings_modal/settings_modal_content.scss)15
-rw-r--r--src/components/settings_modal/settings_modal_admin_content.vue76
-rw-r--r--src/components/settings_modal/settings_modal_user_content.js (renamed from src/components/settings_modal/settings_modal_content.js)0
-rw-r--r--src/components/settings_modal/settings_modal_user_content.scss45
-rw-r--r--src/components/settings_modal/settings_modal_user_content.vue (renamed from src/components/settings_modal/settings_modal_content.vue)4
-rw-r--r--src/components/settings_modal/tabs/filtering_tab.js2
-rw-r--r--src/components/settings_modal/tabs/filtering_tab.vue57
-rw-r--r--src/components/settings_modal/tabs/general_tab.js10
-rw-r--r--src/components/settings_modal/tabs/general_tab.vue44
-rw-r--r--src/components/settings_modal/tabs/mutes_and_blocks_tab.js7
-rw-r--r--src/components/settings_modal/tabs/notifications_tab.js4
-rw-r--r--src/components/settings_modal/tabs/notifications_tab.vue250
-rw-r--r--src/components/settings_modal/tabs/profile_tab.js13
-rw-r--r--src/components/settings_modal/tabs/profile_tab.scss8
-rw-r--r--src/components/settings_modal/tabs/profile_tab.vue80
-rw-r--r--src/components/settings_modal/tabs/security_tab/mfa.vue7
-rw-r--r--src/components/settings_modal/tabs/security_tab/mfa_backup_codes.vue7
-rw-r--r--src/components/settings_modal/tabs/security_tab/mfa_totp.vue1
-rw-r--r--src/components/settings_modal/tabs/security_tab/security_tab.vue31
-rw-r--r--src/components/settings_modal/tabs/theme_tab/preview.vue6
-rw-r--r--src/components/settings_modal/tabs/theme_tab/theme_tab.js23
-rw-r--r--src/components/settings_modal/tabs/theme_tab/theme_tab.scss12
-rw-r--r--src/components/shadow_control/shadow_control.js2
-rw-r--r--src/components/shadow_control/shadow_control.vue34
-rw-r--r--src/components/shout_panel/shout_panel.vue16
-rw-r--r--src/components/side_drawer/side_drawer.js5
-rw-r--r--src/components/side_drawer/side_drawer.vue102
-rw-r--r--src/components/status/post.style.js33
-rw-r--r--src/components/status/status.js86
-rw-r--r--src/components/status/status.scss71
-rw-r--r--src/components/status/status.vue114
-rw-r--r--src/components/status_body/status_body.scss14
-rw-r--r--src/components/status_body/status_body.vue2
-rw-r--r--src/components/status_content/status_content.js4
-rw-r--r--src/components/status_content/status_content.vue2
-rw-r--r--src/components/status_popover/status_popover.vue7
-rw-r--r--src/components/sticker_picker/sticker_picker.vue4
-rw-r--r--src/components/still-image/still-image.vue5
-rw-r--r--src/components/tab_switcher/tab.style.js78
-rw-r--r--src/components/tab_switcher/tab_switcher.jsx10
-rw-r--r--src/components/tab_switcher/tab_switcher.scss30
-rw-r--r--src/components/text.style.js22
-rw-r--r--src/components/thread_tree/thread_tree.vue8
-rw-r--r--src/components/timeline/timeline.js14
-rw-r--r--src/components/timeline/timeline.scss26
-rw-r--r--src/components/timeline/timeline.vue2
-rw-r--r--src/components/timeline_menu/timeline_menu.js3
-rw-r--r--src/components/timeline_menu/timeline_menu.vue63
-rw-r--r--src/components/top_bar.style.js28
-rw-r--r--src/components/underlay.style.js19
-rw-r--r--src/components/update_notification/update_notification.scss4
-rw-r--r--src/components/user_avatar/avatar.style.js22
-rw-r--r--src/components/user_avatar/user_avatar.js8
-rw-r--r--src/components/user_avatar/user_avatar.vue33
-rw-r--r--src/components/user_card/user_card.js2
-rw-r--r--src/components/user_card/user_card.scss72
-rw-r--r--src/components/user_card/user_card.style.js41
-rw-r--r--src/components/user_card/user_card.vue23
-rw-r--r--src/components/user_list_menu/user_list_menu.vue6
-rw-r--r--src/components/user_list_popover/user_list_popover.vue2
-rw-r--r--src/components/user_note/user_note.vue6
-rw-r--r--src/components/user_panel/user_panel.vue13
-rw-r--r--src/components/user_popover/user_popover.vue2
-rw-r--r--src/components/user_profile/user_profile.js5
-rw-r--r--src/components/user_profile/user_profile.vue115
-rw-r--r--src/components/user_reporting_modal/user_reporting_modal.vue10
-rw-r--r--src/components/video_attachment/video_attachment.vue2
242 files changed, 6280 insertions, 2011 deletions
diff --git a/src/components/account_actions/account_actions.vue b/src/components/account_actions/account_actions.vue
index ce19291a..9a7c6824 100644
--- a/src/components/account_actions/account_actions.vue
+++ b/src/components/account_actions/account_actions.vue
@@ -11,14 +11,14 @@
<template v-if="relationship.following">
<button
v-if="relationship.showing_reblogs"
- class="btn button-default dropdown-item"
+ class="dropdown-item menu-item"
@click="hideRepeats"
>
{{ $t('user_card.hide_repeats') }}
</button>
<button
v-if="!relationship.showing_reblogs"
- class="btn button-default dropdown-item"
+ class="dropdown-item menu-item"
@click="showRepeats"
>
{{ $t('user_card.show_repeats') }}
@@ -31,34 +31,34 @@
<UserListMenu :user="user" />
<button
v-if="relationship.followed_by"
- class="btn button-default btn-block dropdown-item"
+ class="dropdown-item menu-item"
@click="removeUserFromFollowers"
>
{{ $t('user_card.remove_follower') }}
</button>
<button
v-if="relationship.blocking"
- class="btn button-default btn-block dropdown-item"
+ class="dropdown-item menu-item"
@click="unblockUser"
>
{{ $t('user_card.unblock') }}
</button>
<button
v-else
- class="btn button-default btn-block dropdown-item"
+ class="dropdown-item menu-item"
@click="blockUser"
>
{{ $t('user_card.block') }}
</button>
<button
- class="btn button-default btn-block dropdown-item"
+ class="dropdown-item menu-item"
@click="reportUser"
>
{{ $t('user_card.report') }}
</button>
<button
v-if="pleromaChatMessagesAvailable"
- class="btn button-default btn-block dropdown-item"
+ class="dropdown-item menu-item"
@click="openChat"
>
{{ $t('user_card.message') }}
@@ -122,19 +122,12 @@
<script src="./account_actions.js"></script>
<style lang="scss">
-@import "../../variables";
-
.AccountActions {
.ellipsis-button {
width: 2.5em;
margin: -0.5em 0;
padding: 0.5em 0;
text-align: center;
-
- &:not(:hover) .icon {
- color: $fallback--lightText;
- color: var(--lightText, $fallback--lightText);
- }
}
}
</style>
diff --git a/src/components/alert.style.js b/src/components/alert.style.js
new file mode 100644
index 00000000..19bd4bbb
--- /dev/null
+++ b/src/components/alert.style.js
@@ -0,0 +1,51 @@
+export default {
+ name: 'Alert',
+ selector: '.alert',
+ validInnerComponents: [
+ 'Text',
+ 'Icon',
+ 'Link',
+ 'Border',
+ 'ButtonUnstyled'
+ ],
+ variants: {
+ normal: '.neutral',
+ error: '.error',
+ warning: '.warning',
+ success: '.success'
+ },
+ defaultRules: [
+ {
+ directives: {
+ background: '--text',
+ opacity: 0.5,
+ blur: '9px'
+ }
+ },
+ {
+ parent: {
+ component: 'Alert'
+ },
+ component: 'Border',
+ textColor: '--parent'
+ },
+ {
+ variant: 'error',
+ directives: {
+ background: '--cRed'
+ }
+ },
+ {
+ variant: 'warning',
+ directives: {
+ background: '--cOrange'
+ }
+ },
+ {
+ variant: 'success',
+ directives: {
+ background: '--cGreen'
+ }
+ }
+ ]
+}
diff --git a/src/components/announcement/announcement.vue b/src/components/announcement/announcement.vue
index a1c5791e..92880831 100644
--- a/src/components/announcement/announcement.vue
+++ b/src/components/announcement/announcement.vue
@@ -99,16 +99,14 @@
<script src="./announcement.js"></script>
<style lang="scss">
-@import "../../variables";
-
.announcement {
- border-bottom: 1px solid var(--border, $fallback--border);
+ border-bottom: 1px solid var(--border);
border-radius: 0;
- padding: var(--status-margin, $status-margin);
+ padding: var(--status-margin);
.heading,
.body {
- margin-bottom: var(--status-margin, $status-margin);
+ margin-bottom: var(--status-margin);
}
.footer {
diff --git a/src/components/announcement_editor/announcement_editor.vue b/src/components/announcement_editor/announcement_editor.vue
index 0f29f9f7..c0a3c30a 100644
--- a/src/components/announcement_editor/announcement_editor.vue
+++ b/src/components/announcement_editor/announcement_editor.vue
@@ -3,7 +3,7 @@
<textarea
ref="textarea"
v-model="announcement.content"
- class="post-textarea"
+ class="input post-textarea"
rows="1"
cols="1"
:placeholder="$t('announcements.post_placeholder')"
@@ -14,6 +14,7 @@
<input
id="announcement-start-time"
v-model="announcement.startsAt"
+ class="input"
:type="announcement.allDay ? 'date' : 'datetime-local'"
:disabled="disabled"
>
@@ -23,6 +24,7 @@
<input
id="announcement-end-time"
v-model="announcement.endsAt"
+ class="input"
:type="announcement.allDay ? 'date' : 'datetime-local'"
:disabled="disabled"
>
diff --git a/src/components/announcements_page/announcements_page.vue b/src/components/announcements_page/announcements_page.vue
index 78d3ecee..25f830ff 100644
--- a/src/components/announcements_page/announcements_page.vue
+++ b/src/components/announcements_page/announcements_page.vue
@@ -61,15 +61,13 @@
<script src="./announcements_page.js"></script>
<style lang="scss">
-@import "../../variables";
-
.announcements-page {
.post-form {
- padding: var(--status-margin, $status-margin);
+ padding: var(--status-margin);
.heading,
.body {
- margin-bottom: var(--status-margin, $status-margin);
+ margin-bottom: var(--status-margin);
}
.post-button {
diff --git a/src/components/attachment/attachment.scss b/src/components/attachment/attachment.scss
index 681bab29..13afbe64 100644
--- a/src/components/attachment/attachment.scss
+++ b/src/components/attachment/attachment.scss
@@ -1,5 +1,3 @@
-@import "../../variables";
-
.Attachment {
display: inline-flex;
flex-direction: column;
@@ -9,10 +7,8 @@
height: 100%;
border-style: solid;
border-width: 1px;
- border-radius: $fallback--attachmentRadius;
- border-radius: var(--attachmentRadius, $fallback--attachmentRadius);
- border-color: $fallback--border;
- border-color: var(--border, $fallback--border);
+ border-radius: var(--roundness);
+ border-color: var(--border);
.attachment-wrapper {
flex: 1 1 auto;
@@ -84,6 +80,13 @@
}
}
+ .video-container {
+ border: none;
+ outline: none;
+ color: inherit;
+ background: transparent;
+ }
+
.audio-container {
display: flex;
align-items: flex-end;
@@ -126,23 +129,12 @@
.attachment-button {
padding: 0;
- border-radius: $fallback--tooltipRadius;
- border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
+ border-radius: var(--roundness);
text-align: center;
width: 2em;
height: 2em;
margin-left: 0.5em;
font-size: 1.25em;
- // TODO: theming? hard to theme with unknown background image color
- background: rgb(230 230 230 / 70%);
-
- .svg-inline--fa {
- color: rgb(0 0 0 / 60%);
- }
-
- &:hover .svg-inline--fa {
- color: rgb(0 0 0 / 90%);
- }
}
}
@@ -217,8 +209,7 @@
&.-placeholder {
display: inline-block;
- color: $fallback--link;
- color: var(--postLink, $fallback--link);
+ color: var(--link);
overflow: hidden;
white-space: nowrap;
height: auto;
diff --git a/src/components/attachment/attachment.style.js b/src/components/attachment/attachment.style.js
new file mode 100644
index 00000000..5fb4701c
--- /dev/null
+++ b/src/components/attachment/attachment.style.js
@@ -0,0 +1,24 @@
+export default {
+ name: 'Attachment',
+ selector: '.Attachment',
+ validInnerComponents: [
+ 'Border',
+ 'ButtonUnstyled',
+ 'Input'
+ ],
+ defaultRules: [
+ {
+ directives: {
+ roundness: 3
+ }
+ },
+ {
+ component: 'ButtonUnstyled',
+ parent: { component: 'Attachment' },
+ directives: {
+ background: '#FFFFFF',
+ opacity: 0.5
+ }
+ }
+ ]
+}
diff --git a/src/components/attachment/attachment.vue b/src/components/attachment/attachment.vue
index 79f62806..9abc2627 100644
--- a/src/components/attachment/attachment.vue
+++ b/src/components/attachment/attachment.vue
@@ -38,7 +38,7 @@
v-if="edit"
v-model="localDescription"
type="text"
- class="description-field"
+ class="input description-field"
:placeholder="$t('post_status.media_description')"
@keydown.enter.prevent=""
>
@@ -175,7 +175,6 @@
:is="videoTag"
v-if="type === 'video' && !hidden"
class="video-container"
- :class="{ 'button-unstyled': 'isModal' }"
:href="attachment.url"
@click.stop.prevent="openModal"
>
@@ -253,7 +252,7 @@
v-if="edit"
v-model="localDescription"
type="text"
- class="description-field"
+ class="input description-field"
:placeholder="$t('post_status.media_description')"
@keydown.enter.prevent=""
>
diff --git a/src/components/autosuggest/autosuggest.vue b/src/components/autosuggest/autosuggest.vue
index 7b7102fd..04f41801 100644
--- a/src/components/autosuggest/autosuggest.vue
+++ b/src/components/autosuggest/autosuggest.vue
@@ -1,3 +1,4 @@
+<!-- FIXME THIS NEEDS TO BE REFACTORED TO USE POPOVER -->
<template>
<div
v-click-outside="onClickOutside"
@@ -6,12 +7,12 @@
<input
v-model="term"
:placeholder="placeholder"
- class="autosuggest-input"
+ class="input autosuggest-input"
@click="onInputClick"
>
<div
v-if="resultsVisible && filtered.length > 0"
- class="autosuggest-results"
+ class="panel autosuggest-results"
>
<slot
v-for="item in filtered"
@@ -24,8 +25,6 @@
<script src="./autosuggest.js"></script>
<style lang="scss">
-@import "../../variables";
-
.autosuggest {
position: relative;
@@ -40,18 +39,15 @@
top: 100%;
right: 0;
max-height: 400px;
- background-color: $fallback--bg;
- background-color: var(--bg, $fallback--bg);
+ background-color: var(--bg);
border-style: solid;
border-width: 1px;
- border-color: $fallback--border;
- border-color: var(--border, $fallback--border);
- border-radius: $fallback--inputRadius;
- border-radius: var(--inputRadius, $fallback--inputRadius);
+ border-color: var(--border);
+ border-radius: var(--roundness);
border-top-left-radius: 0;
border-top-right-radius: 0;
box-shadow: 1px 1px 4px rgb(0 0 0 / 60%);
- box-shadow: var(--panelShadow);
+ box-shadow: var(--shadow);
overflow-y: auto;
z-index: 1;
}
diff --git a/src/components/avatar_list/avatar_list.vue b/src/components/avatar_list/avatar_list.vue
index 2d00cb7b..9252ba32 100644
--- a/src/components/avatar_list/avatar_list.vue
+++ b/src/components/avatar_list/avatar_list.vue
@@ -17,8 +17,6 @@
<script src="./avatar_list.js"></script>
<style lang="scss">
-@import "../../variables";
-
.avatars {
display: flex;
margin: 0;
@@ -36,8 +34,7 @@
}
.avatar-small {
- border-radius: $fallback--avatarAltRadius;
- border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius);
+ border-radius: var(--roundness);
height: 24px;
width: 24px;
}
diff --git a/src/components/badge.style.js b/src/components/badge.style.js
new file mode 100644
index 00000000..0697cac6
--- /dev/null
+++ b/src/components/badge.style.js
@@ -0,0 +1,30 @@
+export default {
+ name: 'Badge',
+ selector: '.badge',
+ validInnerComponents: [
+ 'Text',
+ 'Icon'
+ ],
+ variants: {
+ notification: '.-notification'
+ },
+ defaultRules: [
+ {
+ component: 'Root',
+ directives: {
+ '--badgeNotification': 'color | --cRed'
+ }
+ },
+ {
+ directives: {
+ background: '--cGreen'
+ }
+ },
+ {
+ variant: 'notification',
+ directives: {
+ background: '--cRed'
+ }
+ }
+ ]
+}
diff --git a/src/components/basic_user_card/basic_user_card.vue b/src/components/basic_user_card/basic_user_card.vue
index 705e20f5..9e2b0295 100644
--- a/src/components/basic_user_card/basic_user_card.vue
+++ b/src/components/basic_user_card/basic_user_card.vue
@@ -47,7 +47,6 @@
display: flex;
flex: 1 0;
margin: 0;
- padding: 0.6em 1em;
--emoji-size: 14px;
diff --git a/src/components/border.style.js b/src/components/border.style.js
new file mode 100644
index 00000000..a87ee9c8
--- /dev/null
+++ b/src/components/border.style.js
@@ -0,0 +1,13 @@
+export default {
+ name: 'Border',
+ selector: '/*border*/',
+ virtual: true,
+ defaultRules: [
+ {
+ directives: {
+ textColor: '$mod(--parent, 10)',
+ textAuto: 'no-auto'
+ }
+ }
+ ]
+}
diff --git a/src/components/button.style.js b/src/components/button.style.js
new file mode 100644
index 00000000..6fec67a0
--- /dev/null
+++ b/src/components/button.style.js
@@ -0,0 +1,101 @@
+export default {
+ name: 'Button', // Name of the component
+ selector: '.button-default', // CSS selector/prefix
+ // outOfTreeSelector: '' // out-of-tree selector is used when other components are laid over it but it's not part of the tree, see Underlay component
+ // States, system witll calculate ALL possible combinations of those and prepend "normal" to them + standalone "normal" state
+ states: {
+ // States are a bit expensive - the amount of combinations generated is about (1/6)n^3+n, so adding more state increased number of combination by an order of magnitude!
+ // All states inherit from "normal" state, there is no other inheirtance, i.e. hover+disabled only inherits from "normal", not from hover nor disabled.
+ // However, cascading still works, so resulting state will be result of merging of all relevant states/variants
+ // normal: '' // normal state is implicitly added, it is always included
+ toggled: '.toggled',
+ pressed: ':active',
+ hover: ':hover:not(:disabled)',
+ focused: ':focus-within',
+ disabled: ':disabled'
+ },
+ // Variants are mutually exclusive, each component implicitly has "normal" variant, and all other variants inherit from it.
+ variants: {
+ // Variants save on computation time since adding new variant just adds one more "set".
+ // normal: '', // you can override normal variant, it will be appenended to the main class
+ danger: '.danger'
+ // Overall the compuation difficulty is N*((1/6)M^3+M) where M is number of distinct states and N is number of variants.
+ // This (currently) is further multipled by number of places where component can exist.
+ },
+ // This lists all other components that can possibly exist within one. Recursion is currently not supported (and probably won't be supported ever).
+ validInnerComponents: [
+ 'Text',
+ 'Icon'
+ ],
+ // Default rules, used as "default theme", essentially.
+ defaultRules: [
+ {
+ component: 'Root',
+ directives: {
+ '--defaultButtonHoverGlow': 'shadow | 0 0 4 --text',
+ '--defaultButtonShadow': 'shadow | 0 0 2 #000000',
+ '--defaultButtonBevel': 'shadow | $borderSide(#FFFFFF, top, 0.2) | $borderSide(#000000, bottom, 0.2)',
+ '--pressedButtonBevel': 'shadow | $borderSide(#FFFFFF, bottom, 0.2)| $borderSide(#000000, top, 0.2)'
+ }
+ },
+ {
+ // component: 'Button', // no need to specify components every time unless you're specifying how other component should look
+ // like within it
+ directives: {
+ background: '--fg',
+ shadow: ['--defaultButtonShadow', '--defaultButtonBevel'],
+ roundness: 3
+ }
+ },
+ {
+ state: ['hover'],
+ directives: {
+ shadow: ['--defaultButtonHoverGlow', '--defaultButtonBevel']
+ }
+ },
+ {
+ state: ['pressed'],
+ directives: {
+ shadow: ['--defaultButtonShadow', '--pressedButtonBevel']
+ }
+ },
+ {
+ state: ['hover', 'pressed'],
+ directives: {
+ shadow: ['--defaultButtonHoverGlow', '--pressedButtonBevel']
+ }
+ },
+ {
+ state: ['toggled'],
+ directives: {
+ background: '--inheritedBackground,-14.2',
+ shadow: ['--defaultButtonShadow', '--pressedButtonBevel']
+ }
+ },
+ {
+ state: ['toggled', 'hover'],
+ directives: {
+ background: '--inheritedBackground,-14.2',
+ shadow: ['--defaultButtonHoverGlow', '--pressedButtonBevel']
+ }
+ },
+ {
+ state: ['disabled'],
+ directives: {
+ background: '$blend(--inheritedBackground, 0.25, --parent)',
+ shadow: ['--defaultButtonBevel']
+ }
+ },
+ {
+ component: 'Text',
+ parent: {
+ component: 'Button',
+ state: ['disabled']
+ },
+ directives: {
+ textOpacity: 0.25,
+ textOpacityMode: 'blend'
+ }
+ }
+ ]
+}
diff --git a/src/components/button_unstyled.style.js b/src/components/button_unstyled.style.js
new file mode 100644
index 00000000..65b5c57b
--- /dev/null
+++ b/src/components/button_unstyled.style.js
@@ -0,0 +1,96 @@
+export default {
+ name: 'ButtonUnstyled',
+ selector: '.button-unstyled',
+ states: {
+ toggled: '.toggled',
+ disabled: ':disabled',
+ hover: ':hover:not(:disabled)',
+ focused: ':focus-within'
+ },
+ validInnerComponents: [
+ 'Text',
+ 'Icon',
+ 'Badge'
+ ],
+ defaultRules: [
+ {
+ directives: {
+ background: '#ffffff',
+ opacity: 0,
+ shadow: []
+ }
+ },
+ {
+ component: 'Icon',
+ parent: {
+ component: 'ButtonUnstyled',
+ state: ['hover']
+ },
+ directives: {
+ textColor: '--parent--text'
+ }
+ },
+ {
+ component: 'Icon',
+ parent: {
+ component: 'ButtonUnstyled',
+ state: ['toggled']
+ },
+ directives: {
+ textColor: '--parent--text'
+ }
+ },
+ {
+ component: 'Icon',
+ parent: {
+ component: 'ButtonUnstyled',
+ state: ['toggled', 'hover']
+ },
+ directives: {
+ textColor: '--parent--text'
+ }
+ },
+ {
+ component: 'Icon',
+ parent: {
+ component: 'ButtonUnstyled',
+ state: ['toggled', 'focused']
+ },
+ directives: {
+ textColor: '--parent--text'
+ }
+ },
+ {
+ component: 'Icon',
+ parent: {
+ component: 'ButtonUnstyled',
+ state: ['toggled', 'focused', 'hover']
+ },
+ directives: {
+ textColor: '--parent--text'
+ }
+ },
+ {
+ component: 'Text',
+ parent: {
+ component: 'ButtonUnstyled',
+ state: ['disabled']
+ },
+ directives: {
+ textOpacity: 0.25,
+ textOpacityMode: 'blend'
+ }
+ },
+ {
+ component: 'Icon',
+ parent: {
+ component: 'ButtonUnstyled',
+ state: ['disabled']
+ },
+ directives: {
+ textOpacity: 0.25,
+ textOpacityMode: 'blend'
+ }
+ }
+ ]
+}
diff --git a/src/components/chat/chat.scss b/src/components/chat/chat.scss
index 43e7a5e4..8af710ae 100644
--- a/src/components/chat/chat.scss
+++ b/src/components/chat/chat.scss
@@ -11,15 +11,15 @@
.chat-view-body {
box-sizing: border-box;
- background-color: var(--chatBg, $fallback--bg);
display: flex;
flex-direction: column;
width: 100%;
overflow: visible;
min-height: calc(100vh - var(--navbar-height));
margin: 0;
- border-radius: 10px 10px 0 0;
- border-radius: var(--panelRadius, 10px) var(--panelRadius, 10px) 0 0;
+ border-radius: var(--roundness);
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
&::after {
border-radius: 0;
@@ -37,8 +37,6 @@
.footer {
position: sticky;
bottom: 0;
- background-color: $fallback--bg;
- background-color: var(--bg, $fallback--bg);
z-index: 1;
}
@@ -61,8 +59,6 @@
position: absolute;
right: 1.3em;
top: -3.2em;
- background-color: $fallback--fg;
- background-color: var(--btn, $fallback--fg);
display: flex;
justify-content: center;
align-items: center;
@@ -79,12 +75,6 @@
visibility: visible;
}
- i {
- font-size: 1em;
- color: $fallback--text;
- color: var(--text, $fallback--text);
- }
-
.unread-message-count {
font-size: 0.8em;
left: 50%;
diff --git a/src/components/chat/chat.style.js b/src/components/chat/chat.style.js
new file mode 100644
index 00000000..9ae2b7d7
--- /dev/null
+++ b/src/components/chat/chat.style.js
@@ -0,0 +1,19 @@
+export default {
+ name: 'Chat',
+ selector: '.chat-message-list',
+ validInnerComponents: [
+ 'Text',
+ 'Link',
+ 'Icon',
+ 'Avatar',
+ 'ChatMessage'
+ ],
+ defaultRules: [
+ {
+ directives: {
+ background: '--bg',
+ blur: '5px'
+ }
+ }
+ ]
+}
diff --git a/src/components/chat/chat.vue b/src/components/chat/chat.vue
index b1e5468c..6efe576b 100644
--- a/src/components/chat/chat.vue
+++ b/src/components/chat/chat.vue
@@ -26,7 +26,7 @@
</div>
</div>
<div
- class="message-list"
+ class="chat-message-list message-list"
:style="{ height: scrollableContainerHeight }"
>
<template v-if="!errorLoadingChat">
@@ -61,7 +61,7 @@
<FAIcon icon="chevron-down" />
<div
v-if="newMessageCount"
- class="badge badge-notification unread-chat-count unread-message-count"
+ class="badge -notification unread-chat-count unread-message-count"
>
{{ newMessageCount }}
</div>
@@ -95,6 +95,5 @@
<script src="./chat.js"></script>
<style lang="scss">
-@import "../../variables";
@import "./chat";
</style>
diff --git a/src/components/chat_list/chat_list.vue b/src/components/chat_list/chat_list.vue
index 27a475ed..b33ea67e 100644
--- a/src/components/chat_list/chat_list.vue
+++ b/src/components/chat_list/chat_list.vue
@@ -45,8 +45,6 @@
<script src="./chat_list.js"></script>
<style lang="scss">
-@import "../../variables";
-
.chat-list {
min-height: 25em;
margin-bottom: 0;
@@ -57,8 +55,7 @@
font-size: 1.2em;
display: flex;
justify-content: center;
- color: $fallback--text;
- color: var(--faint, $fallback--text);
+ color: var(--textFaint);
}
</style>
diff --git a/src/components/chat_list_item/chat_list_item.scss b/src/components/chat_list_item/chat_list_item.scss
index 3a84672b..9711b41d 100644
--- a/src/components/chat_list_item/chat_list_item.scss
+++ b/src/components/chat_list_item/chat_list_item.scss
@@ -1,8 +1,6 @@
.chat-list-item {
display: flex;
flex-direction: row;
- padding: 0.75em;
- height: 5em;
overflow: hidden;
box-sizing: border-box;
cursor: pointer;
@@ -11,11 +9,6 @@
outline: none;
}
- &:hover {
- background-color: var(--selectedPost, $fallback--lightBg);
- box-shadow: 0 0 3px 1px rgb(0 0 0 / 10%);
- }
-
.chat-list-item-left {
margin-right: 1em;
}
@@ -29,7 +22,7 @@
.heading {
width: 100%;
- display: inline-flex;
+ display: flex;
justify-content: space-between;
line-height: 1em;
}
@@ -47,18 +40,17 @@
}
.chat-preview {
- display: inline-flex;
+ display: flex;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
margin: 0.35em 0;
- color: $fallback--text;
- color: var(--faint, $fallback--text);
+ color: var(--textFaint);
width: 100%;
}
a {
- color: var(--faintLink, $fallback--link);
+ color: var(--linkFaint);
text-decoration: none;
pointer-events: none;
}
@@ -73,11 +65,6 @@
}
}
- .Avatar {
- border-radius: $fallback--avatarAltRadius;
- border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius);
- }
-
.chat-preview-body {
--emoji-size: 1.4em;
diff --git a/src/components/chat_list_item/chat_list_item.vue b/src/components/chat_list_item/chat_list_item.vue
index 69ad609b..0f9d5c5f 100644
--- a/src/components/chat_list_item/chat_list_item.vue
+++ b/src/components/chat_list_item/chat_list_item.vue
@@ -36,7 +36,7 @@
/>
<div
v-if="chat.unread > 0"
- class="badge badge-notification unread-chat-count"
+ class="badge -notification unread-chat-count"
>
{{ chat.unread }}
</div>
@@ -48,6 +48,5 @@
<script src="./chat_list_item.js"></script>
<style lang="scss">
-@import "../../variables";
@import "./chat_list_item";
</style>
diff --git a/src/components/chat_message/chat_message.scss b/src/components/chat_message/chat_message.scss
index fd5b7aa4..f7254ea3 100644
--- a/src/components/chat_message/chat_message.scss
+++ b/src/components/chat_message/chat_message.scss
@@ -1,5 +1,3 @@
-@import "../../variables";
-
.chat-message-wrapper {
&.hovered-message-chain {
.animated.Avatar {
@@ -27,12 +25,6 @@
.menu-icon {
cursor: pointer;
-
- &:hover,
- .extra-button-popover.open & {
- color: $fallback--text;
- color: var(--text, $fallback--text);
- }
}
.popover {
@@ -61,10 +53,12 @@
}
.status {
- border-radius: $fallback--chatMessageRadius;
- border-radius: var(--chatMessageRadius, $fallback--chatMessageRadius);
+ background-color: var(--background);
+ color: var(--text);
+ border-radius: var(--roundness);
display: flex;
padding: 0.75em;
+ border: 1px solid var(--border);
}
.created-at {
@@ -97,8 +91,7 @@
.error {
.status-content.media-body,
.created-at {
- color: $fallback--cRed;
- color: var(--badgeNotification, $fallback--cRed);
+ color: var(--badgeNotification);
}
}
@@ -117,16 +110,6 @@
align-content: end;
justify-content: flex-end;
- a {
- color: var(--chatMessageOutgoingLink, $fallback--link);
- }
-
- .status {
- color: var(--chatMessageOutgoingText, $fallback--text);
- background-color: var(--chatMessageOutgoingBg, $fallback--lightBg);
- border: 1px solid var(--chatMessageOutgoingBorder, --lightBg);
- }
-
.chat-message-inner {
align-items: flex-end;
}
@@ -137,22 +120,6 @@
}
.incoming {
- a {
- color: var(--chatMessageIncomingLink, $fallback--link);
- }
-
- .status {
- color: var(--chatMessageIncomingText, $fallback--text);
- background-color: var(--chatMessageIncomingBg, $fallback--bg);
- border: 1px solid var(--chatMessageIncomingBorder, --border);
- }
-
- .created-at {
- a {
- color: var(--chatMessageIncomingText, $fallback--text);
- }
- }
-
.chat-message-menu {
left: 0.4rem;
}
@@ -176,6 +143,5 @@
margin: 1.4em 0;
font-size: 0.9em;
user-select: none;
- color: $fallback--text;
- color: var(--faintedText, $fallback--text);
+ color: var(--textFaint);
}
diff --git a/src/components/chat_message/chat_message.style.js b/src/components/chat_message/chat_message.style.js
new file mode 100644
index 00000000..9b57ad37
--- /dev/null
+++ b/src/components/chat_message/chat_message.style.js
@@ -0,0 +1,30 @@
+export default {
+ name: 'ChatMessage',
+ selector: '.chat-message',
+ variants: {
+ outgoing: '.outgoing'
+ },
+ validInnerComponents: [
+ 'Text',
+ 'Icon',
+ 'Border',
+ 'Button',
+ 'RichContent',
+ 'Attachment',
+ 'PollGraph'
+ ],
+ defaultRules: [
+ {
+ directives: {
+ background: '--bg, 2',
+ backgroundNoCssColor: 'yes'
+ }
+ },
+ {
+ variant: 'outgoing',
+ directives: {
+ background: '--bg, 5'
+ }
+ }
+ ]
+}
diff --git a/src/components/chat_message/chat_message.vue b/src/components/chat_message/chat_message.vue
index 381574c3..166889d7 100644
--- a/src/components/chat_message/chat_message.vue
+++ b/src/components/chat_message/chat_message.vue
@@ -53,7 +53,7 @@
<template #content>
<div class="dropdown-menu">
<button
- class="button-default dropdown-item dropdown-item-icon"
+ class="menu-item dropdown-item dropdown-item-icon"
@click="deleteMessage"
>
<FAIcon icon="times" /> {{ $t("chats.delete") }}
diff --git a/src/components/chat_new/chat_new.scss b/src/components/chat_new/chat_new.scss
index b145ecf9..b84e0beb 100644
--- a/src/components/chat_new/chat_new.scss
+++ b/src/components/chat_new/chat_new.scss
@@ -16,11 +16,6 @@
padding-bottom: 0.7rem;
}
- .basic-user-card:hover {
- cursor: pointer;
- background-color: var(--selectedPost, $fallback--lightBg);
- }
-
.go-back-button {
text-align: center;
line-height: 1;
diff --git a/src/components/chat_new/chat_new.vue b/src/components/chat_new/chat_new.vue
index 52306c1d..673ba56b 100644
--- a/src/components/chat_new/chat_new.vue
+++ b/src/components/chat_new/chat_new.vue
@@ -16,27 +16,29 @@
/>
</button>
</div>
- <div class="input-wrap">
- <div class="input-search">
- <FAIcon
- class="search-icon fa-scale-110 fa-old-padding"
- icon="search"
- />
+ <div class="panel-body">
+ <div class="input-wrap">
+ <div class="input-search">
+ <FAIcon
+ class="search-icon fa-scale-110 fa-old-padding"
+ icon="search"
+ />
+ </div>
+ <input
+ ref="search"
+ v-model="query"
+ class="input"
+ placeholder="Search people"
+ @input="onInput"
+ >
</div>
- <input
- ref="search"
- v-model="query"
- placeholder="Search people"
- @input="onInput"
- >
- </div>
- <div class="member-list">
- <div
- v-for="user in availableUsers"
- :key="user.id"
- class="member"
- >
- <div @click.capture.prevent="goToChat(user)">
+ <div class="member-list">
+ <div
+ v-for="user in availableUsers"
+ :key="user.id"
+ class="list-item"
+ @click.capture.prevent="goToChat(user)"
+ >
<BasicUserCard :user="user" />
</div>
</div>
@@ -46,6 +48,5 @@
<script src="./chat_new.js"></script>
<style lang="scss">
-@import "../../variables";
@import "./chat_new";
</style>
diff --git a/src/components/chat_title/chat_title.vue b/src/components/chat_title/chat_title.vue
index 93db4fa7..68ee7a5a 100644
--- a/src/components/chat_title/chat_title.vue
+++ b/src/components/chat_title/chat_title.vue
@@ -26,8 +26,6 @@
<script src="./chat_title.js"></script>
<style lang="scss">
-@import "../../variables";
-
.chat-title {
display: flex;
overflow: hidden;
@@ -54,8 +52,7 @@
margin-right: 0.5em;
height: 1.5em;
width: 1.5em;
- border-radius: $fallback--avatarAltRadius;
- border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius);
+ border-radius: var(--roundness);
&.animated::before {
display: none;
diff --git a/src/components/checkbox/checkbox.vue b/src/components/checkbox/checkbox.vue
index 42f89be9..6261bf3a 100644
--- a/src/components/checkbox/checkbox.vue
+++ b/src/components/checkbox/checkbox.vue
@@ -1,7 +1,7 @@
<template>
<label
class="checkbox"
- :class="{ disabled, indeterminate }"
+ :class="{ disabled, indeterminate, 'indeterminate-fix': indeterminateTransitionFix }"
>
<input
type="checkbox"
@@ -12,8 +12,9 @@
@change="$emit('update:modelValue', $event.target.checked)"
>
<i
- class="checkbox-indicator"
+ class="input -checkbox checkbox-indicator"
:aria-hidden="true"
+ @transitionend.capture="onTransitionEnd"
/>
<span
v-if="!!$slots.default"
@@ -31,12 +32,28 @@ export default {
'indeterminate',
'disabled'
],
- emits: ['update:modelValue']
+ emits: ['update:modelValue'],
+ data: (vm) => ({
+ indeterminateTransitionFix: vm.indeterminate
+ }),
+ watch: {
+ indeterminate (e) {
+ if (e) {
+ this.indeterminateTransitionFix = true
+ }
+ }
+ },
+ methods: {
+ onTransitionEnd (e) {
+ if (!this.indeterminate) {
+ this.indeterminateTransitionFix = false
+ }
+ }
+ }
}
</script>
<style lang="scss">
-@import "../../variables";
@import "../../mixins";
.checkbox {
@@ -44,9 +61,15 @@ export default {
display: inline-block;
min-height: 1.2em;
- &-indicator {
+ & > &-indicator {
+ /* Reset .input stuff */
+ padding: 0;
+ margin: 0;
position: relative;
+ line-height: inherit;
+ display: inline;
padding-left: 1.2em;
+ box-shadow: none;
}
&-indicator::before {
@@ -58,12 +81,9 @@ export default {
transition: color 200ms;
width: 1.1em;
height: 1.1em;
- border-radius: $fallback--checkboxRadius;
- border-radius: var(--checkboxRadius, $fallback--checkboxRadius);
- box-shadow: 0 0 2px black inset;
- box-shadow: var(--inputShadow);
- background-color: $fallback--fg;
- background-color: var(--input, $fallback--fg);
+ border-radius: var(--roundness);
+ box-shadow: var(--shadow);
+ background-color: var(--background);
vertical-align: top;
text-align: center;
line-height: 1.1em;
@@ -80,21 +100,24 @@ export default {
}
.label {
- color: $fallback--faint;
- color: var(--faint, $fallback--faint);
+ color: var(--text);
}
}
input[type="checkbox"] {
&:checked + .checkbox-indicator::before {
- color: $fallback--text;
- color: var(--inputText, $fallback--text);
+ color: var(--text);
}
&:indeterminate + .checkbox-indicator::before {
content: "–";
- color: $fallback--text;
- color: var(--inputText, $fallback--text);
+ color: var(--text);
+ }
+ }
+
+ &.indeterminate-fix {
+ input[type="checkbox"] + .checkbox-indicator::before {
+ content: "–";
}
}
diff --git a/src/components/color_input/color_input.scss b/src/components/color_input/color_input.scss
index ca46199a..b0fc879f 100644
--- a/src/components/color_input/color_input.scss
+++ b/src/components/color_input/color_input.scss
@@ -1,5 +1,3 @@
-@import "../../variables";
-
.color-input {
display: inline-flex;
@@ -11,9 +9,8 @@
padding: 0.2em 8px;
input {
+ color: var(--text);
background: none;
- color: $fallback--lightText;
- color: var(--inputText, $fallback--lightText);
border: none;
padding: 0;
margin: 0;
@@ -23,21 +20,38 @@
min-width: 3em;
padding: 0;
}
+ }
+
+ .nativeColor {
+ cursor: pointer;
+ flex: 0 0 auto;
- &.nativeColor {
- flex: 0 0 2em;
- min-width: 2em;
- align-self: stretch;
- min-height: 100%;
+ input {
+ appearance: none;
+ max-width: 0;
+ min-width: 0;
+ max-height: 0;
+ /* stylelint-disable-next-line declaration-no-important */
+ opacity: 0 !important;
}
}
.computedIndicator,
+ .validIndicator,
+ .invalidIndicator,
.transparentIndicator {
flex: 0 0 2em;
+ margin: 0 0.5em;
min-width: 2em;
align-self: stretch;
- min-height: 100%;
+ min-height: 1.5em;
+ border-radius: var(--roundness);
+ }
+
+ .invalidIndicator {
+ background: transparent;
+ box-sizing: border-box;
+ border: 2px solid var(--cRed);
}
.transparentIndicator {
@@ -58,11 +72,13 @@
&::after {
top: 0;
left: 0;
+ border-top-left-radius: var(--roundness);
}
&::before {
bottom: 0;
right: 0;
+ border-bottom-right-radius: var(--roundness);
}
}
}
diff --git a/src/components/color_input/color_input.vue b/src/components/color_input/color_input.vue
index dfc084f9..66ee9d53 100644
--- a/src/components/color_input/color_input.vue
+++ b/src/components/color_input/color_input.vue
@@ -25,30 +25,51 @@
:disabled="!present || disabled"
@input="$emit('update:modelValue', $event.target.value)"
>
- <input
+ <div
v-if="validColor"
- :id="name"
- class="nativeColor unstyled"
- type="color"
- :value="modelValue || fallback"
- :disabled="!present || disabled"
- @input="$emit('update:modelValue', $event.target.value)"
- >
+ class="validIndicator"
+ :style="{backgroundColor: modelValue || fallback}"
+ />
<div
- v-if="transparentColor"
+ v-else-if="transparentColor"
class="transparentIndicator"
/>
<div
- v-if="computedColor"
+ v-else-if="computedColor"
class="computedIndicator"
:style="{backgroundColor: fallback}"
/>
+ <div
+ v-else
+ class="invalidIndicator"
+ />
+ <label class="nativeColor">
+ <FAIcon icon="eye-dropper" />
+ <input
+ :id="name"
+ class="unstyled"
+ type="color"
+ :value="modelValue || fallback"
+ :disabled="!present || disabled"
+ @input="$emit('update:modelValue', $event.target.value)"
+ >
+ </label>
</div>
</div>
</template>
<script>
import Checkbox from '../checkbox/checkbox.vue'
import { hex2rgb } from '../../services/color_convert/color_convert.js'
+
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faEyeDropper
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faEyeDropper
+)
+
export default {
components: {
Checkbox
@@ -108,12 +129,3 @@ export default {
}
</script>
<style lang="scss" src="./color_input.scss"></style>
-
-<style lang="scss">
-.color-control {
- input.text-input {
- max-width: 7em;
- flex: 1;
- }
-}
-</style>
diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js
index 85e6d8ad..a94d2130 100644
--- a/src/components/conversation/conversation.js
+++ b/src/components/conversation/conversation.js
@@ -56,7 +56,8 @@ const conversation = {
expanded: false,
threadDisplayStatusObject: {}, // id => 'showing' | 'hidden'
statusContentPropertiesObject: {},
- inlineDivePosition: null
+ inlineDivePosition: null,
+ loadStatusError: null
}
},
props: [
@@ -392,11 +393,15 @@ const conversation = {
this.setHighlight(this.originalStatusId)
})
} else {
+ this.loadStatusError = null
this.$store.state.api.backendInteractor.fetchStatus({ id: this.statusId })
.then((status) => {
this.$store.dispatch('addNewStatuses', { statuses: [status] })
this.fetchConversation()
})
+ .catch((error) => {
+ this.loadStatusError = error
+ })
}
},
getReplies (id) {
diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue
index 7577129e..526de5c3 100644
--- a/src/components/conversation/conversation.vue
+++ b/src/components/conversation/conversation.vue
@@ -28,7 +28,27 @@
class="rightside-button"
/>
</div>
- <div class="conversation-body panel-body">
+ <div
+ v-if="isPage && !status"
+ class="conversation-body"
+ :class="{ 'panel-body': isExpanded }"
+ >
+ <p v-if="!loadStatusError">
+ <FAIcon
+ spin
+ icon="circle-notch"
+ />
+ {{ $t('status.loading') }}
+ </p>
+ <p v-else>
+ {{ $t('status.load_error', { error: loadStatusError }) }}
+ </p>
+ </div>
+ <div
+ v-else
+ class="conversation-body"
+ :class="{ 'panel-body': isExpanded }"
+ >
<div
v-if="isTreeView"
class="thread-body"
@@ -203,6 +223,7 @@
</div>
<div
v-else
+ class="Conversation -hidden"
:style="hiddenStyle"
/>
</template>
@@ -210,14 +231,17 @@
<script src="./conversation.js"></script>
<style lang="scss">
-@import "../../variables";
-
.Conversation {
z-index: 1;
+ &.-hidden {
+ background: var(--__panel-background);
+ backdrop-filter: var(--__panel-backdrop-filter);
+ }
+
.conversation-dive-to-top-level-box {
- padding: var(--status-margin, $status-margin);
- border-bottom: 1px solid var(--border, $fallback--border);
+ padding: var(--status-margin);
+ border-bottom: 1px solid var(--border);
border-radius: 0;
/* Make the button stretch along the whole row */
@@ -227,20 +251,22 @@
}
.thread-ancestors {
- margin-left: var(--status-margin, $status-margin);
- border-left: 2px solid var(--border, $fallback--border);
+ margin-left: var(--status-margin);
+ border-left: 2px solid var(--border);
}
- .thread-ancestor.-faded .StatusContent {
- --link: var(--faintLink);
- --text: var(--faint);
-
- color: var(--text);
+ .thread-ancestor.-faded .RichContent {
+ /* stylelint-disable declaration-no-important */
+ --text: var(--textFaint) !important;
+ --link: var(--linkFaint) !important;
+ --funtextGreentext: var(--funtextGreentextFaint) !important;
+ --funtextCyantext: var(--funtextCyantextFaint) !important;
+ /* stylelint-enable declaration-no-important */
}
.thread-ancestor-dive-box {
- padding-left: var(--status-margin, $status-margin);
- border-bottom: 1px solid var(--border, $fallback--border);
+ padding-left: var(--status-margin);
+ border-bottom: 1px solid var(--border);
border-radius: 0;
/* Make the button stretch along the whole row */
@@ -253,16 +279,17 @@
}
.thread-ancestor-dive-box-inner {
- padding: var(--status-margin, $status-margin);
+ padding: var(--status-margin);
}
.conversation-status {
- border-bottom: 1px solid var(--border, $fallback--border);
+ border-bottom: 1px solid var(--border);
border-radius: 0;
}
.thread-ancestor-has-other-replies .conversation-status,
- &:last-child .conversation-status,
+ &:last-child:not(.-expanded) .conversation-status,
+ &.-expanded .conversation-status:last-child,
.thread-ancestor:last-child .conversation-status,
.thread-ancestor:last-child .thread-ancestor-dive-box,
&.-expanded .thread-tree .conversation-status {
@@ -270,20 +297,36 @@
}
.thread-ancestors + .thread-tree > .conversation-status {
- border-top: 1px solid var(--border, $fallback--border);
+ border-top: 1px solid var(--border);
}
/* expanded conversation in timeline */
&.status-fadein.-expanded .thread-body {
- border-left: 4px solid $fallback--cRed;
- border-left-color: var(--cRed, $fallback--cRed);
- border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius;
- border-radius: 0 0 var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius);
- border-bottom: 1px solid var(--border, $fallback--border);
+ border-left: 4px solid var(--cRed);
+ border-radius: var(--roundness);
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+ border-bottom: 1px solid var(--border);
}
&.-expanded.status-fadein {
- margin: calc(var(--status-margin, $status-margin) / 2);
+ --___margin: calc(var(--status-margin) / 2);
+
+ background: var(--background);
+ margin: var(--___margin);
+
+ &::before {
+ z-index: -1;
+ content: "";
+ display: block;
+ position: absolute;
+ top: calc(var(--___margin) * -1);
+ bottom: calc(var(--___margin) * -1);
+ left: calc(var(--___margin) * -1);
+ right: calc(var(--___margin) * -1);
+ background: var(--background);
+ backdrop-filter: var(--__panel-backdrop-filter);
+ }
}
}
</style>
diff --git a/src/components/desktop_nav/desktop_nav.js b/src/components/desktop_nav/desktop_nav.js
index 745b1a81..f6a2e294 100644
--- a/src/components/desktop_nav/desktop_nav.js
+++ b/src/components/desktop_nav/desktop_nav.js
@@ -107,7 +107,10 @@ export default {
this.searchBarHidden = hidden
},
openSettingsModal () {
- this.$store.dispatch('openSettingsModal')
+ this.$store.dispatch('openSettingsModal', 'user')
+ },
+ openAdminModal () {
+ this.$store.dispatch('openSettingsModal', 'admin')
}
}
}
diff --git a/src/components/desktop_nav/desktop_nav.scss b/src/components/desktop_nav/desktop_nav.scss
index c7e02936..61d2541c 100644
--- a/src/components/desktop_nav/desktop_nav.scss
+++ b/src/components/desktop_nav/desktop_nav.scss
@@ -1,5 +1,3 @@
-@import "../../variables";
-
.DesktopNav {
width: 100%;
z-index: var(--ZI_navbar);
@@ -9,7 +7,7 @@
}
a {
- color: var(--topBarLink, $fallback--link);
+ color: var(--link);
}
.inner-nav {
@@ -54,27 +52,7 @@
.button-default {
&,
svg {
- color: $fallback--text;
- color: var(--btnTopBarText, $fallback--text);
- }
-
- &:active {
- background-color: $fallback--fg;
- background-color: var(--btnPressedTopBar, $fallback--fg);
- color: $fallback--text;
- color: var(--btnPressedTopBarText, $fallback--text);
- }
-
- &:disabled {
- color: $fallback--text;
- color: var(--btnDisabledTopBarText, $fallback--text);
- }
-
- &.toggled {
- color: $fallback--text;
- color: var(--btnToggledTopBarText, $fallback--text);
- background-color: $fallback--fg;
- background-color: var(--btnToggledTopBar, $fallback--fg);
+ color: var(--text);
}
}
@@ -94,8 +72,7 @@
mask-repeat: no-repeat;
mask-position: center;
mask-size: contain;
- background-color: $fallback--fg;
- background-color: var(--topBarText, $fallback--fg);
+ background-color: var(--text);
position: absolute;
top: 0;
bottom: 0;
@@ -116,8 +93,7 @@
text-align: center;
.svg-inline--fa {
- color: $fallback--link;
- color: var(--topBarLink, $fallback--link);
+ color: var(--link);
}
}
diff --git a/src/components/desktop_nav/desktop_nav.vue b/src/components/desktop_nav/desktop_nav.vue
index dc8bbfd3..49382f8e 100644
--- a/src/components/desktop_nav/desktop_nav.vue
+++ b/src/components/desktop_nav/desktop_nav.vue
@@ -48,20 +48,19 @@
icon="cog"
/>
</button>
- <a
+ <button
v-if="currentUser && currentUser.role === 'admin'"
- href="/pleroma/admin/#/login-pleroma"
- class="nav-icon"
+ class="button-unstyled nav-icon"
target="_blank"
:title="$t('nav.administration')"
- @click.stop
+ @click.stop="openAdminModal"
>
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
icon="tachometer-alt"
/>
- </a>
+ </button>
<span class="spacer" />
<button
v-if="currentUser"
diff --git a/src/components/dialog_modal/dialog_modal.vue b/src/components/dialog_modal/dialog_modal.vue
index 341cf105..3bceff22 100644
--- a/src/components/dialog_modal/dialog_modal.vue
+++ b/src/components/dialog_modal/dialog_modal.vue
@@ -12,7 +12,7 @@
<slot name="header" />
</div>
</div>
- <div class="dialog-modal-content">
+ <div class="panel-body dialog-modal-content">
<slot name="default" />
</div>
<div class="dialog-modal-footer user-interactions panel-footer">
@@ -25,8 +25,6 @@
<script src="./dialog_modal.js"></script>
<style lang="scss">
-@import "../../variables";
-
// TODO: unify with other modals.
.dark-overlay {
&::before {
@@ -54,8 +52,6 @@
z-index: 2001;
cursor: default;
display: block;
- background-color: $fallback--bg;
- background-color: var(--bg, $fallback--bg);
.dialog-modal-heading {
.title {
@@ -66,18 +62,13 @@
.dialog-modal-content {
margin: 0;
padding: 1rem;
- background-color: $fallback--bg;
- background-color: var(--bg, $fallback--bg);
white-space: normal;
}
.dialog-modal-footer {
margin: 0;
padding: 0.5em;
- background-color: $fallback--bg;
- background-color: var(--bg, $fallback--bg);
- border-top: 1px solid $fallback--border;
- border-top: 1px solid var(--border, $fallback--border);
+ border-top: 1px solid var(--border);
display: flex;
justify-content: flex-end;
diff --git a/src/components/emoji_input/emoji_input.js b/src/components/emoji_input/emoji_input.js
index 68654f69..9baf63f2 100644
--- a/src/components/emoji_input/emoji_input.js
+++ b/src/components/emoji_input/emoji_input.js
@@ -1,4 +1,5 @@
import Completion from '../../services/completion/completion.js'
+import genRandomSeed from '../../services/random_seed/random_seed.service.js'
import EmojiPicker from '../emoji_picker/emoji_picker.vue'
import Popover from 'src/components/popover/popover.vue'
import ScreenReaderNotice from 'src/components/screen_reader_notice/screen_reader_notice.vue'
@@ -110,7 +111,7 @@ const EmojiInput = {
},
data () {
return {
- randomSeed: `${Math.random()}`.replace('.', '-'),
+ randomSeed: genRandomSeed(),
input: undefined,
caretEl: undefined,
highlighted: -1,
diff --git a/src/components/emoji_input/emoji_input.vue b/src/components/emoji_input/emoji_input.vue
index 7f9ecc99..9bd5c8f4 100644
--- a/src/components/emoji_input/emoji_input.vue
+++ b/src/components/emoji_input/emoji_input.vue
@@ -1,7 +1,7 @@
<template>
<div
ref="root"
- class="emoji-input"
+ class="input emoji-input"
:class="{ 'with-picker': !hideEmojiButton }"
>
<slot
@@ -68,9 +68,9 @@
v-for="(suggestion, index) in suggestions"
:id="suggestionItemId(index)"
:key="index"
- class="autocomplete-item"
+ class="menu-item autocomplete-item"
role="option"
- :class="{ highlighted: index === highlighted }"
+ :class="{ '-active': index === highlighted }"
:aria-label="autoCompleteItemLabel(suggestion)"
:aria-selected="index === highlighted"
@click.stop.prevent="onClick($event, suggestion)"
@@ -110,9 +110,8 @@
<script src="./emoji_input.js"></script>
<style lang="scss">
-@import "../../variables";
-
-.emoji-input {
+.input.emoji-input {
+ padding: 0;
display: flex;
flex-direction: column;
position: relative;
@@ -127,8 +126,7 @@
line-height: 24px;
&:hover i {
- color: $fallback--text;
- color: var(--text, $fallback--text);
+ color: var(--text);
}
}
@@ -145,6 +143,12 @@
input,
textarea {
flex: 1 0 auto;
+ color: inherit;
+ /* stylelint-disable-next-line declaration-no-important */
+ background: none !important;
+ box-shadow: none;
+ border: none;
+ outline: none;
}
&.with-picker input {
@@ -179,26 +183,28 @@
position: absolute;
}
- &-item {
+ &-item.menu-item {
display: flex;
- cursor: pointer;
- padding: 0.2em 0.4em;
- border-bottom: 1px solid rgb(0 0 0 / 40%);
- height: 32px;
+ padding-top: 0;
+ padding-bottom: 0;
.image {
- width: 32px;
- height: 32px;
- line-height: 32px;
+ width: calc(var(--__line-height) + var(--__vertical-gap) * 2);
+ height: calc(var(--__line-height) + var(--__vertical-gap) * 2);
+ line-height: var(--__line-height);
text-align: center;
- font-size: 32px;
- margin-right: 4px;
+ margin-right: var(--__horizontal-gap);
img {
- width: 32px;
- height: 32px;
+ width: calc(var(--__line-height) + var(--__vertical-gap) * 2);
+ height: calc(var(--__line-height) + var(--__vertical-gap) * 2);
object-fit: contain;
}
+
+ span {
+ font-size: var(--__line-height);
+ line-height: var(--__line-height);
+ }
}
.label {
@@ -216,17 +222,6 @@
line-height: 9px;
}
}
-
- &.highlighted {
- background-color: $fallback--fg;
- background-color: var(--selectedMenuPopover, $fallback--fg);
- color: var(--selectedMenuPopoverText, $fallback--text);
-
- --faint: var(--selectedMenuPopoverFaintText, $fallback--faint);
- --faintLink: var(--selectedMenuPopoverFaintLink, $fallback--faint);
- --lightText: var(--selectedMenuPopoverLightText, $fallback--lightText);
- --icon: var(--selectedMenuPopoverIcon, $fallback--icon);
- }
}
}
</style>
diff --git a/src/components/emoji_picker/emoji_picker.js b/src/components/emoji_picker/emoji_picker.js
index 349b043d..eb665c40 100644
--- a/src/components/emoji_picker/emoji_picker.js
+++ b/src/components/emoji_picker/emoji_picker.js
@@ -105,6 +105,7 @@ const EmojiPicker = {
default: false
}
},
+ inject: ['popoversZLayer'],
data () {
return {
keyword: '',
@@ -113,6 +114,7 @@ const EmojiPicker = {
groupsScrolledClass: 'scrolled-top',
keepOpen: false,
customEmojiTimeout: null,
+ hideCustomEmojiInPicker: false,
// Lazy-load only after the first time `showing` becomes true.
contentLoaded: false,
groupRefs: {},
@@ -285,7 +287,7 @@ const EmojiPicker = {
return 0
},
allCustomGroups () {
- if (this.hideCustomEmoji) {
+ if (this.hideCustomEmoji || this.hideCustomEmojiInPicker) {
return {}
}
const emojis = this.$store.getters.groupedCustomEmojis
@@ -350,6 +352,9 @@ const EmojiPicker = {
return emoji.displayText
}
+ },
+ isInModal () {
+ return this.popoversZLayer === 'modals'
}
}
}
diff --git a/src/components/emoji_picker/emoji_picker.scss b/src/components/emoji_picker/emoji_picker.scss
index 5bcff33b..5602a16b 100644
--- a/src/components/emoji_picker/emoji_picker.scss
+++ b/src/components/emoji_picker/emoji_picker.scss
@@ -1,5 +1,3 @@
-@import "../../variables";
-
$emoji-picker-header-height: 36px;
$emoji-picker-header-picture-width: 32px;
$emoji-picker-header-picture-height: 32px;
@@ -10,15 +8,6 @@ $emoji-picker-emoji-size: 32px;
max-width: calc(100vw - 20px); // popover gives 10px margin from window edge
display: flex;
flex-direction: column;
- background-color: $fallback--bg;
- background-color: var(--popover, $fallback--bg);
- color: $fallback--link;
- color: var(--popoverText, $fallback--link);
-
- --faint: var(--popoverFaintText, $fallback--faint);
- --faintLink: var(--popoverFaintLink, $fallback--faint);
- --lightText: var(--popoverLightText, $fallback--lightText);
- --icon: var(--popoverIcon, $fallback--icon);
&-header-image {
display: inline-flex;
@@ -39,11 +28,16 @@ $emoji-picker-emoji-size: 32px;
}
.keep-open,
- .too-many-emoji {
+ .too-many-emoji,
+ .hide-custom-emoji {
padding: 7px;
line-height: normal;
}
+ .hide-custom-emoji {
+ padding-top: 0;
+ }
+
.too-many-emoji {
display: flex;
flex-direction: column;
@@ -76,8 +70,7 @@ $emoji-picker-emoji-size: 32px;
.additional-tabs {
display: flex;
border-left: 1px solid;
- border-left-color: $fallback--icon;
- border-left-color: var(--icon, $fallback--icon);
+ border-left-color: var(--border);
padding-left: 7px;
flex: 0 0 auto;
}
@@ -104,13 +97,8 @@ $emoji-picker-emoji-size: 32px;
pointer-events: none;
}
- &.active {
+ &.toggled {
border-bottom: 4px solid;
-
- svg {
- color: $fallback--lightText;
- color: var(--lightText, $fallback--lightText);
- }
}
}
}
diff --git a/src/components/emoji_picker/emoji_picker.vue b/src/components/emoji_picker/emoji_picker.vue
index 6972164b..7c36deaa 100644
--- a/src/components/emoji_picker/emoji_picker.vue
+++ b/src/components/emoji_picker/emoji_picker.vue
@@ -3,25 +3,32 @@
ref="popover"
trigger="click"
popover-class="emoji-picker popover-default"
- :trigger-attrs="{ 'aria-hidden': true }"
+ :trigger-attrs="{ 'aria-hidden': true, tabindex: -1 }"
@show="onPopoverShown"
@close="onPopoverClosed"
>
<template #content>
<div class="heading">
+ <!--
+ Body scroll lock needs to be on every scrollable element on safari iOS.
+ Here we tell it to enable scrolling for this element.
+ See https://github.com/willmcpo/body-scroll-lock#vanilla-js
+ -->
<span
ref="header"
+ v-body-scroll-lock="isInModal"
class="emoji-tabs"
>
<span
v-for="group in filteredEmojiGroups"
:ref="setGroupRef('group-header-' + group.id)"
:key="group.id"
- class="emoji-tabs-item"
+ class="button-unstyled emoji-tabs-item"
:class="{
- active: activeGroupView === group.id
+ toggled: activeGroupView === group.id
}"
:title="group.text"
+ role="button"
@click.prevent="highlight(group.id)"
>
<span
@@ -45,8 +52,8 @@
class="additional-tabs"
>
<span
- class="stickers-tab-icon additional-tabs-item"
- :class="{active: showingStickers}"
+ class="button-unstyled stickers-tab-icon additional-tabs-item"
+ :class="{toggled: showingStickers}"
:title="$t('emoji.stickers')"
@click.prevent="toggleStickers"
>
@@ -70,13 +77,15 @@
ref="search"
v-model="keyword"
type="text"
- class="form-control"
+ class="input form-control"
:placeholder="$t('emoji.search_emoji')"
@input="$event.target.composing = false"
>
</div>
+ <!-- Enables scrolling for this element on safari iOS. See comments for header. -->
<DynamicScroller
ref="emoji-groups"
+ v-body-scroll-lock="isInModal"
class="emoji-groups"
:class="groupsScrolledClass"
:min-item-size="minItemSize"
@@ -108,6 +117,7 @@
:key="group.id + emoji.displayText"
:title="maybeLocalizedEmojiName(emoji)"
class="emoji-item"
+ role="button"
@click.stop.prevent="onEmoji(emoji)"
>
<span
@@ -118,6 +128,7 @@
v-else
class="emoji-picker-emoji -custom"
loading="lazy"
+ :alt="maybeLocalizedEmojiName(emoji)"
:src="emoji.imageUrl"
:data-emoji-name="group.id + emoji.displayText"
/>
@@ -131,6 +142,17 @@
{{ $t('emoji.keep_open') }}
</Checkbox>
</div>
+ <div
+ v-if="!hideCustomEmoji"
+ class="hide-custom-emoji"
+ >
+ <Checkbox
+ v-model="hideCustomEmojiInPicker"
+ @change="onShowing"
+ >
+ {{ $t('emoji.hide_custom_emoji') }}
+ </Checkbox>
+ </div>
</div>
<div
v-if="showingStickers"
diff --git a/src/components/emoji_reactions/emoji_reactions.js b/src/components/emoji_reactions/emoji_reactions.js
index bb11b840..4d5c6c5a 100644
--- a/src/components/emoji_reactions/emoji_reactions.js
+++ b/src/components/emoji_reactions/emoji_reactions.js
@@ -1,5 +1,17 @@
import UserAvatar from '../user_avatar/user_avatar.vue'
import UserListPopover from '../user_list_popover/user_list_popover.vue'
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faPlus,
+ faMinus,
+ faCheck
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faPlus,
+ faMinus,
+ faCheck
+)
const EMOJI_REACTION_COUNT_CUTOFF = 12
@@ -33,6 +45,9 @@ const EmojiReactions = {
},
loggedIn () {
return !!this.$store.state.users.currentUser
+ },
+ remoteInteractionLink () {
+ return this.$store.getters.remoteInteractionLink({ statusId: this.status.id })
}
},
methods: {
@@ -42,10 +57,10 @@ const EmojiReactions = {
reactedWith (emoji) {
return this.status.emoji_reactions.find(r => r.name === emoji).me
},
- fetchEmojiReactionsByIfMissing () {
+ async fetchEmojiReactionsByIfMissing () {
const hasNoAccounts = this.status.emoji_reactions.find(r => !r.accounts)
if (hasNoAccounts) {
- this.$store.dispatch('fetchEmojiReactionsBy', this.status.id)
+ return await this.$store.dispatch('fetchEmojiReactionsBy', this.status.id)
}
},
reactWith (emoji) {
@@ -54,14 +69,26 @@ const EmojiReactions = {
unreact (emoji) {
this.$store.dispatch('unreactWithEmoji', { id: this.status.id, emoji })
},
- emojiOnClick (emoji, event) {
+ async emojiOnClick (emoji, event) {
if (!this.loggedIn) return
+ await this.fetchEmojiReactionsByIfMissing()
if (this.reactedWith(emoji)) {
this.unreact(emoji)
} else {
this.reactWith(emoji)
}
+ },
+ counterTriggerAttrs (reaction) {
+ return {
+ class: [
+ 'btn',
+ 'button-default',
+ 'emoji-reaction-count-button',
+ { '-picked-reaction': this.reactedWith(reaction.name) }
+ ],
+ 'aria-label': this.$tc('status.reaction_count_label', reaction.count, { num: reaction.count })
+ }
}
}
}
diff --git a/src/components/emoji_reactions/emoji_reactions.vue b/src/components/emoji_reactions/emoji_reactions.vue
index eb46018e..ad4a3c0b 100644
--- a/src/components/emoji_reactions/emoji_reactions.vue
+++ b/src/components/emoji_reactions/emoji_reactions.vue
@@ -1,15 +1,19 @@
<template>
<div class="EmojiReactions">
- <UserListPopover
+ <span
v-for="(reaction) in emojiReactions"
:key="reaction.url || reaction.name"
- :users="accountsForEmoji[reaction.name]"
+ class="emoji-reaction-container btn-group"
>
- <button
+ <component
+ :is="loggedIn ? 'button' : 'a'"
+ v-bind="!loggedIn ? { href: remoteInteractionLink } : {}"
+ role="button"
class="emoji-reaction btn button-default"
- :class="{ '-picked-reaction': reactedWith(reaction.name), 'not-clickable': !loggedIn }"
+ :class="{ '-picked-reaction': reactedWith(reaction.name) }"
+ :title="reaction.url ? reaction.name : undefined"
+ :aria-pressed="reactedWith(reaction.name)"
@click="emojiOnClick(reaction.name, $event)"
- @mouseenter="fetchEmojiReactionsByIfMissing()"
>
<span
class="reaction-emoji"
@@ -17,7 +21,6 @@
<img
v-if="reaction.url"
:src="reaction.url"
- :title="reaction.name"
class="reaction-emoji-content"
width="1em"
>
@@ -26,9 +29,36 @@
class="reaction-emoji reaction-emoji-content"
>{{ reaction.name }}</span>
</span>
- <span>{{ reaction.count }}</span>
- </button>
- </UserListPopover>
+ <FALayers>
+ <FAIcon
+ v-if="reactedWith(reaction.name)"
+ class="active-marker"
+ transform="shrink-6 up-9"
+ icon="check"
+ />
+ <FAIcon
+ v-if="!reactedWith(reaction.name)"
+ class="focus-marker"
+ transform="shrink-6 up-9"
+ icon="plus"
+ />
+ <FAIcon
+ v-else
+ class="focus-marker"
+ transform="shrink-6 up-9"
+ icon="minus"
+ />
+ </FALayers>
+ </component>
+ <UserListPopover
+ :users="accountsForEmoji[reaction.name]"
+ class="emoji-reaction-popover"
+ :trigger-attrs="counterTriggerAttrs(reaction)"
+ @show="fetchEmojiReactionsByIfMissing()"
+ >
+ <span class="emoji-reaction-counts">{{ reaction.count }}</span>
+ </UserListPopover>
+ </span>
<a
v-if="tooManyReactions"
class="emoji-reaction-expand faint"
@@ -42,7 +72,7 @@
<script src="./emoji_reactions.js"></script>
<style lang="scss">
-@import "../../variables";
+@import "../../mixins";
.EmojiReactions {
display: flex;
@@ -51,14 +81,43 @@
--emoji-size: calc(1.25em * var(--emojiReactionsScale, 1));
- .emoji-reaction {
- padding: 0 0.5em;
- margin-right: 0.5em;
+ .emoji-reaction-container {
+ display: flex;
+ align-items: stretch;
margin-top: 0.5em;
+ margin-right: 0.5em;
+
+ .emoji-reaction-popover {
+ padding: 0;
+
+ .emoji-reaction-count-button {
+ margin: 0;
+ height: 100%;
+ border-top-left-radius: 0;
+ border-bottom-left-radius: 0;
+ box-sizing: border-box;
+ min-width: 2em;
+ display: inline-flex;
+ justify-content: center;
+ align-items: center;
+
+ &.-picked-reaction {
+ border: 1px solid var(--accent);
+ margin-right: -1px;
+ }
+ }
+ }
+ }
+
+ .emoji-reaction {
+ padding-left: 0.5em;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+ margin: 0;
.reaction-emoji {
width: var(--emoji-size);
@@ -85,19 +144,42 @@
outline: none;
}
- &.not-clickable {
- cursor: default;
-
- &:hover {
- box-shadow: $fallback--buttonShadow;
- box-shadow: var(--buttonShadow);
- }
+ .svg-inline--fa {
+ color: var(--text);
}
&.-picked-reaction {
- border: 1px solid var(--accent, $fallback--link);
+ border: 1px solid var(--accent);
margin-left: -1px; // offset the border, can't use inset shadows either
- margin-right: calc(0.5em - 1px);
+ margin-right: -1px;
+
+ .svg-inline--fa {
+ color: var(--accent);
+ }
+ }
+
+ @include unfocused-style {
+ .focus-marker {
+ visibility: hidden;
+ }
+
+ .active-marker {
+ visibility: visible;
+ }
+ }
+
+ @include focused-style {
+ .svg-inline--fa {
+ color: var(--accent);
+ }
+
+ .focus-marker {
+ visibility: visible;
+ }
+
+ .active-marker {
+ visibility: hidden;
+ }
}
}
diff --git a/src/components/extra_buttons/extra_buttons.js b/src/components/extra_buttons/extra_buttons.js
index 48b960b2..e2c88ceb 100644
--- a/src/components/extra_buttons/extra_buttons.js
+++ b/src/components/extra_buttons/extra_buttons.js
@@ -1,4 +1,5 @@
import Popover from '../popover/popover.vue'
+import genRandomSeed from '../../services/random_seed/random_seed.service.js'
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
@@ -40,7 +41,8 @@ const ExtraButtons = {
data () {
return {
expanded: false,
- showingDeleteDialog: false
+ showingDeleteDialog: false,
+ randomSeed: genRandomSeed()
}
},
methods: {
@@ -152,6 +154,15 @@ const ExtraButtons = {
editingAvailable () { return this.$store.state.instance.editingAvailable },
shouldConfirmDelete () {
return this.$store.getters.mergedConfig.modalOnDelete
+ },
+ triggerAttrs () {
+ return {
+ title: this.$t('status.more_actions'),
+ id: `popup-trigger-${this.randomSeed}`,
+ 'aria-controls': `popup-menu-${this.randomSeed}`,
+ 'aria-expanded': this.expanded,
+ 'aria-haspopup': 'menu'
+ }
}
}
}
diff --git a/src/components/extra_buttons/extra_buttons.vue b/src/components/extra_buttons/extra_buttons.vue
index c1c15c0f..7b38d974 100644
--- a/src/components/extra_buttons/extra_buttons.vue
+++ b/src/components/extra_buttons/extra_buttons.vue
@@ -2,6 +2,7 @@
<Popover
class="ExtraButtons"
trigger="click"
+ :trigger-attrs="triggerAttrs"
placement="top"
:offset="{ y: 5 }"
:bound-to="{ x: 'container' }"
@@ -10,10 +11,15 @@
@close="onClose"
>
<template #content="{close}">
- <div class="dropdown-menu">
+ <div
+ :id="`popup-menu-${randomSeed}`"
+ class="dropdown-menu"
+ role="menu"
+ >
<button
v-if="canMute && !status.thread_muted"
- class="button-default dropdown-item dropdown-item-icon"
+ class="menu-item dropdown-item dropdown-item-icon"
+ role="menuitem"
@click.prevent="muteConversation"
>
<FAIcon
@@ -23,7 +29,8 @@
</button>
<button
v-if="canMute && status.thread_muted"
- class="button-default dropdown-item dropdown-item-icon"
+ class="menu-item dropdown-item dropdown-item-icon"
+ role="menuitem"
@click.prevent="unmuteConversation"
>
<FAIcon
@@ -33,7 +40,8 @@
</button>
<button
v-if="!status.pinned && canPin"
- class="button-default dropdown-item dropdown-item-icon"
+ class="menu-item dropdown-item dropdown-item-icon"
+ role="menuitem"
@click.prevent="pinStatus"
@click="close"
>
@@ -44,7 +52,8 @@
</button>
<button
v-if="status.pinned && canPin"
- class="button-default dropdown-item dropdown-item-icon"
+ class="menu-item dropdown-item dropdown-item-icon"
+ role="menuitem"
@click.prevent="unpinStatus"
@click="close"
>
@@ -56,7 +65,8 @@
<template v-if="canBookmark">
<button
v-if="!status.bookmarked"
- class="button-default dropdown-item dropdown-item-icon"
+ class="menu-item dropdown-item dropdown-item-icon"
+ role="menuitem"
@click.prevent="bookmarkStatus"
@click="close"
>
@@ -67,7 +77,8 @@
</button>
<button
v-if="status.bookmarked"
- class="button-default dropdown-item dropdown-item-icon"
+ class="menu-item dropdown-item dropdown-item-icon"
+ role="menuitem"
@click.prevent="unbookmarkStatus"
@click="close"
>
@@ -79,7 +90,8 @@
</template>
<button
v-if="ownStatus && editingAvailable"
- class="button-default dropdown-item dropdown-item-icon"
+ class="menu-item dropdown-item dropdown-item-icon"
+ role="menuitem"
@click.prevent="editStatus"
@click="close"
>
@@ -90,7 +102,8 @@
</button>
<button
v-if="isEdited && editingAvailable"
- class="button-default dropdown-item dropdown-item-icon"
+ class="menu-item dropdown-item dropdown-item-icon"
+ role="menuitem"
@click.prevent="showStatusHistory"
@click="close"
>
@@ -101,7 +114,8 @@
</button>
<button
v-if="canDelete"
- class="button-default dropdown-item dropdown-item-icon"
+ class="menu-item dropdown-item dropdown-item-icon"
+ role="menuitem"
@click.prevent="deleteStatus"
@click="close"
>
@@ -111,7 +125,8 @@
/><span>{{ $t("status.delete") }}</span>
</button>
<button
- class="button-default dropdown-item dropdown-item-icon"
+ class="menu-item dropdown-item dropdown-item-icon"
+ role="menuitem"
@click.prevent="copyLink"
@click="close"
>
@@ -122,7 +137,8 @@
</button>
<a
v-if="!status.is_local"
- class="button-default dropdown-item dropdown-item-icon"
+ class="menu-item dropdown-item dropdown-item-icon"
+ role="menuitem"
title="Source"
:href="status.external_url"
target="_blank"
@@ -133,7 +149,8 @@
/><span>{{ $t("status.external_source") }}</span>
</a>
<button
- class="button-default dropdown-item dropdown-item-icon"
+ class="menu-item dropdown-item dropdown-item-icon"
+ role="menuitem"
@click.prevent="reportStatus"
@click="close"
>
@@ -184,7 +201,6 @@
<script src="./extra_buttons.js"></script>
<style lang="scss">
-@import "../../variables";
@import "../../mixins";
.ExtraButtons {
@@ -194,8 +210,7 @@
margin: -10px;
&:hover .svg-inline--fa {
- color: $fallback--text;
- color: var(--text, $fallback--text);
+ color: var(--text);
}
}
diff --git a/src/components/extra_notifications/extra_notifications.js b/src/components/extra_notifications/extra_notifications.js
new file mode 100644
index 00000000..1bb0f837
--- /dev/null
+++ b/src/components/extra_notifications/extra_notifications.js
@@ -0,0 +1,48 @@
+import { mapGetters } from 'vuex'
+
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faUserPlus,
+ faComments,
+ faBullhorn
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faUserPlus,
+ faComments,
+ faBullhorn
+)
+
+const ExtraNotifications = {
+ computed: {
+ shouldShowChats () {
+ return this.mergedConfig.showExtraNotifications && this.mergedConfig.showChatsInExtraNotifications && this.unreadChatCount
+ },
+ shouldShowAnnouncements () {
+ return this.mergedConfig.showExtraNotifications && this.mergedConfig.showAnnouncementsInExtraNotifications && this.unreadAnnouncementCount
+ },
+ shouldShowFollowRequests () {
+ return this.mergedConfig.showExtraNotifications && this.mergedConfig.showFollowRequestsInExtraNotifications && this.followRequestCount
+ },
+ hasAnythingToShow () {
+ return this.shouldShowChats || this.shouldShowAnnouncements || this.shouldShowFollowRequests
+ },
+ shouldShowCustomizationTip () {
+ return this.mergedConfig.showExtraNotificationsTip && this.hasAnythingToShow
+ },
+ currentUser () {
+ return this.$store.state.users.currentUser
+ },
+ ...mapGetters(['unreadChatCount', 'unreadAnnouncementCount', 'followRequestCount', 'mergedConfig'])
+ },
+ methods: {
+ openNotificationSettings () {
+ return this.$store.dispatch('openSettingsModalTab', 'notifications')
+ },
+ dismissConfigurationTip () {
+ return this.$store.dispatch('setOption', { name: 'showExtraNotificationsTip', value: false })
+ }
+ }
+}
+
+export default ExtraNotifications
diff --git a/src/components/extra_notifications/extra_notifications.vue b/src/components/extra_notifications/extra_notifications.vue
new file mode 100644
index 00000000..600c99bb
--- /dev/null
+++ b/src/components/extra_notifications/extra_notifications.vue
@@ -0,0 +1,110 @@
+<template>
+ <div class="ExtraNotifications">
+ <div
+ v-if="shouldShowChats"
+ class="notification unseen"
+ >
+ <div class="notification-overlay" />
+ <router-link
+ class="button-unstyled -link extra-notification"
+ :to="{ name: 'chats', params: { username: currentUser.screen_name } }"
+ >
+ <FAIcon
+ fixed-width
+ class="fa-scale-110 icon"
+ icon="comments"
+ />
+ {{ $tc('notifications.unread_chats', unreadChatCount, { num: unreadChatCount }) }}
+ </router-link>
+ </div>
+ <div
+ v-if="shouldShowAnnouncements"
+ class="notification unseen"
+ >
+ <div class="notification-overlay" />
+ <router-link
+ class="button-unstyled -link extra-notification"
+ :to="{ name: 'announcements' }"
+ >
+ <FAIcon
+ fixed-width
+ class="fa-scale-110 icon"
+ icon="bullhorn"
+ />
+ {{ $tc('notifications.unread_announcements', unreadAnnouncementCount, { num: unreadAnnouncementCount }) }}
+ </router-link>
+ </div>
+ <div
+ v-if="shouldShowFollowRequests"
+ class="notification unseen"
+ >
+ <div class="notification-overlay" />
+ <router-link
+ class="button-unstyled -link extra-notification"
+ :to="{ name: 'friend-requests' }"
+ >
+ <FAIcon
+ fixed-width
+ class="fa-scale-110 icon"
+ icon="user-plus"
+ />
+ {{ $tc('notifications.unread_follow_requests', followRequestCount, { num: followRequestCount }) }}
+ </router-link>
+ </div>
+ <i18n-t
+ v-if="shouldShowCustomizationTip"
+ tag="span"
+ class="notification tip extra-notification"
+ keypath="notifications.configuration_tip"
+ >
+ <template #theSettings>
+ <button
+ class="button-unstyled -link"
+ @click="openNotificationSettings"
+ >
+ {{ $t('notifications.configuration_tip_settings') }}
+ </button>
+ </template>
+ <template #dismiss>
+ <button
+ class="button-unstyled -link"
+ @click="dismissConfigurationTip"
+ >
+ {{ $t('notifications.configuration_tip_dismiss') }}
+ </button>
+ </template>
+ </i18n-t>
+ </div>
+</template>
+
+<script src="./extra_notifications.js" />
+
+<style lang="scss">
+.ExtraNotifications {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+
+ .notification {
+ width: 100%;
+ border-bottom: 1px solid;
+ border-color: var(--border);
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+ }
+
+ .extra-notification {
+ padding: 1em;
+ }
+
+ .icon {
+ margin-right: 0.5em;
+ }
+
+ .tip {
+ display: inline;
+ }
+}
+</style>
diff --git a/src/components/favorite_button/favorite_button.vue b/src/components/favorite_button/favorite_button.vue
index 8c883c13..2e0dd047 100644
--- a/src/components/favorite_button/favorite_button.vue
+++ b/src/components/favorite_button/favorite_button.vue
@@ -65,7 +65,6 @@
<script src="./favorite_button.js"></script>
<style lang="scss">
-@import "../../variables";
@import "../../mixins";
.FavoriteButton {
@@ -88,8 +87,7 @@
&:hover .svg-inline--fa,
&.-favorited .svg-inline--fa {
- color: $fallback--cOrange;
- color: var(--cOrange, $fallback--cOrange);
+ color: var(--cOrange);
}
@include unfocused-style {
diff --git a/src/components/flash/flash.vue b/src/components/flash/flash.vue
index 9f58d314..c9fc730b 100644
--- a/src/components/flash/flash.vue
+++ b/src/components/flash/flash.vue
@@ -42,8 +42,6 @@
<script src="./flash.js"></script>
<style lang="scss">
-@import "../../variables";
-
.Flash {
display: inline-block;
width: 100%;
diff --git a/src/components/font_control/font_control.vue b/src/components/font_control/font_control.vue
index e2ba74d1..d2d1b388 100644
--- a/src/components/font_control/font_control.vue
+++ b/src/components/font_control/font_control.vue
@@ -14,7 +14,7 @@
v-if="typeof fallback !== 'undefined'"
:id="name + '-o'"
:aria-labelledby="name + '-label'"
- class="opt exlcude-disabled visible-for-screenreader-only"
+ class="input -checkbox opt exlcude-disabled visible-for-screenreader-only"
type="checkbox"
:checked="present"
@change="$emit('update:modelValue', typeof modelValue === 'undefined' ? fallback : undefined)"
@@ -44,7 +44,7 @@
v-if="isCustom"
:id="name"
v-model="family"
- class="custom-font"
+ class="input custom-font"
type="text"
>
</div>
@@ -53,8 +53,6 @@
<script src="./font_control.js"></script>
<style lang="scss">
-@import "../../variables";
-
.font-control {
input.custom-font {
min-width: 10em;
diff --git a/src/components/fun_text.style.js b/src/components/fun_text.style.js
new file mode 100644
index 00000000..2d3ac154
--- /dev/null
+++ b/src/components/fun_text.style.js
@@ -0,0 +1,40 @@
+export default {
+ name: 'FunText',
+ selector: '/*fun-text*/',
+ virtual: true,
+ variants: {
+ greentext: '.greentext',
+ cyantext: '.cyantext'
+ },
+ states: {
+ faint: '.faint'
+ },
+ defaultRules: [
+ {
+ directives: {
+ textColor: '--text',
+ textAuto: 'preserve'
+ }
+ },
+ {
+ state: ['faint'],
+ directives: {
+ textOpacity: 0.5
+ }
+ },
+ {
+ variant: 'greentext',
+ directives: {
+ textColor: '--cGreen',
+ textAuto: 'preserve'
+ }
+ },
+ {
+ variant: 'cyantext',
+ directives: {
+ textColor: '--cBlue',
+ textAuto: 'preserve'
+ }
+ }
+ ]
+}
diff --git a/src/components/gallery/gallery.vue b/src/components/gallery/gallery.vue
index 96b554e3..40048285 100644
--- a/src/components/gallery/gallery.vue
+++ b/src/components/gallery/gallery.vue
@@ -87,8 +87,6 @@
<script src='./gallery.js'></script>
<style lang="scss">
-@import "../../variables";
-
.Gallery {
.gallery-rows {
display: flex;
diff --git a/src/components/global_notice_list/global_notice_list.vue b/src/components/global_notice_list/global_notice_list.vue
index 0e58476f..140d0d54 100644
--- a/src/components/global_notice_list/global_notice_list.vue
+++ b/src/components/global_notice_list/global_notice_list.vue
@@ -4,7 +4,7 @@
v-for="(notice, index) in notices"
:key="index"
class="alert global-notice"
- :class="{ ['global-' + notice.level]: true }"
+ :class="{ [notice.level]: true }"
>
<div class="notice-message">
{{ $t(notice.messageKey, notice.messageArgs) }}
@@ -25,14 +25,12 @@
<script src="./global_notice_list.js"></script>
<style lang="scss">
-@import "../../variables";
-
.global-notice-list {
position: fixed;
top: calc(var(--navbar-height) + 0.5em);
width: 100%;
pointer-events: none;
- z-index: var(--ZI_navbar_popovers);
+ z-index: var(--ZI_modals_popovers);
display: flex;
flex-direction: column;
align-items: center;
@@ -52,48 +50,8 @@
}
}
- .global-error {
- background-color: var(--alertPopupError, $fallback--cRed);
- color: var(--alertPopupErrorText, $fallback--text);
-
- .svg-inline--fa {
- color: var(--alertPopupErrorText, $fallback--text);
- }
- }
-
- .global-warning {
- background-color: var(--alertPopupWarning, $fallback--cOrange);
- color: var(--alertPopupWarningText, $fallback--text);
-
- .svg-inline--fa {
- color: var(--alertPopupWarningText, $fallback--text);
- }
- }
-
- .global-success {
- background-color: var(--alertPopupSuccess, $fallback--cGreen);
- color: var(--alertPopupSuccessText, $fallback--text);
-
- .svg-inline--fa {
- color: var(--alertPopupSuccessText, $fallback--text);
- }
- }
-
- .global-info {
- background-color: var(--alertPopupNeutral, $fallback--fg);
- color: var(--alertPopupNeutralText, $fallback--text);
-
- .svg-inline--fa {
- color: var(--alertPopupNeutralText, $fallback--text);
- }
- }
-
.close-notice {
padding-right: 0.2em;
-
- .svg-inline--fa:hover {
- opacity: 0.6;
- }
}
}
</style>
diff --git a/src/components/icon.style.js b/src/components/icon.style.js
new file mode 100644
index 00000000..6cb9e4e3
--- /dev/null
+++ b/src/components/icon.style.js
@@ -0,0 +1,14 @@
+export default {
+ name: 'Icon',
+ virtual: true,
+ selector: '.svg-inline--fa',
+ defaultRules: [
+ {
+ component: 'Icon',
+ directives: {
+ textColor: '$blend(--stack, 0.5, --parent--text)',
+ textAuto: 'no-auto'
+ }
+ }
+ ]
+}
diff --git a/src/components/image_cropper/image_cropper.vue b/src/components/image_cropper/image_cropper.vue
index 8c48a387..8647ed4d 100644
--- a/src/components/image_cropper/image_cropper.vue
+++ b/src/components/image_cropper/image_cropper.vue
@@ -41,7 +41,7 @@
<input
ref="input"
type="file"
- class="image-cropper-img-input"
+ class="input image-cropper-img-input"
:accept="mimes"
>
</div>
diff --git a/src/components/importer/importer.vue b/src/components/importer/importer.vue
index 2a63b31a..12779b45 100644
--- a/src/components/importer/importer.vue
+++ b/src/components/importer/importer.vue
@@ -3,6 +3,7 @@
<form>
<input
ref="input"
+ class="input"
type="file"
@change="change"
>
diff --git a/src/components/input.style.js b/src/components/input.style.js
new file mode 100644
index 00000000..139a0034
--- /dev/null
+++ b/src/components/input.style.js
@@ -0,0 +1,60 @@
+const hoverGlow = {
+ x: 0,
+ y: 0,
+ blur: 4,
+ spread: 0,
+ color: '--text',
+ alpha: 1
+}
+
+export default {
+ name: 'Input',
+ selector: '.input',
+ variant: {
+ checkbox: '.-checkbox',
+ radio: '.-radio'
+ },
+ states: {
+ disabled: ':disabled',
+ hover: ':hover:not(:disabled)',
+ focused: ':focus-within'
+ },
+ validInnerComponents: [
+ 'Text'
+ ],
+ defaultRules: [
+ {
+ component: 'Root',
+ directives: {
+ '--defaultInputBevel': 'shadow | $borderSide(#FFFFFF, bottom, 0.2)| $borderSide(#000000, top, 0.2)'
+ }
+ },
+ {
+ variant: 'checkbox',
+ directives: {
+ roundness: 1
+ }
+ },
+ {
+ directives: {
+ '--font': 'generic | inherit',
+ background: '--fg, -5',
+ roundness: 3,
+ shadow: [{
+ x: 0,
+ y: 0,
+ blur: 2,
+ spread: 0,
+ color: '#000000',
+ alpha: 1
+ }, '--defaultInputBevel']
+ }
+ },
+ {
+ state: ['hover'],
+ directives: {
+ shadow: [hoverGlow, '--defaultInputBevel']
+ }
+ }
+ ]
+}
diff --git a/src/components/interactions/interactions.js b/src/components/interactions/interactions.js
index 1ae1d01c..fc441b90 100644
--- a/src/components/interactions/interactions.js
+++ b/src/components/interactions/interactions.js
@@ -3,6 +3,7 @@ import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
const tabModeDict = {
mentions: ['mention'],
+ statuses: ['status'],
'likes+repeats': ['repeat', 'like'],
follows: ['follow'],
reactions: ['pleroma:emoji_reaction'],
diff --git a/src/components/interactions/interactions.vue b/src/components/interactions/interactions.vue
index b7291c02..a2c56af4 100644
--- a/src/components/interactions/interactions.vue
+++ b/src/components/interactions/interactions.vue
@@ -10,10 +10,14 @@
:on-switch="onModeSwitch"
>
<span
- key="mentions"
+ key="statuses"
:label="$t('nav.mentions')"
/>
<span
+ key="statuses"
+ :label="$t('interactions.statuses')"
+ />
+ <span
key="likes+repeats"
:label="$t('interactions.favs_repeats')"
/>
@@ -39,6 +43,7 @@
<Notifications
ref="notifications"
:no-heading="true"
+ :no-extra="true"
:minimal-mode="true"
:filter-mode="filterMode"
/>
diff --git a/src/components/interface_language_switcher/interface_language_switcher.vue b/src/components/interface_language_switcher/interface_language_switcher.vue
index a57e8761..f7d5ef7e 100644
--- a/src/components/interface_language_switcher/interface_language_switcher.vue
+++ b/src/components/interface_language_switcher/interface_language_switcher.vue
@@ -36,7 +36,9 @@
<button
class="button-default btn"
@click="addLanguage"
- >{{ $t('settings.add_language') }}</button>
+ >
+ {{ $t('settings.add_language') }}
+ </button>
</li>
</ul>
</div>
@@ -102,8 +104,6 @@ export default {
</script>
<style lang="scss">
-@import "../../variables";
-
.interface-language-switcher {
.language-select {
margin-right: 1em;
diff --git a/src/components/link-preview/link-preview.vue b/src/components/link-preview/link-preview.vue
index 09f341ac..8eb5a501 100644
--- a/src/components/link-preview/link-preview.vue
+++ b/src/components/link-preview/link-preview.vue
@@ -33,8 +33,6 @@
<script src="./link-preview.js"></script>
<style lang="scss">
-@import "../../variables";
-
.link-preview-card {
display: flex;
flex-direction: row;
@@ -51,8 +49,7 @@
width: 100%;
height: 100%;
object-fit: cover;
- border-radius: $fallback--attachmentRadius;
- border-radius: var(--attachmentRadius, $fallback--attachmentRadius);
+ border-radius: var(--roundness);
}
}
@@ -82,13 +79,10 @@
margin: 2em 0;
}
- color: $fallback--text;
- color: var(--text, $fallback--text);
+ color: var(--text);
border-style: solid;
border-width: 1px;
- border-radius: $fallback--attachmentRadius;
- border-radius: var(--attachmentRadius, $fallback--attachmentRadius);
- border-color: $fallback--border;
- border-color: var(--border, $fallback--border);
+ border-radius: var(--roundness);
+ border-color: var(--border);
}
</style>
diff --git a/src/components/link.style.js b/src/components/link.style.js
new file mode 100644
index 00000000..d13cef33
--- /dev/null
+++ b/src/components/link.style.js
@@ -0,0 +1,24 @@
+export default {
+ name: 'Link',
+ selector: 'a',
+ virtual: true,
+ states: {
+ faint: '.faint'
+ },
+ defaultRules: [
+ {
+ component: 'Link',
+ directives: {
+ textColor: '--link'
+ }
+ },
+ {
+ component: 'Link',
+ state: ['faint'],
+ directives: {
+ textOpacity: 0.5,
+ textOpacityMode: 'fake'
+ }
+ }
+ ]
+}
diff --git a/src/components/list/list.vue b/src/components/list/list.vue
index f17766b4..c885bacd 100644
--- a/src/components/list/list.vue
+++ b/src/components/list/list.vue
@@ -1,9 +1,14 @@
<template>
- <div class="list">
+ <div
+ class="list"
+ role="list"
+ >
<div
v-for="item in items"
:key="getKey(item)"
class="list-item"
+ :class="[getClass(item), nonInteractive ? '-non-interactive' : '']"
+ role="listitem"
>
<slot
name="item"
@@ -29,24 +34,15 @@ export default {
getKey: {
type: Function,
default: item => item.id
+ },
+ getClass: {
+ type: Function,
+ default: item => ''
+ },
+ nonInteractive: {
+ type: Boolean,
+ default: false
}
}
}
</script>
-
-<style lang="scss">
-@import "../../variables";
-
-.list {
- &-item:not(:last-child) {
- border-bottom: 1px solid;
- border-bottom-color: $fallback--border;
- border-bottom-color: var(--border, $fallback--border);
- }
-
- &-empty-content {
- text-align: center;
- padding: 10px;
- }
-}
-</style>
diff --git a/src/components/list/list_item.style.js b/src/components/list/list_item.style.js
new file mode 100644
index 00000000..e82a0a83
--- /dev/null
+++ b/src/components/list/list_item.style.js
@@ -0,0 +1,48 @@
+export default {
+ name: 'ListItem',
+ selector: '.list-item',
+ states: {
+ active: '.-active',
+ hover: ':hover:not(.-non-interactive)'
+ },
+ validInnerComponents: [
+ 'Text',
+ 'Link',
+ 'Icon',
+ 'Border',
+ 'Button',
+ 'ButtonUnstyled',
+ 'RichContent',
+ 'Input',
+ 'Avatar'
+ ],
+ defaultRules: [
+ {
+ directives: {
+ background: '--bg',
+ opacity: 0
+ }
+ },
+ {
+ state: ['active'],
+ directives: {
+ background: '--inheritedBackground, 10',
+ opacity: 1
+ }
+ },
+ {
+ state: ['hover'],
+ directives: {
+ background: '--inheritedBackground, 10',
+ opacity: 1
+ }
+ },
+ {
+ state: ['hover', 'active'],
+ directives: {
+ background: '--inheritedBackground, 20',
+ opacity: 1
+ }
+ }
+ ]
+}
diff --git a/src/components/lists_card/lists_card.vue b/src/components/lists_card/lists_card.vue
index 925da3a5..a5dc6371 100644
--- a/src/components/lists_card/lists_card.vue
+++ b/src/components/lists_card/lists_card.vue
@@ -21,8 +21,6 @@
<script src="./lists_card.js"></script>
<style lang="scss">
-@import "../../variables";
-
.list-card {
display: flex;
}
@@ -35,18 +33,6 @@
.button-list-edit {
margin: 0;
padding: 1em;
- color: $fallback--link;
- color: var(--link, $fallback--link);
-
- &:hover {
- background-color: $fallback--lightBg;
- background-color: var(--selectedMenu, $fallback--lightBg);
- color: $fallback--link;
- color: var(--selectedMenuText, $fallback--link);
-
- --faint: var(--selectedMenuFaintText, $fallback--faint);
- --faintLink: var(--selectedMenuFaintLink, $fallback--faint);
- --lightText: var(--selectedMenuLightText, $fallback--lightText);
- }
+ color: var(--link);
}
</style>
diff --git a/src/components/lists_edit/lists_edit.vue b/src/components/lists_edit/lists_edit.vue
index eec0f978..3c2066f6 100644
--- a/src/components/lists_edit/lists_edit.vue
+++ b/src/components/lists_edit/lists_edit.vue
@@ -36,6 +36,7 @@
id="list-edit-title"
ref="title"
v-model="titleDraft"
+ class="input"
>
<button
v-if="id"
@@ -164,8 +165,6 @@
<script src="./lists_edit.js"></script>
<style lang="scss">
-@import "../../variables";
-
.ListEdit {
--panel-body-padding: 0.5em;
diff --git a/src/components/lists_user_search/lists_user_search.vue b/src/components/lists_user_search/lists_user_search.vue
index 6ca107e6..0f6ec125 100644
--- a/src/components/lists_user_search/lists_user_search.vue
+++ b/src/components/lists_user_search/lists_user_search.vue
@@ -10,6 +10,7 @@
<input
ref="search"
v-model="query"
+ class="input"
:placeholder="$t('lists.search')"
@input="onInput"
>
@@ -27,8 +28,6 @@
<script src="./lists_user_search.js"></script>
<style lang="scss">
-@import "../../variables";
-
.ListsUserSearch {
.input-wrap {
display: flex;
diff --git a/src/components/login_form/login_form.vue b/src/components/login_form/login_form.vue
index 829e88ad..adc2dd5b 100644
--- a/src/components/login_form/login_form.vue
+++ b/src/components/login_form/login_form.vue
@@ -18,7 +18,7 @@
id="username"
v-model="user.username"
:disabled="loggingIn"
- class="form-control"
+ class="input form-control"
:placeholder="$t('login.placeholder')"
>
</div>
@@ -29,7 +29,7 @@
ref="passwordInput"
v-model="user.password"
:disabled="loggingIn"
- class="form-control"
+ class="input form-control"
type="password"
>
</div>
@@ -93,8 +93,6 @@
<script src="./login_form.js"></script>
<style lang="scss">
-@import "../../variables";
-
.login-form {
display: flex;
flex-direction: column;
diff --git a/src/components/media_upload/media_upload.js b/src/components/media_upload/media_upload.js
index cfd42d4c..8c9e5f71 100644
--- a/src/components/media_upload/media_upload.js
+++ b/src/components/media_upload/media_upload.js
@@ -23,6 +23,11 @@ const mediaUpload = {
}
},
methods: {
+ onClick () {
+ if (this.uploadReady) {
+ this.$refs.input.click()
+ }
+ },
uploadFile (file) {
const self = this
const store = this.$store
@@ -69,10 +74,15 @@ const mediaUpload = {
this.multiUpload(target.files)
}
},
- props: [
- 'dropFiles',
- 'disabled'
- ],
+ props: {
+ dropFiles: Object,
+ disabled: Boolean,
+ normalButton: Boolean,
+ acceptTypes: {
+ type: String,
+ default: '*/*'
+ }
+ },
watch: {
dropFiles: function (fileInfos) {
if (!this.uploading) {
diff --git a/src/components/media_upload/media_upload.vue b/src/components/media_upload/media_upload.vue
index 2799495b..047e3483 100644
--- a/src/components/media_upload/media_upload.vue
+++ b/src/components/media_upload/media_upload.vue
@@ -1,8 +1,9 @@
<template>
- <label
+ <button
class="media-upload"
- :class="{ disabled: disabled }"
+ :class="[normalButton ? 'button-default btn' : 'button-unstyled', { disabled }]"
:title="$t('tool_tip.media_upload')"
+ @click="onClick"
>
<FAIcon
v-if="uploading"
@@ -15,27 +16,33 @@
class="new-icon"
icon="upload"
/>
+ <template v-if="normalButton">
+ {{ ' ' }}
+ {{ uploading ? $t('general.loading') : $t('tool_tip.media_upload') }}
+ </template>
<input
v-if="uploadReady"
+ ref="input"
class="hidden-input-file"
:disabled="disabled"
type="file"
multiple="true"
+ :accept="acceptTypes"
@change="change"
>
- </label>
+ </button>
</template>
<script src="./media_upload.js"></script>
<style lang="scss">
-@import "../../variables";
-
.media-upload {
- cursor: pointer; // We use <label> for interactivity... i wonder if it's fine
-
.hidden-input-file {
display: none;
}
}
- </style>
+
+label.media-upload {
+ cursor: pointer; // We use <label> for interactivity... i wonder if it's fine
+}
+</style>
diff --git a/src/components/mention_link/mention_link.scss b/src/components/mention_link/mention_link.scss
index 69e9fed1..42f57294 100644
--- a/src/components/mention_link/mention_link.scss
+++ b/src/components/mention_link/mention_link.scss
@@ -1,10 +1,7 @@
-@import "../../variables";
-
.MentionLink {
position: relative;
white-space: normal;
display: inline;
- color: var(--link);
word-break: normal;
& .new,
@@ -14,7 +11,7 @@
}
.mention-avatar {
- border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius);
+ border-radius: var(--roundness);
width: 1.5em;
height: 1.5em;
vertical-align: middle;
@@ -61,8 +58,10 @@
}
&.-has-selection {
- color: var(--alertNeutralText, $fallback--text);
- background-color: var(--alertNeutral, $fallback--fg);
+ --color: var(--selectionText);
+ --link: var(--selectionText);
+
+ background-color: var(--selectionBackground);
}
.at {
@@ -102,7 +101,7 @@
}
.serverName.-faded {
- color: var(--faintLink, $fallback--link);
+ color: var(--linkFaint);
}
}
diff --git a/src/components/mention_link/mention_link.vue b/src/components/mention_link/mention_link.vue
index 869a3257..5db837a2 100644
--- a/src/components/mention_link/mention_link.vue
+++ b/src/components/mention_link/mention_link.vue
@@ -22,7 +22,7 @@
:class="classnames"
>
<a
- class="short button-unstyled"
+ class="short"
:class="{ '-with-tooltip': shouldShowTooltip }"
:href="url"
@click.prevent="onClick"
diff --git a/src/components/mentions_line/mentions_line.vue b/src/components/mentions_line/mentions_line.vue
index 64c19bf1..daab64a3 100644
--- a/src/components/mentions_line/mentions_line.vue
+++ b/src/components/mentions_line/mentions_line.vue
@@ -22,13 +22,13 @@
/>
</span><button
v-if="!expanded"
- class="button-unstyled showMoreLess"
+ class="button-unstyled -link showMoreLess"
@click="toggleShowMore"
>
{{ $t('status.plus_more', { number: extraMentions.length }) }}
</button><button
v-if="expanded"
- class="button-unstyled showMoreLess"
+ class="button-unstyled -link showMoreLess"
@click="toggleShowMore"
>
{{ $t('general.show_less') }}
diff --git a/src/components/menu_item.style.js b/src/components/menu_item.style.js
new file mode 100644
index 00000000..51388155
--- /dev/null
+++ b/src/components/menu_item.style.js
@@ -0,0 +1,90 @@
+export default {
+ name: 'MenuItem',
+ selector: '.menu-item',
+ validInnerComponents: [
+ 'Text',
+ 'Icon',
+ 'Input',
+ 'Border',
+ 'ButtonUnstyled',
+ 'Badge',
+ 'Avatar'
+ ],
+ states: {
+ hover: ':hover',
+ active: '.-active'
+ },
+ defaultRules: [
+ {
+ directives: {
+ background: '--bg',
+ opacity: 0
+ }
+ },
+ {
+ state: ['hover'],
+ directives: {
+ background: '$mod(--bg, 5)',
+ opacity: 1
+ }
+ },
+ {
+ state: ['active'],
+ directives: {
+ background: '$mod(--bg, 10)',
+ opacity: 1
+ }
+ },
+ {
+ state: ['active', 'hover'],
+ directives: {
+ background: '$mod(--bg, 15)',
+ opacity: 1
+ }
+ },
+ {
+ component: 'Text',
+ parent: {
+ component: 'MenuItem',
+ state: ['hover']
+ },
+ directives: {
+ textColor: '--link',
+ textAuto: 'no-preserve'
+ }
+ },
+ {
+ component: 'Text',
+ parent: {
+ component: 'MenuItem',
+ state: ['active']
+ },
+ directives: {
+ textColor: '--link',
+ textAuto: 'no-preserve'
+ }
+ },
+ {
+ component: 'Icon',
+ parent: {
+ component: 'MenuItem',
+ state: ['active']
+ },
+ directives: {
+ textColor: '--link',
+ textAuto: 'no-preserve'
+ }
+ },
+ {
+ component: 'Icon',
+ parent: {
+ component: 'MenuItem',
+ state: ['hover']
+ },
+ directives: {
+ textColor: '--link',
+ textAuto: 'no-preserve'
+ }
+ }
+ ]
+}
diff --git a/src/components/mfa_form/recovery_form.vue b/src/components/mfa_form/recovery_form.vue
index 5988fa51..27184fb4 100644
--- a/src/components/mfa_form/recovery_form.vue
+++ b/src/components/mfa_form/recovery_form.vue
@@ -16,7 +16,7 @@
<input
id="code"
v-model="code"
- class="form-control"
+ class="input form-control"
>
</div>
diff --git a/src/components/mfa_form/totp_form.vue b/src/components/mfa_form/totp_form.vue
index 709eb9b8..3ec617e9 100644
--- a/src/components/mfa_form/totp_form.vue
+++ b/src/components/mfa_form/totp_form.vue
@@ -18,7 +18,7 @@
<input
id="code"
v-model="code"
- class="form-control"
+ class="input form-control"
>
</div>
diff --git a/src/components/mobile_drawer.style.js b/src/components/mobile_drawer.style.js
new file mode 100644
index 00000000..398bc186
--- /dev/null
+++ b/src/components/mobile_drawer.style.js
@@ -0,0 +1,41 @@
+export default {
+ name: 'MobileDrawer',
+ selector: '.mobile-drawer',
+ validInnerComponents: [
+ 'Text',
+ 'Link',
+ 'Icon',
+ 'Border',
+ 'Button',
+ 'ButtonUnstyled',
+ 'Input',
+ 'PanelHeader',
+ 'MenuItem',
+ 'Notification',
+ 'Alert',
+ 'UserCard'
+ ],
+ defaultRules: [
+ {
+ directives: {
+ background: '--bg',
+ backgroundNoCssColor: 'yes'
+ }
+ },
+ {
+ component: 'PanelHeader',
+ parent: { component: 'MobileDrawer' },
+ directives: {
+ background: '--fg',
+ shadow: [{
+ x: 0,
+ y: 0,
+ blur: 4,
+ spread: 0,
+ color: '#000000',
+ alpha: 0.6
+ }]
+ }
+ }
+ ]
+}
diff --git a/src/components/mobile_nav/mobile_nav.js b/src/components/mobile_nav/mobile_nav.js
index dad1f6aa..8c9261b0 100644
--- a/src/components/mobile_nav/mobile_nav.js
+++ b/src/components/mobile_nav/mobile_nav.js
@@ -1,7 +1,10 @@
import SideDrawer from '../side_drawer/side_drawer.vue'
import Notifications from '../notifications/notifications.vue'
import ConfirmModal from '../confirm_modal/confirm_modal.vue'
-import { unseenNotificationsFromStore } from '../../services/notification_utils/notification_utils'
+import {
+ unseenNotificationsFromStore,
+ countExtraNotifications
+} from '../../services/notification_utils/notification_utils'
import GestureService from '../../services/gesture_service/gesture_service'
import NavigationPins from 'src/components/navigation/navigation_pins.vue'
import { mapGetters } from 'vuex'
@@ -11,7 +14,8 @@ import {
faBell,
faBars,
faArrowUp,
- faMinus
+ faMinus,
+ faCheckDouble
} from '@fortawesome/free-solid-svg-icons'
library.add(
@@ -19,7 +23,8 @@ library.add(
faBell,
faBars,
faArrowUp,
- faMinus
+ faMinus,
+ faCheckDouble
)
const MobileNav = {
@@ -50,8 +55,14 @@ const MobileNav = {
return unseenNotificationsFromStore(this.$store)
},
unseenNotificationsCount () {
+ return this.unseenNotifications.length + countExtraNotifications(this.$store)
+ },
+ unseenCount () {
return this.unseenNotifications.length
},
+ unseenCountBadgeText () {
+ return `${this.unseenCount ? this.unseenCount : ''}`
+ },
hideSitename () { return this.$store.state.instance.hideSitename },
sitename () { return this.$store.state.instance.name },
isChat () {
@@ -64,6 +75,9 @@ const MobileNav = {
shouldConfirmLogout () {
return this.$store.getters.mergedConfig.modalOnLogout
},
+ closingDrawerMarksAsSeen () {
+ return this.$store.getters.mergedConfig.closingDrawerMarksAsSeen
+ },
...mapGetters(['unreadChatCount'])
},
methods: {
@@ -78,7 +92,7 @@ const MobileNav = {
// make sure to mark notifs seen only when the notifs were open and not
// from close-calls.
this.notificationsOpen = false
- if (markRead) {
+ if (markRead && this.closingDrawerMarksAsSeen) {
this.markNotificationsAsSeen()
}
}
@@ -114,7 +128,6 @@ const MobileNav = {
this.hideConfirmLogout()
},
markNotificationsAsSeen () {
- // this.$refs.notifications.markAsSeen()
this.$store.dispatch('markNotificationsAsSeen')
},
onScroll ({ target: { scrollTop, clientHeight, scrollHeight } }) {
diff --git a/src/components/mobile_nav/mobile_nav.vue b/src/components/mobile_nav/mobile_nav.vue
index c2746abe..6e134ef2 100644
--- a/src/components/mobile_nav/mobile_nav.vue
+++ b/src/components/mobile_nav/mobile_nav.vue
@@ -20,7 +20,7 @@
/>
<div
v-if="(unreadChatCount && !chatsPinned) || unreadAnnouncementCount"
- class="alert-dot"
+ class="badge -dot -notification"
/>
</button>
<NavigationPins class="pins" />
@@ -37,20 +37,26 @@
/>
<div
v-if="unseenNotificationsCount"
- class="alert-dot"
+ class="badge -dot -notification"
/>
</button>
</div>
</nav>
<aside
v-if="currentUser"
- class="mobile-notifications-drawer"
+ class="mobile-notifications-drawer mobile-drawer"
:class="{ '-closed': !notificationsOpen }"
@touchstart.stop="notificationsTouchStart"
@touchmove.stop="notificationsTouchMove"
>
- <div class="mobile-notifications-header">
- <span class="title">{{ $t('notifications.notifications') }}</span>
+ <div class="panel-heading mobile-notifications-header">
+ <span class="title">
+ {{ $t('notifications.notifications') }}
+ <span
+ v-if="unseenCountBadgeText"
+ class="badge -notification unseen-count"
+ >{{ unseenCountBadgeText }}</span>
+ </span>
<span class="spacer" />
<button
v-if="notificationsAtTop"
@@ -67,6 +73,17 @@
</FALayers>
</button>
<button
+ v-if="!closingDrawerMarksAsSeen"
+ class="button-unstyled mobile-nav-button"
+ :title="$t('nav.mobile_notifications_mark_as_seen')"
+ @click.stop.prevent="markNotificationsAsSeen()"
+ >
+ <FAIcon
+ class="fa-scale-110 fa-old-padding"
+ icon="check-double"
+ />
+ </button>
+ <button
class="button-unstyled mobile-nav-button"
:title="$t('nav.mobile_notifications_close')"
@click.stop.prevent="closeMobileNotifications(true)"
@@ -106,8 +123,6 @@
<script src="./mobile_nav.js"></script>
<style lang="scss">
-@import "../../variables";
-
.MobileNav {
z-index: var(--ZI_navbar);
@@ -120,7 +135,7 @@
box-sizing: border-box;
a {
- color: var(--topBarLink, $fallback--link);
+ color: var(--link);
}
}
@@ -148,19 +163,6 @@
display: flex;
}
- .alert-dot {
- border-radius: 100%;
- height: 8px;
- width: 8px;
- position: absolute;
- left: calc(50% - 4px);
- top: calc(50% - 4px);
- margin-left: 6px;
- margin-top: -6px;
- background-color: $fallback--cRed;
- background-color: var(--badgeNotification, $fallback--cRed);
- }
-
.mobile-notifications-drawer {
width: 100%;
height: 100vh;
@@ -168,13 +170,13 @@
position: fixed;
top: 0;
left: 0;
- box-shadow: 1px 1px 4px rgb(0 0 0 / 60%);
- box-shadow: var(--panelShadow);
+ box-shadow: var(--shadow);
transition-property: transform;
transition-duration: 0.25s;
transform: translateX(0);
z-index: var(--ZI_navbar);
-webkit-overflow-scrolling: touch;
+ background: var(--background);
&.-closed {
transform: translateX(100%);
@@ -191,11 +193,7 @@
height: 50px;
line-height: 50px;
position: absolute;
- color: var(--topBarText);
- background-color: $fallback--fg;
- background-color: var(--topBar, $fallback--fg);
- box-shadow: 0 0 4px rgb(0 0 0 / 60%);
- box-shadow: var(--topBarShadow);
+ box-shadow: var(--shadow);
.spacer {
flex: 1;
@@ -221,10 +219,6 @@
height: calc(100vh - var(--navbar-height));
overflow-x: hidden;
overflow-y: scroll;
- color: $fallback--text;
- color: var(--text, $fallback--text);
- background-color: $fallback--bg;
- background-color: var(--bg, $fallback--bg);
.notifications {
padding: 0;
diff --git a/src/components/mobile_post_status_button/mobile_post_status_button.vue b/src/components/mobile_post_status_button/mobile_post_status_button.vue
index ef0f51fe..ba77cb2d 100644
--- a/src/components/mobile_post_status_button/mobile_post_status_button.vue
+++ b/src/components/mobile_post_status_button/mobile_post_status_button.vue
@@ -13,8 +13,6 @@
<script src="./mobile_post_status_button.js"></script>
<style lang="scss">
-@import "../../variables";
-
.MobilePostButton {
&.button-default {
width: 5em;
@@ -25,8 +23,6 @@
right: 1.5em;
// TODO: this needs its own color, it has to stand out enough and link color
// is not very optimal for this particular use.
- background-color: $fallback--fg;
- background-color: var(--btn, $fallback--fg);
display: flex;
justify-content: center;
align-items: center;
@@ -42,8 +38,7 @@
svg {
font-size: 1.5em;
- color: $fallback--text;
- color: var(--text, $fallback--text);
+ color: var(--text);
}
}
diff --git a/src/components/modal/modals.style.js b/src/components/modal/modals.style.js
new file mode 100644
index 00000000..c401a0cd
--- /dev/null
+++ b/src/components/modal/modals.style.js
@@ -0,0 +1,9 @@
+export default {
+ name: 'Modals',
+ selector: '.modal-view',
+ lazy: true,
+ validInnerComponents: [
+ 'Panel'
+ ],
+ defaultRules: []
+}
diff --git a/src/components/moderation_tools/moderation_tools.vue b/src/components/moderation_tools/moderation_tools.vue
index b708cdc8..df3bb655 100644
--- a/src/components/moderation_tools/moderation_tools.vue
+++ b/src/components/moderation_tools/moderation_tools.vue
@@ -12,13 +12,13 @@
<div class="dropdown-menu">
<span v-if="canGrantRole">
<button
- class="button-default dropdown-item"
+ class="menu-item dropdown-item menu-item"
@click="toggleRight(&quot;admin&quot;)"
>
{{ $t(!!user.rights.admin ? 'user_card.admin_menu.revoke_admin' : 'user_card.admin_menu.grant_admin') }}
</button>
<button
- class="button-default dropdown-item"
+ class="menu-item dropdown-item menu-item"
@click="toggleRight(&quot;moderator&quot;)"
>
{{ $t(!!user.rights.moderator ? 'user_card.admin_menu.revoke_moderator' : 'user_card.admin_menu.grant_moderator') }}
@@ -31,14 +31,14 @@
</span>
<button
v-if="canChangeActivationState"
- class="button-default dropdown-item"
+ class="menu-item dropdown-item menu-item"
@click="toggleActivationStatus()"
>
{{ $t(!!user.deactivated ? 'user_card.admin_menu.activate_account' : 'user_card.admin_menu.deactivate_account') }}
</button>
<button
v-if="canDeleteAccount"
- class="button-default dropdown-item"
+ class="menu-item dropdown-item menu-item"
@click="deleteUserDialog(true)"
>
{{ $t('user_card.admin_menu.delete_account') }}
@@ -50,74 +50,74 @@
/>
<span v-if="canUseTagPolicy">
<button
- class="button-default dropdown-item"
+ class="menu-item dropdown-item menu-item"
@click="toggleTag(tags.FORCE_NSFW)"
>
<span
- class="menu-checkbox"
+ class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.FORCE_NSFW) }"
/>
{{ $t('user_card.admin_menu.force_nsfw') }}
</button>
<button
- class="button-default dropdown-item"
+ class="menu-item dropdown-item menu-item"
@click="toggleTag(tags.STRIP_MEDIA)"
>
<span
- class="menu-checkbox"
+ class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.STRIP_MEDIA) }"
/>
{{ $t('user_card.admin_menu.strip_media') }}
</button>
<button
- class="button-default dropdown-item"
+ class="menu-item dropdown-item menu-item"
@click="toggleTag(tags.FORCE_UNLISTED)"
>
<span
- class="menu-checkbox"
+ class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.FORCE_UNLISTED) }"
/>
{{ $t('user_card.admin_menu.force_unlisted') }}
</button>
<button
- class="button-default dropdown-item"
+ class="menu-item dropdown-item menu-item"
@click="toggleTag(tags.SANDBOX)"
>
<span
- class="menu-checkbox"
+ class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.SANDBOX) }"
/>
{{ $t('user_card.admin_menu.sandbox') }}
</button>
<button
v-if="user.is_local"
- class="button-default dropdown-item"
+ class="menu-item dropdown-item menu-item"
@click="toggleTag(tags.DISABLE_REMOTE_SUBSCRIPTION)"
>
<span
- class="menu-checkbox"
+ class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.DISABLE_REMOTE_SUBSCRIPTION) }"
/>
{{ $t('user_card.admin_menu.disable_remote_subscription') }}
</button>
<button
v-if="user.is_local"
- class="button-default dropdown-item"
+ class="menu-item dropdown-item menu-item"
@click="toggleTag(tags.DISABLE_ANY_SUBSCRIPTION)"
>
<span
- class="menu-checkbox"
+ class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.DISABLE_ANY_SUBSCRIPTION) }"
/>
{{ $t('user_card.admin_menu.disable_any_subscription') }}
</button>
<button
v-if="user.is_local"
- class="button-default dropdown-item"
+ class="menu-item dropdown-item menu-item"
@click="toggleTag(tags.QUARANTINE)"
>
<span
- class="menu-checkbox"
+ class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.QUARANTINE) }"
/>
{{ $t('user_card.admin_menu.quarantine') }}
@@ -166,8 +166,6 @@
<script src="./moderation_tools.js"></script>
<style lang="scss">
-@import "../../variables";
-
.moderation-tools-popover {
height: 100%;
diff --git a/src/components/mrf_transparency_panel/mrf_transparency_panel.vue b/src/components/mrf_transparency_panel/mrf_transparency_panel.vue
index 97af4787..6dc86738 100644
--- a/src/components/mrf_transparency_panel/mrf_transparency_panel.vue
+++ b/src/components/mrf_transparency_panel/mrf_transparency_panel.vue
@@ -227,6 +227,5 @@
<script src="./mrf_transparency_panel.js"></script>
<style lang="scss">
-@import "../../variables";
@import "./mrf_transparency_panel";
</style>
diff --git a/src/components/nav_panel/nav_panel.vue b/src/components/nav_panel/nav_panel.vue
index 1a826cc4..bf608936 100644
--- a/src/components/nav_panel/nav_panel.vue
+++ b/src/components/nav_panel/nav_panel.vue
@@ -37,7 +37,8 @@
</NavigationEntry>
<div
v-show="showTimelines"
- class="timelines-background"
+ class="timelines-background menu-item-collapsible"
+ :class="{ '-expanded': showTimelines }"
>
<div class="timelines">
<NavigationEntry
@@ -57,12 +58,11 @@
>
<router-link
:title="$t('lists.manage_lists')"
- class="extra-button"
+ class="button-unstyled extra-button"
:to="{ name: 'lists' }"
@click.stop
>
<FAIcon
- class="extra-button"
fixed-width
icon="wrench"
/>
@@ -75,7 +75,8 @@
</NavigationEntry>
<div
v-show="showLists"
- class="timelines-background"
+ class="timelines-background menu-item-collapsible"
+ :class="{ '-expanded': showLists }"
>
<ListsMenuContent
:show-pin="editMode || forceEditMode"
@@ -102,12 +103,10 @@
<script src="./nav_panel.js"></script>
<style lang="scss">
-@import "../../variables";
-
.NavPanel {
.panel {
overflow: hidden;
- box-shadow: var(--panelShadow);
+ box-shadow: var(--shadow);
}
ul {
@@ -116,33 +115,6 @@
padding: 0;
}
- li {
- position: relative;
- border-bottom: 1px solid;
- border-color: $fallback--border;
- border-color: var(--border, $fallback--border);
- }
-
- > li {
- &:first-child .menu-item {
- border-top-right-radius: $fallback--panelRadius;
- border-top-right-radius: var(--panelRadius, $fallback--panelRadius);
- border-top-left-radius: $fallback--panelRadius;
- border-top-left-radius: var(--panelRadius, $fallback--panelRadius);
- }
-
- &:last-child .menu-item {
- border-bottom-right-radius: $fallback--panelRadius;
- border-bottom-right-radius: var(--panelRadius, $fallback--panelRadius);
- border-bottom-left-radius: $fallback--panelRadius;
- border-bottom-left-radius: var(--panelRadius, $fallback--panelRadius);
- }
- }
-
- li:last-child {
- border: none;
- }
-
.navigation-chevron {
margin-left: 0.8em;
margin-right: 0.8em;
@@ -156,16 +128,6 @@
.timelines-background {
padding: 0 0 0 0.6em;
- background-color: $fallback--lightBg;
- background-color: var(--selectedMenu, $fallback--lightBg);
- border-bottom: 1px solid;
- border-color: $fallback--border;
- border-color: var(--border, $fallback--border);
- }
-
- .timelines {
- background-color: $fallback--bg;
- background-color: var(--bg, $fallback--bg);
}
.nav-panel-heading {
diff --git a/src/components/navigation/navigation_entry.vue b/src/components/navigation/navigation_entry.vue
index 411ca536..4ea54ee3 100644
--- a/src/components/navigation/navigation_entry.vue
+++ b/src/components/navigation/navigation_entry.vue
@@ -1,7 +1,6 @@
<template>
<OptionalRouterLink
v-slot="{ isActive, href, navigate } = {}"
- ass="ass"
:to="routeTo"
>
<li
@@ -11,7 +10,7 @@
>
<component
:is="routeTo ? 'a' : 'button'"
- class="main-link button-unstyled"
+ class="main-link"
:href="href"
@click="navigate"
>
@@ -35,7 +34,7 @@
<slot />
<div
v-if="item.badgeGetter && getters[item.badgeGetter]"
- class="badge badge-notification"
+ class="badge -notification"
>
{{ getters[item.badgeGetter] }}
</div>
@@ -63,73 +62,53 @@
<script src="./navigation_entry.js"></script>
<style lang="scss">
-@import "../../variables";
+.NavigationEntry.menu-item {
+ --__line-height: 2.5em;
+ --__horizontal-gap: 0.5em;
+ --__vertical-gap: 0.4em;
-.NavigationEntry {
+ padding: 0;
display: flex;
- box-sizing: border-box;
align-items: baseline;
- height: 3.5em;
- line-height: 3.5em;
- padding: 0 1em;
- width: 100%;
- color: $fallback--link;
- color: var(--link, $fallback--link);
- .timelines-chevron {
- margin-right: 0;
+ &[aria-expanded] {
+ padding-right: var(--__horizontal-gap);
}
.main-link {
+ line-height: var(--__line-height);
+ box-sizing: border-box;
flex: 1;
+ padding: var(--__vertical-gap) var(--__horizontal-gap);
}
.menu-icon {
- margin-right: 0.8em;
+ line-height: var(--__line-height);
+ padding: 0;
+ width: var(--__line-height);
+ margin-right: var(--__horizontal-gap);
+ }
+
+ .timelines-chevron {
+ line-height: var(--__line-height);
+ padding: 0;
+ width: var(--__line-height);
+ margin-right: 0;
}
.extra-button {
- width: 3em;
+ line-height: var(--__line-height);
+ padding: 0;
+ width: var(--__line-height);
text-align: center;
&:last-child {
- margin-right: -0.8em;
- }
- }
-
- &:hover {
- background-color: $fallback--lightBg;
- background-color: var(--selectedMenu, $fallback--lightBg);
- color: $fallback--link;
- color: var(--selectedMenuText, $fallback--link);
-
- --faint: var(--selectedMenuFaintText, $fallback--faint);
- --faintLink: var(--selectedMenuFaintLink, $fallback--faint);
- --lightText: var(--selectedMenuLightText, $fallback--lightText);
-
- .menu-icon {
- --icon: var(--text, $fallback--icon);
+ margin-right: calc(-1 * var(--__horizontal-gap));
}
}
- &.-active {
- font-weight: bolder;
- background-color: $fallback--lightBg;
- background-color: var(--selectedMenu, $fallback--lightBg);
- color: $fallback--text;
- color: var(--selectedMenuText, $fallback--text);
-
- --faint: var(--selectedMenuFaintText, $fallback--faint);
- --faintLink: var(--selectedMenuFaintLink, $fallback--faint);
- --lightText: var(--selectedMenuLightText, $fallback--lightText);
-
- .menu-icon {
- --icon: var(--text, $fallback--icon);
- }
-
- &:hover {
- text-decoration: underline;
- }
+ .badge {
+ margin: 0 var(--__horizontal-gap);
}
}
</style>
diff --git a/src/components/navigation/navigation_pins.js b/src/components/navigation/navigation_pins.js
index ef78e44c..86c33d1f 100644
--- a/src/components/navigation/navigation_pins.js
+++ b/src/components/navigation/navigation_pins.js
@@ -45,6 +45,7 @@ const NavPanel = {
privateMode: state => state.instance.private,
federating: state => state.instance.federating,
pleromaChatMessagesAvailable: state => state.instance.pleromaChatMessagesAvailable,
+ supportsAnnouncements: state => state.announcements.supportsAnnouncements,
pinnedItems: state => new Set(state.serverSideStorage.prefsStorage.collections.pinnedNavItems)
}),
pinnedList () {
@@ -56,6 +57,7 @@ const NavPanel = {
],
{
hasChats: this.pleromaChatMessagesAvailable,
+ hasAnnouncements: this.supportsAnnouncements,
isFederating: this.federating,
isPrivate: this.privateMode,
currentUser: this.currentUser
@@ -75,6 +77,7 @@ const NavPanel = {
],
{
hasChats: this.pleromaChatMessagesAvailable,
+ hasAnnouncements: this.supportsAnnouncements,
isFederating: this.federating,
isPrivate: this.privateMode,
currentUser: this.currentUser
diff --git a/src/components/navigation/navigation_pins.vue b/src/components/navigation/navigation_pins.vue
index 4fbb4f95..36eb1ebe 100644
--- a/src/components/navigation/navigation_pins.vue
+++ b/src/components/navigation/navigation_pins.vue
@@ -3,7 +3,8 @@
<router-link
v-for="item in pinnedList"
:key="item.name"
- class="pinned-item"
+ class="button-unstyled pinned-item"
+ active-class="toggled"
:to="getRouteTo(item)"
:title="item.labelRaw || $t(item.label)"
>
@@ -18,7 +19,7 @@
>{{ item.iconLetter }}</span>
<div
v-if="item.badgeGetter && getters[item.badgeGetter]"
- class="alert-dot"
+ class="badge -dot -notification"
/>
</router-link>
</span>
@@ -27,25 +28,12 @@
<script src="./navigation_pins.js"></script>
<style lang="scss">
-@import "../../variables";
-
.NavigationPins {
display: flex;
flex-wrap: wrap;
overflow: hidden;
height: 100%;
- .alert-dot {
- border-radius: 100%;
- height: 0.5em;
- width: 0.5em;
- position: absolute;
- right: calc(50% - 0.75em);
- top: calc(50% - 0.5em);
- background-color: $fallback--cRed;
- background-color: var(--badgeNotification, $fallback--cRed);
- }
-
.pinned-item {
position: relative;
flex: 1 0 3em;
@@ -60,15 +48,8 @@
margin: 0;
}
- &.router-link-active {
- color: $fallback--text;
- color: var(--panelText, $fallback--text);
+ &.toggled {
border-bottom: 4px solid;
-
- & .svg-inline--fa,
- & .iconLetter {
- color: inherit;
- }
}
}
}
diff --git a/src/components/notification/notification.js b/src/components/notification/notification.js
index 420db4f0..0e938c42 100644
--- a/src/components/notification/notification.js
+++ b/src/components/notification/notification.js
@@ -50,6 +50,7 @@ const Notification = {
}
},
props: ['notification'],
+ emits: ['interacted'],
components: {
StatusContent,
UserAvatar,
@@ -72,6 +73,9 @@ const Notification = {
getUser (notification) {
return this.$store.state.users.usersObject[notification.from_profile.id]
},
+ interacted () {
+ this.$emit('interacted')
+ },
toggleMute () {
this.unmuted = !this.unmuted
},
@@ -95,6 +99,7 @@ const Notification = {
}
},
doApprove () {
+ this.$emit('interacted')
this.$store.state.api.backendInteractor.approveUser({ id: this.user.id })
this.$store.dispatch('removeFollowRequest', this.user)
this.$store.dispatch('markSingleNotificationAsSeen', { id: this.notification.id })
@@ -114,6 +119,7 @@ const Notification = {
}
},
doDeny () {
+ this.$emit('interacted')
this.$store.state.api.backendInteractor.denyUser({ id: this.user.id })
.then(() => {
this.$store.dispatch('dismissNotificationLocal', { id: this.notification.id })
diff --git a/src/components/notification/notification.scss b/src/components/notification/notification.scss
index 654aca3c..2dbced09 100644
--- a/src/components/notification/notification.scss
+++ b/src/components/notification/notification.scss
@@ -1,13 +1,15 @@
-@import "../../variables";
-
// TODO Copypaste from Status, should unify it somehow
.Notification {
border-bottom: 1px solid;
- border-color: $fallback--border;
- border-color: var(--border, $fallback--border);
+ border-color: var(--border);
word-wrap: break-word;
word-break: break-word;
+ &.Status {
+ /* stylelint-disable-next-line declaration-no-important */
+ background-color: transparent !important;
+ }
+
--emoji-size: 14px;
&:hover {
@@ -71,28 +73,22 @@
}
&.-type--repeat .type-icon {
- color: $fallback--cGreen;
- color: var(--cGreen, $fallback--cGreen);
+ color: var(--cGreen);
}
&.-type--follow .type-icon {
- color: $fallback--cBlue;
- color: var(--cBlue, $fallback--cBlue);
+ color: var(--cBlue);
}
&.-type--follow-request .type-icon {
- color: $fallback--cBlue;
- color: var(--cBlue, $fallback--cBlue);
+ color: var(--cBlue);
}
&.-type--like .type-icon {
- color: orange;
- color: $fallback--cOrange;
- color: var(--cOrange, $fallback--cOrange);
+ color: var(--cOrange);
}
&.-type--move .type-icon {
- color: $fallback--cBlue;
- color: var(--cBlue, $fallback--cBlue);
+ color: var(--cBlue);
}
}
diff --git a/src/components/notification/notification.style.js b/src/components/notification/notification.style.js
new file mode 100644
index 00000000..0d36760a
--- /dev/null
+++ b/src/components/notification/notification.style.js
@@ -0,0 +1,17 @@
+export default {
+ name: 'Notification',
+ selector: '.Notification',
+ validInnerComponents: [
+ 'Text',
+ 'Link',
+ 'Icon',
+ 'Border',
+ 'Button',
+ 'ButtonUnstyled',
+ 'RichContent',
+ 'Input',
+ 'Avatar',
+ 'Attachment'
+ ],
+ defaultRules: []
+}
diff --git a/src/components/notification/notification.vue b/src/components/notification/notification.vue
index 4d801c5e..f84b75de 100644
--- a/src/components/notification/notification.vue
+++ b/src/components/notification/notification.vue
@@ -1,11 +1,12 @@
<template>
<article
- v-if="notification.type === 'mention'"
+ v-if="notification.type === 'mention' || notification.type === 'status'"
>
<Status
class="Notification"
:compact="true"
:statusoid="notification.status"
+ @interacted="interacted"
/>
</article>
<article v-else>
@@ -125,7 +126,8 @@
v-if="notification.emoji_url"
class="emoji-reaction-emoji emoji-reaction-emoji-image"
:src="notification.emoji_url"
- :name="notification.emoji"
+ :alt="notification.emoji"
+ :title="notification.emoji"
>
<span
v-else
@@ -153,7 +155,7 @@
<router-link
v-if="notification.status"
:to="{ name: 'conversation', params: { id: notification.status.id } }"
- class="timeago-link faint-link"
+ class="timeago-link faint"
>
<Timeago
:time="notification.created_at"
@@ -162,8 +164,8 @@
</router-link>
<button
class="button-unstyled expand-icon"
- :aria-expanded="statusExpanded"
:title="$t('tool_tip.toggle_expand')"
+ :aria-expanded="statusExpanded"
@click.prevent="toggleStatusExpanded"
>
<FAIcon
@@ -245,9 +247,8 @@
/>
<template v-else>
<StatusContent
- :class="{ faint: !statusExpanded }"
:compact="!statusExpanded"
- :status="notification.action"
+ :status="notification.status"
/>
</template>
</div>
diff --git a/src/components/notifications/notification_filters.vue b/src/components/notifications/notification_filters.vue
index 1315b51a..497a5156 100644
--- a/src/components/notifications/notification_filters.vue
+++ b/src/components/notifications/notification_filters.vue
@@ -8,65 +8,74 @@
<template #content>
<div class="dropdown-menu">
<button
- class="button-default dropdown-item"
+ class="menu-item dropdown-item"
@click="toggleNotificationFilter('likes')"
>
<span
- class="menu-checkbox"
+ class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': filters.likes }"
/>{{ $t('settings.notification_visibility_likes') }}
</button>
<button
- class="button-default dropdown-item"
+ class="menu-item dropdown-item"
@click="toggleNotificationFilter('repeats')"
>
<span
- class="menu-checkbox"
+ class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': filters.repeats }"
/>{{ $t('settings.notification_visibility_repeats') }}
</button>
<button
- class="button-default dropdown-item"
+ class="menu-item dropdown-item"
@click="toggleNotificationFilter('follows')"
>
<span
- class="menu-checkbox"
+ class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': filters.follows }"
/>{{ $t('settings.notification_visibility_follows') }}
</button>
<button
- class="button-default dropdown-item"
+ class="menu-item dropdown-item"
@click="toggleNotificationFilter('mentions')"
>
<span
- class="menu-checkbox"
+ class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': filters.mentions }"
/>{{ $t('settings.notification_visibility_mentions') }}
</button>
<button
- class="button-default dropdown-item"
+ class="menu-item dropdown-item"
+ @click="toggleNotificationFilter('statuses')"
+ >
+ <span
+ class="input menu-checkbox"
+ :class="{ 'menu-checkbox-checked': filters.statuses }"
+ />{{ $t('settings.notification_visibility_statuses') }}
+ </button>
+ <button
+ class="menu-item dropdown-item"
@click="toggleNotificationFilter('emojiReactions')"
>
<span
- class="menu-checkbox"
+ class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': filters.emojiReactions }"
/>{{ $t('settings.notification_visibility_emoji_reactions') }}
</button>
<button
- class="button-default dropdown-item"
+ class="menu-item dropdown-item"
@click="toggleNotificationFilter('moves')"
>
<span
- class="menu-checkbox"
+ class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': filters.moves }"
/>{{ $t('settings.notification_visibility_moves') }}
</button>
<button
- class="button-default dropdown-item"
+ class="menu-item dropdown-item"
@click="toggleNotificationFilter('polls')"
>
<span
- class="menu-checkbox"
+ class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': filters.polls }"
/>{{ $t('settings.notification_visibility_polls') }}
</button>
diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js
index d499d3d6..85d3662e 100644
--- a/src/components/notifications/notifications.js
+++ b/src/components/notifications/notifications.js
@@ -1,12 +1,15 @@
import { computed } from 'vue'
import { mapGetters } from 'vuex'
import Notification from '../notification/notification.vue'
+import ExtraNotifications from '../extra_notifications/extra_notifications.vue'
import NotificationFilters from './notification_filters.vue'
import notificationsFetcher from '../../services/notifications_fetcher/notifications_fetcher.service.js'
import {
notificationsFromStore,
filteredNotificationsFromStore,
- unseenNotificationsFromStore
+ unseenNotificationsFromStore,
+ countExtraNotifications,
+ ACTIONABLE_NOTIFICATION_TYPES
} from '../../services/notification_utils/notification_utils.js'
import FaviconService from '../../services/favicon_service/favicon_service.js'
import { library } from '@fortawesome/fontawesome-svg-core'
@@ -23,14 +26,20 @@ const DEFAULT_SEEN_TO_DISPLAY_COUNT = 30
const Notifications = {
components: {
Notification,
- NotificationFilters
+ NotificationFilters,
+ ExtraNotifications
},
props: {
// Disables panel styles, unread mark, potentially other notification-related actions
// meant for "Interactions" timeline
minimalMode: Boolean,
- // Custom filter mode, an array of strings, possible values 'mention', 'repeat', 'like', 'follow', used to override global filter for use in "Interactions" timeline
+ // Custom filter mode, an array of strings, possible values 'mention', 'status', 'repeat', 'like', 'follow', used to override global filter for use in "Interactions" timeline
filterMode: Array,
+ // Do not show extra notifications
+ noExtra: {
+ type: Boolean,
+ default: false
+ },
// Disable teleporting (i.e. for /users/user/notifications)
disableTeleport: Boolean
},
@@ -57,22 +66,36 @@ const Notifications = {
return notificationsFromStore(this.$store)
},
error () {
- return this.$store.state.statuses.notifications.error
+ return this.$store.state.notifications.error
},
unseenNotifications () {
return unseenNotificationsFromStore(this.$store)
},
filteredNotifications () {
- return filteredNotificationsFromStore(this.$store, this.filterMode)
+ if (this.unseenAtTop) {
+ return [
+ ...filteredNotificationsFromStore(this.$store).filter(n => this.shouldShowUnseen(n)),
+ ...filteredNotificationsFromStore(this.$store).filter(n => !this.shouldShowUnseen(n))
+ ]
+ } else {
+ return filteredNotificationsFromStore(this.$store, this.filterMode)
+ }
+ },
+ unseenCountBadgeText () {
+ return `${this.unseenCount ? this.unseenCount : ''}${this.extraNotificationsCount ? '*' : ''}`
},
unseenCount () {
return this.unseenNotifications.length
},
+ ignoreInactionableSeen () { return this.$store.getters.mergedConfig.ignoreInactionableSeen },
+ extraNotificationsCount () {
+ return countExtraNotifications(this.$store)
+ },
unseenCountTitle () {
- return this.unseenCount + (this.unreadChatCount) + this.unreadAnnouncementCount
+ return this.unseenNotifications.length + (this.unreadChatCount) + this.unreadAnnouncementCount
},
loading () {
- return this.$store.state.statuses.notifications.loading
+ return this.$store.state.notifications.loading
},
noHeading () {
const { layoutType } = this.$store.state.interface
@@ -94,6 +117,10 @@ const Notifications = {
return this.filteredNotifications.slice(0, this.unseenCount + this.seenToDisplayCount)
},
noSticky () { return this.$store.getters.mergedConfig.disableStickyHeaders },
+ unseenAtTop () { return this.$store.getters.mergedConfig.unseenAtTop },
+ showExtraNotifications () {
+ return !this.noExtra
+ },
...mapGetters(['unreadChatCount', 'unreadAnnouncementCount'])
},
mounted () {
@@ -137,11 +164,28 @@ const Notifications = {
scrollToTop () {
const scrollable = this.scrollerRef
scrollable.scrollTo({ top: this.$refs.root.offsetTop })
- // this.$refs.root.scrollIntoView({ behavior: 'smooth', block: 'start' })
},
updateScrollPosition () {
this.showScrollTop = this.$refs.root.offsetTop < this.scrollerRef.scrollTop
},
+ shouldShowUnseen (notification) {
+ if (notification.seen) return false
+
+ const actionable = ACTIONABLE_NOTIFICATION_TYPES.has(notification.type)
+ return this.ignoreInactionableSeen ? actionable : true
+ },
+ /* "Interacted" really refers to "actionable" notifications that require user input,
+ * everything else (likes/repeats/reacts) cannot be acted and therefore we just clear
+ * the "seen" status upon any clicks on them
+ */
+ notificationClicked (notification) {
+ const { id } = notification
+ this.$store.dispatch('notificationClicked', { id })
+ },
+ notificationInteracted (notification) {
+ const { id } = notification
+ this.$store.dispatch('markSingleNotificationAsSeen', { id })
+ },
markAsSeen () {
this.$store.dispatch('markNotificationsAsSeen')
this.seenToDisplayCount = DEFAULT_SEEN_TO_DISPLAY_COUNT
diff --git a/src/components/notifications/notifications.scss b/src/components/notifications/notifications.scss
index 61f7317e..cfc1f3d6 100644
--- a/src/components/notifications/notifications.scss
+++ b/src/components/notifications/notifications.scss
@@ -1,5 +1,3 @@
-@import "../../variables";
-
.Notifications {
&:not(.minimal) {
// a bit of a hack to allow scrolling below notifications
@@ -7,8 +5,7 @@
}
.loadmore-error {
- color: $fallback--text;
- color: var(--text, $fallback--text);
+ color: var(--text);
}
.notification {
@@ -25,7 +22,7 @@
&.unseen {
.notification-overlay {
- background-image: linear-gradient(135deg, var(--badgeNotification, $fallback--cRed) 4px, transparent 10px);
+ background-image: linear-gradient(135deg, var(--badgeNotification) 4px, transparent 10px);
}
}
}
@@ -35,6 +32,11 @@
.notification {
box-sizing: border-box;
+ /* TODO cleanup this */
+ .Status {
+ flex: 1;
+ }
+
&:hover .animated.Avatar {
canvas {
display: none;
@@ -60,24 +62,17 @@
width: 32px;
height: 32px;
}
-
- .faint {
- --link: var(--faintLink);
- --text: var(--faint);
- }
}
.follow-request-accept {
&:hover {
- color: $fallback--text;
- color: var(--text, $fallback--text);
+ color: var(--text);
}
}
.follow-request-reject {
&:hover {
- color: $fallback--cRed;
- color: var(--cRed, $fallback--cRed);
+ color: var(--cRed);
}
}
@@ -97,11 +92,6 @@
}
}
- /* TODO cleanup this */
- .Status {
- flex: 1;
- }
-
time {
white-space: nowrap;
}
@@ -136,6 +126,7 @@
.emoji-reaction-emoji-image {
vertical-align: middle;
+ object-fit: contain;
}
.notification-details {
diff --git a/src/components/notifications/notifications.vue b/src/components/notifications/notifications.vue
index 633efca6..87e7b68d 100644
--- a/src/components/notifications/notifications.vue
+++ b/src/components/notifications/notifications.vue
@@ -17,9 +17,9 @@
<div class="title">
{{ $t('notifications.notifications') }}
<span
- v-if="unseenCount"
- class="badge badge-notification unseen-count"
- >{{ unseenCount }}</span>
+ v-if="unseenCountBadgeText"
+ class="badge -notification unseen-count"
+ >{{ unseenCountBadgeText }}</span>
</div>
<div
v-if="showScrollTop"
@@ -55,14 +55,25 @@
role="feed"
>
<div
+ v-if="showExtraNotifications"
+ role="listitem"
+ class="notification"
+ >
+ <extra-notifications />
+ </div>
+ <div
v-for="notification in notificationsToDisplay"
:key="notification.id"
role="listitem"
class="notification"
- :class="{unseen: !minimalMode && !notification.seen}"
+ :class="{unseen: !minimalMode && shouldShowUnseen(notification)}"
+ @click="e => notificationClicked(notification)"
>
<div class="notification-overlay" />
- <notification :notification="notification" />
+ <notification
+ :notification="notification"
+ @interacted="e => notificationInteracted(notification)"
+ />
</div>
</div>
<div class="panel-footer">
@@ -74,7 +85,7 @@
</div>
<button
v-else-if="!loading"
- class="button-unstyled -link -fullwidth"
+ class="button-unstyled -link text-center"
@click.prevent="fetchOlderNotifications()"
>
<div class="new-status-notification text-center">
diff --git a/src/components/opacity_input/opacity_input.vue b/src/components/opacity_input/opacity_input.vue
index 15d08e04..a45bdd92 100644
--- a/src/components/opacity_input/opacity_input.vue
+++ b/src/components/opacity_input/opacity_input.vue
@@ -18,7 +18,7 @@
/>
<input
:id="name"
- class="input-number"
+ class="input input-number"
type="number"
:value="modelValue || fallback"
:disabled="!present || disabled"
diff --git a/src/components/panel.style.js b/src/components/panel.style.js
new file mode 100644
index 00000000..ad16c18f
--- /dev/null
+++ b/src/components/panel.style.js
@@ -0,0 +1,41 @@
+export default {
+ name: 'Panel',
+ selector: '.panel',
+ validInnerComponents: [
+ 'Text',
+ 'Link',
+ 'Icon',
+ 'Border',
+ 'Button',
+ 'ButtonUnstyled',
+ 'Input',
+ 'PanelHeader',
+ 'MenuItem',
+ 'Post',
+ 'Notification',
+ 'Alert',
+ 'UserCard',
+ 'Chat',
+ 'Attachment',
+ 'Tab',
+ 'ListItem'
+ ],
+ defaultRules: [
+ {
+ directives: {
+ backgroundNoCssColor: 'yes',
+ background: '--bg',
+ roundness: 3,
+ blur: '5px',
+ shadow: [{
+ x: 1,
+ y: 1,
+ blur: 4,
+ spread: 0,
+ color: '#000000',
+ alpha: 0.6
+ }]
+ }
+ }
+ ]
+}
diff --git a/src/components/panel_header.style.js b/src/components/panel_header.style.js
new file mode 100644
index 00000000..32464bc5
--- /dev/null
+++ b/src/components/panel_header.style.js
@@ -0,0 +1,24 @@
+export default {
+ name: 'PanelHeader',
+ selector: '.panel-heading',
+ validInnerComponents: [
+ 'Text',
+ 'Link',
+ 'Icon',
+ 'Button',
+ 'ButtonUnstyled',
+ 'Badge',
+ 'Alert',
+ 'Avatar'
+ ],
+ defaultRules: [
+ {
+ component: 'PanelHeader',
+ directives: {
+ backgroundNoCssColor: 'yes',
+ background: '--fg',
+ shadow: []
+ }
+ }
+ ]
+}
diff --git a/src/components/panel_loading/panel_loading.vue b/src/components/panel_loading/panel_loading.vue
index 17458e49..7a832122 100644
--- a/src/components/panel_loading/panel_loading.vue
+++ b/src/components/panel_loading/panel_loading.vue
@@ -23,22 +23,18 @@ export default {}
</script>
<style lang="scss">
-@import "src/variables";
-
.panel-loading {
display: flex;
height: 100%;
align-items: center;
justify-content: center;
font-size: 2em;
- color: $fallback--text;
- color: var(--text, $fallback--text);
+ color: var(--text);
.loading-text svg {
line-height: 0;
vertical-align: middle;
- color: $fallback--text;
- color: var(--text, $fallback--text);
+ color: var(--text);
}
}
</style>
diff --git a/src/components/password_reset/password_reset.vue b/src/components/password_reset/password_reset.vue
index d6e10250..037b859e 100644
--- a/src/components/password_reset/password_reset.vue
+++ b/src/components/password_reset/password_reset.vue
@@ -30,7 +30,7 @@
<div v-else>
<p
v-if="passwordResetRequested"
- class="password-reset-required error"
+ class="alert password-reset-required error"
>
{{ $t('password_reset.password_reset_required') }}
</p>
@@ -43,7 +43,7 @@
v-model="user.email"
:disabled="isPending"
:placeholder="$t('password_reset.placeholder')"
- class="form-control"
+ class="input form-control"
type="input"
>
</div>
@@ -77,8 +77,6 @@
<script src="./password_reset.js"></script>
<style lang="scss">
-@import "../../variables";
-
.password-reset-form {
display: flex;
flex-direction: column;
@@ -117,11 +115,6 @@
margin: 0.3em 0 1em;
}
- .password-reset-required {
- background-color: var(--alertError, $fallback--alertError);
- padding: 10px 0;
- }
-
.notice-dismissible {
padding-right: 2rem;
}
diff --git a/src/components/poll/poll.js b/src/components/poll/poll.js
index e4d6869a..9ce0e29e 100644
--- a/src/components/poll/poll.js
+++ b/src/components/poll/poll.js
@@ -1,4 +1,5 @@
import Timeago from 'components/timeago/timeago.vue'
+import genRandomSeed from '../../services/random_seed/random_seed.service.js'
import RichContent from 'components/rich_content/rich_content.jsx'
import { forEach, map } from 'lodash'
@@ -13,7 +14,7 @@ export default {
return {
loading: false,
choices: [],
- randomSeed: `${Math.random()}`.replace('.', '-')
+ randomSeed: genRandomSeed()
}
},
created () {
@@ -37,7 +38,7 @@ export default {
return (this.poll && this.poll.options) || []
},
expiresAt () {
- return (this.poll && this.poll.expires_at) || 0
+ return (this.poll && this.poll.expires_at) || null
},
expired () {
return (this.poll && this.poll.expired) || false
diff --git a/src/components/poll/poll.vue b/src/components/poll/poll.vue
index b3a74c49..580e5377 100644
--- a/src/components/poll/poll.vue
+++ b/src/components/poll/poll.vue
@@ -37,12 +37,14 @@
:role="poll.multiple ? 'checkbox' : 'radio'"
:aria-labelledby="`option-vote-${randomSeed}-${index}`"
:aria-checked="choices[index]"
+ class="input unstyled"
@click="activateOption(index)"
>
+ <!-- TODO: USE CHECKBOX -->
<input
v-if="poll.multiple"
type="checkbox"
- class="poll-checkbox"
+ class="input -checkbox poll-checkbox"
:disabled="loading"
:value="index"
>
@@ -51,6 +53,7 @@
type="radio"
:disabled="loading"
:value="index"
+ class="input -radio"
>
<label class="option-vote">
<RichContent
@@ -75,13 +78,16 @@
</button>
<div class="total">
<template v-if="typeof poll.voters_count === 'number'">
- {{ $tc("polls.people_voted_count", poll.voters_count, { count: poll.voters_count }) }}&nbsp;·&nbsp;
+ {{ $tc("polls.people_voted_count", poll.voters_count, { count: poll.voters_count }) }}
</template>
<template v-else>
- {{ $tc("polls.votes_count", poll.votes_count, { count: poll.votes_count }) }}&nbsp;·&nbsp;
+ {{ $tc("polls.votes_count", poll.votes_count, { count: poll.votes_count }) }}
</template>
+ <span v-if="expiresAt !== null">
+ &nbsp;·&nbsp;
+ </span>
</div>
- <span>
+ <span v-if="expiresAt !== null">
<i18n-t
scope="global"
:keypath="expired ? 'polls.expired' : 'polls.expires_in'"
@@ -100,8 +106,6 @@
<script src="./poll.js"></script>
<style lang="scss">
-@import "../../variables";
-
.poll {
.votes {
display: flex;
@@ -111,6 +115,10 @@
.poll-option {
margin: 0.75em 0.5em;
+
+ .input {
+ line-height: inherit;
+ }
}
.option-result {
@@ -118,8 +126,7 @@
display: flex;
flex-direction: row;
position: relative;
- color: $fallback--lightText;
- color: var(--lightText, $fallback--lightText);
+ color: var(--textLight);
}
.option-result-label {
@@ -138,12 +145,7 @@
.result-fill {
height: 100%;
position: absolute;
- color: $fallback--text;
- color: var(--pollText, $fallback--text);
- background-color: $fallback--lightBg;
- background-color: var(--poll, $fallback--lightBg);
- border-radius: $fallback--panelRadius;
- border-radius: var(--panelRadius, $fallback--panelRadius);
+ border-radius: var(--roundness);
top: 0;
left: 0;
transition: width 0.5s;
diff --git a/src/components/poll/poll_form.vue b/src/components/poll/poll_form.vue
index 09d411ca..4eb9d594 100644
--- a/src/components/poll/poll_form.vue
+++ b/src/components/poll/poll_form.vue
@@ -13,7 +13,7 @@
:id="`poll-${index}`"
v-model="options[index]"
size="1"
- class="poll-option-input"
+ class="input poll-option-input"
type="text"
:placeholder="$t('polls.option')"
:maxlength="maxLength"
@@ -67,7 +67,7 @@
<input
v-model="expiryAmount"
type="number"
- class="expiry-amount hide-number-spinner"
+ class="input expiry-amount hide-number-spinner"
:min="minExpirationInCurrentUnit"
:max="maxExpirationInCurrentUnit"
@change="expiryAmountChange"
@@ -95,8 +95,6 @@
<script src="./poll_form.js"></script>
<style lang="scss">
-@import "../../variables";
-
.poll-form {
display: flex;
flex-direction: column;
diff --git a/src/components/poll/poll_graph.style.js b/src/components/poll/poll_graph.style.js
new file mode 100644
index 00000000..247a266a
--- /dev/null
+++ b/src/components/poll/poll_graph.style.js
@@ -0,0 +1,12 @@
+export default {
+ name: 'PollGraph',
+ selector: '.result-fill',
+ defaultRules: [
+ {
+ directives: {
+ background: '--accent',
+ opacity: 0.5
+ }
+ }
+ ]
+}
diff --git a/src/components/popover.style.js b/src/components/popover.style.js
new file mode 100644
index 00000000..0197271b
--- /dev/null
+++ b/src/components/popover.style.js
@@ -0,0 +1,36 @@
+export default {
+ name: 'Popover',
+ selector: '.popover',
+ lazy: true,
+ variants: {
+ modal: '.modal'
+ },
+ validInnerComponents: [
+ 'Text',
+ 'Link',
+ 'Icon',
+ 'Border',
+ 'Button',
+ 'ButtonUnstyled',
+ 'Input',
+ 'MenuItem',
+ 'Post',
+ 'UserCard'
+ ],
+ defaultRules: [
+ {
+ directives: {
+ background: '--bg',
+ blur: '10px',
+ shadow: [{
+ x: 2,
+ y: 2,
+ blur: 3,
+ spread: 0,
+ color: '#000000',
+ alpha: 0.5
+ }]
+ }
+ }
+ ]
+}
diff --git a/src/components/popover/popover.js b/src/components/popover/popover.js
index d44b266b..bc078533 100644
--- a/src/components/popover/popover.js
+++ b/src/components/popover/popover.js
@@ -45,6 +45,9 @@ const Popover = {
// Lets hover popover stay when clicking inside of it
stayOnClick: Boolean,
+ // Use styled button (to avoid nested buttons)
+ normalButton: Boolean,
+
triggerAttrs: {
type: Object,
default: {}
diff --git a/src/components/popover/popover.vue b/src/components/popover/popover.vue
index fd0fd821..0c5b372e 100644
--- a/src/components/popover/popover.vue
+++ b/src/components/popover/popover.vue
@@ -5,7 +5,8 @@
>
<button
ref="trigger"
- class="button-unstyled popover-trigger-button"
+ class="popover-trigger-button"
+ :class="normalButton ? 'button-default btn' : 'button-unstyled'"
type="button"
v-bind="triggerAttrs"
@click="onClick"
@@ -41,8 +42,6 @@
<script src="./popover.js" />
<style lang="scss">
-@import "../../variables";
-
.popover-trigger-button {
display: inline-block;
}
@@ -52,81 +51,54 @@
position: fixed;
min-width: 0;
max-width: calc(100vw - 20px);
- box-shadow: 2px 2px 3px rgb(0 0 0 / 50%);
- box-shadow: var(--popupShadow);
+ box-shadow: var(--shadow);
}
.popover-default {
&::after {
content: "";
position: absolute;
- top: 0;
- bottom: 0;
- left: 0;
- right: 0;
- z-index: 3;
- box-shadow: 1px 1px 4px rgb(0 0 0 / 60%);
- box-shadow: var(--panelShadow);
+ top: -1px;
+ bottom: -1px;
+ left: -1px;
+ right: -1px;
+ z-index: -1px;
+ box-shadow: var(--shadow);
pointer-events: none;
}
- border-radius: $fallback--btnRadius;
- border-radius: var(--btnRadius, $fallback--btnRadius);
- background-color: $fallback--bg;
- background-color: var(--popover, $fallback--bg);
- color: $fallback--text;
- color: var(--popoverText, $fallback--text);
-
- --faint: var(--popoverFaintText, $fallback--faint);
- --faintLink: var(--popoverFaintLink, $fallback--faint);
- --lightText: var(--popoverLightText, $fallback--lightText);
- --postLink: var(--popoverPostLink, $fallback--link);
- --postFaintLink: var(--popoverPostFaintLink, $fallback--link);
- --icon: var(--popoverIcon, $fallback--icon);
+ border-radius: var(--roundness);
+ border-color: var(--border);
+ border-style: solid;
+ border-width: 1px;
+ background-color: var(--background);
}
.dropdown-menu {
display: block;
- padding: 0.5rem 0;
+ padding: 0;
font-size: 1em;
text-align: left;
list-style: none;
max-width: 100vw;
z-index: var(--ZI_popover_override, var(--ZI_popovers));
white-space: nowrap;
+ background-color: var(--background);
.dropdown-divider {
height: 0;
margin: 0.5rem 0;
overflow: hidden;
- border-top: 1px solid $fallback--border;
- border-top: 1px solid var(--border, $fallback--border);
+ border-top: 1px solid var(--border);
}
.dropdown-item {
- line-height: 21px;
- overflow: hidden;
- display: block;
- padding: 0.5em 0.75em;
- clear: both;
- font-weight: 400;
- text-align: inherit;
- white-space: nowrap;
border: none;
- border-radius: 0;
- background-color: transparent;
- box-shadow: none;
- width: 100%;
- height: 100%;
- box-sizing: border-box;
-
- --btnText: var(--popoverText, $fallback--text);
&-icon {
svg {
- width: 22px;
- margin-right: 0.75rem;
- color: var(--menuPopoverIcon, $fallback--icon);
+ width: var(--__line-height);
+ margin-right: var(--__horizontal-gap);
}
}
@@ -137,40 +109,18 @@
}
}
- &:active,
- &:hover {
- background-color: $fallback--lightBg;
- background-color: var(--selectedMenuPopover, $fallback--lightBg);
- box-shadow: none;
-
- --btnText: var(--selectedMenuPopoverText, $fallback--link);
- --faint: var(--selectedMenuPopoverFaintText, $fallback--faint);
- --faintLink: var(--selectedMenuPopoverFaintLink, $fallback--faint);
- --lightText: var(--selectedMenuPopoverLightText, $fallback--lightText);
- --icon: var(--selectedMenuPopoverIcon, $fallback--icon);
-
- svg {
- color: var(--selectedMenuPopoverIcon, $fallback--icon);
-
- --icon: var(--selectedMenuPopoverIcon, $fallback--icon);
- }
- }
-
.menu-checkbox {
display: inline-block;
vertical-align: middle;
- min-width: 22px;
- max-width: 22px;
- min-height: 22px;
- max-height: 22px;
- line-height: 22px;
+ min-width: calc(var(--__line-height) + 1px);
+ max-width: calc(var(--__line-height) + 1px);
+ min-height: calc(var(--__line-height) + 1px);
+ max-height: calc(var(--__line-height) + 1px);
+ line-height: var(--__line-height);
text-align: center;
border-radius: 0;
- background-color: $fallback--fg;
- background-color: var(--input, $fallback--fg);
- box-shadow: 0 0 2px black inset;
- box-shadow: var(--inputShadow);
- margin-right: 0.75em;
+ box-shadow: var(--shadow);
+ margin-right: var(--__horizontal-gap);
&.menu-checkbox-checked::after {
font-size: 1.25em;
@@ -187,30 +137,5 @@
}
}
}
-
- .button-default.dropdown-item {
- &,
- i[class*="icon-"] {
- color: $fallback--text;
- color: var(--btnText, $fallback--text);
- }
-
- &:active {
- background-color: $fallback--lightBg;
- background-color: var(--selectedMenuPopover, $fallback--lightBg);
- color: $fallback--link;
- color: var(--selectedMenuPopoverText, $fallback--link);
- }
-
- &:disabled {
- color: $fallback--text;
- color: var(--btnDisabledText, $fallback--text);
- }
-
- &.toggled {
- color: $fallback--text;
- color: var(--btnToggledText, $fallback--text);
- }
- }
}
</style>
diff --git a/src/components/post_status_form/post_status_form.js b/src/components/post_status_form/post_status_form.js
index b75fee69..563dfb96 100644
--- a/src/components/post_status_form/post_status_form.js
+++ b/src/components/post_status_form/post_status_form.js
@@ -1,4 +1,5 @@
import statusPoster from '../../services/status_poster/status_poster.service.js'
+import genRandomSeed from '../../services/random_seed/random_seed.service.js'
import MediaUpload from '../media_upload/media_upload.vue'
import ScopeSelector from '../scope_selector/scope_selector.vue'
import EmojiInput from '../emoji_input/emoji_input.vue'
@@ -86,7 +87,8 @@ const PostStatusForm = {
'fileLimit',
'submitOnEnter',
'emojiPickerPlacement',
- 'optimisticPosting'
+ 'optimisticPosting',
+ 'profileMention'
],
emits: [
'posted',
@@ -124,7 +126,7 @@ const PostStatusForm = {
const { scopeCopy } = this.$store.getters.mergedConfig
- if (this.replyTo) {
+ if (this.replyTo || this.profileMention) {
const currentUser = this.$store.state.users.currentUser
statusText = buildMentionsString({ user: this.repliedUser, attentions: this.attentions }, currentUser)
}
@@ -156,11 +158,13 @@ const PostStatusForm = {
poll: this.statusPoll || {},
mediaDescriptions: this.statusMediaDescriptions || {},
visibility: this.statusScope || scope,
- contentType: statusContentType
+ contentType: statusContentType,
+ quoting: false
}
}
return {
+ randomSeed: genRandomSeed(),
dropFiles: [],
uploadingFiles: false,
error: null,
@@ -265,6 +269,30 @@ const PostStatusForm = {
isEdit () {
return typeof this.statusId !== 'undefined' && this.statusId.trim() !== ''
},
+ quotable () {
+ if (!this.$store.state.instance.quotingAvailable) {
+ return false
+ }
+
+ if (!this.replyTo) {
+ return false
+ }
+
+ const repliedStatus = this.$store.state.statuses.allStatusesObject[this.replyTo]
+ if (!repliedStatus) {
+ return false
+ }
+
+ if (repliedStatus.visibility === 'public' ||
+ repliedStatus.visibility === 'unlisted' ||
+ repliedStatus.visibility === 'local') {
+ return true
+ } else if (repliedStatus.visibility === 'private') {
+ return repliedStatus.user.id === this.$store.state.users.currentUser.id
+ }
+
+ return false
+ },
...mapGetters(['mergedConfig']),
...mapState({
mobileLayout: state => state.interface.mobileLayout
@@ -292,7 +320,8 @@ const PostStatusForm = {
visibility: newStatus.visibility,
contentType: newStatus.contentType,
poll: {},
- mediaDescriptions: {}
+ mediaDescriptions: {},
+ quoting: false
}
this.pollFormVisible = false
this.$refs.mediaUpload && this.$refs.mediaUpload.clearFile()
@@ -340,6 +369,8 @@ const PostStatusForm = {
return
}
+ const replyOrQuoteAttr = newStatus.quoting ? 'quoteId' : 'inReplyToStatusId'
+
const postingOptions = {
status: newStatus.status,
spoilerText: newStatus.spoilerText || null,
@@ -347,7 +378,7 @@ const PostStatusForm = {
sensitive: newStatus.nsfw,
media: newStatus.files,
store: this.$store,
- inReplyToStatusId: this.replyTo,
+ [replyOrQuoteAttr]: this.replyTo,
contentType: newStatus.contentType,
poll,
idempotencyKey: this.idempotencyKey
@@ -373,6 +404,7 @@ const PostStatusForm = {
}
const newStatus = this.newStatus
this.previewLoading = true
+ const replyOrQuoteAttr = newStatus.quoting ? 'quoteId' : 'inReplyToStatusId'
statusPoster.postStatus({
status: newStatus.status,
spoilerText: newStatus.spoilerText || null,
@@ -380,7 +412,7 @@ const PostStatusForm = {
sensitive: newStatus.nsfw,
media: [],
store: this.$store,
- inReplyToStatusId: this.replyTo,
+ [replyOrQuoteAttr]: this.replyTo,
contentType: newStatus.contentType,
poll: {},
preview: true
diff --git a/src/components/post_status_form/post_status_form.vue b/src/components/post_status_form/post_status_form.vue
index 86c1f907..cf411ff1 100644
--- a/src/components/post_status_form/post_status_form.vue
+++ b/src/components/post_status_form/post_status_form.vue
@@ -126,12 +126,42 @@
class="preview-status"
/>
</div>
+ <div
+ v-if="quotable"
+ role="radiogroup"
+ class="btn-group reply-or-quote-selector"
+ >
+ <button
+ :id="`reply-or-quote-option-${randomSeed}-reply`"
+ class="btn button-default reply-or-quote-option"
+ :class="{ toggled: !newStatus.quoting }"
+ tabindex="0"
+ role="radio"
+ :aria-labelledby="`reply-or-quote-option-${randomSeed}-reply`"
+ :aria-checked="!newStatus.quoting"
+ @click="newStatus.quoting = false"
+ >
+ {{ $t('post_status.reply_option') }}
+ </button>
+ <button
+ :id="`reply-or-quote-option-${randomSeed}-quote`"
+ class="btn button-default reply-or-quote-option"
+ :class="{ toggled: newStatus.quoting }"
+ tabindex="0"
+ role="radio"
+ :aria-labelledby="`reply-or-quote-option-${randomSeed}-quote`"
+ :aria-checked="newStatus.quoting"
+ @click="newStatus.quoting = true"
+ >
+ {{ $t('post_status.quote_option') }}
+ </button>
+ </div>
<EmojiInput
v-if="!disableSubject && (newStatus.spoilerText || alwaysShowSubject)"
v-model="newStatus.spoilerText"
enable-emoji-picker
:suggest="emojiSuggestor"
- class="form-control"
+ class="input form-control"
>
<template #default="inputProps">
<input
@@ -141,7 +171,7 @@
:disabled="posting && !optimisticPosting"
v-bind="propsToNative(inputProps)"
size="1"
- class="form-post-subject"
+ class="input form-post-subject"
>
</template>
</EmojiInput>
@@ -150,7 +180,7 @@
v-model="newStatus.status"
:suggest="emojiUserSuggestor"
:placement="emojiPickerPlacement"
- class="form-control main-input"
+ class="input form-control main-input"
enable-emoji-picker
hide-emoji-button
:newline-on-ctrl-enter="submitOnEnter"
@@ -168,7 +198,7 @@
rows="1"
cols="1"
:disabled="posting && !optimisticPosting"
- class="form-post-body"
+ class="input form-post-body"
:class="{ 'scrollable-form': !!maxHeight }"
v-bind="propsToNative(inputProps)"
@keydown.exact.enter="submitOnEnter && postStatus($event, newStatus)"
@@ -207,7 +237,7 @@
<Select
id="post-content-type"
v-model="newStatus.contentType"
- class="form-control"
+ class="input form-control"
:attrs="{ 'aria-label': $t('post_status.content_type_selection') }"
>
<option
@@ -345,8 +375,6 @@
<script src="./post_status_form.js"></script>
<style lang="scss">
-@import "../../variables";
-
.post-status-form {
position: relative;
@@ -407,23 +435,23 @@
.preview-error {
font-style: italic;
- color: $fallback--faint;
- color: var(--faint, $fallback--faint);
+ color: var(--textFaint);
}
.preview-status {
- border: 1px solid $fallback--border;
- border: 1px solid var(--border, $fallback--border);
- border-radius: $fallback--tooltipRadius;
- border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
+ border: 1px solid var(--border);
+ border-radius: var(--roundness);
padding: 0.5em;
margin: 0;
}
+ .reply-or-quote-selector {
+ margin-bottom: 0.5em;
+ }
+
.text-format {
.only-format {
- color: $fallback--faint;
- color: var(--faint, $fallback--faint);
+ color: var(--textFaint);
}
}
@@ -469,31 +497,6 @@
padding: 0 0.1em;
display: flex;
align-items: center;
-
- &.selected,
- &:hover {
- // needs to be specific to override icon default color
- svg,
- i,
- label {
- color: $fallback--lightText;
- color: var(--lightText, $fallback--lightText);
- }
- }
-
- &.disabled {
- svg,
- i {
- cursor: not-allowed;
- color: $fallback--icon;
- color: var(--btnDisabledText, $fallback--icon);
-
- &:hover {
- color: $fallback--icon;
- color: var(--btnDisabledText, $fallback--icon);
- }
- }
- }
}
.error {
@@ -546,7 +549,7 @@
line-height: 1.85;
}
- .form-post-body {
+ .input.form-post-body {
// TODO: make a resizable textarea component?
box-sizing: content-box; // needed for easier computation of dynamic size
overflow: hidden;
@@ -557,6 +560,7 @@
height: calc(var(--post-line-height) * 1em);
min-height: calc(var(--post-line-height) * 1em);
resize: none;
+ background: transparent;
&.scrollable-form {
overflow-y: auto;
@@ -575,8 +579,7 @@
margin: 0 0.5em;
&.error {
- color: $fallback--cRed;
- color: var(--cRed, $fallback--cRed);
+ color: var(--cRed);
}
}
@@ -599,14 +602,10 @@
align-items: center;
justify-content: center;
opacity: 0.6;
- color: $fallback--text;
- color: var(--text, $fallback--text);
- background-color: $fallback--bg;
- background-color: var(--bg, $fallback--bg);
- border-radius: $fallback--tooltipRadius;
- border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
- border: 2px dashed $fallback--text;
- border: 2px dashed var(--text, $fallback--text);
+ color: var(--text);
+ background-color: var(--bg);
+ border-radius: var(--roundness);
+ border: 2px dashed var(--text);
}
}
</style>
diff --git a/src/components/post_status_modal/post_status_modal.js b/src/components/post_status_modal/post_status_modal.js
index b44354db..8970dd9b 100644
--- a/src/components/post_status_modal/post_status_modal.js
+++ b/src/components/post_status_modal/post_status_modal.js
@@ -44,6 +44,10 @@ const PostStatusModal = {
methods: {
closeModal () {
this.$store.dispatch('closePostStatusModal')
+ },
+ resetAndClose () {
+ this.$store.dispatch('resetPostStatusModal')
+ this.$store.dispatch('closePostStatusModal')
}
}
}
diff --git a/src/components/post_status_modal/post_status_modal.vue b/src/components/post_status_modal/post_status_modal.vue
index dbcd321e..bc2cad4a 100644
--- a/src/components/post_status_modal/post_status_modal.vue
+++ b/src/components/post_status_modal/post_status_modal.vue
@@ -12,7 +12,7 @@
<PostStatusForm
class="panel-body"
v-bind="params"
- @posted="closeModal"
+ @posted="resetAndClose"
/>
</div>
</Modal>
diff --git a/src/components/quick_filter_settings/quick_filter_settings.js b/src/components/quick_filter_settings/quick_filter_settings.js
index e67e3a4b..0d667df5 100644
--- a/src/components/quick_filter_settings/quick_filter_settings.js
+++ b/src/components/quick_filter_settings/quick_filter_settings.js
@@ -63,6 +63,13 @@ const QuickFilterSettings = {
const value = !this.muteBotStatuses
this.$store.dispatch('setOption', { name: 'muteBotStatuses', value })
}
+ },
+ muteSensitiveStatuses: {
+ get () { return this.mergedConfig.muteSensitiveStatuses },
+ set () {
+ const value = !this.muteSensitiveStatuses
+ this.$store.dispatch('setOption', { name: 'muteSensitiveStatuses', value })
+ }
}
}
}
diff --git a/src/components/quick_filter_settings/quick_filter_settings.vue b/src/components/quick_filter_settings/quick_filter_settings.vue
index b81215a1..adb96916 100644
--- a/src/components/quick_filter_settings/quick_filter_settings.vue
+++ b/src/components/quick_filter_settings/quick_filter_settings.vue
@@ -16,39 +16,39 @@
>
<button
v-if="!conversation"
- class="button-default dropdown-item"
+ class="menu-item dropdown-item"
:aria-checked="replyVisibilityAll"
role="menuitemradio"
@click="replyVisibilityAll = true"
>
<span
- class="menu-checkbox -radio"
+ class="input menu-checkbox -radio"
:class="{ 'menu-checkbox-checked': replyVisibilityAll }"
:aria-hidden="true"
/>{{ $t('settings.reply_visibility_all') }}
</button>
<button
v-if="!conversation"
- class="button-default dropdown-item"
+ class="menu-item dropdown-item"
:aria-checked="replyVisibilityFollowing"
role="menuitemradio"
@click="replyVisibilityFollowing = true"
>
<span
- class="menu-checkbox -radio"
+ class="input menu-checkbox -radio"
:class="{ 'menu-checkbox-checked': replyVisibilityFollowing }"
:aria-hidden="true"
/>{{ $t('settings.reply_visibility_following_short') }}
</button>
<button
v-if="!conversation"
- class="button-default dropdown-item"
+ class="menu-item dropdown-item"
:aria-checked="replyVisibilitySelf"
role="menuitemradio"
@click="replyVisibilitySelf = true"
>
<span
- class="menu-checkbox -radio"
+ class="input menu-checkbox -radio"
:class="{ 'menu-checkbox-checked': replyVisibilitySelf }"
:aria-hidden="true"
/>{{ $t('settings.reply_visibility_self_short') }}
@@ -60,43 +60,55 @@
/>
</div>
<button
- class="button-default dropdown-item"
+ class="menu-item dropdown-item"
role="menuitemcheckbox"
:aria-checked="muteBotStatuses"
@click="muteBotStatuses = !muteBotStatuses"
>
<span
- class="menu-checkbox"
+ class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': muteBotStatuses }"
:aria-hidden="true"
/>{{ $t('settings.mute_bot_posts') }}
</button>
<button
- class="button-default dropdown-item"
+ class="menu-item dropdown-item"
+ role="menuitemcheckbox"
+ :aria-checked="muteSensitiveStatuses"
+ @click="muteSensitiveStatuses = !muteSensitiveStatuses"
+ >
+ <span
+ class="input menu-checkbox"
+ :class="{ 'menu-checkbox-checked': muteSensitiveStatuses }"
+ :aria-hidden="true"
+ />{{ $t('settings.mute_sensitive_posts') }}
+ </button>
+ <button
+ class="menu-item dropdown-item"
role="menuitemcheckbox"
:aria-checked="hideMedia"
@click="hideMedia = !hideMedia"
>
<span
- class="menu-checkbox"
+ class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': hideMedia }"
:aria-hidden="true"
/>{{ $t('settings.hide_media_previews') }}
</button>
<button
- class="button-default dropdown-item"
+ class="menu-item dropdown-item"
role="menuitemcheckbox"
:aria-checked="hideMutedPosts"
@click="hideMutedPosts = !hideMutedPosts"
>
<span
- class="menu-checkbox"
+ class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': hideMutedPosts }"
:aria-hidden="true"
/>{{ $t('settings.hide_all_muted_posts') }}
</button>
<button
- class="button-default dropdown-item dropdown-item-icon"
+ class="menu-item dropdown-item dropdown-item-icon"
role="menuitem"
@click="openTab('filtering')"
>
diff --git a/src/components/quick_view_settings/quick_view_settings.js b/src/components/quick_view_settings/quick_view_settings.js
index 2798f37a..e35fd579 100644
--- a/src/components/quick_view_settings/quick_view_settings.js
+++ b/src/components/quick_view_settings/quick_view_settings.js
@@ -52,7 +52,6 @@ const QuickViewSettings = {
get () { return this.mergedConfig.mentionLinkShowAvatar },
set () {
const value = !this.showUserAvatars
- console.log(value)
this.$store.dispatch('setOption', { name: 'mentionLinkShowAvatar', value })
}
},
@@ -62,6 +61,13 @@ const QuickViewSettings = {
const value = !this.muteBotStatuses
this.$store.dispatch('setOption', { name: 'muteBotStatuses', value })
}
+ },
+ muteSensitiveStatuses: {
+ get () { return this.mergedConfig.muteSensitiveStatuses },
+ set () {
+ const value = !this.muteSensitiveStatuses
+ this.$store.dispatch('setOption', { name: 'muteSensitiveStatuses', value })
+ }
}
}
}
diff --git a/src/components/quick_view_settings/quick_view_settings.vue b/src/components/quick_view_settings/quick_view_settings.vue
index 9f5cdabc..e93dd8e0 100644
--- a/src/components/quick_view_settings/quick_view_settings.vue
+++ b/src/components/quick_view_settings/quick_view_settings.vue
@@ -12,13 +12,13 @@
>
<div role="group">
<button
- class="button-default dropdown-item"
+ class="menu-item dropdown-item"
:aria-checked="conversationDisplay === 'tree'"
role="menuitemradio"
@click="conversationDisplay = 'tree'"
>
<span
- class="menu-checkbox -radio"
+ class="input menu-checkbox -radio"
:aria-hidden="true"
:class="{ 'menu-checkbox-checked': conversationDisplay === 'tree' }"
/><FAIcon
@@ -27,13 +27,13 @@
/> {{ $t('settings.conversation_display_tree_quick') }}
</button>
<button
- class="button-default dropdown-item"
+ class="menu-item dropdown-item"
:aria-checked="conversationDisplay === 'linear'"
role="menuitemradio"
@click="conversationDisplay = 'linear'"
>
<span
- class="menu-checkbox -radio"
+ class="input menu-checkbox -radio"
:class="{ 'menu-checkbox-checked': conversationDisplay === 'linear' }"
:aria-hidden="true"
/><FAIcon
@@ -47,45 +47,45 @@
class="dropdown-divider"
/>
<button
- class="button-default dropdown-item"
+ class="menu-item dropdown-item"
role="menuitemcheckbox"
:aria-checked="showUserAvatars"
@click="showUserAvatars = !showUserAvatars"
>
<span
- class="menu-checkbox"
+ class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': showUserAvatars }"
:aria-hidden="true"
/>{{ $t('settings.mention_link_show_avatar_quick') }}
</button>
<button
v-if="!conversation"
- class="button-default dropdown-item"
+ class="menu-item dropdown-item"
role="menuitemcheckbox"
:aria-checked="autoUpdate"
@click="autoUpdate = !autoUpdate"
>
<span
- class="menu-checkbox"
+ class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': autoUpdate }"
:aria-hidden="true"
/>{{ $t('settings.auto_update') }}
</button>
<button
v-if="!conversation"
- class="button-default dropdown-item"
+ class="menu-item dropdown-item"
role="menuitemcheckbox"
:aria-checked="collapseWithSubjects"
@click="collapseWithSubjects = !collapseWithSubjects"
>
<span
- class="menu-checkbox"
+ class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': collapseWithSubjects }"
:aria-hidden="true"
/>{{ $t('settings.collapse_subject') }}
</button>
<button
- class="button-default dropdown-item dropdown-item-icon"
+ class="menu-item dropdown-item dropdown-item-icon"
role="menuitem"
@click="openTab('general')"
>
diff --git a/src/components/quotes_timeline/quotes_timeline.js b/src/components/quotes_timeline/quotes_timeline.js
new file mode 100644
index 00000000..a5f42da5
--- /dev/null
+++ b/src/components/quotes_timeline/quotes_timeline.js
@@ -0,0 +1,26 @@
+import Timeline from '../timeline/timeline.vue'
+
+const QuotesTimeline = {
+ created () {
+ this.$store.commit('clearTimeline', { timeline: 'quotes' })
+ this.$store.dispatch('startFetchingTimeline', { timeline: 'quotes', statusId: this.statusId })
+ },
+ components: {
+ Timeline
+ },
+ computed: {
+ statusId () { return this.$route.params.id },
+ timeline () { return this.$store.state.statuses.timelines.quotes }
+ },
+ watch: {
+ statusId () {
+ this.$store.commit('clearTimeline', { timeline: 'quotes' })
+ this.$store.dispatch('startFetchingTimeline', { timeline: 'quotes', statusId: this.statusId })
+ }
+ },
+ unmounted () {
+ this.$store.dispatch('stopFetchingTimeline', 'quotes')
+ }
+}
+
+export default QuotesTimeline
diff --git a/src/components/quotes_timeline/quotes_timeline.vue b/src/components/quotes_timeline/quotes_timeline.vue
new file mode 100644
index 00000000..835abd12
--- /dev/null
+++ b/src/components/quotes_timeline/quotes_timeline.vue
@@ -0,0 +1,10 @@
+<template>
+ <Timeline
+ :title="$t('nav.quotes')"
+ :timeline="timeline"
+ :timeline-name="'quotes'"
+ :status-id="statusId"
+ />
+</template>
+
+<script src='./quotes_timeline.js'></script>
diff --git a/src/components/range_input/range_input.vue b/src/components/range_input/range_input.vue
index 1e720105..2f8645c0 100644
--- a/src/components/range_input/range_input.vue
+++ b/src/components/range_input/range_input.vue
@@ -14,7 +14,7 @@
v-if="typeof fallback !== 'undefined'"
:id="name + '-o'"
:aria-labelledby="name + '-label'"
- class="opt visible-for-screenreader-only"
+ class="input -checkbox opt visible-for-screenreader-only"
type="checkbox"
:checked="present"
@change="$emit('update:modelValue', !present ? fallback : undefined)"
@@ -27,7 +27,7 @@
/>
<input
:id="name"
- class="input-number"
+ class="input input-number"
type="range"
:value="modelValue || fallback"
:disabled="!present || disabled"
@@ -38,7 +38,7 @@
>
<input
:id="name + '-numeric'"
- class="input-number"
+ class="input input-number"
type="number"
:aria-labelledby="name + '-label'"
:value="modelValue || fallback"
diff --git a/src/components/react_button/react_button.js b/src/components/react_button/react_button.js
index 8eed4b60..0d252155 100644
--- a/src/components/react_button/react_button.js
+++ b/src/components/react_button/react_button.js
@@ -46,7 +46,7 @@ const ReactButton = {
},
computed: {
hideCustomEmoji () {
- return !this.$store.state.instance.pleromaChatMessagesAvailable
+ return !this.$store.state.instance.pleromaCustomEmojiReactionsAvailable
}
}
}
diff --git a/src/components/react_button/react_button.vue b/src/components/react_button/react_button.vue
index 947536a1..e648e7e3 100644
--- a/src/components/react_button/react_button.vue
+++ b/src/components/react_button/react_button.vue
@@ -11,6 +11,8 @@
/>
<span
class="button-unstyled popover-trigger"
+ role="button"
+ :tabindex="0"
:title="$t('tool_tip.add_reaction')"
@click.stop.prevent="show"
>
@@ -39,7 +41,6 @@
<script src="./react_button.js"></script>
<style lang="scss">
-@import "../../variables";
@import "../../mixins";
.ReactButton {
@@ -56,7 +57,7 @@
height: 1px;
width: 100%;
margin: 0.5em;
- background-color: var(--border, $fallback--border);
+ background-color: var(--border);
}
.reaction-picker {
@@ -97,11 +98,6 @@
padding: 10px;
margin: -10px;
- &:hover .svg-inline--fa {
- color: $fallback--text;
- color: var(--text, $fallback--text);
- }
-
@include unfocused-style {
.focus-marker {
visibility: hidden;
diff --git a/src/components/registration/registration.js b/src/components/registration/registration.js
index b88bdeec..78d31980 100644
--- a/src/components/registration/registration.js
+++ b/src/components/registration/registration.js
@@ -83,6 +83,8 @@ const registration = {
signedIn: (state) => !!state.users.currentUser,
isPending: (state) => state.users.signUpPending,
serverValidationErrors: (state) => state.users.signUpErrors,
+ signUpNotice: (state) => state.users.signUpNotice,
+ hasSignUpNotice: (state) => !!state.users.signUpNotice.message,
termsOfService: (state) => state.instance.tos,
accountActivationRequired: (state) => state.instance.accountActivationRequired,
accountApprovalRequired: (state) => state.instance.accountApprovalRequired,
@@ -107,8 +109,12 @@ const registration = {
if (!this.v$.$invalid) {
try {
- await this.signUp(this.user)
- this.$router.push({ name: 'friends' })
+ const status = await this.signUp(this.user)
+ if (status === 'ok') {
+ this.$router.push({ name: 'friends' })
+ }
+ // If status is not 'ok' (i.e. it needs further actions to be done
+ // before you can login), display sign up notice, do not switch anywhere
} catch (error) {
console.warn('Registration failed: ', error)
this.setCaptcha()
diff --git a/src/components/registration/registration.vue b/src/components/registration/registration.vue
index 7438a5f4..a2008d87 100644
--- a/src/components/registration/registration.vue
+++ b/src/components/registration/registration.vue
@@ -3,7 +3,10 @@
<div class="panel-heading">
{{ $t('registration.registration') }}
</div>
- <div class="panel-body">
+ <div
+ v-if="!hasSignUpNotice"
+ class="panel-body"
+ >
<form
class="registration-form"
@submit.prevent="submit(user)"
@@ -22,7 +25,7 @@
id="sign-up-username"
v-model.trim="v$.user.username.$model"
:disabled="isPending"
- class="form-control"
+ class="input form-control"
:aria-required="true"
:placeholder="$t('registration.username_placeholder')"
>
@@ -50,7 +53,7 @@
id="sign-up-fullname"
v-model.trim="v$.user.fullname.$model"
:disabled="isPending"
- class="form-control"
+ class="input form-control"
:aria-required="true"
:placeholder="$t('registration.fullname_placeholder')"
>
@@ -78,7 +81,7 @@
id="email"
v-model="v$.user.email.$model"
:disabled="isPending"
- class="form-control"
+ class="input form-control"
type="email"
:aria-required="accountActivationRequired"
>
@@ -103,7 +106,7 @@
id="bio"
v-model="user.bio"
:disabled="isPending"
- class="form-control"
+ class="input form-control"
:placeholder="bioPlaceholder"
/>
</div>
@@ -120,7 +123,7 @@
id="sign-up-password"
v-model="user.password"
:disabled="isPending"
- class="form-control"
+ class="input form-control"
type="password"
:aria-required="true"
>
@@ -148,7 +151,7 @@
id="sign-up-password-confirmation"
v-model="user.confirm"
:disabled="isPending"
- class="form-control"
+ class="input form-control"
type="password"
:aria-required="true"
>
@@ -181,7 +184,7 @@
id="sign-up-birthday"
v-model="user.birthday"
:disabled="isPending"
- class="form-control"
+ class="input form-control"
type="date"
:max="birthdayRequired ? birthdayMinAttr : undefined"
:aria-required="birthdayRequired"
@@ -226,7 +229,7 @@
id="reason"
v-model="user.reason"
:disabled="isPending"
- class="form-control"
+ class="input form-control"
:placeholder="reasonPlaceholder"
/>
</div>
@@ -253,7 +256,7 @@
id="captcha-answer"
v-model="captcha.solution"
:disabled="isPending"
- class="form-control"
+ class="input form-control"
type="text"
autocomplete="off"
autocorrect="off"
@@ -272,7 +275,7 @@
id="token"
v-model="token"
disabled="true"
- class="form-control"
+ class="input form-control"
type="text"
>
</div>
@@ -307,14 +310,16 @@
</div>
</form>
</div>
+ <div v-else>
+ <p class="registration-notice">
+ {{ signUpNotice.message }}
+ </p>
+ </div>
</div>
</template>
<script src="./registration.js"></script>
<style lang="scss">
-@import "../../variables";
-$validations-cRed: #f04124;
-
.registration-form {
display: flex;
flex-direction: column;
@@ -361,8 +366,7 @@ $validations-cRed: #f04124;
}
.form-group--error .form--label {
- color: $validations-cRed;
- color: var(--cRed, $validations-cRed);
+ color: var(--cRed);
}
.form-error {
@@ -404,6 +408,10 @@ $validations-cRed: #f04124;
}
}
+.registration-notice {
+ margin: 0.6em;
+}
+
@media all and (max-width: 800px) {
.registration-form .container {
flex-direction: column-reverse;
diff --git a/src/components/reply_button/reply_button.vue b/src/components/reply_button/reply_button.vue
index 60a40a08..87c06e39 100644
--- a/src/components/reply_button/reply_button.vue
+++ b/src/components/reply_button/reply_button.vue
@@ -59,7 +59,6 @@
<script src="./reply_button.js"></script>
<style lang="scss">
-@import "../../variables";
@import "../../mixins";
.ReplyButton {
@@ -78,8 +77,7 @@
.interactive {
&:hover .svg-inline--fa,
&.-active .svg-inline--fa {
- color: $fallback--cBlue;
- color: var(--cBlue, $fallback--cBlue);
+ color: var(--cBlue);
}
@include unfocused-style {
diff --git a/src/components/report/report.js b/src/components/report/report.js
index 76055764..f8675c0f 100644
--- a/src/components/report/report.js
+++ b/src/components/report/report.js
@@ -1,6 +1,7 @@
import Select from '../select/select.vue'
import StatusContent from '../status_content/status_content.vue'
import Timeago from '../timeago/timeago.vue'
+import RichContent from 'src/components/rich_content/rich_content.jsx'
import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
const Report = {
@@ -10,7 +11,8 @@ const Report = {
components: {
Select,
StatusContent,
- Timeago
+ Timeago,
+ RichContent
},
computed: {
report () {
diff --git a/src/components/report/report.scss b/src/components/report/report.scss
index 9762400b..4249b850 100644
--- a/src/components/report/report.scss
+++ b/src/components/report/report.scss
@@ -1,5 +1,3 @@
-@import "../../variables";
-
.Report {
.report-content {
margin: 0.5em 0 1em;
@@ -10,12 +8,8 @@
}
.reported-status {
- border: 1px solid $fallback--faint;
- border-color: var(--faint, $fallback--faint);
- border-radius: $fallback--inputRadius;
- border-radius: var(--inputRadius, $fallback--inputRadius);
- color: $fallback--text;
- color: var(--text, $fallback--text);
+ border: 1px solid var(--border);
+ border-radius: var(--roundness);
display: block;
padding: 0.5em;
margin: 0.5em 0;
diff --git a/src/components/report/report.vue b/src/components/report/report.vue
index 1f19cc25..32aaaffd 100644
--- a/src/components/report/report.vue
+++ b/src/components/report/report.vue
@@ -17,7 +17,7 @@
<Select
:id="report-state"
v-model="state"
- class="form-control"
+ class="input form-control"
>
<option
v-for="state in ['open', 'closed', 'resolved']"
diff --git a/src/components/retweet_button/retweet_button.vue b/src/components/retweet_button/retweet_button.vue
index e1b6b153..adda9a42 100644
--- a/src/components/retweet_button/retweet_button.vue
+++ b/src/components/retweet_button/retweet_button.vue
@@ -84,7 +84,6 @@
<script src="./retweet_button.js"></script>
<style lang="scss">
-@import "../../variables";
@import "../../mixins";
.RetweetButton {
@@ -107,8 +106,7 @@
&:hover .svg-inline--fa,
&.-repeated .svg-inline--fa {
- color: $fallback--cGreen;
- color: var(--cGreen, $fallback--cGreen);
+ color: var(--cGreen);
}
@include unfocused-style {
diff --git a/src/components/rich_content/rich_content.jsx b/src/components/rich_content/rich_content.jsx
index 7881e365..99d7daca 100644
--- a/src/components/rich_content/rich_content.jsx
+++ b/src/components/rich_content/rich_content.jsx
@@ -8,6 +8,27 @@ import HashtagLink from 'src/components/hashtag_link/hashtag_link.vue'
import './rich_content.scss'
+const MAYBE_LINE_BREAKING_ELEMENTS = [
+ 'blockquote',
+ 'br',
+ 'hr',
+ 'ul',
+ 'ol',
+ 'li',
+ 'p',
+ 'table',
+ 'tbody',
+ 'td',
+ 'th',
+ 'thead',
+ 'tr',
+ 'h1',
+ 'h2',
+ 'h3',
+ 'h4',
+ 'h5'
+]
+
/**
* RichContent, The Über-powered component for rendering Post HTML.
*
@@ -58,6 +79,12 @@ export default {
required: false,
type: Boolean,
default: false
+ },
+ // Faint style (for notifs)
+ faint: {
+ required: false,
+ type: Boolean,
+ default: false
}
},
// NEVER EVER TOUCH DATA INSIDE RENDER
@@ -149,7 +176,9 @@ export default {
// Handle tag nodes
if (Array.isArray(item)) {
const [opener, children, closer] = item
- const Tag = getTagName(opener)
+ let Tag = getTagName(opener)
+ if (Tag.toLowerCase() === 'script') Tag = 'js-exploit'
+ if (Tag.toLowerCase() === 'style') Tag = 'css-exploit'
const fullAttrs = getAttrs(opener, () => true)
const attrs = getAttrs(opener)
const previouslyMentions = currentMentions !== null
@@ -164,25 +193,22 @@ export default {
!(children && typeof children[0] === 'string' && children[0].match(/^\s/))
? lastSpacing
: ''
- switch (Tag) {
- case 'br':
+ if (MAYBE_LINE_BREAKING_ELEMENTS.includes(Tag)) {
+ // all the elements that can cause a line change
+ currentMentions = null
+ } else if (Tag === 'img') { // replace images with StillImage
+ return ['', [mentionsLinePadding, renderImage(opener)], '']
+ } else if (Tag === 'a' && this.handleLinks) { // replace mentions with MentionLink
+ if (fullAttrs.class && fullAttrs.class.includes('mention')) {
+ // Handling mentions here
+ return renderMention(attrs, children)
+ } else {
currentMentions = null
- break
- case 'img': // replace images with StillImage
- return ['', [mentionsLinePadding, renderImage(opener)], '']
- case 'a': // replace mentions with MentionLink
- if (!this.handleLinks) break
- if (fullAttrs.class && fullAttrs.class.includes('mention')) {
- // Handling mentions here
- return renderMention(attrs, children)
- } else {
- currentMentions = null
- break
- }
- case 'span':
- if (this.handleLinks && fullAttrs.class && fullAttrs.class.includes('h-card')) {
- return ['', children.map(processItem), '']
- }
+ }
+ } else if (Tag === 'span') {
+ if (this.handleLinks && fullAttrs.class && fullAttrs.class.includes('h-card')) {
+ return ['', children.map(processItem), '']
+ }
}
if (children !== undefined) {
@@ -257,7 +283,7 @@ export default {
// DO NOT USE SLOTS they cause a re-render feedback loop here.
// slots updated -> rerender -> emit -> update up the tree -> rerender -> ...
// at least until vue3?
- const result = <span class="RichContent">
+ const result = <span class={['RichContent', this.faint ? '-faint' : '']}>
{ pass2 }
</span>
diff --git a/src/components/rich_content/rich_content.scss b/src/components/rich_content/rich_content.scss
index e5d353ac..118b6acf 100644
--- a/src/components/rich_content/rich_content.scss
+++ b/src/components/rich_content/rich_content.scss
@@ -1,10 +1,19 @@
-@import "../../variables";
-
.RichContent {
+ font-family: var(--font);
+
+ &.-faint {
+ /* stylelint-disable declaration-no-important */
+ --text: var(--textFaint) !important;
+ --link: var(--linkFaint) !important;
+ --funtextGreentext: var(--funtextGreentextFaint) !important;
+ --funtextCyantext: var(--funtextCyantextFaint) !important;
+ /* stylelint-enable declaration-no-important */
+ }
+
blockquote {
margin: 0.2em 0 0.2em 0.2em;
font-style: italic;
- border-left: 0.2em solid var(--faint, $fallback--faint);
+ border-left: 0.2em solid var(--textFaint);
padding-left: 1em;
}
@@ -17,7 +26,7 @@
kbd,
var,
pre {
- font-family: var(--postCodeFont, monospace);
+ font-family: var(--monoFont);
}
p {
@@ -65,4 +74,17 @@
vertical-align: middle;
object-fit: contain;
}
+
+ .greentext {
+ color: var(--funtextGreentext);
+ }
+
+ .cyantext {
+ color: var(--funtextCyantext);
+ }
+}
+
+a .RichContent {
+ /* stylelint-disable-next-line declaration-no-important */
+ color: var(--link) !important;
}
diff --git a/src/components/rich_content/rich_content.style.js b/src/components/rich_content/rich_content.style.js
new file mode 100644
index 00000000..c8314000
--- /dev/null
+++ b/src/components/rich_content/rich_content.style.js
@@ -0,0 +1,18 @@
+export default {
+ name: 'RichContent',
+ selector: '.RichContent',
+ validInnerComponents: [
+ 'Text',
+ 'FunText',
+ 'Link'
+ ],
+ defaultRules: [
+ {
+ directives: {
+ '--font': 'generic | inherit',
+ '--monoFont': 'generic | monospace',
+ textNoCssColor: 'yes'
+ }
+ }
+ ]
+}
diff --git a/src/components/root.style.js b/src/components/root.style.js
new file mode 100644
index 00000000..f9bdf16e
--- /dev/null
+++ b/src/components/root.style.js
@@ -0,0 +1,44 @@
+export default {
+ name: 'Root',
+ selector: ':root',
+ validInnerComponents: [
+ 'Underlay',
+ 'Modals',
+ 'Popover',
+ 'TopBar',
+ 'Scrollbar',
+ 'ScrollbarElement',
+ 'MobileDrawer',
+ 'Alert',
+ 'Button' // mobile post button
+ ],
+ defaultRules: [
+ {
+ directives: {
+ // These are here just to establish order,
+ // themes should override those
+ '--bg': 'color | #121a24',
+ '--fg': 'color | #182230',
+ '--text': 'color | #b9b9ba',
+ '--link': 'color | #d8a070',
+ '--accent': 'color | #d8a070',
+ '--cRed': 'color | #FF0000',
+ '--cBlue': 'color | #0095ff',
+ '--cGreen': 'color | #0fa00f',
+ '--cOrange': 'color | #ffa500',
+
+ // Fonts
+ '--font': 'generic | sans-serif',
+ '--monoFont': 'generic | monospace',
+
+ // Fallback no-background-image color
+ // (also useful in some other places like scrollbars)
+ '--wallpaper': 'color | --bg, -2',
+
+ // Selection colors
+ '--selectionBackground': 'color | --accent',
+ '--selectionText': 'color | $textColor(--accent, --text, no-preserve)'
+ }
+ }
+ ]
+}
diff --git a/src/components/scope_selector/scope_selector.js b/src/components/scope_selector/scope_selector.js
index 74bf7284..52cda368 100644
--- a/src/components/scope_selector/scope_selector.js
+++ b/src/components/scope_selector/scope_selector.js
@@ -44,10 +44,10 @@ const ScopeSelector = {
},
css () {
return {
- public: { selected: this.currentScope === 'public' },
- unlisted: { selected: this.currentScope === 'unlisted' },
- private: { selected: this.currentScope === 'private' },
- direct: { selected: this.currentScope === 'direct' }
+ public: { toggled: this.currentScope === 'public' },
+ unlisted: { toggled: this.currentScope === 'unlisted' },
+ private: { toggled: this.currentScope === 'private' },
+ direct: { toggled: this.currentScope === 'direct' }
}
}
},
diff --git a/src/components/scope_selector/scope_selector.vue b/src/components/scope_selector/scope_selector.vue
index d6e7265b..b90ae020 100644
--- a/src/components/scope_selector/scope_selector.vue
+++ b/src/components/scope_selector/scope_selector.vue
@@ -64,8 +64,6 @@
<script src="./scope_selector.js"></script>
<style lang="scss">
-@import "../../variables";
-
.ScopeSelector {
.scope {
display: inline-block;
@@ -73,11 +71,6 @@
min-width: 1.3em;
min-height: 1.3em;
text-align: center;
-
- &.selected svg {
- color: $fallback--lightText;
- color: var(--lightText, $fallback--lightText);
- }
}
}
</style>
diff --git a/src/components/screen_reader_notice/screen_reader_notice.js b/src/components/screen_reader_notice/screen_reader_notice.js
index 3b8eaf37..794b855a 100644
--- a/src/components/screen_reader_notice/screen_reader_notice.js
+++ b/src/components/screen_reader_notice/screen_reader_notice.js
@@ -2,7 +2,7 @@ const ScreenReaderNotice = {
props: {
ariaLive: {
type: String,
- defualt: 'assertive'
+ default: 'assertive'
}
},
data () {
diff --git a/src/components/scrollbar.style.js b/src/components/scrollbar.style.js
new file mode 100644
index 00000000..94e6135d
--- /dev/null
+++ b/src/components/scrollbar.style.js
@@ -0,0 +1,11 @@
+export default {
+ name: 'Scrollbar',
+ selector: '::-webkit-scrollbar',
+ defaultRules: [
+ {
+ directives: {
+ background: '--wallpaper'
+ }
+ }
+ ]
+}
diff --git a/src/components/scrollbar_element.style.js b/src/components/scrollbar_element.style.js
new file mode 100644
index 00000000..da942ab2
--- /dev/null
+++ b/src/components/scrollbar_element.style.js
@@ -0,0 +1,101 @@
+const border = (top, shadow) => ({
+ x: 0,
+ y: top ? 1 : -1,
+ blur: 0,
+ spread: 0,
+ color: shadow ? '#000000' : '#FFFFFF',
+ alpha: 0.2,
+ inset: true
+})
+
+const buttonInsetFakeBorders = [border(true, false), border(false, true)]
+const inputInsetFakeBorders = [border(true, true), border(false, false)]
+const buttonOuterShadow = {
+ x: 0,
+ y: 0,
+ blur: 2,
+ spread: 0,
+ color: '#000000',
+ alpha: 1
+}
+
+const hoverGlow = {
+ x: 0,
+ y: 0,
+ blur: 4,
+ spread: 0,
+ color: '--text',
+ alpha: 1
+}
+
+export default {
+ name: 'ScrollbarElement',
+ selector: '::-webkit-scrollbar-button',
+ states: {
+ pressed: ':active',
+ hover: ':hover:not(:disabled)',
+ disabled: ':disabled'
+ },
+ validInnerComponents: [
+ 'Text'
+ ],
+ defaultRules: [
+ {
+ directives: {
+ background: '--fg',
+ shadow: [buttonOuterShadow, ...buttonInsetFakeBorders],
+ roundness: 3
+ }
+ },
+ {
+ state: ['hover'],
+ directives: {
+ shadow: [hoverGlow, ...buttonInsetFakeBorders]
+ }
+ },
+ {
+ state: ['pressed'],
+ directives: {
+ shadow: [buttonOuterShadow, ...inputInsetFakeBorders]
+ }
+ },
+ {
+ state: ['hover', 'pressed'],
+ directives: {
+ shadow: [hoverGlow, ...inputInsetFakeBorders]
+ }
+ },
+ {
+ state: ['toggled'],
+ directives: {
+ background: '--accent,-24.2',
+ shadow: [buttonOuterShadow, ...inputInsetFakeBorders]
+ }
+ },
+ {
+ state: ['toggled', 'hover'],
+ directives: {
+ background: '--accent,-24.2',
+ shadow: [hoverGlow, ...inputInsetFakeBorders]
+ }
+ },
+ {
+ state: ['disabled'],
+ directives: {
+ background: '$blend(--inheritedBackground, 0.25, --parent)',
+ shadow: [...buttonInsetFakeBorders]
+ }
+ },
+ {
+ component: 'Text',
+ parent: {
+ component: 'Button',
+ state: ['disabled']
+ },
+ directives: {
+ textOpacity: 0.25,
+ textOpacityMode: 'blend'
+ }
+ }
+ ]
+}
diff --git a/src/components/search/search.vue b/src/components/search/search.vue
index 19b9c577..bd384ad3 100644
--- a/src/components/search/search.vue
+++ b/src/components/search/search.vue
@@ -1,15 +1,15 @@
<template>
- <div class="panel panel-default">
+ <div class="Search panel panel-default">
<div class="panel-heading">
<div class="title">
{{ $t('nav.search') }}
</div>
</div>
- <div class="search-input-container">
+ <div class="panel-body search-input-container">
<input
ref="searchInput"
v-model="searchTerm"
- class="search-input"
+ class="input search-input"
:placeholder="$t('nav.search')"
@keyup.enter="newQuery(searchTerm)"
>
@@ -23,7 +23,7 @@
</div>
<div
v-if="loading && statusesOffset == 0"
- class="text-center loading-icon"
+ class="panel-body text-center loading-icon"
>
<FAIcon
icon="circle-notch"
@@ -67,7 +67,7 @@
/>
<button
v-if="!loading && loaded && lastStatusFetchCount > 0"
- class="more-statuses-button button-unstyled -link -fullwidth"
+ class="more-statuses-button button-unstyled -link"
@click.prevent="search(searchTerm, 'statuses')"
>
<div class="new-status-notification text-center">
@@ -148,11 +148,8 @@
<script src="./search.js"></script>
<style lang="scss">
-@import "../../variables";
-
.search-result-heading {
- color: $fallback--faint;
- color: var(--faint, $fallback--faint);
+ color: var(--faint);
padding: 0.75rem;
text-align: center;
}
@@ -171,17 +168,7 @@
.search-result {
box-sizing: border-box;
border-bottom: 1px solid;
- border-color: $fallback--border;
- border-color: var(--border, $fallback--border);
-}
-
-.search-result-footer {
- border-width: 1px 0 0;
- border-style: solid;
- border-color: var(--border, $fallback--border);
- padding: 10px;
- background-color: $fallback--fg;
- background-color: var(--panel, $fallback--fg);
+ border-color: var(--border);
}
.search-input-container {
@@ -212,8 +199,7 @@
.hashtag {
flex: 1 1 auto;
- color: $fallback--text;
- color: var(--text, $fallback--text);
+ color: var(--text);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@@ -226,14 +212,14 @@
line-height: 2.25rem;
font-weight: 500;
text-align: center;
- color: $fallback--text;
- color: var(--text, $fallback--text);
+ color: var(--text);
}
}
.more-statuses-button {
height: 3.5em;
line-height: 3.5em;
+ width: 100%;
}
</style>
diff --git a/src/components/search_bar/search_bar.vue b/src/components/search_bar/search_bar.vue
index 9da2b272..d06b4e77 100644
--- a/src/components/search_bar/search_bar.vue
+++ b/src/components/search_bar/search_bar.vue
@@ -22,7 +22,7 @@
id="search-bar-input"
ref="searchInput"
v-model="searchTerm"
- class="search-bar-input"
+ class="input search-bar-input"
:placeholder="$t('nav.search')"
type="text"
@keyup.enter="find(searchTerm)"
@@ -60,8 +60,6 @@
<script src="./search_bar.js"></script>
<style lang="scss">
-@import "../../variables";
-
.SearchBar {
display: inline-flex;
align-items: baseline;
@@ -86,8 +84,7 @@
}
.cancel-icon {
- color: $fallback--text;
- color: var(--btnTopBarText, $fallback--text);
+ color: var(--text);
}
}
diff --git a/src/components/select/select.vue b/src/components/select/select.vue
index 1797afc8..32832126 100644
--- a/src/components/select/select.vue
+++ b/src/components/select/select.vue
@@ -22,8 +22,6 @@
<script src="./select.js"> </script>
<style lang="scss">
-@import "../../variables";
-
/* TODO fix order of styles */
label.Select {
padding: 0;
@@ -32,12 +30,10 @@ label.Select {
appearance: none;
background: transparent;
border: none;
- color: $fallback--text;
- color: var(--inputText, --text, $fallback--text);
+ color: var(--text);
margin: 0;
padding: 0 2em 0 0.2em;
- font-family: sans-serif;
- font-family: var(--inputFont, sans-serif);
+ font-family: var(--font);
font-size: 1em;
width: 100%;
z-index: 1;
@@ -52,8 +48,7 @@ label.Select {
right: 5px;
height: 100%;
width: 0.875em;
- color: $fallback--text;
- color: var(--inputText, $fallback--text);
+ font-family: var(--font);
line-height: 2;
z-index: 0;
pointer-events: none;
diff --git a/src/components/selectable_list/selectable_list.vue b/src/components/selectable_list/selectable_list.vue
index 14910fc5..3d3a5ff0 100644
--- a/src/components/selectable_list/selectable_list.vue
+++ b/src/components/selectable_list/selectable_list.vue
@@ -23,16 +23,19 @@
<List
:items="items"
:get-key="getKey"
+ :get-class="item => isSelected(item) ? '-active' : ''"
>
<template #item="{item}">
<div
class="selectable-list-item-inner"
:class="{ 'selectable-list-item-selected-inner': isSelected(item) }"
+ @click.stop="toggle(!isSelected(item), item)"
>
<div class="selectable-list-checkbox-wrapper">
<Checkbox
:model-value="isSelected(item)"
@update:model-value="checked => toggle(checked, item)"
+ @click.stop
/>
</div>
<slot
@@ -51,9 +54,11 @@
<script src="./selectable_list.js"></script>
<style lang="scss">
-@import "../../variables";
-
.selectable-list {
+ --__line-height: 1.5em;
+ --__horizontal-gap: 0.75em;
+ --__vertical-gap: 0.5em;
+
&-item-inner {
display: flex;
align-items: center;
@@ -63,24 +68,12 @@
}
}
- &-item-selected-inner {
- background-color: $fallback--lightBg;
- background-color: var(--selectedMenu, $fallback--lightBg);
- color: var(--selectedMenuText, $fallback--text);
-
- --faint: var(--selectedMenuFaintText, $fallback--faint);
- --faintLink: var(--selectedMenuFaintLink, $fallback--faint);
- --lightText: var(--selectedMenuLightText, $fallback--lightText);
- --icon: var(--selectedMenuIcon, $fallback--icon);
- }
-
&-header {
display: flex;
align-items: center;
- padding: 0.6em 0;
- border-bottom: 2px solid;
- border-bottom-color: $fallback--border;
- border-bottom-color: var(--border, $fallback--border);
+ padding: var(--__vertical-gap) var(--__horizontal-gap);
+ border-bottom: 1px solid;
+ border-bottom-color: var(--border);
&-actions {
flex: 1;
@@ -88,7 +81,7 @@
}
&-checkbox-wrapper {
- padding: 0 10px;
+ padding-right: var(--__horizontal-gap);
flex: none;
}
}
diff --git a/src/components/settings_modal/admin_tabs/emoji_tab.js b/src/components/settings_modal/admin_tabs/emoji_tab.js
new file mode 100644
index 00000000..58e1468f
--- /dev/null
+++ b/src/components/settings_modal/admin_tabs/emoji_tab.js
@@ -0,0 +1,257 @@
+import { clone, assign } from 'lodash'
+import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
+import StringSetting from '../helpers/string_setting.vue'
+import Checkbox from 'components/checkbox/checkbox.vue'
+import StillImage from 'components/still-image/still-image.vue'
+import Select from 'components/select/select.vue'
+import Popover from 'components/popover/popover.vue'
+import ConfirmModal from 'components/confirm_modal/confirm_modal.vue'
+import ModifiedIndicator from '../helpers/modified_indicator.vue'
+import EmojiEditingPopover from '../helpers/emoji_editing_popover.vue'
+
+const EmojiTab = {
+ components: {
+ TabSwitcher,
+ StringSetting,
+ Checkbox,
+ StillImage,
+ Select,
+ Popover,
+ ConfirmModal,
+ ModifiedIndicator,
+ EmojiEditingPopover
+ },
+
+ data () {
+ return {
+ knownLocalPacks: { },
+ knownRemotePacks: { },
+ editedMetadata: { },
+ packName: '',
+ newPackName: '',
+ deleteModalVisible: false,
+ remotePackInstance: '',
+ remotePackDownloadAs: ''
+ }
+ },
+
+ provide () {
+ return { emojiAddr: this.emojiAddr }
+ },
+
+ computed: {
+ pack () {
+ return this.packName !== '' ? this.knownPacks[this.packName] : undefined
+ },
+ packMeta () {
+ if (this.editedMetadata[this.packName] === undefined) {
+ this.editedMetadata[this.packName] = clone(this.pack.pack)
+ }
+
+ return this.editedMetadata[this.packName]
+ },
+ knownPacks () {
+ // Copy the object itself but not the children, so they are still passed by reference and modified
+ const result = clone(this.knownLocalPacks)
+ for (const instName in this.knownRemotePacks) {
+ for (const instPack in this.knownRemotePacks[instName]) {
+ result[`${instPack}@${instName}`] = this.knownRemotePacks[instName][instPack]
+ }
+ }
+
+ return result
+ },
+ downloadWillReplaceLocal () {
+ return (this.remotePackDownloadAs.trim() === '' && this.pack.remote && this.pack.remote.baseName in this.knownLocalPacks) ||
+ (this.remotePackDownloadAs in this.knownLocalPacks)
+ }
+ },
+
+ methods: {
+ reloadEmoji () {
+ this.$store.state.api.backendInteractor.reloadEmoji()
+ },
+ importFromFS () {
+ this.$store.state.api.backendInteractor.importEmojiFromFS()
+ },
+ emojiAddr (name) {
+ if (this.pack.remote !== undefined) {
+ // Remote pack
+ return `${this.pack.remote.instance}/emoji/${encodeURIComponent(this.pack.remote.baseName)}/${name}`
+ } else {
+ return `${this.$store.state.instance.server}/emoji/${encodeURIComponent(this.packName)}/${name}`
+ }
+ },
+
+ createEmojiPack () {
+ this.$store.state.api.backendInteractor.createEmojiPack(
+ { name: this.newPackName }
+ ).then(resp => resp.json()).then(resp => {
+ if (resp === 'ok') {
+ return this.refreshPackList()
+ } else {
+ this.displayError(resp.error)
+ return Promise.reject(resp)
+ }
+ }).then(done => {
+ this.$refs.createPackPopover.hidePopover()
+
+ this.packName = this.newPackName
+ this.newPackName = ''
+ })
+ },
+ deleteEmojiPack () {
+ this.$store.state.api.backendInteractor.deleteEmojiPack(
+ { name: this.packName }
+ ).then(resp => resp.json()).then(resp => {
+ if (resp === 'ok') {
+ return this.refreshPackList()
+ } else {
+ this.displayError(resp.error)
+ return Promise.reject(resp)
+ }
+ }).then(done => {
+ delete this.editedMetadata[this.packName]
+
+ this.deleteModalVisible = false
+ this.packName = ''
+ })
+ },
+
+ metaEdited (prop) {
+ if (!this.pack) return
+
+ const def = this.pack.pack[prop] || ''
+ const edited = this.packMeta[prop] || ''
+ return edited !== def
+ },
+ savePackMetadata () {
+ this.$store.state.api.backendInteractor.saveEmojiPackMetadata({ name: this.packName, newData: this.packMeta }).then(
+ resp => resp.json()
+ ).then(resp => {
+ if (resp.error !== undefined) {
+ this.displayError(resp.error)
+ return
+ }
+
+ // Update actual pack data
+ this.pack.pack = resp
+ // Delete edited pack data, should auto-update itself
+ delete this.editedMetadata[this.packName]
+ })
+ },
+
+ updatePackFiles (newFiles) {
+ this.pack.files = newFiles
+ this.sortPackFiles(this.packName)
+ },
+
+ loadPacksPaginated (listFunction) {
+ const pageSize = 25
+ const allPacks = {}
+
+ return listFunction({ instance: this.remotePackInstance, page: 1, pageSize: 0 })
+ .then(data => data.json())
+ .then(data => {
+ if (data.error !== undefined) { return Promise.reject(data.error) }
+
+ let resultingPromise = Promise.resolve({})
+ for (let i = 0; i < Math.ceil(data.count / pageSize); i++) {
+ resultingPromise = resultingPromise.then(() => listFunction({ instance: this.remotePackInstance, page: i, pageSize })
+ ).then(data => data.json()).then(pageData => {
+ if (pageData.error !== undefined) { return Promise.reject(pageData.error) }
+
+ assign(allPacks, pageData.packs)
+ })
+ }
+
+ return resultingPromise
+ })
+ .then(finished => allPacks)
+ .catch(data => {
+ this.displayError(data)
+ })
+ },
+
+ refreshPackList () {
+ this.loadPacksPaginated(this.$store.state.api.backendInteractor.listEmojiPacks)
+ .then(allPacks => {
+ this.knownLocalPacks = allPacks
+ for (const name of Object.keys(this.knownLocalPacks)) {
+ this.sortPackFiles(name)
+ }
+ })
+ },
+ listRemotePacks () {
+ this.loadPacksPaginated(this.$store.state.api.backendInteractor.listRemoteEmojiPacks)
+ .then(allPacks => {
+ let inst = this.remotePackInstance
+ if (!inst.startsWith('http')) { inst = 'https://' + inst }
+ const instUrl = new URL(inst)
+ inst = instUrl.host
+
+ for (const packName in allPacks) {
+ allPacks[packName].remote = {
+ baseName: packName,
+ instance: instUrl.origin
+ }
+ }
+
+ this.knownRemotePacks[inst] = allPacks
+ for (const pack in this.knownRemotePacks[inst]) {
+ this.sortPackFiles(`${pack}@${inst}`)
+ }
+
+ this.$refs.remotePackPopover.hidePopover()
+ })
+ .catch(data => {
+ this.displayError(data)
+ })
+ },
+ downloadRemotePack () {
+ if (this.remotePackDownloadAs.trim() === '') {
+ this.remotePackDownloadAs = this.pack.remote.baseName
+ }
+
+ this.$store.state.api.backendInteractor.downloadRemoteEmojiPack({
+ instance: this.pack.remote.instance, packName: this.pack.remote.baseName, as: this.remotePackDownloadAs
+ })
+ .then(data => data.json())
+ .then(resp => {
+ if (resp === 'ok') {
+ this.$refs.dlPackPopover.hidePopover()
+
+ return this.refreshPackList()
+ } else {
+ this.displayError(resp.error)
+ return Promise.reject(resp)
+ }
+ }).then(done => {
+ this.packName = this.remotePackDownloadAs
+ this.remotePackDownloadAs = ''
+ })
+ },
+ displayError (msg) {
+ this.$store.dispatch('pushGlobalNotice', {
+ messageKey: 'admin_dash.emoji.error',
+ messageArgs: [msg],
+ level: 'error'
+ })
+ },
+ sortPackFiles (nameOfPack) {
+ // Sort by key
+ const sorted = Object.keys(this.knownPacks[nameOfPack].files).sort().reduce((acc, key) => {
+ if (key.length === 0) return acc
+ acc[key] = this.knownPacks[nameOfPack].files[key]
+ return acc
+ }, {})
+ this.knownPacks[nameOfPack].files = sorted
+ }
+ },
+
+ mounted () {
+ this.refreshPackList()
+ }
+}
+
+export default EmojiTab
diff --git a/src/components/settings_modal/admin_tabs/emoji_tab.scss b/src/components/settings_modal/admin_tabs/emoji_tab.scss
new file mode 100644
index 00000000..3e77e019
--- /dev/null
+++ b/src/components/settings_modal/admin_tabs/emoji_tab.scss
@@ -0,0 +1,59 @@
+.emoji-tab {
+ .btn-group .btn:not(:first-child) {
+ margin-left: 0.5em;
+ }
+
+ .pack-info-wrapper {
+ margin-top: 1em;
+ }
+
+ .emoji-info-input {
+ width: 100%;
+ }
+
+ .emoji-data-input {
+ width: 40%;
+ margin-left: 0.5em;
+ margin-right: 0.5em;
+ }
+
+ .emoji {
+ width: 32px;
+ height: 32px;
+ }
+
+ .emoji-unsaved {
+ box-shadow: 0 3px 5px var(--cBlue);
+ }
+
+ .emoji-list {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 1em 1em;
+ }
+}
+
+.emoji-tab-popover-button:not(:first-child) {
+ margin-left: 0.5em;
+}
+
+.emoji-tab-popover-input {
+ margin-bottom: 0.5em;
+
+ label {
+ display: block;
+ margin-bottom: 0.5em;
+ }
+
+ input {
+ width: 20em;
+ }
+
+ .emoji-tab-popover-file {
+ padding-top: 3px;
+ }
+
+ .warning {
+ color: var(--cOrange);
+ }
+}
diff --git a/src/components/settings_modal/admin_tabs/emoji_tab.vue b/src/components/settings_modal/admin_tabs/emoji_tab.vue
new file mode 100644
index 00000000..5742d2ce
--- /dev/null
+++ b/src/components/settings_modal/admin_tabs/emoji_tab.vue
@@ -0,0 +1,358 @@
+<template>
+ <div
+ class="emoji-tab"
+ :label="$t('admin_dash.tabs.emoji')"
+ >
+ <div class="setting-item">
+ <h2>{{ $t('admin_dash.tabs.emoji') }}</h2>
+
+ <ul class="setting-list">
+ <h3>{{ $t('admin_dash.emoji.global_actions') }}</h3>
+
+ <li class="btn-group setting-item">
+ <button
+ class="button button-default btn"
+ type="button"
+ @click="reloadEmoji"
+ >
+ {{ $t('admin_dash.emoji.reload') }}
+ </button>
+ <button
+ class="button button-default btn"
+ type="button"
+ @click="importFromFS"
+ >
+ {{ $t('admin_dash.emoji.importFS') }}
+ </button>
+ </li>
+
+ <li class="btn-group setting-item">
+ <button
+ class="button button-default btn"
+ type="button"
+ @click="$refs.remotePackPopover.showPopover"
+ >
+ {{ $t('admin_dash.emoji.remote_packs') }}
+
+ <Popover
+ ref="remotePackPopover"
+ popover-class="emoji-tab-edit-popover popover-default"
+ trigger="click"
+ placement="bottom"
+ bound-to-selector=".emoji-tab"
+ :bound-to="{ x: 'container' }"
+ :offset="{ y: 5 }"
+ >
+ <template #content>
+ <div class="emoji-tab-popover-input">
+ <h3>{{ $t('admin_dash.emoji.remote_pack_instance') }}</h3>
+ <input
+ v-model="remotePackInstance"
+ class="input"
+ :placeholder="$t('admin_dash.emoji.remote_pack_instance')"
+ >
+ <button
+ class="button button-default btn emoji-tab-popover-button"
+ type="button"
+ @click="listRemotePacks"
+ >
+ {{ $t('admin_dash.emoji.do_list') }}
+ </button>
+ </div>
+ </template>
+ </Popover>
+ </button>
+ </li>
+
+ <h3>{{ $t('admin_dash.emoji.emoji_packs') }}</h3>
+
+ <li>
+ <h4>{{ $t('admin_dash.emoji.edit_pack') }}</h4>
+
+ <Select
+ v-model="packName"
+ class="form-control"
+ >
+ <option
+ value=""
+ disabled
+ hidden
+ >
+ {{ $t('admin_dash.emoji.emoji_pack') }}
+ </option>
+ <option
+ v-for="(pack, listPackName) in knownPacks"
+ :key="listPackName"
+ :label="listPackName"
+ >
+ {{ listPackName }}
+ </option>
+ </Select>
+
+ <button
+ class="button button-default btn emoji-tab-popover-button"
+ type="button"
+ @click="$refs.createPackPopover.showPopover"
+ >
+ {{ $t('admin_dash.emoji.create_pack') }}
+ </button>
+ <Popover
+ ref="createPackPopover"
+ popover-class="emoji-tab-edit-popover popover-default"
+ trigger="click"
+ placement="bottom"
+ bound-to-selector=".emoji-tab"
+ :bound-to="{ x: 'container' }"
+ :offset="{ y: 5 }"
+ >
+ <template #content>
+ <div class="emoji-tab-popover-input">
+ <h3>{{ $t('admin_dash.emoji.new_pack_name') }}</h3>
+ <input
+ v-model="newPackName"
+ :placeholder="$t('admin_dash.emoji.new_pack_name')"
+ class="input"
+ >
+ <button
+ class="button button-default btn emoji-tab-popover-button"
+ type="button"
+ @click="createEmojiPack"
+ >
+ {{ $t('admin_dash.emoji.create') }}
+ </button>
+ </div>
+ </template>
+ </Popover>
+ </li>
+ </ul>
+
+ <div v-if="pack">
+ <div class="pack-info-wrapper">
+ <ul class="setting-list">
+ <li>
+ <label>
+ {{ $t('admin_dash.emoji.description') }}
+ <ModifiedIndicator
+ :changed="metaEdited('description')"
+ message-key="admin_dash.emoji.metadata_changed"
+ />
+
+ <textarea
+ v-model="packMeta.description"
+ :disabled="pack.remote !== undefined"
+ class="bio resize-height input"
+ />
+ </label>
+ </li>
+ <li>
+ <label>
+ {{ $t('admin_dash.emoji.homepage') }}
+ <ModifiedIndicator
+ :changed="metaEdited('homepage')"
+ message-key="admin_dash.emoji.metadata_changed"
+ />
+
+ <input
+ v-model="packMeta.homepage"
+ class="emoji-info-input input"
+ :disabled="pack.remote !== undefined"
+ >
+ </label>
+ </li>
+ <li>
+ <label>
+ {{ $t('admin_dash.emoji.fallback_src') }}
+ <ModifiedIndicator
+ :changed="metaEdited('fallback-src')"
+ message-key="admin_dash.emoji.metadata_changed"
+ />
+
+ <input
+ v-model="packMeta['fallback-src']"
+ class="emoji-info-input input"
+ :disabled="pack.remote !== undefined"
+ >
+ </label>
+ </li>
+ <li>
+ <label>
+ {{ $t('admin_dash.emoji.fallback_sha256') }}
+
+ <input
+ v-model="packMeta['fallback-src-sha256']"
+ :disabled="true"
+ class="emoji-info-input input"
+ >
+ </label>
+ </li>
+ <li>
+ <Checkbox
+ v-model="packMeta['share-files']"
+ :disabled="pack.remote !== undefined"
+ >
+ {{ $t('admin_dash.emoji.share') }}
+ </Checkbox>
+
+ <ModifiedIndicator
+ :changed="metaEdited('share-files')"
+ message-key="admin_dash.emoji.metadata_changed"
+ />
+ </li>
+ <li class="btn-group">
+ <button
+ v-if="pack.remote === undefined"
+ class="button button-default btn"
+ type="button"
+ @click="savePackMetadata"
+ >
+ {{ $t('admin_dash.emoji.save_meta') }}
+ </button>
+ <button
+ v-if="pack.remote === undefined"
+ class="button button-default btn"
+ type="button"
+ @click="savePackMetadata"
+ >
+ {{ $t('admin_dash.emoji.revert_meta') }}
+ </button>
+
+ <button
+ v-if="pack.remote === undefined"
+ class="button button-default btn"
+ type="button"
+ @click="deleteModalVisible = true"
+ >
+ {{ $t('admin_dash.emoji.delete_pack') }}
+
+ <ConfirmModal
+ v-if="deleteModalVisible"
+ :title="$t('admin_dash.emoji.delete_title')"
+ :cancel-text="$t('status.delete_confirm_cancel_button')"
+ :confirm-text="$t('status.delete_confirm_accept_button')"
+ @cancelled="deleteModalVisible = false"
+ @accepted="deleteEmojiPack"
+ >
+ {{ $t('admin_dash.emoji.delete_confirm', [packName]) }}
+ </ConfirmModal>
+ </button>
+
+ <button
+ v-if="pack.remote !== undefined"
+ class="button button-default btn"
+ type="button"
+ @click="$refs.dlPackPopover.showPopover"
+ >
+ {{ $t('admin_dash.emoji.download_pack') }}
+
+ <Popover
+ ref="dlPackPopover"
+ trigger="click"
+ placement="bottom"
+ bound-to-selector=".emoji-tab"
+ popover-class="emoji-tab-edit-popover popover-default"
+ :bound-to="{ x: 'container' }"
+ :offset="{ y: 5 }"
+ >
+ <template #content>
+ <h3>{{ $t('admin_dash.emoji.downloading_pack', [packName]) }}</h3>
+ <div>
+ <div>
+ <div class="emoji-tab-popover-input">
+ <label>
+ {{ $t('admin_dash.emoji.download_as_name') }}
+ <input
+ v-model="remotePackDownloadAs"
+ class="emoji-data-input input"
+ :placeholder="$t('admin_dash.emoji.download_as_name_full')"
+ >
+ </label>
+
+ <div
+ v-if="downloadWillReplaceLocal"
+ class="warning"
+ >
+ <em>{{ $t('admin_dash.emoji.replace_warning') }}</em>
+ </div>
+ </div>
+
+ <button
+ class="button button-default btn"
+ type="button"
+ @click="downloadRemotePack"
+ >
+ {{ $t('admin_dash.emoji.download') }}
+ </button>
+ </div>
+ </div>
+ </template>
+ </Popover>
+ </button>
+ </li>
+ </ul>
+ </div>
+
+ <ul class="setting-list">
+ <h4>
+ {{ $t('admin_dash.emoji.files') }}
+
+ <ModifiedIndicator
+ v-if="pack"
+ :changed="$refs.emojiPopovers && $refs.emojiPopovers.some(p => p.isEdited)"
+ message-key="admin_dash.emoji.emoji_changed"
+ />
+ </h4>
+
+ <div
+ v-if="pack"
+ class="emoji-list"
+ >
+ <EmojiEditingPopover
+ v-if="pack.remote === undefined"
+ placement="bottom"
+ new-upload
+ :title="$t('admin_dash.emoji.adding_new')"
+ :pack-name="packName"
+ @updatePackFiles="updatePackFiles"
+ @displayError="displayError"
+ >
+ <template #trigger>
+ <FAIcon
+ icon="plus"
+ size="2x"
+ :title="$t('admin_dash.emoji.add_file')"
+ />
+ </template>
+ </EmojiEditingPopover>
+
+ <EmojiEditingPopover
+ v-for="(file, shortcode) in pack.files"
+ ref="emojiPopovers"
+ :key="shortcode"
+ placement="top"
+ :title="$t('admin_dash.emoji.editing', [shortcode])"
+ :disabled="pack.remote !== undefined"
+ :shortcode="shortcode"
+ :file="file"
+ :pack-name="packName"
+ @updatePackFiles="updatePackFiles"
+ @displayError="displayError"
+ >
+ <template #trigger>
+ <StillImage
+ class="emoji"
+ :src="emojiAddr(file)"
+ :title="`:${shortcode}:`"
+ :alt="`:${shortcode}:`"
+ />
+ </template>
+ </EmojiEditingPopover>
+ </div>
+ </ul>
+ </div>
+ </div>
+ </div>
+</template>
+
+<script src="./emoji_tab.js"></script>
+
+<style lang="scss" src="./emoji_tab.scss"></style>
diff --git a/src/components/settings_modal/admin_tabs/frontends_tab.js b/src/components/settings_modal/admin_tabs/frontends_tab.js
new file mode 100644
index 00000000..f57310ee
--- /dev/null
+++ b/src/components/settings_modal/admin_tabs/frontends_tab.js
@@ -0,0 +1,113 @@
+import BooleanSetting from '../helpers/boolean_setting.vue'
+import ChoiceSetting from '../helpers/choice_setting.vue'
+import IntegerSetting from '../helpers/integer_setting.vue'
+import StringSetting from '../helpers/string_setting.vue'
+import GroupSetting from '../helpers/group_setting.vue'
+import Popover from 'src/components/popover/popover.vue'
+import PanelLoading from 'src/components/panel_loading/panel_loading.vue'
+
+import SharedComputedObject from '../helpers/shared_computed_object.js'
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faGlobe
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faGlobe
+)
+
+const FrontendsTab = {
+ provide () {
+ return {
+ defaultDraftMode: true,
+ defaultSource: 'admin'
+ }
+ },
+ data () {
+ return {
+ working: false
+ }
+ },
+ components: {
+ BooleanSetting,
+ ChoiceSetting,
+ IntegerSetting,
+ StringSetting,
+ GroupSetting,
+ PanelLoading,
+ Popover
+ },
+ created () {
+ if (this.user.rights.admin) {
+ this.$store.dispatch('loadFrontendsStuff')
+ }
+ },
+ computed: {
+ frontends () {
+ return this.$store.state.adminSettings.frontends
+ },
+ ...SharedComputedObject()
+ },
+ methods: {
+ canInstall (frontend) {
+ const fe = this.frontends.find(f => f.name === frontend.name)
+ if (!fe) return false
+ return fe.refs.includes(frontend.ref)
+ },
+ getSuggestedRef (frontend) {
+ if (this.adminDraft) {
+ const defaultFe = this.adminDraft[':pleroma'][':frontends'][':primary']
+ if (defaultFe?.name === frontend.name && this.canInstall(defaultFe)) {
+ return defaultFe.ref
+ } else {
+ return frontend.refs[0]
+ }
+ } else {
+ return frontend.refs[0]
+ }
+ },
+ update (frontend, suggestRef) {
+ const ref = suggestRef || this.getSuggestedRef(frontend)
+ const { name } = frontend
+ const payload = { name, ref }
+
+ this.working = true
+ this.$store.state.api.backendInteractor.installFrontend({ payload })
+ .finally(() => {
+ this.working = false
+ })
+ .then(async (response) => {
+ this.$store.dispatch('loadFrontendsStuff')
+ if (response.error) {
+ const reason = await response.error.json()
+ this.$store.dispatch('pushGlobalNotice', {
+ level: 'error',
+ messageKey: 'admin_dash.frontend.failure_installing_frontend',
+ messageArgs: {
+ version: name + '/' + ref,
+ reason: reason.error
+ },
+ timeout: 5000
+ })
+ } else {
+ this.$store.dispatch('pushGlobalNotice', {
+ level: 'success',
+ messageKey: 'admin_dash.frontend.success_installing_frontend',
+ messageArgs: {
+ version: name + '/' + ref
+ },
+ timeout: 2000
+ })
+ }
+ })
+ },
+ setDefault (frontend, suggestRef) {
+ const ref = suggestRef || this.getSuggestedRef(frontend)
+ const { name } = frontend
+
+ this.$store.commit('updateAdminDraft', { path: [':pleroma', ':frontends', ':primary'], value: { name, ref } })
+ }
+ }
+}
+
+export default FrontendsTab
diff --git a/src/components/settings_modal/admin_tabs/frontends_tab.scss b/src/components/settings_modal/admin_tabs/frontends_tab.scss
new file mode 100644
index 00000000..420d20b3
--- /dev/null
+++ b/src/components/settings_modal/admin_tabs/frontends_tab.scss
@@ -0,0 +1,29 @@
+.frontends-tab {
+ .cards-list {
+ padding: 0;
+ }
+
+ .relative {
+ position: relative;
+ }
+
+ .overlay {
+ position: absolute;
+ background: var(--bg);
+ // fix buttons showing through
+ z-index: 2;
+ opacity: 0.9;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ }
+
+ dd {
+ text-overflow: ellipsis;
+ word-wrap: nowrap;
+ white-space: nowrap;
+ overflow-x: hidden;
+ max-width: 10em;
+ }
+}
diff --git a/src/components/settings_modal/admin_tabs/frontends_tab.vue b/src/components/settings_modal/admin_tabs/frontends_tab.vue
new file mode 100644
index 00000000..8fb3d399
--- /dev/null
+++ b/src/components/settings_modal/admin_tabs/frontends_tab.vue
@@ -0,0 +1,209 @@
+<template>
+ <div
+ class="frontends-tab"
+ :label="$t('admin_dash.tabs.frontends')"
+ >
+ <div class="setting-item">
+ <h2>{{ $t('admin_dash.tabs.frontends') }}</h2>
+ <p>{{ $t('admin_dash.frontend.wip_notice') }}</p>
+ <ul
+ v-if="adminDraft"
+ class="setting-list"
+ >
+ <li>
+ <h3>{{ $t('admin_dash.frontend.default_frontend') }}</h3>
+ <p>{{ $t('admin_dash.frontend.default_frontend_tip') }}</p>
+ <ul class="setting-list">
+ <li>
+ <StringSetting path=":pleroma.:frontends.:primary.name" />
+ </li>
+ <li>
+ <StringSetting path=":pleroma.:frontends.:primary.ref" />
+ </li>
+ <li>
+ <GroupSetting path=":pleroma.:frontends.:primary" />
+ </li>
+ </ul>
+ </li>
+ </ul>
+ <div
+ v-else
+ class="setting-list"
+ >
+ {{ $t('admin_dash.frontend.default_frontend_unavail') }}
+ </div>
+
+ <div class="setting-list relative">
+ <PanelLoading
+ v-if="working"
+ class="overlay"
+ />
+ <h3>{{ $t('admin_dash.frontend.available_frontends') }}</h3>
+ <ul class="cards-list">
+ <li
+ v-for="frontend in frontends"
+ :key="frontend.name"
+ >
+ <strong>{{ frontend.name }}</strong>
+ {{ ' ' }}
+ <span v-if="adminDraft && adminDraft[':pleroma'][':frontends'][':primary']?.name === frontend.name">
+ <i18n-t
+ v-if="adminDraft && adminDraft[':pleroma'][':frontends'][':primary']?.ref === frontend.refs[0]"
+ keypath="admin_dash.frontend.is_default"
+ />
+ <i18n-t
+ v-else
+ keypath="admin_dash.frontend.is_default_custom"
+ >
+ <template #version>
+ <code>{{ adminDraft && adminDraft[':pleroma'][':frontends'][':primary'].ref }}</code>
+ </template>
+ </i18n-t>
+ </span>
+ <dl>
+ <dt>{{ $t('admin_dash.frontend.repository') }}</dt>
+ <dd>
+ <a
+ :href="frontend.git"
+ target="_blank"
+ >{{ frontend.git }}</a>
+ </dd>
+ <template v-if="expertLevel">
+ <dt>{{ $t('admin_dash.frontend.versions') }}</dt>
+ <dd
+ v-for="ref in frontend.refs"
+ :key="ref"
+ >
+ <code>{{ ref }}</code>
+ </dd>
+ </template>
+ <dt v-if="expertLevel">
+ {{ $t('admin_dash.frontend.build_url') }}
+ </dt>
+ <dd v-if="expertLevel">
+ <a
+ :href="frontend.build_url"
+ target="_blank"
+ >{{ frontend.build_url }}</a>
+ </dd>
+ </dl>
+ <div>
+ <span class="btn-group">
+ <button
+ class="button button-default btn"
+ type="button"
+ @click="update(frontend)"
+ >
+ {{
+ frontend.installed
+ ? $t('admin_dash.frontend.reinstall')
+ : $t('admin_dash.frontend.install')
+ }}
+ <code>
+ {{
+ getSuggestedRef(frontend)
+ }}
+ </code>
+ </button>
+ <Popover
+ v-if="frontend.refs.length > 1"
+ trigger="click"
+ class="button-dropdown"
+ placement="bottom"
+ >
+ <template #content="{close}">
+ <div class="dropdown-menu">
+ <button
+ v-for="ref in frontend.refs"
+ :key="ref"
+ class="menu-item dropdown-item"
+ @click.prevent="update(frontend, ref)"
+ @click="close"
+ >
+ <i18n-t keypath="admin_dash.frontend.install_version">
+ <template #version>
+ <code>{{ ref }}</code>
+ </template>
+ </i18n-t>
+ </button>
+ </div>
+ </template>
+ <template #trigger>
+ <button
+ class="button button-default btn dropdown-button"
+ type="button"
+ :title="$t('admin_dash.frontend.more_install_options')"
+ >
+ <FAIcon icon="chevron-down" />
+ </button>
+ </template>
+ </Popover>
+ </span>
+ <span
+ v-if="frontend.installed && frontend.name !== 'admin-fe'"
+ class="btn-group"
+ >
+ <button
+ class="button button-default btn"
+ type="button"
+ :disabled="
+ !adminDraft || adminDraft[':pleroma'][':frontends'][':primary']?.name === frontend.name &&
+ adminDraft[':pleroma'][':frontends'][':primary']?.ref === frontend.refs[0]
+ "
+ @click="setDefault(frontend)"
+ >
+ {{
+ $t('admin_dash.frontend.set_default')
+ }}
+ <code>
+ {{
+ getSuggestedRef(frontend)
+ }}
+ </code>
+ </button>
+ {{ ' ' }}
+ <Popover
+ v-if="frontend.refs.length > 1"
+ trigger="click"
+ class="button-dropdown"
+ placement="bottom"
+ >
+ <template #content="{close}">
+ <div class="dropdown-menu">
+ <button
+ v-for="ref in frontend.installedRefs || frontend.refs"
+ :key="ref"
+ class="menu-item dropdown-item"
+ @click.prevent="setDefault(frontend, ref)"
+ @click="close"
+ >
+ <i18n-t keypath="admin_dash.frontend.set_default_version">
+ <template #version>
+ <code>{{ ref }}</code>
+ </template>
+ </i18n-t>
+ </button>
+ </div>
+ </template>
+ <template #trigger>
+ <button
+ class="button button-default btn dropdown-button"
+ type="button"
+ :title="$t('admin_dash.frontend.more_default_options')"
+ >
+ <FAIcon icon="chevron-down" />
+ </button>
+ </template>
+ </Popover>
+ </span>
+ </div>
+ </li>
+ </ul>
+ </div>
+ </div>
+ </div>
+</template>
+
+<script src="./frontends_tab.js"></script>
+
+<style lang="scss" src="./frontends_tab.scss"></style>
diff --git a/src/components/settings_modal/admin_tabs/instance_tab.js b/src/components/settings_modal/admin_tabs/instance_tab.js
new file mode 100644
index 00000000..b07bafe8
--- /dev/null
+++ b/src/components/settings_modal/admin_tabs/instance_tab.js
@@ -0,0 +1,38 @@
+import BooleanSetting from '../helpers/boolean_setting.vue'
+import ChoiceSetting from '../helpers/choice_setting.vue'
+import IntegerSetting from '../helpers/integer_setting.vue'
+import StringSetting from '../helpers/string_setting.vue'
+import GroupSetting from '../helpers/group_setting.vue'
+import AttachmentSetting from '../helpers/attachment_setting.vue'
+
+import SharedComputedObject from '../helpers/shared_computed_object.js'
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faGlobe
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faGlobe
+)
+
+const InstanceTab = {
+ provide () {
+ return {
+ defaultDraftMode: true,
+ defaultSource: 'admin'
+ }
+ },
+ components: {
+ BooleanSetting,
+ ChoiceSetting,
+ IntegerSetting,
+ StringSetting,
+ AttachmentSetting,
+ GroupSetting
+ },
+ computed: {
+ ...SharedComputedObject()
+ }
+}
+
+export default InstanceTab
diff --git a/src/components/settings_modal/admin_tabs/instance_tab.vue b/src/components/settings_modal/admin_tabs/instance_tab.vue
new file mode 100644
index 00000000..32e8df25
--- /dev/null
+++ b/src/components/settings_modal/admin_tabs/instance_tab.vue
@@ -0,0 +1,206 @@
+<template>
+ <div :label="$t('admin_dash.tabs.instance')">
+ <div class="setting-item">
+ <h2>{{ $t('admin_dash.instance.instance') }}</h2>
+ <ul class="setting-list">
+ <li>
+ <StringSetting path=":pleroma.:instance.:name" />
+ </li>
+ <!-- See https://git.pleroma.social/pleroma/pleroma/-/merge_requests/3963 -->
+ <li v-if="adminDraft[':pleroma'][':instance'][':favicon'] !== undefined">
+ <AttachmentSetting
+ compact
+ path=":pleroma.:instance.:favicon"
+ />
+ </li>
+ <li>
+ <StringSetting path=":pleroma.:instance.:email" />
+ </li>
+ <li>
+ <StringSetting path=":pleroma.:instance.:description" />
+ </li>
+ <li>
+ <StringSetting path=":pleroma.:instance.:short_description" />
+ </li>
+ <li>
+ <AttachmentSetting
+ compact
+ path=":pleroma.:instance.:instance_thumbnail"
+ />
+ </li>
+ <li>
+ <AttachmentSetting path=":pleroma.:instance.:background_image" />
+ </li>
+ </ul>
+ </div>
+ <div class="setting-item">
+ <h2>{{ $t('admin_dash.instance.registrations') }}</h2>
+ <ul class="setting-list">
+ <li>
+ <BooleanSetting path=":pleroma.:instance.:registrations_open" />
+ <ul class="setting-list suboptions">
+ <li>
+ <BooleanSetting
+ path=":pleroma.:instance.:invites_enabled"
+ parent-path=":pleroma.:instance.:registrations_open"
+ parent-invert
+ />
+ </li>
+ </ul>
+ </li>
+ <li>
+ <BooleanSetting path=":pleroma.:instance.:birthday_required" />
+ <ul class="setting-list suboptions">
+ <li>
+ <IntegerSetting
+ path=":pleroma.:instance.:birthday_min_age"
+ parent-path=":pleroma.:instance.:birthday_required"
+ />
+ </li>
+ </ul>
+ </li>
+ <li>
+ <BooleanSetting path=":pleroma.:instance.:account_activation_required" />
+ </li>
+ <li>
+ <BooleanSetting path=":pleroma.:instance.:account_approval_required" />
+ </li>
+ <li>
+ <h3>{{ $t('admin_dash.instance.captcha_header') }}</h3>
+ <ul class="setting-list">
+ <li>
+ <BooleanSetting :path="[':pleroma', 'Pleroma.Captcha', ':enabled']" />
+ <ul class="setting-list suboptions">
+ <li>
+ <ChoiceSetting
+ :path="[':pleroma', 'Pleroma.Captcha', ':method']"
+ :parent-path="[':pleroma', 'Pleroma.Captcha', ':enabled']"
+ :option-label-map="{
+ 'Pleroma.Captcha.Native': $t('admin_dash.captcha.native'),
+ 'Pleroma.Captcha.Kocaptcha': $t('admin_dash.captcha.kocaptcha')
+ }"
+ />
+ <IntegerSetting
+ :path="[':pleroma', 'Pleroma.Captcha', ':seconds_valid']"
+ :parent-path="[':pleroma', 'Pleroma.Captcha', ':enabled']"
+ />
+ </li>
+ <li
+ v-if="adminDraft[':pleroma']['Pleroma.Captcha'][':enabled'] && adminDraft[':pleroma']['Pleroma.Captcha'][':method'] === 'Pleroma.Captcha.Kocaptcha'"
+ >
+ <h4>{{ $t('admin_dash.instance.kocaptcha') }}</h4>
+ <ul class="setting-list">
+ <li>
+ <StringSetting :path="[':pleroma', 'Pleroma.Captcha.Kocaptcha', ':endpoint']" />
+ </li>
+ </ul>
+ </li>
+ </ul>
+ </li>
+ </ul>
+ </li>
+ </ul>
+ </div>
+ <div class="setting-item">
+ <h2>{{ $t('admin_dash.instance.access') }}</h2>
+ <ul class="setting-list">
+ <li>
+ <BooleanSetting
+ override-backend-description
+ override-backend-description-label
+ path=":pleroma.:instance.:public"
+ />
+ </li>
+ <li>
+ <ChoiceSetting
+ override-backend-description
+ override-backend-description-label
+ path=":pleroma.:instance.:limit_to_local_content"
+ />
+ </li>
+ <li v-if="expertLevel">
+ <h3>{{ $t('admin_dash.instance.restrict.header') }}</h3>
+ <p>
+ {{ $t('admin_dash.instance.restrict.description') }}
+ </p>
+ <ul class="setting-list">
+ <li>
+ <h4>{{ $t('admin_dash.instance.restrict.timelines') }}</h4>
+ <ul class="setting-list">
+ <li>
+ <BooleanSetting
+ path=":pleroma.:restrict_unauthenticated.:timelines.:local"
+ indeterminate-state=":if_instance_is_private"
+ swap-description-and-label
+ hide-description
+ />
+ </li>
+ <li>
+ <BooleanSetting
+ path=":pleroma.:restrict_unauthenticated.:timelines.:federated"
+ indeterminate-state=":if_instance_is_private"
+ swap-description-and-label
+ hide-description
+ />
+ </li>
+ <li>
+ <GroupSetting path=":pleroma.:restrict_unauthenticated.:timelines" />
+ </li>
+ </ul>
+ </li>
+ <li>
+ <h4>{{ $t('admin_dash.instance.restrict.profiles') }}</h4>
+ <ul class="setting-list">
+ <li>
+ <BooleanSetting
+ path=":pleroma.:restrict_unauthenticated.:profiles.:local"
+ indeterminate-state=":if_instance_is_private"
+ swap-description-and-label
+ hide-description
+ />
+ </li>
+ <li>
+ <BooleanSetting
+ path=":pleroma.:restrict_unauthenticated.:profiles.:remote"
+ indeterminate-state=":if_instance_is_private"
+ swap-description-and-label
+ hide-description
+ />
+ </li>
+ <li>
+ <GroupSetting path=":pleroma.:restrict_unauthenticated.:profiles" />
+ </li>
+ </ul>
+ </li>
+ <li>
+ <h4>{{ $t('admin_dash.instance.restrict.activities') }}</h4>
+ <ul class="setting-list">
+ <li>
+ <BooleanSetting
+ path=":pleroma.:restrict_unauthenticated.:activities.:local"
+ indeterminate-state=":if_instance_is_private"
+ swap-description-and-label
+ hide-description
+ />
+ </li>
+ <li>
+ <BooleanSetting
+ path=":pleroma.:restrict_unauthenticated.:activities.:remote"
+ indeterminate-state=":if_instance_is_private"
+ swap-description-and-label
+ hide-description
+ />
+ </li>
+ <li>
+ <GroupSetting path=":pleroma.:restrict_unauthenticated.:activities" />
+ </li>
+ </ul>
+ </li>
+ </ul>
+ </li>
+ </ul>
+ </div>
+ </div>
+</template>
+
+<script src="./instance_tab.js"></script>
diff --git a/src/components/settings_modal/admin_tabs/limits_tab.js b/src/components/settings_modal/admin_tabs/limits_tab.js
new file mode 100644
index 00000000..684739c3
--- /dev/null
+++ b/src/components/settings_modal/admin_tabs/limits_tab.js
@@ -0,0 +1,29 @@
+import BooleanSetting from '../helpers/boolean_setting.vue'
+import ChoiceSetting from '../helpers/choice_setting.vue'
+import IntegerSetting from '../helpers/integer_setting.vue'
+import StringSetting from '../helpers/string_setting.vue'
+
+import SharedComputedObject from '../helpers/shared_computed_object.js'
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faGlobe
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faGlobe
+)
+
+const LimitsTab = {
+ data () {},
+ components: {
+ BooleanSetting,
+ ChoiceSetting,
+ IntegerSetting,
+ StringSetting
+ },
+ computed: {
+ ...SharedComputedObject()
+ }
+}
+
+export default LimitsTab
diff --git a/src/components/settings_modal/admin_tabs/limits_tab.vue b/src/components/settings_modal/admin_tabs/limits_tab.vue
new file mode 100644
index 00000000..ef4b9271
--- /dev/null
+++ b/src/components/settings_modal/admin_tabs/limits_tab.vue
@@ -0,0 +1,136 @@
+<template>
+ <div :label="$t('admin_dash.tabs.limits')">
+ <div class="setting-item">
+ <h2>{{ $t('admin_dash.limits.arbitrary_limits') }}</h2>
+ <ul class="setting-list">
+ <li>
+ <h3>{{ $t('admin_dash.limits.posts') }}</h3>
+ <ul class="setting-list">
+ <li>
+ <IntegerSetting
+ source="admin"
+ path=":pleroma.:instance.:limit"
+ draft-mode
+ />
+ </li>
+ <li>
+ <IntegerSetting
+ source="admin"
+ path=":pleroma.:instance.:remote_limit"
+ expert="1"
+ draft-mode
+ />
+ </li>
+ </ul>
+ </li>
+ <li>
+ <h3>{{ $t('admin_dash.limits.uploads') }}</h3>
+ <ul class="setting-list">
+ <li>
+ <IntegerSetting
+ source="admin"
+ path=":pleroma.:instance.:description_limit"
+ draft-mode
+ />
+ </li>
+ <li>
+ <IntegerSetting
+ source="admin"
+ path=":pleroma.:instance.:upload_limit"
+ draft-mode
+ />
+ </li>
+ <li>
+ <IntegerSetting
+ source="admin"
+ path=":pleroma.:instance.:max_media_attachments"
+ draft-mode
+ />
+ </li>
+ </ul>
+ </li>
+ <li>
+ <h3>{{ $t('admin_dash.limits.users') }}</h3>
+ <ul class="setting-list">
+ <li>
+ <IntegerSetting
+ source="admin"
+ path=":pleroma.:instance.:max_pinned_statuses"
+ draft-mode
+ />
+ </li>
+ <li>
+ <IntegerSetting
+ source="admin"
+ path=":pleroma.:instance.:user_bio_length"
+ draft-mode
+ />
+ </li>
+ <li>
+ <IntegerSetting
+ source="admin"
+ path=":pleroma.:instance.:user_name_length"
+ draft-mode
+ />
+ </li>
+ <li>
+ <h4>{{ $t('admin_dash.limits.profile_fields') }}</h4>
+ <ul class="setting-list">
+ <li>
+ <IntegerSetting
+ source="admin"
+ path=":pleroma.:instance.:max_account_fields"
+ draft-mode
+ />
+ </li>
+ <li>
+ <IntegerSetting
+ source="admin"
+ path=":pleroma.:instance.:max_remote_account_fields"
+ draft-mode
+ expert="1"
+ />
+ </li>
+ <li>
+ <IntegerSetting
+ source="admin"
+ path=":pleroma.:instance.:account_field_name_length"
+ draft-mode
+ />
+ </li>
+ <li>
+ <IntegerSetting
+ source="admin"
+ path=":pleroma.:instance.:account_field_value_length"
+ draft-mode
+ />
+ </li>
+ </ul>
+ </li>
+ <li>
+ <h4>{{ $t('admin_dash.limits.user_uploads') }}</h4>
+ <ul class="setting-list">
+ <li>
+ <IntegerSetting
+ source="admin"
+ path=":pleroma.:instance.:avatar_upload_limit"
+ draft-mode
+ />
+ </li>
+ <li>
+ <IntegerSetting
+ source="admin"
+ path=":pleroma.:instance.:banner_upload_limit"
+ draft-mode
+ />
+ </li>
+ </ul>
+ </li>
+ </ul>
+ </li>
+ </ul>
+ </div>
+ </div>
+</template>
+
+<script src="./limits_tab.js"></script>
diff --git a/src/components/settings_modal/helpers/attachment_setting.js b/src/components/settings_modal/helpers/attachment_setting.js
new file mode 100644
index 00000000..c4c04b2b
--- /dev/null
+++ b/src/components/settings_modal/helpers/attachment_setting.js
@@ -0,0 +1,44 @@
+import Setting from './setting.js'
+import { fileTypeExt } from 'src/services/file_type/file_type.service.js'
+import MediaUpload from 'src/components/media_upload/media_upload.vue'
+import Attachment from 'src/components/attachment/attachment.vue'
+
+export default {
+ ...Setting,
+ props: {
+ ...Setting.props,
+ compact: Boolean,
+ acceptTypes: {
+ type: String,
+ required: false,
+ default: 'image/*'
+ }
+ },
+ components: {
+ ...Setting.components,
+ MediaUpload,
+ Attachment
+ },
+ computed: {
+ ...Setting.computed,
+ attachment () {
+ const path = this.realDraftMode ? this.draft : this.state
+ // The "server" part is primarily for local dev, but could be useful for alt-domain or multiuser usage.
+ const url = path.includes('://') ? path : this.$store.state.instance.server + path
+ return {
+ mimetype: fileTypeExt(url),
+ url
+ }
+ }
+ },
+ methods: {
+ ...Setting.methods,
+ setMediaFile (fileInfo) {
+ if (this.realDraftMode) {
+ this.draft = fileInfo.url
+ } else {
+ this.configSink(this.path, fileInfo.url)
+ }
+ }
+ }
+}
diff --git a/src/components/settings_modal/helpers/attachment_setting.vue b/src/components/settings_modal/helpers/attachment_setting.vue
new file mode 100644
index 00000000..96c80ab1
--- /dev/null
+++ b/src/components/settings_modal/helpers/attachment_setting.vue
@@ -0,0 +1,126 @@
+<template>
+ <span
+ v-if="matchesExpertLevel"
+ class="AttachmentSetting"
+ :class="{ '-compact': compact }"
+ >
+ <label
+ :for="path"
+ :class="{ 'faint': shouldBeDisabled }"
+ >
+ <template v-if="backendDescriptionLabel">
+ {{ backendDescriptionLabel + ' ' }}
+ </template>
+ <template v-else-if="source === 'admin'">
+ MISSING LABEL FOR {{ path }}
+ </template>
+ <slot v-else />
+
+ </label>
+ <p
+ v-if="backendDescriptionDescription"
+ class="setting-description"
+ :class="{ 'faint': shouldBeDisabled }"
+ >
+ {{ backendDescriptionDescription + ' ' }}
+ </p>
+ <div class="attachment-input">
+ <div class="controls control-field">
+ <label for="path">{{ $t('settings.url') }}</label>
+ <input
+ :id="path"
+ class="input string-input"
+ :disabled="shouldBeDisabled"
+ :value="realDraftMode ? draft : state"
+ @change="update"
+ >
+ {{ ' ' }}
+ <ModifiedIndicator
+ :changed="isChanged"
+ :onclick="reset"
+ />
+ <ProfileSettingIndicator :is-profile="isProfileSetting" />
+ </div>
+ <div v-if="!compact">{{ $t('settings.preview') }}</div>
+ <Attachment
+ class="attachment"
+ :compact="compact"
+ :attachment="attachment"
+ size="small"
+ hide-description
+ @setMedia="onMedia"
+ @naturalSizeLoad="onNaturalSizeLoad"
+ />
+ <div class="controls control-upload">
+ <MediaUpload
+ ref="mediaUpload"
+ class="media-upload-icon"
+ :drop-files="dropFiles"
+ normal-button
+ :accept-types="acceptTypes"
+ @uploaded="setMediaFile"
+ @upload-failed="uploadFailed"
+ />
+ </div>
+ </div>
+ <DraftButtons />
+ </span>
+</template>
+
+<script src="./attachment_setting.js"></script>
+
+<style lang="scss">
+.AttachmentSetting {
+ .attachment {
+ display: block;
+ width: 100%;
+ height: 15em;
+ margin-bottom: 0.5em;
+ }
+
+ .attachment-input {
+ margin-left: 1em;
+ display: flex;
+ flex-direction: column;
+ width: 20em;
+ }
+
+ &.-compact {
+ .attachment-input {
+ flex-direction: row;
+ align-items: flex-end;
+ }
+
+ .attachment {
+ flex: 0;
+ order: 0;
+ display: block;
+ min-width: 4em;
+ height: 4em;
+ align-self: center;
+ margin-bottom: 0;
+ }
+
+ .control-field {
+ order: 1;
+ min-width: 12em;
+ margin-left: 0.5em;
+ }
+
+ .control-upload {
+ order: 2;
+ min-width: 12em;
+ padding: 0 0.5em;
+ }
+ }
+
+ .controls {
+ margin-bottom: 0.5em;
+
+ input,
+ button {
+ width: 100%;
+ }
+ }
+}
+</style>
diff --git a/src/components/settings_modal/helpers/boolean_setting.js b/src/components/settings_modal/helpers/boolean_setting.js
index 2e6992cb..199d3d0f 100644
--- a/src/components/settings_modal/helpers/boolean_setting.js
+++ b/src/components/settings_modal/helpers/boolean_setting.js
@@ -1,56 +1,31 @@
-import { get, set } from 'lodash'
import Checkbox from 'src/components/checkbox/checkbox.vue'
-import ModifiedIndicator from './modified_indicator.vue'
-import ServerSideIndicator from './server_side_indicator.vue'
+import Setting from './setting.js'
+
export default {
+ ...Setting,
+ props: {
+ ...Setting.props,
+ indeterminateState: [String, Object]
+ },
components: {
- Checkbox,
- ModifiedIndicator,
- ServerSideIndicator
+ ...Setting.components,
+ Checkbox
},
- props: [
- 'path',
- 'disabled',
- 'expert'
- ],
computed: {
- pathDefault () {
- const [firstSegment, ...rest] = this.path.split('.')
- return [firstSegment + 'DefaultValue', ...rest].join('.')
- },
- state () {
- const value = get(this.$parent, this.path)
- if (value === undefined) {
- return this.defaultState
- } else {
- return value
- }
- },
- defaultState () {
- return get(this.$parent, this.pathDefault)
- },
- isServerSide () {
- return this.path.startsWith('serverSide_')
- },
- isChanged () {
- return !this.path.startsWith('serverSide_') && this.state !== this.defaultState
- },
- matchesExpertLevel () {
- return (this.expert || 0) <= this.$parent.expertLevel
+ ...Setting.computed,
+ isIndeterminate () {
+ return this.visibleState === this.indeterminateState
}
},
methods: {
- update (e) {
- const [firstSegment, ...rest] = this.path.split('.')
- set(this.$parent, this.path, e)
- // Updating nested properties does not trigger update on its parent.
- // probably still not as reliable, but works for depth=1 at least
- if (rest.length > 0) {
- set(this.$parent, firstSegment, { ...get(this.$parent, firstSegment) })
+ ...Setting.methods,
+ getValue (e) {
+ // Basic tri-state toggle implementation
+ if (!!this.indeterminateState && !e && this.visibleState === true) {
+ // If we have indeterminate state, switching from true to false first goes through indeterminate
+ return this.indeterminateState
}
- },
- reset () {
- set(this.$parent, this.path, this.defaultState)
+ return e
}
}
}
diff --git a/src/components/settings_modal/helpers/boolean_setting.vue b/src/components/settings_modal/helpers/boolean_setting.vue
index 41142966..5a9eab34 100644
--- a/src/components/settings_modal/helpers/boolean_setting.vue
+++ b/src/components/settings_modal/helpers/boolean_setting.vue
@@ -4,23 +4,37 @@
class="BooleanSetting"
>
<Checkbox
- :model-value="state"
- :disabled="disabled"
+ :model-value="visibleState"
+ :disabled="shouldBeDisabled"
+ :indeterminate="isIndeterminate"
@update:modelValue="update"
>
<span
- v-if="!!$slots.default"
class="label"
+ :class="{ 'faint': shouldBeDisabled }"
>
- <slot />
+ <template v-if="backendDescriptionLabel">
+ {{ backendDescriptionLabel }}
+ </template>
+ <template v-else-if="source === 'admin'">
+ MISSING LABEL FOR {{ path }}
+ </template>
+ <slot v-else />
</span>
- {{ ' ' }}
- <ModifiedIndicator
- :changed="isChanged"
- :onclick="reset"
- />
- <ServerSideIndicator :server-side="isServerSide" />
</Checkbox>
+ <ModifiedIndicator
+ :changed="isChanged"
+ :onclick="reset"
+ />
+ <ProfileSettingIndicator :is-profile="isProfileSetting" />
+ <DraftButtons />
+ <p
+ v-if="backendDescriptionDescription"
+ class="setting-description"
+ :class="{ 'faint': shouldBeDisabled }"
+ >
+ {{ backendDescriptionDescription + ' ' }}
+ </p>
</label>
</template>
diff --git a/src/components/settings_modal/helpers/choice_setting.js b/src/components/settings_modal/helpers/choice_setting.js
index 3da559fe..bdeece76 100644
--- a/src/components/settings_modal/helpers/choice_setting.js
+++ b/src/components/settings_modal/helpers/choice_setting.js
@@ -1,51 +1,41 @@
-import { get, set } from 'lodash'
import Select from 'src/components/select/select.vue'
-import ModifiedIndicator from './modified_indicator.vue'
-import ServerSideIndicator from './server_side_indicator.vue'
+import Setting from './setting.js'
+
export default {
+ ...Setting,
components: {
- Select,
- ModifiedIndicator,
- ServerSideIndicator
+ ...Setting.components,
+ Select
},
- props: [
- 'path',
- 'disabled',
- 'options',
- 'expert'
- ],
- computed: {
- pathDefault () {
- const [firstSegment, ...rest] = this.path.split('.')
- return [firstSegment + 'DefaultValue', ...rest].join('.')
+ props: {
+ ...Setting.props,
+ options: {
+ type: Array,
+ required: false
},
- state () {
- const value = get(this.$parent, this.path)
- if (value === undefined) {
- return this.defaultState
- } else {
- return value
+ optionLabelMap: {
+ type: Object,
+ required: false,
+ default: {}
+ }
+ },
+ computed: {
+ ...Setting.computed,
+ realOptions () {
+ if (this.realSource === 'admin') {
+ return this.backendDescriptionSuggestions.map(x => ({
+ key: x,
+ value: x,
+ label: this.optionLabelMap[x] || x
+ }))
}
- },
- defaultState () {
- return get(this.$parent, this.pathDefault)
- },
- isServerSide () {
- return this.path.startsWith('serverSide_')
- },
- isChanged () {
- return !this.path.startsWith('serverSide_') && this.state !== this.defaultState
- },
- matchesExpertLevel () {
- return (this.expert || 0) <= this.$parent.expertLevel
+ return this.options
}
},
methods: {
- update (e) {
- set(this.$parent, this.path, e)
- },
- reset () {
- set(this.$parent, this.path, this.defaultState)
+ ...Setting.methods,
+ getValue (e) {
+ return e
}
}
}
diff --git a/src/components/settings_modal/helpers/choice_setting.vue b/src/components/settings_modal/helpers/choice_setting.vue
index 8fdbb5d3..114e9b7d 100644
--- a/src/components/settings_modal/helpers/choice_setting.vue
+++ b/src/components/settings_modal/helpers/choice_setting.vue
@@ -3,15 +3,20 @@
v-if="matchesExpertLevel"
class="ChoiceSetting"
>
- <slot />
+ <template v-if="backendDescriptionLabel">
+ {{ backendDescriptionLabel }}
+ </template>
+ <template v-else>
+ <slot />
+ </template>
{{ ' ' }}
<Select
- :model-value="state"
+ :model-value="realDraftMode ? draft :state"
:disabled="disabled"
@update:modelValue="update"
>
<option
- v-for="option in options"
+ v-for="option in realOptions"
:key="option.key"
:value="option.value"
>
@@ -23,7 +28,14 @@
:changed="isChanged"
:onclick="reset"
/>
- <ServerSideIndicator :server-side="isServerSide" />
+ <ProfileSettingIndicator :is-profile="isProfileSetting" />
+ <DraftButtons />
+ <p
+ v-if="backendDescriptionDescription"
+ class="setting-description"
+ >
+ {{ backendDescriptionDescription + ' ' }}
+ </p>
</label>
</template>
diff --git a/src/components/settings_modal/helpers/draft_buttons.vue b/src/components/settings_modal/helpers/draft_buttons.vue
new file mode 100644
index 00000000..46a70e86
--- /dev/null
+++ b/src/components/settings_modal/helpers/draft_buttons.vue
@@ -0,0 +1,88 @@
+<!-- this is a helper exclusive to Setting components -->
+<!-- TODO make it reusable -->
+<template>
+ <span
+ class="DraftButtons"
+ >
+ <Popover
+ v-if="$parent.isDirty"
+ trigger="hover"
+ normal-button
+ :trigger-attrs="{ 'aria-label': $t('settings.commit_value_tooltip') }"
+ @click="$parent.commitDraft"
+ >
+ <template #trigger>
+ {{ $t('settings.commit_value') }}
+ </template>
+ <template #content>
+ <div class="modified-tooltip">
+ {{ $t('settings.commit_value_tooltip') }}
+ </div>
+ </template>
+ </Popover>
+ <Popover
+ v-if="$parent.isDirty"
+ trigger="hover"
+ normal-button
+ :trigger-attrs="{ 'aria-label': $t('settings.reset_value_tooltip') }"
+ @click="$parent.reset"
+ >
+ <template #trigger>
+ {{ $t('settings.reset_value') }}
+ </template>
+ <template #content>
+ <div class="modified-tooltip">
+ {{ $t('settings.reset_value_tooltip') }}
+ </div>
+ </template>
+ </Popover>
+ <Popover
+ v-if="$parent.canHardReset"
+ trigger="hover"
+ normal-button
+ :trigger-attrs="{ 'aria-label': $t('settings.hard_reset_value_tooltip') }"
+ @click="$parent.hardReset"
+ >
+ <template #trigger>
+ {{ $t('settings.hard_reset_value') }}
+ </template>
+ <template #content>
+ <div class="modified-tooltip">
+ {{ $t('settings.hard_reset_value_tooltip') }}
+ </div>
+ </template>
+ </Popover>
+ </span>
+</template>
+
+<script>
+import Popover from 'src/components/popover/popover.vue'
+import { library } from '@fortawesome/fontawesome-svg-core'
+import { faWrench } from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faWrench
+)
+
+export default {
+ components: { Popover },
+ props: ['changed']
+}
+</script>
+
+<style lang="scss">
+.DraftButtons {
+ display: inline-block;
+ position: relative;
+
+ .button-default {
+ margin-left: 0.5em;
+ }
+}
+
+.draft-tooltip {
+ margin: 0.5em 1em;
+ min-width: 10em;
+ text-align: center;
+}
+</style>
diff --git a/src/components/settings_modal/helpers/emoji_editing_popover.vue b/src/components/settings_modal/helpers/emoji_editing_popover.vue
new file mode 100644
index 00000000..f0465dd5
--- /dev/null
+++ b/src/components/settings_modal/helpers/emoji_editing_popover.vue
@@ -0,0 +1,227 @@
+<template>
+ <Popover
+ ref="emojiPopover"
+ trigger="click"
+ :placement="placement"
+ bound-to-selector=".emoji-list"
+ popover-class="emoji-tab-edit-popover popover-default"
+ :bound-to="{ x: 'container' }"
+ :offset="{ y: 5 }"
+ :disabled="disabled"
+ :class="{'emoji-unsaved': isEdited}"
+ >
+ <template #trigger>
+ <slot name="trigger" />
+ </template>
+ <template #content>
+ <h3>
+ {{ title }}
+ </h3>
+
+ <StillImage
+ v-if="emojiPreview"
+ class="emoji"
+ :src="emojiPreview"
+ />
+ <div
+ v-else
+ class="emoji"
+ />
+
+ <div
+ v-if="newUpload"
+ class="emoji-tab-popover-input"
+ >
+ <input
+ type="file"
+ accept="image/*"
+ class="emoji-tab-popover-file input"
+ @change="uploadFile = $event.target.files"
+ >
+ </div>
+ <div>
+ <div class="emoji-tab-popover-input">
+ <label>
+ {{ $t('admin_dash.emoji.shortcode') }}
+ <input
+ v-model="editedShortcode"
+ class="emoji-data-input input"
+ :placeholder="$t('admin_dash.emoji.new_shortcode')"
+ >
+ </label>
+ </div>
+
+ <div class="emoji-tab-popover-input">
+ <label>
+ {{ $t('admin_dash.emoji.filename') }}
+
+ <input
+ v-model="editedFile"
+ class="emoji-data-input input"
+ :placeholder="$t('admin_dash.emoji.new_filename')"
+ >
+ </label>
+ </div>
+
+ <button
+ class="button button-default btn"
+ type="button"
+ :disabled="newUpload ? uploadFile.length == 0 : !isEdited"
+ @click="newUpload ? uploadEmoji() : saveEditedEmoji()"
+ >
+ {{ $t('admin_dash.emoji.save') }}
+ </button>
+
+ <template v-if="!newUpload">
+ <button
+ class="button button-default btn emoji-tab-popover-button"
+ type="button"
+ @click="deleteModalVisible = true"
+ >
+ {{ $t('admin_dash.emoji.delete') }}
+ </button>
+ <button
+ class="button button-default btn emoji-tab-popover-button"
+ type="button"
+ @click="revertEmoji"
+ >
+ {{ $t('admin_dash.emoji.revert') }}
+ </button>
+ <ConfirmModal
+ v-if="deleteModalVisible"
+ :title="$t('admin_dash.emoji.delete_title')"
+ :cancel-text="$t('status.delete_confirm_cancel_button')"
+ :confirm-text="$t('status.delete_confirm_accept_button')"
+ @cancelled="deleteModalVisible = false"
+ @accepted="deleteEmoji"
+ >
+ {{ $t('admin_dash.emoji.delete_confirm', [shortcode]) }}
+ </ConfirmModal>
+ </template>
+ </div>
+ </template>
+ </Popover>
+</template>
+
+<script>
+import Popover from 'components/popover/popover.vue'
+import ConfirmModal from 'components/confirm_modal/confirm_modal.vue'
+import StillImage from 'components/still-image/still-image.vue'
+
+export default {
+ components: { Popover, ConfirmModal, StillImage },
+ inject: ['emojiAddr'],
+ props: {
+ placement: String,
+ disabled: {
+ type: Boolean,
+ default: false
+ },
+
+ newUpload: Boolean,
+
+ title: String,
+ packName: String,
+ shortcode: {
+ type: String,
+ // Only exists when this is not a new upload
+ default: ''
+ },
+ file: {
+ type: String,
+ // Only exists when this is not a new upload
+ default: ''
+ }
+ },
+ emits: ['updatePackFiles', 'displayError'],
+ data () {
+ return {
+ uploadFile: [],
+ editedShortcode: this.shortcode,
+ editedFile: this.file,
+ deleteModalVisible: false
+ }
+ },
+ computed: {
+ emojiPreview () {
+ if (this.newUpload && this.uploadFile.length > 0) {
+ return URL.createObjectURL(this.uploadFile[0])
+ } else if (!this.newUpload) {
+ return this.emojiAddr(this.file)
+ }
+
+ return null
+ },
+ isEdited () {
+ return !this.newUpload && (this.editedShortcode !== this.shortcode || this.editedFile !== this.file)
+ }
+ },
+ methods: {
+ saveEditedEmoji () {
+ if (!this.isEdited) return
+
+ this.$store.state.api.backendInteractor.updateEmojiFile(
+ { packName: this.packName, shortcode: this.shortcode, newShortcode: this.editedShortcode, newFilename: this.editedFile, force: false }
+ ).then(resp => {
+ if (resp.error !== undefined) {
+ this.$emit('displayError', resp.error)
+ return Promise.reject(resp.error)
+ }
+
+ return resp.json()
+ }).then(resp => this.$emit('updatePackFiles', resp))
+ },
+ uploadEmoji () {
+ this.$store.state.api.backendInteractor.addNewEmojiFile({
+ packName: this.packName,
+ file: this.uploadFile[0],
+ shortcode: this.editedShortcode,
+ filename: this.editedFile
+ }).then(resp => resp.json()).then(resp => {
+ if (resp.error !== undefined) {
+ this.$emit('displayError', resp.error)
+ return
+ }
+
+ this.$emit('updatePackFiles', resp)
+ this.$refs.emojiPopover.hidePopover()
+
+ this.editedFile = ''
+ this.editedShortcode = ''
+ this.uploadFile = []
+ })
+ },
+ revertEmoji () {
+ this.editedFile = this.file
+ this.editedShortcode = this.shortcode
+ },
+ deleteEmoji () {
+ this.deleteModalVisible = false
+
+ this.$store.state.api.backendInteractor.deleteEmojiFile(
+ { packName: this.packName, shortcode: this.shortcode }
+ ).then(resp => resp.json()).then(resp => {
+ if (resp.error !== undefined) {
+ this.$emit('displayError', resp.error)
+ return
+ }
+
+ this.$emit('updatePackFiles', resp)
+ })
+ }
+ }
+}
+</script>
+
+<style lang="scss">
+ .emoji-tab-edit-popover {
+ padding-left: 0.5em;
+ padding-right: 0.5em;
+ padding-bottom: 0.5em;
+
+ .emoji {
+ width: 32px;
+ height: 32px;
+ }
+ }
+</style>
diff --git a/src/components/settings_modal/helpers/group_setting.js b/src/components/settings_modal/helpers/group_setting.js
new file mode 100644
index 00000000..23a2a202
--- /dev/null
+++ b/src/components/settings_modal/helpers/group_setting.js
@@ -0,0 +1,13 @@
+import { isEqual } from 'lodash'
+
+import Setting from './setting.js'
+
+export default {
+ ...Setting,
+ computed: {
+ ...Setting.computed,
+ isDirty () {
+ return !isEqual(this.state, this.draft)
+ }
+ }
+}
diff --git a/src/components/settings_modal/helpers/group_setting.vue b/src/components/settings_modal/helpers/group_setting.vue
new file mode 100644
index 00000000..a4df4bf3
--- /dev/null
+++ b/src/components/settings_modal/helpers/group_setting.vue
@@ -0,0 +1,15 @@
+<template>
+ <span
+ v-if="matchesExpertLevel"
+ class="GroupSetting"
+ >
+ <ModifiedIndicator
+ :changed="isChanged"
+ :onclick="reset"
+ />
+ <ProfileSettingIndicator :is-profile="isProfileSetting" />
+ <DraftButtons />
+ </span>
+</template>
+
+<script src="./group_setting.js"></script>
diff --git a/src/components/settings_modal/helpers/modified_indicator.vue b/src/components/settings_modal/helpers/modified_indicator.vue
index 45db3fc2..a747cebd 100644
--- a/src/components/settings_modal/helpers/modified_indicator.vue
+++ b/src/components/settings_modal/helpers/modified_indicator.vue
@@ -15,7 +15,7 @@
</template>
<template #content>
<div class="modified-tooltip">
- {{ $t('settings.setting_changed') }}
+ {{ $t(messageKey) }}
</div>
</template>
</Popover>
@@ -33,7 +33,13 @@ library.add(
export default {
components: { Popover },
- props: ['changed']
+ props: {
+ changed: Boolean,
+ messageKey: {
+ type: String,
+ default: 'settings.setting_changed'
+ }
+ }
}
</script>
diff --git a/src/components/settings_modal/helpers/number_setting.js b/src/components/settings_modal/helpers/number_setting.js
index 73c39948..676a0d22 100644
--- a/src/components/settings_modal/helpers/number_setting.js
+++ b/src/components/settings_modal/helpers/number_setting.js
@@ -1,56 +1,24 @@
-import { get, set } from 'lodash'
-import ModifiedIndicator from './modified_indicator.vue'
+import Setting from './setting.js'
+
export default {
- components: {
- ModifiedIndicator
- },
+ ...Setting,
props: {
- path: String,
- disabled: Boolean,
- min: Number,
- step: Number,
- truncate: Number,
- expert: [Number, String]
- },
- computed: {
- pathDefault () {
- const [firstSegment, ...rest] = this.path.split('.')
- return [firstSegment + 'DefaultValue', ...rest].join('.')
- },
- parent () {
- return this.$parent.$parent
- },
- state () {
- const value = get(this.parent, this.path)
- if (value === undefined) {
- return this.defaultState
- } else {
- return value
- }
- },
- defaultState () {
- return get(this.parent, this.pathDefault)
- },
- isChanged () {
- return this.state !== this.defaultState
- },
- matchesExpertLevel () {
- return (this.expert || 0) <= this.parent.expertLevel
+ ...Setting.props,
+ truncate: {
+ type: Number,
+ required: false,
+ default: 1
}
},
methods: {
- truncateValue (value) {
- if (!this.truncate) {
- return value
+ ...Setting.methods,
+ getValue (e) {
+ if (!this.truncate === 1) {
+ return parseInt(e.target.value)
+ } else if (this.truncate > 1) {
+ return Math.trunc(e.target.value / this.truncate) * this.truncate
}
-
- return Math.trunc(value / this.truncate) * this.truncate
- },
- update (e) {
- set(this.parent, this.path, this.truncateValue(parseFloat(e.target.value)))
- },
- reset () {
- set(this.parent, this.path, this.defaultState)
+ return parseFloat(e.target.value)
}
}
}
diff --git a/src/components/settings_modal/helpers/number_setting.vue b/src/components/settings_modal/helpers/number_setting.vue
index 3eab5178..23c1a5dd 100644
--- a/src/components/settings_modal/helpers/number_setting.vue
+++ b/src/components/settings_modal/helpers/number_setting.vue
@@ -3,17 +3,26 @@
v-if="matchesExpertLevel"
class="NumberSetting"
>
- <label :for="path">
- <slot />
+ <label
+ :for="path"
+ :class="{ 'faint': shouldBeDisabled }"
+ >
+ <template v-if="backendDescriptionLabel">
+ {{ backendDescriptionLabel + ' ' }}
+ </template>
+ <template v-else-if="source === 'admin'">
+ MISSING LABEL FOR {{ path }}
+ </template>
+ <slot v-else />
</label>
<input
:id="path"
- class="number-input"
+ class="input number-input"
type="number"
:step="step || 1"
- :disabled="disabled"
+ :disabled="shouldBeDisabled"
:min="min || 0"
- :value="state"
+ :value="realDraftMode ? draft :state"
@change="update"
>
{{ ' ' }}
@@ -21,6 +30,15 @@
:changed="isChanged"
:onclick="reset"
/>
+ <ProfileSettingIndicator :is-profile="isProfileSetting" />
+ <DraftButtons />
+ <p
+ v-if="backendDescriptionDescription"
+ class="setting-description"
+ :class="{ 'faint': shouldBeDisabled }"
+ >
+ {{ backendDescriptionDescription + ' ' }}
+ </p>
</span>
</template>
diff --git a/src/components/settings_modal/helpers/server_side_indicator.vue b/src/components/settings_modal/helpers/profile_setting_indicator.vue
index bf181959..d160781b 100644
--- a/src/components/settings_modal/helpers/server_side_indicator.vue
+++ b/src/components/settings_modal/helpers/profile_setting_indicator.vue
@@ -1,7 +1,7 @@
<template>
<span
- v-if="serverSide"
- class="ServerSideIndicator"
+ v-if="isProfile"
+ class="ProfileSettingIndicator"
>
<Popover
trigger="hover"
@@ -14,7 +14,7 @@
/>
</template>
<template #content>
- <div class="serverside-tooltip">
+ <div class="profilesetting-tooltip">
{{ $t('settings.setting_server_side') }}
</div>
</template>
@@ -33,17 +33,17 @@ library.add(
export default {
components: { Popover },
- props: ['serverSide']
+ props: ['isProfile']
}
</script>
<style lang="scss">
-.ServerSideIndicator {
+.ProfileSettingIndicator {
display: inline-block;
position: relative;
}
-.serverside-tooltip {
+.profilesetting-tooltip {
margin: 0.5em 1em;
min-width: 10em;
text-align: center;
diff --git a/src/components/settings_modal/helpers/setting.js b/src/components/settings_modal/helpers/setting.js
new file mode 100644
index 00000000..abf9cfdf
--- /dev/null
+++ b/src/components/settings_modal/helpers/setting.js
@@ -0,0 +1,238 @@
+import ModifiedIndicator from './modified_indicator.vue'
+import ProfileSettingIndicator from './profile_setting_indicator.vue'
+import DraftButtons from './draft_buttons.vue'
+import { get, set, cloneDeep } from 'lodash'
+
+export default {
+ components: {
+ ModifiedIndicator,
+ DraftButtons,
+ ProfileSettingIndicator
+ },
+ props: {
+ path: {
+ type: [String, Array],
+ required: true
+ },
+ disabled: {
+ type: Boolean,
+ default: false
+ },
+ parentPath: {
+ type: [String, Array]
+ },
+ parentInvert: {
+ type: Boolean,
+ default: false
+ },
+ expert: {
+ type: [Number, String],
+ default: 0
+ },
+ source: {
+ type: String,
+ default: undefined
+ },
+ hideDescription: {
+ type: Boolean
+ },
+ swapDescriptionAndLabel: {
+ type: Boolean
+ },
+ overrideBackendDescription: {
+ type: Boolean
+ },
+ overrideBackendDescriptionLabel: {
+ type: Boolean
+ },
+ draftMode: {
+ type: Boolean,
+ default: undefined
+ }
+ },
+ inject: {
+ defaultSource: {
+ default: 'default'
+ },
+ defaultDraftMode: {
+ default: false
+ }
+ },
+ data () {
+ return {
+ localDraft: null
+ }
+ },
+ created () {
+ if (this.realDraftMode && this.realSource !== 'admin') {
+ this.draft = this.state
+ }
+ },
+ computed: {
+ draft: {
+ // TODO allow passing shared draft object?
+ get () {
+ if (this.realSource === 'admin') {
+ return get(this.$store.state.adminSettings.draft, this.canonPath)
+ } else {
+ return this.localDraft
+ }
+ },
+ set (value) {
+ if (this.realSource === 'admin') {
+ this.$store.commit('updateAdminDraft', { path: this.canonPath, value })
+ } else {
+ this.localDraft = value
+ }
+ }
+ },
+ state () {
+ const value = get(this.configSource, this.canonPath)
+ if (value === undefined) {
+ return this.defaultState
+ } else {
+ return value
+ }
+ },
+ visibleState () {
+ return this.realDraftMode ? this.draft : this.state
+ },
+ realSource () {
+ return this.source || this.defaultSource
+ },
+ realDraftMode () {
+ return typeof this.draftMode === 'undefined' ? this.defaultDraftMode : this.draftMode
+ },
+ backendDescription () {
+ return get(this.$store.state.adminSettings.descriptions, this.path)
+ },
+ backendDescriptionLabel () {
+ if (this.realSource !== 'admin') return ''
+ if (!this.backendDescription || this.overrideBackendDescriptionLabel) {
+ return this.$t([
+ 'admin_dash',
+ 'temp_overrides',
+ ...this.canonPath.map(p => p.replace(/\./g, '_DOT_')),
+ 'label'
+ ].join('.'))
+ } else {
+ return this.swapDescriptionAndLabel
+ ? this.backendDescription?.description
+ : this.backendDescription?.label
+ }
+ },
+ backendDescriptionDescription () {
+ if (this.realSource !== 'admin') return ''
+ if (this.hideDescription) return null
+ if (!this.backendDescription || this.overrideBackendDescription) {
+ return this.$t([
+ 'admin_dash',
+ 'temp_overrides',
+ ...this.canonPath.map(p => p.replace(/\./g, '_DOT_')),
+ 'description'
+ ].join('.'))
+ } else {
+ return this.swapDescriptionAndLabel
+ ? this.backendDescription?.label
+ : this.backendDescription?.description
+ }
+ },
+ backendDescriptionSuggestions () {
+ return this.backendDescription?.suggestions
+ },
+ shouldBeDisabled () {
+ const parentValue = this.parentPath !== undefined ? get(this.configSource, this.parentPath) : null
+ return this.disabled || (parentValue !== null ? (this.parentInvert ? parentValue : !parentValue) : false)
+ },
+ configSource () {
+ switch (this.realSource) {
+ case 'profile':
+ return this.$store.state.profileConfig
+ case 'admin':
+ return this.$store.state.adminSettings.config
+ default:
+ return this.$store.getters.mergedConfig
+ }
+ },
+ configSink () {
+ switch (this.realSource) {
+ case 'profile':
+ return (k, v) => this.$store.dispatch('setProfileOption', { name: k, value: v })
+ case 'admin':
+ return (k, v) => this.$store.dispatch('pushAdminSetting', { path: k, value: v })
+ default:
+ return (k, v) => this.$store.dispatch('setOption', { name: k, value: v })
+ }
+ },
+ defaultState () {
+ switch (this.realSource) {
+ case 'profile':
+ return {}
+ default:
+ return get(this.$store.getters.defaultConfig, this.path)
+ }
+ },
+ isProfileSetting () {
+ return this.realSource === 'profile'
+ },
+ isChanged () {
+ switch (this.realSource) {
+ case 'profile':
+ case 'admin':
+ return false
+ default:
+ return this.state !== this.defaultState
+ }
+ },
+ canonPath () {
+ return Array.isArray(this.path) ? this.path : this.path.split('.')
+ },
+ isDirty () {
+ if (this.realSource === 'admin' && this.canonPath.length > 3) {
+ return false // should not show draft buttons for "grouped" values
+ } else {
+ return this.realDraftMode && this.draft !== this.state
+ }
+ },
+ canHardReset () {
+ return this.realSource === 'admin' && this.$store.state.adminSettings.modifiedPaths &&
+ this.$store.state.adminSettings.modifiedPaths.has(this.canonPath.join(' -> '))
+ },
+ matchesExpertLevel () {
+ return (this.expert || 0) <= this.$store.state.config.expertLevel > 0
+ }
+ },
+ methods: {
+ getValue (e) {
+ return e.target.value
+ },
+ update (e) {
+ if (this.realDraftMode) {
+ this.draft = this.getValue(e)
+ } else {
+ this.configSink(this.path, this.getValue(e))
+ }
+ },
+ commitDraft () {
+ if (this.realDraftMode) {
+ this.configSink(this.path, this.draft)
+ }
+ },
+ reset () {
+ if (this.realDraftMode) {
+ this.draft = cloneDeep(this.state)
+ } else {
+ set(this.$store.getters.mergedConfig, this.path, cloneDeep(this.defaultState))
+ }
+ },
+ hardReset () {
+ switch (this.realSource) {
+ case 'admin':
+ return this.$store.dispatch('resetAdminSetting', { path: this.path })
+ .then(() => { this.draft = this.state })
+ default:
+ console.warn('Hard reset not implemented yet!')
+ }
+ }
+ }
+}
diff --git a/src/components/settings_modal/helpers/shared_computed_object.js b/src/components/settings_modal/helpers/shared_computed_object.js
index 12431dca..bb3d36ac 100644
--- a/src/components/settings_modal/helpers/shared_computed_object.js
+++ b/src/components/settings_modal/helpers/shared_computed_object.js
@@ -1,52 +1,18 @@
-import { defaultState as configDefaultState } from 'src/modules/config.js'
-import { defaultState as serverSideConfigDefaultState } from 'src/modules/serverSideConfig.js'
-
const SharedComputedObject = () => ({
user () {
return this.$store.state.users.currentUser
},
- // Getting values for default properties
- ...Object.keys(configDefaultState)
- .map(key => [
- key + 'DefaultValue',
- function () {
- return this.$store.getters.defaultConfig[key]
- }
- ])
- .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}),
- // Generating computed values for vuex properties
- ...Object.keys(configDefaultState)
- .map(key => [key, {
- get () { return this.$store.getters.mergedConfig[key] },
- set (value) {
- this.$store.dispatch('setOption', { name: key, value })
- }
- }])
- .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}),
- ...Object.keys(serverSideConfigDefaultState)
- .map(key => ['serverSide_' + key, {
- get () { return this.$store.state.serverSideConfig[key] },
- set (value) {
- this.$store.dispatch('setServerSideOption', { name: key, value })
- }
- }])
- .reduce((acc, [key, value]) => ({ ...acc, [key]: value }), {}),
- // Special cases (need to transform values or perform actions first)
- useStreamingApi: {
- get () { return this.$store.getters.mergedConfig.useStreamingApi },
- set (value) {
- const promise = value
- ? this.$store.dispatch('enableMastoSockets')
- : this.$store.dispatch('disableMastoSockets')
-
- promise.then(() => {
- this.$store.dispatch('setOption', { name: 'useStreamingApi', value })
- }).catch((e) => {
- console.error('Failed starting MastoAPI Streaming socket', e)
- this.$store.dispatch('disableMastoSockets')
- this.$store.dispatch('setOption', { name: 'useStreamingApi', value: false })
- })
- }
+ expertLevel () {
+ return this.$store.getters.mergedConfig.expertLevel > 0
+ },
+ mergedConfig () {
+ return this.$store.getters.mergedConfig
+ },
+ adminConfig () {
+ return this.$store.state.adminSettings.config
+ },
+ adminDraft () {
+ return this.$store.state.adminSettings.draft
}
})
diff --git a/src/components/settings_modal/helpers/size_setting.js b/src/components/settings_modal/helpers/size_setting.js
deleted file mode 100644
index 58697412..00000000
--- a/src/components/settings_modal/helpers/size_setting.js
+++ /dev/null
@@ -1,67 +0,0 @@
-import { get, set } from 'lodash'
-import ModifiedIndicator from './modified_indicator.vue'
-import Select from 'src/components/select/select.vue'
-
-export const allCssUnits = ['cm', 'mm', 'in', 'px', 'pt', 'pc', 'em', 'ex', 'ch', 'rem', 'vw', 'vh', 'vmin', 'vmax', '%']
-export const defaultHorizontalUnits = ['px', 'rem', 'vw']
-export const defaultVerticalUnits = ['px', 'rem', 'vh']
-
-export default {
- components: {
- ModifiedIndicator,
- Select
- },
- props: {
- path: String,
- disabled: Boolean,
- min: Number,
- units: {
- type: [String],
- default: () => allCssUnits
- },
- expert: [Number, String]
- },
- computed: {
- pathDefault () {
- const [firstSegment, ...rest] = this.path.split('.')
- return [firstSegment + 'DefaultValue', ...rest].join('.')
- },
- stateUnit () {
- return (this.state || '').replace(/\d+/, '')
- },
- stateValue () {
- return (this.state || '').replace(/\D+/, '')
- },
- state () {
- const value = get(this.$parent, this.path)
- if (value === undefined) {
- return this.defaultState
- } else {
- return value
- }
- },
- defaultState () {
- return get(this.$parent, this.pathDefault)
- },
- isChanged () {
- return this.state !== this.defaultState
- },
- matchesExpertLevel () {
- return (this.expert || 0) <= this.$parent.expertLevel
- }
- },
- methods: {
- update (e) {
- set(this.$parent, this.path, e)
- },
- reset () {
- set(this.$parent, this.path, this.defaultState)
- },
- updateValue (e) {
- set(this.$parent, this.path, parseInt(e.target.value) + this.stateUnit)
- },
- updateUnit (e) {
- set(this.$parent, this.path, this.stateValue + e.target.value)
- }
- }
-}
diff --git a/src/components/settings_modal/helpers/string_setting.js b/src/components/settings_modal/helpers/string_setting.js
new file mode 100644
index 00000000..b368cfc8
--- /dev/null
+++ b/src/components/settings_modal/helpers/string_setting.js
@@ -0,0 +1,5 @@
+import Setting from './setting.js'
+
+export default {
+ ...Setting
+}
diff --git a/src/components/settings_modal/helpers/string_setting.vue b/src/components/settings_modal/helpers/string_setting.vue
new file mode 100644
index 00000000..7b30d1b9
--- /dev/null
+++ b/src/components/settings_modal/helpers/string_setting.vue
@@ -0,0 +1,42 @@
+<template>
+ <label
+ v-if="matchesExpertLevel"
+ class="StringSetting"
+ >
+ <label
+ :for="path"
+ :class="{ 'faint': shouldBeDisabled }"
+ >
+ <template v-if="backendDescriptionLabel">
+ {{ backendDescriptionLabel + ' ' }}
+ </template>
+ <template v-else-if="source === 'admin'">
+ MISSING LABEL FOR {{ path }}
+ </template>
+ <slot v-else />
+ </label>
+ <input
+ :id="path"
+ class="input string-input"
+ :disabled="shouldBeDisabled"
+ :value="realDraftMode ? draft : state"
+ @change="update"
+ >
+ {{ ' ' }}
+ <ModifiedIndicator
+ :changed="isChanged"
+ :onclick="reset"
+ />
+ <ProfileSettingIndicator :is-profile="isProfileSetting" />
+ <DraftButtons />
+ <p
+ v-if="backendDescriptionDescription"
+ class="setting-description"
+ :class="{ 'faint': shouldBeDisabled }"
+ >
+ {{ backendDescriptionDescription + ' ' }}
+ </p>
+ </label>
+</template>
+
+<script src="./string_setting.js"></script>
diff --git a/src/components/settings_modal/helpers/unit_setting.js b/src/components/settings_modal/helpers/unit_setting.js
new file mode 100644
index 00000000..c9c23cb0
--- /dev/null
+++ b/src/components/settings_modal/helpers/unit_setting.js
@@ -0,0 +1,48 @@
+import Select from 'src/components/select/select.vue'
+import Setting from './setting.js'
+
+export const allCssUnits = ['cm', 'mm', 'in', 'px', 'pt', 'pc', 'em', 'ex', 'ch', 'rem', 'vw', 'vh', 'vmin', 'vmax', '%']
+export const defaultHorizontalUnits = ['px', 'rem', 'vw']
+export const defaultVerticalUnits = ['px', 'rem', 'vh']
+
+export default {
+ ...Setting,
+ components: {
+ ...Setting.components,
+ Select
+ },
+ props: {
+ ...Setting.props,
+ min: Number,
+ units: {
+ type: Array,
+ default: () => allCssUnits
+ },
+ unitSet: {
+ type: String,
+ default: 'none'
+ }
+ },
+ computed: {
+ ...Setting.computed,
+ stateUnit () {
+ return this.state.replace(/\d+/, '')
+ },
+ stateValue () {
+ return this.state.replace(/\D+/, '')
+ }
+ },
+ methods: {
+ ...Setting.methods,
+ getUnitString (value) {
+ if (this.unitSet === 'none') return value
+ return this.$t(['settings', 'units', this.unitSet, value].join('.'))
+ },
+ updateValue (e) {
+ this.configSink(this.path, parseInt(e.target.value) + this.stateUnit)
+ },
+ updateUnit (e) {
+ this.configSink(this.path, this.stateValue + e.target.value)
+ }
+ }
+}
diff --git a/src/components/settings_modal/helpers/size_setting.vue b/src/components/settings_modal/helpers/unit_setting.vue
index 5a78f100..68f52b1c 100644
--- a/src/components/settings_modal/helpers/size_setting.vue
+++ b/src/components/settings_modal/helpers/unit_setting.vue
@@ -1,7 +1,7 @@
<template>
<span
v-if="matchesExpertLevel"
- class="SizeSetting"
+ class="UnitSetting"
>
<label
:for="path"
@@ -11,7 +11,7 @@
</label>
<input
:id="path"
- class="number-input"
+ class="input number-input"
type="number"
step="1"
:disabled="disabled"
@@ -23,7 +23,7 @@
:id="path"
:model-value="stateUnit"
:disabled="disabled"
- class="css-unit-input"
+ class="unit-input unstyled"
@change="updateUnit"
>
<option
@@ -31,7 +31,7 @@
:key="option"
:value="option"
>
- {{ option }}
+ {{ getUnitString(option) }}
</option>
</Select>
{{ ' ' }}
@@ -42,14 +42,20 @@
</span>
</template>
-<script src="./size_setting.js"></script>
+<script src="./unit_setting.js"></script>
<style lang="scss">
-.css-unit-input,
-.css-unit-input select {
- margin-left: 0.5em;
- width: 4em;
- max-width: 4em;
- min-width: 4em;
+.UnitSetting {
+ .number-input {
+ max-width: 6.5em;
+ text-align: right;
+ }
+
+ .unit-input,
+ .unit-input select {
+ min-width: 4em;
+ width: auto;
+ }
}
+
</style>
diff --git a/src/components/settings_modal/settings_modal.js b/src/components/settings_modal/settings_modal.js
index 0a72dca1..ff58f2c3 100644
--- a/src/components/settings_modal/settings_modal.js
+++ b/src/components/settings_modal/settings_modal.js
@@ -5,7 +5,7 @@ import getResettableAsyncComponent from 'src/services/resettable_async_component
import Popover from '../popover/popover.vue'
import Checkbox from 'src/components/checkbox/checkbox.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
-import { cloneDeep } from 'lodash'
+import { cloneDeep, isEqual } from 'lodash'
import {
newImporter,
newExporter
@@ -53,8 +53,16 @@ const SettingsModal = {
Modal,
Popover,
Checkbox,
- SettingsModalContent: getResettableAsyncComponent(
- () => import('./settings_modal_content.vue'),
+ SettingsModalUserContent: getResettableAsyncComponent(
+ () => import('./settings_modal_user_content.vue'),
+ {
+ loadingComponent: PanelLoading,
+ errorComponent: AsyncComponentError,
+ delay: 0
+ }
+ ),
+ SettingsModalAdminContent: getResettableAsyncComponent(
+ () => import('./settings_modal_admin_content.vue'),
{
loadingComponent: PanelLoading,
errorComponent: AsyncComponentError,
@@ -147,6 +155,12 @@ const SettingsModal = {
PLEROMAFE_SETTINGS_MINOR_VERSION
]
return clone
+ },
+ resetAdminDraft () {
+ this.$store.commit('resetAdminDraft')
+ },
+ pushAdminDraft () {
+ this.$store.dispatch('pushAdminDraft')
}
},
computed: {
@@ -156,8 +170,14 @@ const SettingsModal = {
modalActivated () {
return this.$store.state.interface.settingsModalState !== 'hidden'
},
- modalOpenedOnce () {
- return this.$store.state.interface.settingsModalLoaded
+ modalMode () {
+ return this.$store.state.interface.settingsModalMode
+ },
+ modalOpenedOnceUser () {
+ return this.$store.state.interface.settingsModalLoadedUser
+ },
+ modalOpenedOnceAdmin () {
+ return this.$store.state.interface.settingsModalLoadedAdmin
},
modalPeeked () {
return this.$store.state.interface.settingsModalState === 'minimized'
@@ -167,9 +187,14 @@ const SettingsModal = {
return this.$store.state.config.expertLevel > 0
},
set (value) {
- console.log(value)
this.$store.dispatch('setOption', { name: 'expertLevel', value: value ? 1 : 0 })
}
+ },
+ adminDraftAny () {
+ return !isEqual(
+ this.$store.state.adminSettings.config,
+ this.$store.state.adminSettings.draft
+ )
}
}
}
diff --git a/src/components/settings_modal/settings_modal.scss b/src/components/settings_modal/settings_modal.scss
index f5861229..d01553db 100644
--- a/src/components/settings_modal/settings_modal.scss
+++ b/src/components/settings_modal/settings_modal.scss
@@ -1,8 +1,10 @@
-@import "src/variables";
-
.settings-modal {
overflow: hidden;
+ h4 {
+ margin-bottom: 0.5em;
+ }
+
.setting-list,
.option-list {
list-style-type: none;
@@ -15,6 +17,20 @@
.suboptions {
margin-top: 0.3em;
}
+
+ &.two-column {
+ column-count: 2;
+
+ > li {
+ break-inside: avoid;
+ }
+ }
+ }
+
+ .setting-description {
+ margin-top: 0.2em;
+ margin-bottom: 2em;
+ font-size: 70%;
}
.settings-modal-panel {
@@ -37,7 +53,9 @@
.btn {
min-height: 2em;
- min-width: 10em;
+ }
+
+ .btn:not(.dropdown-button) {
padding: 0 2em;
}
}
@@ -45,6 +63,8 @@
.settings-footer {
display: flex;
+ flex-wrap: wrap;
+ line-height: 2;
>* {
margin-right: 0.5em;
diff --git a/src/components/settings_modal/settings_modal.vue b/src/components/settings_modal/settings_modal.vue
index 7b457371..50859c94 100644
--- a/src/components/settings_modal/settings_modal.vue
+++ b/src/components/settings_modal/settings_modal.vue
@@ -8,13 +8,13 @@
<div class="settings-modal-panel panel">
<div class="panel-heading">
<span class="title">
- {{ $t('settings.settings') }}
+ {{ modalMode === 'user' ? $t('settings.settings') : $t('admin_dash.window_title') }}
</span>
<transition name="fade">
<div
v-if="currentSaveStateNotice"
class="alert"
- :class="{ transparent: !currentSaveStateNotice.error, error: currentSaveStateNotice.error}"
+ :class="{ success: !currentSaveStateNotice.error, error: currentSaveStateNotice.error}"
@click.prevent
>
{{ currentSaveStateNotice.error ? $t('settings.saving_err') : $t('settings.saving_ok') }}
@@ -42,10 +42,12 @@
</button>
</div>
<div class="panel-body">
- <SettingsModalContent v-if="modalOpenedOnce" />
+ <SettingsModalUserContent v-if="modalMode === 'user' && modalOpenedOnceUser" />
+ <SettingsModalAdminContent v-if="modalMode === 'admin' && modalOpenedOnceAdmin" />
</div>
- <div class="panel-footer settings-footer">
+ <div class="panel-footer settings-footer -flexible-height">
<Popover
+ v-if="modalMode === 'user'"
class="export"
trigger="click"
placement="top"
@@ -68,7 +70,7 @@
<template #content="{close}">
<div class="dropdown-menu">
<button
- class="button-default dropdown-item dropdown-item-icon"
+ class="menu-item dropdown-item dropdown-item-icon"
@click.prevent="backup"
@click="close"
>
@@ -78,7 +80,7 @@
/><span>{{ $t("settings.file_export_import.backup_settings") }}</span>
</button>
<button
- class="button-default dropdown-item dropdown-item-icon"
+ class="menu-item dropdown-item dropdown-item-icon"
@click.prevent="backupWithTheme"
@click="close"
>
@@ -88,7 +90,7 @@
/><span>{{ $t("settings.file_export_import.backup_settings_theme") }}</span>
</button>
<button
- class="button-default dropdown-item dropdown-item-icon"
+ class="menu-item dropdown-item dropdown-item-icon"
@click.prevent="restore"
@click="close"
>
@@ -107,10 +109,42 @@
>
{{ $t("settings.expert_mode") }}
</Checkbox>
+ <span v-if="modalMode === 'admin'">
+ <i18n-t keypath="admin_dash.wip_notice">
+ <template #adminFeLink>
+ <a
+ href="/pleroma/admin/#/login-pleroma"
+ target="_blank"
+ >
+ {{ $t("admin_dash.old_ui_link") }}
+ </a>
+ </template>
+ </i18n-t>
+ </span>
<span
id="unscrolled-content"
class="extra-content"
/>
+ <span
+ v-if="modalMode === 'admin'"
+ class="admin-buttons"
+ >
+ <button
+ class="button-default btn"
+ :disabled="!adminDraftAny"
+ @click="resetAdminDraft"
+ >
+ {{ $t("admin_dash.reset_all") }}
+ </button>
+ {{ ' ' }}
+ <button
+ class="button-default btn"
+ :disabled="!adminDraftAny"
+ @click="pushAdminDraft"
+ >
+ {{ $t("admin_dash.commit_all") }}
+ </button>
+ </span>
</div>
</div>
</Modal>
diff --git a/src/components/settings_modal/settings_modal_admin_content.js b/src/components/settings_modal/settings_modal_admin_content.js
new file mode 100644
index 00000000..ce835bf2
--- /dev/null
+++ b/src/components/settings_modal/settings_modal_admin_content.js
@@ -0,0 +1,95 @@
+import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
+
+import InstanceTab from './admin_tabs/instance_tab.vue'
+import LimitsTab from './admin_tabs/limits_tab.vue'
+import FrontendsTab from './admin_tabs/frontends_tab.vue'
+import EmojiTab from './admin_tabs/emoji_tab.vue'
+
+import { library } from '@fortawesome/fontawesome-svg-core'
+import {
+ faWrench,
+ faHand,
+ faLaptopCode,
+ faPaintBrush,
+ faBell,
+ faDownload,
+ faEyeSlash,
+ faInfo
+} from '@fortawesome/free-solid-svg-icons'
+
+library.add(
+ faWrench,
+ faHand,
+ faLaptopCode,
+ faPaintBrush,
+ faBell,
+ faDownload,
+ faEyeSlash,
+ faInfo
+)
+
+const SettingsModalAdminContent = {
+ components: {
+ TabSwitcher,
+
+ InstanceTab,
+ LimitsTab,
+ FrontendsTab,
+ EmojiTab
+ },
+ computed: {
+ user () {
+ return this.$store.state.users.currentUser
+ },
+ isLoggedIn () {
+ return !!this.$store.state.users.currentUser
+ },
+ open () {
+ return this.$store.state.interface.settingsModalState !== 'hidden'
+ },
+ bodyLock () {
+ return this.$store.state.interface.settingsModalState === 'visible'
+ },
+ adminDbLoaded () {
+ return this.$store.state.adminSettings.loaded
+ },
+ adminDescriptionsLoaded () {
+ return this.$store.state.adminSettings.descriptions !== null
+ },
+ noDb () {
+ return this.$store.state.adminSettings.dbConfigEnabled === false
+ }
+ },
+ created () {
+ if (this.user.rights.admin) {
+ this.$store.dispatch('loadAdminStuff')
+ }
+ },
+ methods: {
+ onOpen () {
+ const targetTab = this.$store.state.interface.settingsModalTargetTab
+ // We're being told to open in specific tab
+ if (targetTab) {
+ const tabIndex = this.$refs.tabSwitcher.$slots.default().findIndex(elm => {
+ return elm.props && elm.props['data-tab-name'] === targetTab
+ })
+ if (tabIndex >= 0) {
+ this.$refs.tabSwitcher.setTab(tabIndex)
+ }
+ }
+ // Clear the state of target tab, so that next time settings is opened
+ // it doesn't force it.
+ this.$store.dispatch('clearSettingsModalTargetTab')
+ }
+ },
+ mounted () {
+ this.onOpen()
+ },
+ watch: {
+ open: function (value) {
+ if (value) this.onOpen()
+ }
+ }
+}
+
+export default SettingsModalAdminContent
diff --git a/src/components/settings_modal/settings_modal_content.scss b/src/components/settings_modal/settings_modal_admin_content.scss
index 87df7982..a5314fe1 100644
--- a/src/components/settings_modal/settings_modal_content.scss
+++ b/src/components/settings_modal/settings_modal_admin_content.scss
@@ -1,10 +1,8 @@
-@import "src/variables";
-
.settings_tab-switcher {
height: 100%;
.setting-item {
- border-bottom: 2px solid var(--fg, $fallback--fg);
+ border-bottom: 2px solid var(--border);
margin: 1em 1em 1.4em;
padding-bottom: 1.4em;
@@ -33,10 +31,6 @@
margin-bottom: 1em;
}
- select {
- min-width: 10em;
- }
-
textarea {
width: 100%;
max-width: 100%;
@@ -45,12 +39,7 @@
.unavailable,
.unavailable svg {
- color: var(--cRed, $fallback--cRed);
- color: $fallback--cRed;
- }
-
- .number-input {
- max-width: 6em;
+ color: var(--cRed);
}
}
}
diff --git a/src/components/settings_modal/settings_modal_admin_content.vue b/src/components/settings_modal/settings_modal_admin_content.vue
new file mode 100644
index 00000000..65e23b7e
--- /dev/null
+++ b/src/components/settings_modal/settings_modal_admin_content.vue
@@ -0,0 +1,76 @@
+<template>
+ <tab-switcher
+ v-if="adminDescriptionsLoaded && (noDb || adminDbLoaded)"
+ ref="tabSwitcher"
+ class="settings_tab-switcher"
+ :side-tab-bar="true"
+ :scrollable-tabs="true"
+ :render-only-focused="true"
+ :body-scroll-lock="bodyLock"
+ >
+ <div
+ v-if="noDb"
+ :label="$t('admin_dash.tabs.nodb')"
+ icon="exclamation-triangle"
+ data-tab-name="nodb-notice"
+ >
+ <div :label="$t('admin_dash.tabs.nodb')">
+ <div class="setting-item">
+ <h2>{{ $t('admin_dash.nodb.heading') }}</h2>
+ <i18n-t keypath="admin_dash.nodb.text">
+ <template #documentation>
+ <a
+ href="https://docs-develop.pleroma.social/backend/configuration/howto_database_config/"
+ target="_blank"
+ >
+ {{ $t("admin_dash.nodb.documentation") }}
+ </a>
+ </template>
+ <template #property>
+ <code>config :pleroma, configurable_from_database</code>
+ </template>
+ <template #value>
+ <code>true</code>
+ </template>
+ </i18n-t>
+ <p>{{ $t('admin_dash.nodb.text2') }}</p>
+ </div>
+ </div>
+ </div>
+ <div
+ v-if="adminDbLoaded"
+ :label="$t('admin_dash.tabs.instance')"
+ icon="wrench"
+ data-tab-name="general"
+ >
+ <InstanceTab />
+ </div>
+ <div
+ v-if="adminDbLoaded"
+ :label="$t('admin_dash.tabs.limits')"
+ icon="hand"
+ data-tab-name="limits"
+ >
+ <LimitsTab />
+ </div>
+ <div
+ :label="$t('admin_dash.tabs.frontends')"
+ icon="laptop-code"
+ data-tab-name="frontends"
+ >
+ <FrontendsTab />
+ </div>
+
+ <div
+ :label="$t('admin_dash.tabs.emoji')"
+ icon="face-smile-beam"
+ data-tab-name="emoji"
+ >
+ <EmojiTab />
+ </div>
+ </tab-switcher>
+</template>
+
+<script src="./settings_modal_admin_content.js"></script>
+
+<style src="./settings_modal_admin_content.scss" lang="scss"></style>
diff --git a/src/components/settings_modal/settings_modal_content.js b/src/components/settings_modal/settings_modal_user_content.js
index 9ac0301f..9ac0301f 100644
--- a/src/components/settings_modal/settings_modal_content.js
+++ b/src/components/settings_modal/settings_modal_user_content.js
diff --git a/src/components/settings_modal/settings_modal_user_content.scss b/src/components/settings_modal/settings_modal_user_content.scss
new file mode 100644
index 00000000..a5314fe1
--- /dev/null
+++ b/src/components/settings_modal/settings_modal_user_content.scss
@@ -0,0 +1,45 @@
+.settings_tab-switcher {
+ height: 100%;
+
+ .setting-item {
+ border-bottom: 2px solid var(--border);
+ margin: 1em 1em 1.4em;
+ padding-bottom: 1.4em;
+
+ > div,
+ > label {
+ display: block;
+ margin-bottom: 0.5em;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+
+ .select-multiple {
+ display: flex;
+
+ .option-list {
+ margin: 0;
+ padding-left: 0.5em;
+ }
+ }
+
+ &:last-child {
+ border-bottom: none;
+ padding-bottom: 0;
+ margin-bottom: 1em;
+ }
+
+ textarea {
+ width: 100%;
+ max-width: 100%;
+ height: 100px;
+ }
+
+ .unavailable,
+ .unavailable svg {
+ color: var(--cRed);
+ }
+ }
+}
diff --git a/src/components/settings_modal/settings_modal_content.vue b/src/components/settings_modal/settings_modal_user_content.vue
index 0be76d22..0221cccb 100644
--- a/src/components/settings_modal/settings_modal_content.vue
+++ b/src/components/settings_modal/settings_modal_user_content.vue
@@ -78,6 +78,6 @@
</tab-switcher>
</template>
-<script src="./settings_modal_content.js"></script>
+<script src="./settings_modal_user_content.js"></script>
-<style src="./settings_modal_content.scss" lang="scss"></style>
+<style src="./settings_modal_user_content.scss" lang="scss"></style>
diff --git a/src/components/settings_modal/tabs/filtering_tab.js b/src/components/settings_modal/tabs/filtering_tab.js
index 7c37f0bc..fbace15d 100644
--- a/src/components/settings_modal/tabs/filtering_tab.js
+++ b/src/components/settings_modal/tabs/filtering_tab.js
@@ -1,6 +1,7 @@
import { filter, trim, debounce } from 'lodash'
import BooleanSetting from '../helpers/boolean_setting.vue'
import ChoiceSetting from '../helpers/choice_setting.vue'
+import UnitSetting from '../helpers/unit_setting.vue'
import IntegerSetting from '../helpers/integer_setting.vue'
import SharedComputedObject from '../helpers/shared_computed_object.js'
@@ -19,6 +20,7 @@ const FilteringTab = {
components: {
BooleanSetting,
ChoiceSetting,
+ UnitSetting,
IntegerSetting
},
computed: {
diff --git a/src/components/settings_modal/tabs/filtering_tab.vue b/src/components/settings_modal/tabs/filtering_tab.vue
index 97046ff0..821ca750 100644
--- a/src/components/settings_modal/tabs/filtering_tab.vue
+++ b/src/components/settings_modal/tabs/filtering_tab.vue
@@ -7,13 +7,11 @@
<BooleanSetting path="hideFilteredStatuses">
{{ $t('settings.hide_filtered_statuses') }}
</BooleanSetting>
- <ul
- class="setting-list suboptions"
- :class="[{disabled: !streaming}]"
- >
+ <ul class="setting-list suboptions">
<li>
<BooleanSetting
- :disabled="hideFilteredStatuses"
+ parent-path="hideFilteredStatuses"
+ :parent-invert="true"
path="hideWordFilteredPosts"
>
{{ $t('settings.hide_wordfiltered_statuses') }}
@@ -22,7 +20,8 @@
<li>
<BooleanSetting
v-if="user"
- :disabled="hideFilteredStatuses"
+ parent-path="hideFilteredStatuses"
+ :parent-invert="true"
path="hideMutedThreads"
>
{{ $t('settings.hide_muted_threads') }}
@@ -31,7 +30,8 @@
<li>
<BooleanSetting
v-if="user"
- :disabled="hideFilteredStatuses"
+ parent-path="hideFilteredStatuses"
+ :parent-invert="true"
path="hideMutedPosts"
>
{{ $t('settings.hide_muted_posts') }}
@@ -45,13 +45,36 @@
</BooleanSetting>
</li>
<li>
+ <BooleanSetting path="muteSensitiveStatuses">
+ {{ $t('settings.mute_sensitive_posts') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting path="hideMutedFederationRestrictions">
+ {{ $t('settings.hide_muted_federation_restrictions') }}
+ </BooleanSetting>
+ <ul
+ class="setting-list suboptions"
+ :class="[{disabled: !streaming}]"
+ >
+ <li
+ v-for="item in muteFederationRestrictionsLevels"
+ :key="'mute_' + item + '_federation_restriction'"
+ >
+ <BooleanSetting :path="'muteFederationRestrictions.' + item">
+ {{ $t('settings.mute_' + item + '_federation_restriction') }}
+ </BooleanSetting>
+ </li>
+ </ul>
+ </li>
+ <li>
<BooleanSetting path="hidePostStats">
{{ $t('settings.hide_post_stats') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="hideBotIndication">
- {{ $t('settings.hide_bot_indication') }}
+ {{ $t('settings.hide_actor_type_indication') }}
</BooleanSetting>
</li>
<ChoiceSetting
@@ -67,7 +90,7 @@
<textarea
id="muteWords"
v-model="muteWordsString"
- class="resize-height"
+ class="input resize-height"
/>
<div>{{ $t('settings.filtering_explanation') }}</div>
</li>
@@ -91,6 +114,22 @@
{{ $t('settings.hide_attachments_in_convo') }}
</BooleanSetting>
</li>
+ <li>
+ <BooleanSetting path="hideScrobbles">
+ {{ $t('settings.hide_scrobbles') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <UnitSetting
+ key="hideScrobblesAfter"
+ path="hideScrobblesAfter"
+ :units="['m', 'h', 'd']"
+ unitSet="time"
+ expert="1"
+ >
+ {{ $t('settings.hide_scrobbles_after') }}
+ </UnitSetting>
+ </li>
</ul>
</div>
<div
diff --git a/src/components/settings_modal/tabs/general_tab.js b/src/components/settings_modal/tabs/general_tab.js
index be97710f..7d701d34 100644
--- a/src/components/settings_modal/tabs/general_tab.js
+++ b/src/components/settings_modal/tabs/general_tab.js
@@ -3,11 +3,11 @@ import ChoiceSetting from '../helpers/choice_setting.vue'
import ScopeSelector from 'src/components/scope_selector/scope_selector.vue'
import IntegerSetting from '../helpers/integer_setting.vue'
import FloatSetting from '../helpers/float_setting.vue'
-import SizeSetting, { defaultHorizontalUnits } from '../helpers/size_setting.vue'
+import UnitSetting, { defaultHorizontalUnits } from '../helpers/unit_setting.vue'
import InterfaceLanguageSwitcher from 'src/components/interface_language_switcher/interface_language_switcher.vue'
import SharedComputedObject from '../helpers/shared_computed_object.js'
-import ServerSideIndicator from '../helpers/server_side_indicator.vue'
+import ProfileSettingIndicator from '../helpers/profile_setting_indicator.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faGlobe
@@ -64,10 +64,10 @@ const GeneralTab = {
ChoiceSetting,
IntegerSetting,
FloatSetting,
- SizeSetting,
+ UnitSetting,
InterfaceLanguageSwitcher,
ScopeSelector,
- ServerSideIndicator
+ ProfileSettingIndicator
},
computed: {
horizontalUnits () {
@@ -110,7 +110,7 @@ const GeneralTab = {
},
methods: {
changeDefaultScope (value) {
- this.$store.dispatch('setServerSideOption', { name: 'defaultScope', value })
+ this.$store.dispatch('setProfileOption', { name: 'defaultScope', value })
}
}
}
diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue
index 21e2d855..208c49ee 100644
--- a/src/components/settings_modal/tabs/general_tab.vue
+++ b/src/components/settings_modal/tabs/general_tab.vue
@@ -29,14 +29,11 @@
<BooleanSetting path="streaming">
{{ $t('settings.streaming') }}
</BooleanSetting>
- <ul
- class="setting-list suboptions"
- :class="[{disabled: !streaming}]"
- >
+ <ul class="setting-list suboptions">
<li>
<BooleanSetting
path="pauseOnUnfocused"
- :disabled="!streaming"
+ parent-path="streaming"
>
{{ $t('settings.pause_on_unfocused') }}
</BooleanSetting>
@@ -137,7 +134,7 @@
<li v-if="expertLevel > 0">
{{ $t('settings.column_sizes') }}
<div class="column-settings">
- <SizeSetting
+ <UnitSetting
v-for="column in columns"
:key="column"
:path="column + 'ColumnWidth'"
@@ -145,7 +142,7 @@
expert="1"
>
{{ $t('settings.column_sizes_' + column) }}
- </SizeSetting>
+ </UnitSetting>
</div>
</li>
<li class="select-multiple">
@@ -204,6 +201,14 @@
<h2>{{ $t('settings.post_look_feel') }}</h2>
<ul class="setting-list">
<li>
+ <BooleanSetting
+ path="forceThemeRecompilation"
+ :expert="1"
+ >
+ {{ $t('settings.force_theme_recompilation_debug') }}
+ </BooleanSetting>
+ </li>
+ <li>
<ChoiceSetting
id="conversationDisplay"
path="conversationDisplay"
@@ -213,7 +218,7 @@
</ChoiceSetting>
</li>
<ul
- v-if="conversationDisplay !== 'linear'"
+ v-if="mergedConfig.conversationDisplay !== 'linear'"
class="setting-list suboptions"
>
<li>
@@ -265,7 +270,8 @@
<li>
<BooleanSetting
v-if="user"
- path="serverSide_stripRichContent"
+ source="profile"
+ path="stripRichContent"
expert="1"
>
{{ $t('settings.no_rich_text_description') }}
@@ -299,7 +305,7 @@
<BooleanSetting
path="preloadImage"
expert="1"
- :disabled="!hideNsfw"
+ parent-path="hideNsfw"
>
{{ $t('settings.preload_images') }}
</BooleanSetting>
@@ -308,7 +314,7 @@
<BooleanSetting
path="useOneClickNsfw"
expert="1"
- :disabled="!hideNsfw"
+ parent-path="hideNsfw"
>
{{ $t('settings.use_one_click_nsfw') }}
</BooleanSetting>
@@ -321,15 +327,13 @@
>
{{ $t('settings.loop_video') }}
</BooleanSetting>
- <ul
- class="setting-list suboptions"
- :class="[{disabled: !streaming}]"
- >
+ <ul class="setting-list suboptions">
<li>
<BooleanSetting
path="loopVideoSilentOnly"
expert="1"
- :disabled="!loopVideo || !loopSilentAvailable"
+ parent-path="loopVideo"
+ :disabled="!loopSilentAvailable"
>
{{ $t('settings.loop_video_silent_only') }}
</BooleanSetting>
@@ -427,18 +431,18 @@
<ul class="setting-list">
<li>
<label for="default-vis">
- {{ $t('settings.default_vis') }} <ServerSideIndicator :server-side="true" />
+ {{ $t('settings.default_vis') }} <ProfileSettingIndicator :is-profile="true" />
<ScopeSelector
class="scope-selector"
:show-all="true"
- :user-default="serverSide_defaultScope"
- :initial-scope="serverSide_defaultScope"
+ :user-default="$store.state.profileConfig.defaultScope"
+ :initial-scope="$store.state.profileConfig.defaultScope"
:on-scope-change="changeDefaultScope"
/>
</label>
</li>
<li>
- <!-- <BooleanSetting path="serverSide_defaultNSFW"> -->
+ <!-- <BooleanSetting source="profile" path="defaultNSFW"> -->
<BooleanSetting path="sensitiveByDefault">
{{ $t('settings.sensitive_by_default') }}
</BooleanSetting>
diff --git a/src/components/settings_modal/tabs/mutes_and_blocks_tab.js b/src/components/settings_modal/tabs/mutes_and_blocks_tab.js
index 6cfeea35..51974f9f 100644
--- a/src/components/settings_modal/tabs/mutes_and_blocks_tab.js
+++ b/src/components/settings_modal/tabs/mutes_and_blocks_tab.js
@@ -9,17 +9,20 @@ import DomainMuteCard from 'src/components/domain_mute_card/domain_mute_card.vue
import SelectableList from 'src/components/selectable_list/selectable_list.vue'
import ProgressButton from 'src/components/progress_button/progress_button.vue'
import withSubscription from 'src/components/../hocs/with_subscription/with_subscription'
+import withLoadMore from 'src/components/../hocs/with_load_more/with_load_more'
import Checkbox from 'src/components/checkbox/checkbox.vue'
-const BlockList = withSubscription({
+const BlockList = withLoadMore({
fetch: (props, $store) => $store.dispatch('fetchBlocks'),
select: (props, $store) => get($store.state.users.currentUser, 'blockIds', []),
+ destroy: () => {},
childPropName: 'items'
})(SelectableList)
-const MuteList = withSubscription({
+const MuteList = withLoadMore({
fetch: (props, $store) => $store.dispatch('fetchMutes'),
select: (props, $store) => get($store.state.users.currentUser, 'muteIds', []),
+ destroy: () => {},
childPropName: 'items'
})(SelectableList)
diff --git a/src/components/settings_modal/tabs/notifications_tab.js b/src/components/settings_modal/tabs/notifications_tab.js
index 3c6ab87f..c53b5889 100644
--- a/src/components/settings_modal/tabs/notifications_tab.js
+++ b/src/components/settings_modal/tabs/notifications_tab.js
@@ -16,6 +16,10 @@ const NotificationsTab = {
user () {
return this.$store.state.users.currentUser
},
+ canReceiveReports () {
+ if (!this.user) { return false }
+ return this.user.privileges.includes('reports_manage_reports')
+ },
...SharedComputedObject()
},
methods: {
diff --git a/src/components/settings_modal/tabs/notifications_tab.vue b/src/components/settings_modal/tabs/notifications_tab.vue
index dd3806ed..10228888 100644
--- a/src/components/settings_modal/tabs/notifications_tab.vue
+++ b/src/components/settings_modal/tabs/notifications_tab.vue
@@ -1,49 +1,239 @@
<template>
<div :label="$t('settings.notifications')">
<div class="setting-item">
+ <h2>{{ $t('settings.notification_setting_annoyance') }}</h2>
+ <ul class="setting-list">
+ <li>
+ <BooleanSetting path="closingDrawerMarksAsSeen">
+ {{ $t('settings.notification_setting_drawer_marks_as_seen') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting path="ignoreInactionableSeen">
+ {{ $t('settings.notification_setting_ignore_inactionable_seen') }}
+ </BooleanSetting>
+ <div>
+ <small>
+ {{ $t('settings.notification_setting_ignore_inactionable_seen_tip') }}
+ </small>
+ </div>
+ </li>
+ <li>
+ <BooleanSetting
+ path="unseenAtTop"
+ expert="1"
+ >
+ {{ $t('settings.notification_setting_unseen_at_top') }}
+ </BooleanSetting>
+ </li>
+ </ul>
+ </div>
+ <div class="setting-item">
<h2>{{ $t('settings.notification_setting_filters') }}</h2>
<ul class="setting-list">
<li>
- <BooleanSetting path="serverSide_blockNotificationsFromStrangers">
+ <BooleanSetting
+ source="profile"
+ path="blockNotificationsFromStrangers"
+ >
{{ $t('settings.notification_setting_block_from_strangers') }}
</BooleanSetting>
</li>
- <li class="select-multiple">
- <span class="label">{{ $t('settings.notification_visibility') }}</span>
- <ul class="option-list">
+ <li>
+ <h3> {{ $t('settings.notification_visibility') }}</h3>
+ <p v-if="expertLevel > 0">
+ {{ $t('settings.notification_setting_filters_chrome_push') }}
+ </p>
+ <ul class="setting-list two-column">
<li>
- <BooleanSetting path="notificationVisibility.likes">
- {{ $t('settings.notification_visibility_likes') }}
- </BooleanSetting>
+ <h4> {{ $t('settings.notification_visibility_mentions') }}</h4>
+ <ul class="setting-list">
+ <li>
+ <BooleanSetting path="notificationVisibility.mentions">
+ {{ $t('settings.notification_visibility_in_column') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting path="notificationNative.mentions">
+ {{ $t('settings.notification_visibility_native_notifications') }}
+ </BooleanSetting>
+ </li>
+ </ul>
</li>
<li>
- <BooleanSetting path="notificationVisibility.repeats">
- {{ $t('settings.notification_visibility_repeats') }}
- </BooleanSetting>
+ <h4> {{ $t('settings.notification_visibility_statuses') }}</h4>
+ <ul class="setting-list">
+ <li>
+ <BooleanSetting path="notificationVisibility.statuses">
+ {{ $t('settings.notification_visibility_in_column') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting path="notificationNative.statuses">
+ {{ $t('settings.notification_visibility_native_notifications') }}
+ </BooleanSetting>
+ </li>
+ </ul>
</li>
<li>
- <BooleanSetting path="notificationVisibility.follows">
- {{ $t('settings.notification_visibility_follows') }}
- </BooleanSetting>
+ <h4> {{ $t('settings.notification_visibility_likes') }}</h4>
+ <ul class="setting-list">
+ <li>
+ <BooleanSetting path="notificationVisibility.likes">
+ {{ $t('settings.notification_visibility_in_column') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting path="notificationNative.likes">
+ {{ $t('settings.notification_visibility_native_notifications') }}
+ </BooleanSetting>
+ </li>
+ </ul>
</li>
<li>
- <BooleanSetting path="notificationVisibility.mentions">
- {{ $t('settings.notification_visibility_mentions') }}
+ <h4> {{ $t('settings.notification_visibility_repeats') }}</h4>
+ <ul class="setting-list">
+ <li>
+ <BooleanSetting path="notificationVisibility.repeats">
+ {{ $t('settings.notification_visibility_in_column') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting path="notificationNative.repeats">
+ {{ $t('settings.notification_visibility_native_notifications') }}
+ </BooleanSetting>
+ </li>
+ </ul>
+ </li>
+ <li>
+ <h4> {{ $t('settings.notification_visibility_emoji_reactions') }}</h4>
+ <ul class="setting-list">
+ <li>
+ <BooleanSetting path="notificationVisibility.emojiReactions">
+ {{ $t('settings.notification_visibility_in_column') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting path="notificationNative.emojiReactions">
+ {{ $t('settings.notification_visibility_native_notifications') }}
+ </BooleanSetting>
+ </li>
+ </ul>
+ </li>
+ <li>
+ <h4> {{ $t('settings.notification_visibility_follows') }}</h4>
+ <ul class="setting-list">
+ <li>
+ <BooleanSetting path="notificationVisibility.follows">
+ {{ $t('settings.notification_visibility_in_column') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting path="notificationNative.follows">
+ {{ $t('settings.notification_visibility_native_notifications') }}
+ </BooleanSetting>
+ </li>
+ </ul>
+ </li>
+ <li>
+ <h4> {{ $t('settings.notification_visibility_follow_requests') }}</h4>
+ <ul class="setting-list">
+ <li>
+ <BooleanSetting path="notificationVisibility.followRequest">
+ {{ $t('settings.notification_visibility_in_column') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting path="notificationNative.followRequest">
+ {{ $t('settings.notification_visibility_native_notifications') }}
+ </BooleanSetting>
+ </li>
+ </ul>
+ </li>
+ <li>
+ <h4> {{ $t('settings.notification_visibility_moves') }}</h4>
+ <ul class="setting-list">
+ <li>
+ <BooleanSetting path="notificationVisibility.moves">
+ {{ $t('settings.notification_visibility_in_column') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting path="notificationNative.moves">
+ {{ $t('settings.notification_visibility_native_notifications') }}
+ </BooleanSetting>
+ </li>
+ </ul>
+ </li>
+ <li>
+ <h4> {{ $t('settings.notification_visibility_polls') }}</h4>
+ <ul class="setting-list">
+ <li>
+ <BooleanSetting path="notificationVisibility.polls">
+ {{ $t('settings.notification_visibility_in_column') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting path="notificationNative.polls">
+ {{ $t('settings.notification_visibility_native_notifications') }}
+ </BooleanSetting>
+ </li>
+ </ul>
+ </li>
+ <li v-if="canReceiveReports">
+ <h4> {{ $t('settings.notification_visibility_reports') }}</h4>
+ <ul class="setting-list">
+ <li>
+ <BooleanSetting path="notificationVisibility.reports">
+ {{ $t('settings.notification_visibility_in_column') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <BooleanSetting path="notificationNative.reports">
+ {{ $t('settings.notification_visibility_native_notifications') }}
+ </BooleanSetting>
+ </li>
+ </ul>
+ </li>
+ </ul>
+ </li>
+ <li>
+ <BooleanSetting path="showExtraNotifications">
+ {{ $t('settings.notification_show_extra') }}
+ </BooleanSetting>
+ </li>
+ <li>
+ <ul class="setting-list suboptions">
+ <li>
+ <BooleanSetting
+ path="showChatsInExtraNotifications"
+ :disabled="!mergedConfig.showExtraNotifications"
+ >
+ {{ $t('settings.notification_extra_chats') }}
</BooleanSetting>
</li>
<li>
- <BooleanSetting path="notificationVisibility.moves">
- {{ $t('settings.notification_visibility_moves') }}
+ <BooleanSetting
+ path="showAnnouncementsInExtraNotifications"
+ :disabled="!mergedConfig.showExtraNotifications"
+ >
+ {{ $t('settings.notification_extra_announcements') }}
</BooleanSetting>
</li>
<li>
- <BooleanSetting path="notificationVisibility.emojiReactions">
- {{ $t('settings.notification_visibility_emoji_reactions') }}
+ <BooleanSetting
+ path="showFollowRequestsInExtraNotifications"
+ :disabled="!mergedConfig.showExtraNotifications"
+ >
+ {{ $t('settings.notification_extra_follow_requests') }}
</BooleanSetting>
</li>
<li>
- <BooleanSetting path="notificationVisibility.polls">
- {{ $t('settings.notification_visibility_polls') }}
+ <BooleanSetting
+ path="showExtraNotificationsTip"
+ :disabled="!mergedConfig.showExtraNotifications"
+ >
+ {{ $t('settings.notification_extra_tip') }}
</BooleanSetting>
</li>
</ul>
@@ -64,10 +254,26 @@
>
{{ $t('settings.enable_web_push_notifications') }}
</BooleanSetting>
+ <ul class="setting-list suboptions">
+ <li>
+ <BooleanSetting
+ path="webPushAlwaysShowNotifications"
+ :disabled="!mergedConfig.webPushNotifications"
+ >
+ {{ $t('settings.enable_web_push_always_show') }}
+ </BooleanSetting>
+ <div :class="{ faint: !mergedConfig.webPushNotifications }">
+ <small>
+ {{ $t('settings.enable_web_push_always_show_tip') }}
+ </small>
+ </div>
+ </li>
+ </ul>
</li>
<li>
<BooleanSetting
- path="serverSide_webPushHideContents"
+ source="profile"
+ path="webPushHideContents"
expert="1"
>
{{ $t('settings.notification_setting_hide_notification_contents') }}
diff --git a/src/components/settings_modal/tabs/profile_tab.js b/src/components/settings_modal/tabs/profile_tab.js
index eeacad48..dee17450 100644
--- a/src/components/settings_modal/tabs/profile_tab.js
+++ b/src/components/settings_modal/tabs/profile_tab.js
@@ -9,6 +9,7 @@ import suggestor from 'src/components/emoji_input/suggestor.js'
import Autosuggest from 'src/components/autosuggest/autosuggest.vue'
import Checkbox from 'src/components/checkbox/checkbox.vue'
import InterfaceLanguageSwitcher from 'src/components/interface_language_switcher/interface_language_switcher.vue'
+import Select from 'src/components/select/select.vue'
import BooleanSetting from '../helpers/boolean_setting.vue'
import SharedComputedObject from '../helpers/shared_computed_object.js'
import localeService from 'src/services/locale/locale.service.js'
@@ -39,6 +40,7 @@ const ProfileTab = {
showRole: this.$store.state.users.currentUser.show_role,
role: this.$store.state.users.currentUser.role,
bot: this.$store.state.users.currentUser.bot,
+ actorType: this.$store.state.users.currentUser.actor_type,
pickAvatarBtnVisible: true,
bannerUploading: false,
backgroundUploading: false,
@@ -57,7 +59,8 @@ const ProfileTab = {
ProgressButton,
Checkbox,
BooleanSetting,
- InterfaceLanguageSwitcher
+ InterfaceLanguageSwitcher,
+ Select
},
computed: {
user () {
@@ -116,6 +119,12 @@ const ProfileTab = {
bannerImgSrc () {
const src = this.$store.state.users.currentUser.cover_photo
return (!src) ? this.defaultBanner : src
+ },
+ groupActorAvailable () {
+ return this.$store.state.instance.groupActorAvailable
+ },
+ availableActorTypes () {
+ return this.groupActorAvailable ? ['Person', 'Service', 'Group'] : ['Person', 'Service']
}
},
methods: {
@@ -127,7 +136,7 @@ const ProfileTab = {
/* eslint-disable camelcase */
display_name: this.newName,
fields_attributes: this.newFields.filter(el => el != null),
- bot: this.bot,
+ actor_type: this.actorType,
show_role: this.showRole,
birthday: this.newBirthday || '',
show_birthday: this.showBirthday
diff --git a/src/components/settings_modal/tabs/profile_tab.scss b/src/components/settings_modal/tabs/profile_tab.scss
index ee253ffe..7eda943b 100644
--- a/src/components/settings_modal/tabs/profile_tab.scss
+++ b/src/components/settings_modal/tabs/profile_tab.scss
@@ -1,5 +1,3 @@
-@import "../../../variables";
-
.profile-tab {
.bio {
margin: 0;
@@ -43,16 +41,14 @@
display: block;
width: 100%;
height: 100%;
- border-radius: $fallback--avatarRadius;
- border-radius: var(--avatarRadius, $fallback--avatarRadius);
+ border-radius: var(--roundness);
}
.reset-button {
position: absolute;
top: 0.2em;
right: 0.2em;
- border-radius: $fallback--tooltipRadius;
- border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
+ border-radius: var(--roundness);
background-color: rgb(0 0 0 / 60%);
opacity: 0.7;
width: 1.5em;
diff --git a/src/components/settings_modal/tabs/profile_tab.vue b/src/components/settings_modal/tabs/profile_tab.vue
index 6a5b478a..034034a1 100644
--- a/src/components/settings_modal/tabs/profile_tab.vue
+++ b/src/components/settings_modal/tabs/profile_tab.vue
@@ -12,7 +12,7 @@
<input
id="username"
v-model="newName"
- class="name-changer"
+ class="input name-changer"
v-bind="propsToNative(inputProps)"
>
</template>
@@ -26,7 +26,7 @@
<template #default="inputProps">
<textarea
v-model="newBio"
- class="bio resize-height"
+ class="input bio resize-height"
v-bind="propsToNative(inputProps)"
/>
</template>
@@ -47,7 +47,7 @@
id="birthday"
v-model="newBirthday"
type="date"
- class="birthday-input"
+ class="input birthday-input"
>
<Checkbox v-model="showBirthday">
{{ $t('settings.birthday.show_birthday') }}
@@ -71,6 +71,7 @@
v-model="newFields[i].name"
:placeholder="$t('settings.profile_fields.name')"
v-bind="propsToNative(inputProps)"
+ class="input"
>
</template>
</EmojiInput>
@@ -85,6 +86,7 @@
v-model="newFields[i].value"
:placeholder="$t('settings.profile_fields.value')"
v-bind="propsToNative(inputProps)"
+ class="input"
>
</template>
</EmojiInput>
@@ -109,10 +111,24 @@
</button>
</div>
<p>
- <Checkbox v-model="bot">
- {{ $t('settings.bot') }}
- </Checkbox>
+ <label>
+ {{ $t('settings.actor_type') }}
+ <Select v-model="actorType">
+ <option
+ v-for="option in availableActorTypes"
+ :key="option"
+ :value="option"
+ >
+ {{ $t('settings.actor_type_' + option) }}
+ </option>
+ </Select>
+ </label>
</p>
+ <div v-if="groupActorAvailable">
+ <small>
+ {{ $t('settings.actor_type_description') }}
+ </small>
+ </div>
<p>
<interface-language-switcher
:prompt-text="$t('settings.email_language')"
@@ -191,6 +207,7 @@
<div>
<input
type="file"
+ class="input"
@change="uploadFile('banner', $event)"
>
</div>
@@ -233,6 +250,7 @@
<div>
<input
type="file"
+ class="input"
@change="uploadFile('background', $event)"
>
</div>
@@ -254,37 +272,50 @@
<h2>{{ $t('settings.account_privacy') }}</h2>
<ul class="setting-list">
<li>
- <BooleanSetting path="serverSide_locked">
+ <BooleanSetting
+ source="profile"
+ path="locked"
+ >
{{ $t('settings.lock_account_description') }}
</BooleanSetting>
</li>
<li>
- <BooleanSetting path="serverSide_discoverable">
+ <BooleanSetting
+ source="profile"
+ path="discoverable"
+ >
{{ $t('settings.discoverable') }}
</BooleanSetting>
</li>
<li>
- <BooleanSetting path="serverSide_allowFollowingMove">
+ <BooleanSetting
+ source="profile"
+ path="allowFollowingMove"
+ >
{{ $t('settings.allow_following_move') }}
</BooleanSetting>
</li>
<li>
- <BooleanSetting path="serverSide_hideFavorites">
+ <BooleanSetting
+ source="profile"
+ path="hideFavorites"
+ >
{{ $t('settings.hide_favorites_description') }}
</BooleanSetting>
</li>
<li>
- <BooleanSetting path="serverSide_hideFollowers">
+ <BooleanSetting
+ source="profile"
+ path="hideFollowers"
+ >
{{ $t('settings.hide_followers_description') }}
</BooleanSetting>
- <ul
- class="setting-list suboptions"
- :class="[{disabled: !serverSide_hideFollowers}]"
- >
+ <ul class="setting-list suboptions">
<li>
<BooleanSetting
- path="serverSide_hideFollowersCount"
- :disabled="!serverSide_hideFollowers"
+ source="profile"
+ path="hideFollowersCount"
+ parent-path="hideFollowers"
>
{{ $t('settings.hide_followers_count_description') }}
</BooleanSetting>
@@ -292,17 +323,18 @@
</ul>
</li>
<li>
- <BooleanSetting path="serverSide_hideFollows">
+ <BooleanSetting
+ source="profile"
+ path="hideFollows"
+ >
{{ $t('settings.hide_follows_description') }}
</BooleanSetting>
- <ul
- class="setting-list suboptions"
- :class="[{disabled: !serverSide_hideFollows}]"
- >
+ <ul class="setting-list suboptions">
<li>
<BooleanSetting
- path="serverSide_hideFollowsCount"
- :disabled="!serverSide_hideFollows"
+ source="profile"
+ path="hideFollowsCount"
+ parent-path="hideFollows"
>
{{ $t('settings.hide_follows_count_description') }}
</BooleanSetting>
diff --git a/src/components/settings_modal/tabs/security_tab/mfa.vue b/src/components/settings_modal/tabs/security_tab/mfa.vue
index ee5b6b13..9421b16e 100644
--- a/src/components/settings_modal/tabs/security_tab/mfa.vue
+++ b/src/components/settings_modal/tabs/security_tab/mfa.vue
@@ -99,12 +99,14 @@
<input
v-model="otpConfirmToken"
type="text"
+ class="input"
>
<p>{{ $t('settings.enter_current_password_to_confirm') }}:</p>
<input
v-model="currentPassword"
type="password"
+ class="input"
>
<div class="confirm-otp-actions">
<button
@@ -137,8 +139,6 @@
<script src="./mfa.js"></script>
<style lang="scss">
-@import "../../../../variables";
-
.mfa-settings {
.mfa-heading,
.method-item {
@@ -149,8 +149,7 @@
}
.warning {
- color: $fallback--cOrange;
- color: var(--cOrange, $fallback--cOrange);
+ color: var(--cOrange);
}
.setup-otp {
diff --git a/src/components/settings_modal/tabs/security_tab/mfa_backup_codes.vue b/src/components/settings_modal/tabs/security_tab/mfa_backup_codes.vue
index 923161b2..32a8a759 100644
--- a/src/components/settings_modal/tabs/security_tab/mfa_backup_codes.vue
+++ b/src/components/settings_modal/tabs/security_tab/mfa_backup_codes.vue
@@ -21,16 +21,13 @@
</template>
<script src="./mfa_backup_codes.js"></script>
<style lang="scss">
-@import "../../../../variables";
-
.mfa-backup-codes {
.warning {
- color: $fallback--cOrange;
- color: var(--cOrange, $fallback--cOrange);
+ color: var(--cOrange);
}
.backup-codes {
- font-family: var(--postCodeFont, monospace);
+ font-family: var(--monoFont);
}
}
</style>
diff --git a/src/components/settings_modal/tabs/security_tab/mfa_totp.vue b/src/components/settings_modal/tabs/security_tab/mfa_totp.vue
index 8e767bd0..99b66818 100644
--- a/src/components/settings_modal/tabs/security_tab/mfa_totp.vue
+++ b/src/components/settings_modal/tabs/security_tab/mfa_totp.vue
@@ -30,6 +30,7 @@
<input
v-model="currentPassword"
type="password"
+ class="input"
>
</confirm>
<div
diff --git a/src/components/settings_modal/tabs/security_tab/security_tab.vue b/src/components/settings_modal/tabs/security_tab/security_tab.vue
index 6e03bef4..74103f6f 100644
--- a/src/components/settings_modal/tabs/security_tab/security_tab.vue
+++ b/src/components/settings_modal/tabs/security_tab/security_tab.vue
@@ -8,6 +8,7 @@
v-model="newEmail"
type="email"
autocomplete="email"
+ class="input"
>
</div>
<div>
@@ -16,6 +17,7 @@
v-model="changeEmailPassword"
type="password"
autocomplete="current-password"
+ class="input"
>
</div>
<button
@@ -40,6 +42,7 @@
<input
v-model="changePasswordInputs[0]"
type="password"
+ class="input"
>
</div>
<div>
@@ -47,6 +50,7 @@
<input
v-model="changePasswordInputs[1]"
type="password"
+ class="input"
>
</div>
<div>
@@ -54,6 +58,7 @@
<input
v-model="changePasswordInputs[2]"
type="password"
+ class="input"
>
</div>
<button
@@ -143,8 +148,8 @@
/>
</div>
<div>
- <i18n
- path="settings.new_alias_target"
+ <i18n-t
+ keypath="settings.new_alias_target"
tag="p"
>
<code
@@ -152,9 +157,10 @@
>
foo@example.org
</code>
- </i18n>
+ </i18n-t>
<input
v-model="addAliasTarget"
+ class="input"
>
</div>
<button
@@ -175,18 +181,19 @@
<h2>{{ $t('settings.move_account') }}</h2>
<p>{{ $t('settings.move_account_notes') }}</p>
<div>
- <i18n
- path="settings.move_account_target"
+ <i18n-t
+ keypath="settings.move_account_target"
tag="p"
>
- <code
- place="example"
- >
- foo@example.org
- </code>
- </i18n>
+ <template #example>
+ <code>
+ foo@example.org
+ </code>
+ </template>
+ </i18n-t>
<input
v-model="moveAccountTarget"
+ class="input"
>
</div>
<div>
@@ -195,6 +202,7 @@
v-model="moveAccountPassword"
type="password"
autocomplete="current-password"
+ class="input"
>
</div>
<button
@@ -222,6 +230,7 @@
<input
v-model="deleteAccountConfirmPasswordInput"
type="password"
+ class="input"
>
<button
class="btn button-default"
diff --git a/src/components/settings_modal/tabs/theme_tab/preview.vue b/src/components/settings_modal/tabs/theme_tab/preview.vue
index d755279a..1837620f 100644
--- a/src/components/settings_modal/tabs/theme_tab/preview.vue
+++ b/src/components/settings_modal/tabs/theme_tab/preview.vue
@@ -5,7 +5,7 @@
<div class="panel-heading">
<div class="title">
{{ $t('settings.style.preview.header') }}
- <span class="badge badge-notification">
+ <span class="badge -notification">
99
</span>
</div>
@@ -81,7 +81,7 @@
class="faint"
scope="global"
>
- <a style="color: var(--faintLink);">
+ <a style="color: var(--linkFaint);">
{{ $t('settings.style.preview.faint_link') }}
</a>
</i18n-t>
@@ -95,6 +95,7 @@
<input
:value="$t('settings.style.preview.input')"
type="text"
+ class="input"
>
<div class="actions">
@@ -103,6 +104,7 @@
id="preview_checkbox"
checked="very yes"
type="checkbox"
+ class="input"
>
<label for="preview_checkbox">{{ $t('settings.style.preview.checkbox') }}</label>
</span>
diff --git a/src/components/settings_modal/tabs/theme_tab/theme_tab.js b/src/components/settings_modal/tabs/theme_tab/theme_tab.js
index 4a739f73..11c90b03 100644
--- a/src/components/settings_modal/tabs/theme_tab/theme_tab.js
+++ b/src/components/settings_modal/tabs/theme_tab/theme_tab.js
@@ -4,15 +4,7 @@ import {
getContrastRatioLayers
} from 'src/services/color_convert/color_convert.js'
import {
- DEFAULT_SHADOWS,
- generateColors,
- generateShadows,
- generateRadii,
- generateFonts,
- composePreset,
- getThemes,
- shadows2to3,
- colors2to3
+ getThemes
} from 'src/services/style_setter/style_setter.js'
import {
newImporter,
@@ -25,7 +17,15 @@ import {
CURRENT_VERSION,
OPACITIES,
getLayers,
- getOpacitySlot
+ getOpacitySlot,
+ DEFAULT_SHADOWS,
+ generateColors,
+ generateShadows,
+ generateRadii,
+ generateFonts,
+ composePreset,
+ shadows2to3,
+ colors2to3
} from 'src/services/theme_data/theme_data.service.js'
import ColorInput from 'src/components/color_input/color_input.vue'
import RangeInput from 'src/components/range_input/range_input.vue'
@@ -514,6 +514,7 @@ export default {
this.$store.dispatch('setOption', {
name: 'customTheme',
value: {
+ themeFileVersion: this.selectedVersion,
themeEngineVersion: CURRENT_VERSION,
...this.previewTheme
}
@@ -521,6 +522,7 @@ export default {
this.$store.dispatch('setOption', {
name: 'customThemeSource',
value: {
+ themeFileVersion: this.selectedVersion,
themeEngineVersion: CURRENT_VERSION,
shadows: this.shadowsLocal,
fonts: this.fontsLocal,
@@ -755,7 +757,6 @@ export default {
selected () {
this.selectedTheme = Object.entries(this.availableStyles).find(([k, s]) => {
if (Array.isArray(s)) {
- console.log(s[0] === this.selected, this.selected)
return s[0] === this.selected
} else {
return s.name === this.selected
diff --git a/src/components/settings_modal/tabs/theme_tab/theme_tab.scss b/src/components/settings_modal/tabs/theme_tab/theme_tab.scss
index 9935c2e7..5e633120 100644
--- a/src/components/settings_modal/tabs/theme_tab/theme_tab.scss
+++ b/src/components/settings_modal/tabs/theme_tab/theme_tab.scss
@@ -1,5 +1,3 @@
-@import "src/variables";
-
.theme-tab {
padding-bottom: 2em;
@@ -162,8 +160,7 @@
.preview-container {
border-top: 1px dashed;
border-bottom: 1px dashed;
- border-color: $fallback--border;
- border-color: var(--border, $fallback--border);
+ border-color: var(--border);
margin: 1em 0;
padding: 1em;
background-color: var(--wallpaper);
@@ -227,8 +224,6 @@
min-width: 20px;
min-height: 20px;
line-height: 20px;
- border-radius: $fallback--avatarAltRadius;
- border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius);
}
.avatar {
@@ -254,8 +249,7 @@
.separator {
margin: 1em;
border-bottom: 1px solid;
- border-color: $fallback--border;
- border-color: var(--border, $fallback--border);
+ border-color: var(--border);
}
.btn {
@@ -296,7 +290,7 @@
border: 0;
box-shadow: none;
background: transparent;
- color: var(--faint, $fallback--faint);
+ color: var(--textFaint);
align-self: stretch;
}
diff --git a/src/components/shadow_control/shadow_control.js b/src/components/shadow_control/shadow_control.js
index a1d1012b..f8e12dbf 100644
--- a/src/components/shadow_control/shadow_control.js
+++ b/src/components/shadow_control/shadow_control.js
@@ -1,7 +1,7 @@
import ColorInput from '../color_input/color_input.vue'
import OpacityInput from '../opacity_input/opacity_input.vue'
import Select from '../select/select.vue'
-import { getCssShadow } from '../../services/style_setter/style_setter.js'
+import { getCssShadow } from '../../services/theme_data/theme_data.service.js'
import { hex2rgb } from '../../services/color_convert/color_convert.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
diff --git a/src/components/shadow_control/shadow_control.vue b/src/components/shadow_control/shadow_control.vue
index 1f3c26aa..c3b956cd 100644
--- a/src/components/shadow_control/shadow_control.vue
+++ b/src/components/shadow_control/shadow_control.vue
@@ -11,14 +11,14 @@
<input
v-model="selected.y"
:disabled="!present"
- class="input-number"
+ class="input input-number"
type="number"
>
<div class="wrap">
<input
v-model="selected.y"
:disabled="!present"
- class="input-range"
+ class="input input-range"
type="range"
max="20"
min="-20"
@@ -38,14 +38,14 @@
<input
v-model="selected.x"
:disabled="!present"
- class="input-number"
+ class="input input-number"
type="number"
>
<div class="wrap">
<input
v-model="selected.x"
:disabled="!present"
- class="input-range"
+ class="input input-range"
type="range"
max="20"
min="-20"
@@ -129,7 +129,7 @@
v-model="selected.inset"
:disabled="!present"
name="inset"
- class="input-inset visible-for-screenreader-only"
+ class="input -checkbox input-inset visible-for-screenreader-only"
type="checkbox"
>
<label
@@ -153,7 +153,7 @@
v-model="selected.blur"
:disabled="!present"
name="blur"
- class="input-range"
+ class="input input-range"
type="range"
max="20"
min="0"
@@ -161,7 +161,7 @@
<input
v-model="selected.blur"
:disabled="!present"
- class="input-number"
+ class="input input-number"
type="number"
min="0"
>
@@ -181,7 +181,7 @@
v-model="selected.spread"
:disabled="!present"
name="spread"
- class="input-range"
+ class="input input-range"
type="range"
max="20"
min="-20"
@@ -189,7 +189,7 @@
<input
v-model="selected.spread"
:disabled="!present"
- class="input-number"
+ class="input input-number"
type="number"
>
</div>
@@ -219,8 +219,6 @@
<script src="./shadow_control.js"></script>
<style lang="scss">
-@import "../../variables";
-
.shadow-control {
display: flex;
flex-wrap: wrap;
@@ -237,8 +235,6 @@
display: flex;
flex-wrap: wrap;
- $side: 15em;
-
input[type="number"] {
width: 5em;
min-width: 2em;
@@ -261,7 +257,7 @@
.x-shift-control .wrap,
input[type="range"] {
margin: 0;
- width: $side;
+ width: 15em;
height: 2em;
}
@@ -271,7 +267,7 @@
.wrap {
width: 2em;
- height: $side;
+ height: 15em;
}
input[type="range"] {
@@ -293,16 +289,12 @@
linear-gradient(-45deg, transparent 75%, #666 75%);
background-size: 20px 20px;
background-position: 0 0, 0 10px, 10px -10px, -10px 0;
- border-radius: $fallback--inputRadius;
- border-radius: var(--inputRadius, $fallback--inputRadius);
+ border-radius: var(--roundness);
.preview-block {
width: 33%;
height: 33%;
- background-color: $fallback--bg;
- background-color: var(--bg, $fallback--bg);
- border-radius: $fallback--panelRadius;
- border-radius: var(--panelRadius, $fallback--panelRadius);
+ border-radius: var(--roundness);
}
}
}
diff --git a/src/components/shout_panel/shout_panel.vue b/src/components/shout_panel/shout_panel.vue
index a7013469..9113211e 100644
--- a/src/components/shout_panel/shout_panel.vue
+++ b/src/components/shout_panel/shout_panel.vue
@@ -5,7 +5,7 @@
>
<div class="panel panel-default">
<div
- class="panel-heading timeline-heading"
+ class="panel-heading"
:class="{ 'shout-heading': floating }"
@click.stop.prevent="togglePanel"
>
@@ -18,7 +18,7 @@
/>
</div>
</div>
- <div class="shout-window">
+ <div class="panel-body shout-window">
<div
v-for="message in messages"
:key="message.id"
@@ -41,10 +41,10 @@
</div>
</div>
</div>
- <div class="shout-input">
+ <div class="panel-body shout-input">
<textarea
v-model="currentMessage"
- class="shout-input-textarea"
+ class="shout-input-textarea input"
rows="1"
@keyup.enter="submit(currentMessage)"
/>
@@ -75,8 +75,6 @@
<script src="./shout_panel.js"></script>
<style lang="scss">
-@import "../../variables";
-
.floating-shout {
position: fixed;
bottom: 0.5em;
@@ -97,8 +95,7 @@
cursor: pointer;
.icon {
- color: $fallback--text;
- color: var(--panelText, $fallback--text);
+ color: var(--text);
margin-right: 0.5em;
}
@@ -128,8 +125,7 @@
img {
height: 24px;
width: 24px;
- border-radius: $fallback--avatarRadius;
- border-radius: var(--avatarRadius, $fallback--avatarRadius);
+ border-radius: var(--roundness);
margin-right: 0.5em;
margin-top: 0.25em;
}
diff --git a/src/components/side_drawer/side_drawer.js b/src/components/side_drawer/side_drawer.js
index 27019577..81c5a612 100644
--- a/src/components/side_drawer/side_drawer.js
+++ b/src/components/side_drawer/side_drawer.js
@@ -115,7 +115,10 @@ const SideDrawer = {
GestureService.updateSwipe(e, this.closeGesture)
},
openSettingsModal () {
- this.$store.dispatch('openSettingsModal')
+ this.$store.dispatch('openSettingsModal', 'user')
+ },
+ openAdminModal () {
+ this.$store.dispatch('openSettingsModal', 'admin')
}
}
}
diff --git a/src/components/side_drawer/side_drawer.vue b/src/components/side_drawer/side_drawer.vue
index 994ac953..608b5608 100644
--- a/src/components/side_drawer/side_drawer.vue
+++ b/src/components/side_drawer/side_drawer.vue
@@ -1,6 +1,6 @@
<template>
<div
- class="side-drawer-container"
+ class="side-drawer-container mobile-drawer"
:class="{ 'side-drawer-container-closed': closed, 'side-drawer-container-open': !closed }"
>
<div
@@ -35,7 +35,10 @@
v-if="!currentUser"
@click="toggleDrawer"
>
- <router-link :to="{ name: 'login' }">
+ <router-link
+ :to="{ name: 'login' }"
+ class="menu-item"
+ >
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
@@ -47,7 +50,10 @@
v-if="currentUser || !privateMode"
@click="toggleDrawer"
>
- <router-link :to="timelinesRoute">
+ <router-link
+ :to="timelinesRoute"
+ class="menu-item"
+ >
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
@@ -59,7 +65,10 @@
v-if="currentUser"
@click="toggleDrawer"
>
- <router-link :to="{ name: 'lists' }">
+ <router-link
+ :to="{ name: 'lists' }"
+ class="menu-item"
+ >
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
@@ -74,6 +83,7 @@
<router-link
:to="{ name: 'chats', params: { username: currentUser.screen_name } }"
style="position: relative;"
+ class="menu-item"
>
<FAIcon
fixed-width
@@ -82,7 +92,7 @@
/> {{ $t("nav.chats") }}
<span
v-if="unreadChatCount"
- class="badge badge-notification"
+ class="badge -notification"
>
{{ unreadChatCount }}
</span>
@@ -91,7 +101,10 @@
</ul>
<ul v-if="currentUser">
<li @click="toggleDrawer">
- <router-link :to="{ name: 'interactions', params: { username: currentUser.screen_name } }">
+ <router-link
+ :to="{ name: 'interactions', params: { username: currentUser.screen_name } }"
+ class="menu-item"
+ >
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
@@ -103,7 +116,10 @@
v-if="currentUser.locked"
@click="toggleDrawer"
>
- <router-link to="/friend-requests">
+ <router-link
+ to="/friend-requests"
+ class="menu-item"
+ >
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
@@ -111,7 +127,7 @@
/> {{ $t("nav.friend_requests") }}
<span
v-if="followRequestCount > 0"
- class="badge badge-notification"
+ class="badge -notification"
>
{{ followRequestCount }}
</span>
@@ -121,7 +137,10 @@
v-if="shout"
@click="toggleDrawer"
>
- <router-link :to="{ name: 'shout-panel' }">
+ <router-link
+ :to="{ name: 'shout-panel' }"
+ class="menu-item"
+ >
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
@@ -135,7 +154,10 @@
v-if="currentUser || !privateMode"
@click="toggleDrawer"
>
- <router-link :to="{ name: 'search' }">
+ <router-link
+ :to="{ name: 'search' }"
+ class="menu-item"
+ >
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
@@ -147,7 +169,10 @@
v-if="currentUser && suggestionsEnabled"
@click="toggleDrawer"
>
- <router-link :to="{ name: 'who-to-follow' }">
+ <router-link
+ :to="{ name: 'who-to-follow' }"
+ class="menu-item"
+ >
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
@@ -157,7 +182,7 @@
</li>
<li @click="toggleDrawer">
<button
- class="button-unstyled -link -fullwidth"
+ class="menu-item"
@click="openSettingsModal"
>
<FAIcon
@@ -168,7 +193,10 @@
</button>
</li>
<li @click="toggleDrawer">
- <router-link :to="{ name: 'about'}">
+ <router-link
+ :to="{ name: 'about'}"
+ class="menu-item"
+ >
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
@@ -180,16 +208,16 @@
v-if="currentUser && currentUser.role === 'admin'"
@click="toggleDrawer"
>
- <a
- href="/pleroma/admin/#/login-pleroma"
- target="_blank"
+ <button
+ class="menu-item"
+ @click.stop="openAdminModal"
>
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
icon="tachometer-alt"
/> {{ $t("nav.administration") }}
- </a>
+ </button>
</li>
<li
v-if="currentUser && supportsAnnouncements"
@@ -197,6 +225,7 @@
>
<router-link
:to="{ name: 'announcements' }"
+ class="menu-item"
>
<FAIcon
fixed-width
@@ -205,7 +234,7 @@
/> {{ $t("nav.announcements") }}
<span
v-if="unreadAnnouncementCount"
- class="badge badge-notification"
+ class="badge -notification"
>
{{ unreadAnnouncementCount }}
</span>
@@ -215,7 +244,10 @@
v-if="currentUser"
@click="toggleDrawer"
>
- <router-link :to="{ name: 'edit-navigation' }">
+ <router-link
+ :to="{ name: 'edit-navigation' }"
+ class="menu-item"
+ >
<FAIcon
fixed-width
class="fa-scale-110 fa-old-padding"
@@ -228,7 +260,7 @@
@click="toggleDrawer"
>
<button
- class="button-unstyled -link -fullwidth"
+ class="menu-item"
@click="doLogout"
>
<FAIcon
@@ -251,8 +283,6 @@
<script src="./side_drawer.js"></script>
<style lang="scss">
-@import "../../variables";
-
.side-drawer-container {
position: fixed;
z-index: var(--ZI_navbar);
@@ -305,17 +335,8 @@
width: 80%;
max-width: 20em;
flex: 0 0 80%;
- box-shadow: 1px 1px 4px rgb(0 0 0 / 60%);
- box-shadow: var(--panelShadow);
- background-color: $fallback--bg;
- background-color: var(--popover, $fallback--bg);
- color: $fallback--link;
- color: var(--popoverText, $fallback--link);
-
- --faint: var(--popoverFaintText, $fallback--faint);
- --faintLink: var(--popoverFaintLink, $fallback--faint);
- --lightText: var(--popoverLightText, $fallback--lightText);
- --icon: var(--popoverIcon, $fallback--icon);
+ box-shadow: var(--shadow);
+ background-color: var(--background);
.badge {
margin-left: 10px;
@@ -362,8 +383,7 @@
margin: 0;
padding: 0;
border-bottom: 1px solid;
- border-color: $fallback--border;
- border-color: var(--border, $fallback--border);
+ border-color: var(--border);
}
.side-drawer ul:last-child {
@@ -380,18 +400,6 @@
height: 3em;
line-height: 3em;
padding: 0 0.7em;
-
- &:hover {
- background-color: $fallback--lightBg;
- background-color: var(--selectedMenuPopover, $fallback--lightBg);
- color: $fallback--text;
- color: var(--selectedMenuPopoverText, $fallback--text);
-
- --faint: var(--selectedMenuPopoverFaintText, $fallback--faint);
- --faintLink: var(--selectedMenuPopoverFaintLink, $fallback--faint);
- --lightText: var(--selectedMenuPopoverLightText, $fallback--lightText);
- --icon: var(--selectedMenuPopoverIcon, $fallback--icon);
- }
}
}
</style>
diff --git a/src/components/status/post.style.js b/src/components/status/post.style.js
new file mode 100644
index 00000000..8dce527e
--- /dev/null
+++ b/src/components/status/post.style.js
@@ -0,0 +1,33 @@
+export default {
+ name: 'Post',
+ selector: '.Status',
+ states: {
+ selected: '.-focused'
+ },
+ validInnerComponents: [
+ 'Text',
+ 'Link',
+ 'Icon',
+ 'Border',
+ 'Button',
+ 'ButtonUnstyled',
+ 'RichContent',
+ 'Input',
+ 'Avatar',
+ 'Attachment',
+ 'PollGraph'
+ ],
+ defaultRules: [
+ {
+ directives: {
+ background: '--bg'
+ }
+ },
+ {
+ state: ['selected'],
+ directives: {
+ background: '--inheritedBackground, 10'
+ }
+ }
+ ]
+}
diff --git a/src/components/status/status.js b/src/components/status/status.js
index 9a9bca7a..bf4e4275 100644
--- a/src/components/status/status.js
+++ b/src/components/status/status.js
@@ -39,7 +39,8 @@ import {
faThumbtack,
faChevronUp,
faChevronDown,
- faAngleDoubleRight
+ faAngleDoubleRight,
+ faPlay
} from '@fortawesome/free-solid-svg-icons'
library.add(
@@ -59,7 +60,8 @@ library.add(
faThumbtack,
faChevronUp,
faChevronDown,
- faAngleDoubleRight
+ faAngleDoubleRight,
+ faPlay
)
const camelCase = name => name.charAt(0).toUpperCase() + name.slice(1)
@@ -133,6 +135,7 @@ const Status = {
'showPinned',
'inProfile',
'profileUserId',
+ 'inQuote',
'simpleTree',
'controlledThreadDisplayStatus',
@@ -151,6 +154,7 @@ const Status = {
'controlledSetMediaPlaying',
'dive'
],
+ emits: ['interacted'],
data () {
return {
uncontrolledReplying: false,
@@ -159,7 +163,8 @@ const Status = {
uncontrolledMediaPlaying: [],
suspendable: true,
error: null,
- headTailLinks: null
+ headTailLinks: null,
+ displayQuote: !this.inQuote
}
},
computed: {
@@ -227,17 +232,14 @@ const Status = {
muteWordHits () {
return muteWordHits(this.status, this.muteWords)
},
- rtBotStatus () {
- return this.statusoid.user.bot
- },
botStatus () {
- return this.status.user.bot
+ return this.status.user.actor_type === 'Service'
},
- botIndicator () {
- return this.botStatus && !this.hideBotIndication
+ showActorTypeIndicator () {
+ return !this.hideBotIndication
},
- rtBotIndicator () {
- return this.rtBotStatus && !this.hideBotIndication
+ sensitiveStatus () {
+ return this.status.nsfw
},
mentionsLine () {
if (!this.headTailLinks) return []
@@ -266,7 +268,9 @@ const Status = {
// Wordfiltered
this.muteWordHits.length > 0 ||
// bot status
- (this.muteBotStatuses && this.botStatus && !this.compact)
+ (this.muteBotStatuses && this.botStatus && !this.compact) ||
+ // sensitive status
+ (this.muteSensitiveStatuses && this.sensitiveStatus && !this.compact)
return !this.unmuted && !this.shouldNotMute && reasonsToMute
},
userIsMuted () {
@@ -369,9 +373,15 @@ const Status = {
hidePostStats () {
return this.mergedConfig.hidePostStats
},
+ shouldDisplayFavsAndRepeats () {
+ return !this.hidePostStats && this.isFocused && (this.combinedFavsAndRepeatsUsers.length > 0 || this.statusFromGlobalRepository.quotes_count)
+ },
muteBotStatuses () {
return this.mergedConfig.muteBotStatuses
},
+ muteSensitiveStatuses () {
+ return this.mergedConfig.muteSensitiveStatuses
+ },
hideBotIndication () {
return this.mergedConfig.hideBotIndication
},
@@ -401,6 +411,44 @@ const Status = {
},
editingAvailable () {
return this.$store.state.instance.editingAvailable
+ },
+ hasVisibleQuote () {
+ return this.status.quote_url && this.status.quote_visible
+ },
+ hasInvisibleQuote () {
+ return this.status.quote_url && !this.status.quote_visible
+ },
+ quotedStatus () {
+ return this.status.quote_id ? this.$store.state.statuses.allStatusesObject[this.status.quote_id] : undefined
+ },
+ shouldDisplayQuote () {
+ return this.quotedStatus && this.displayQuote
+ },
+ scrobblePresent () {
+ if (this.mergedConfig.hideScrobbles) return false
+ if (!this.status.user.latestScrobble) return false
+ const value = this.mergedConfig.hideScrobblesAfter.match(/\d+/gs)[0]
+ const unit = this.mergedConfig.hideScrobblesAfter.match(/\D+/gs)[0]
+ let multiplier = 60 * 1000 // minutes is smallest unit
+ switch (unit) {
+ case 'm':
+ break
+ case 'h':
+ multiplier *= 60 // hour
+ break
+ case 'd':
+ multiplier *= 60 // hour
+ multiplier *= 24 // day
+ break
+ }
+ const maxAge = Number(value) * multiplier
+ const createdAt = Date.parse(this.status.user.latestScrobble.created_at)
+ const age = Date.now() - createdAt
+ if (age > maxAge) return false
+ return this.status.user.latestScrobble.artist
+ },
+ scrobble () {
+ return this.status.user.latestScrobble
}
},
methods: {
@@ -420,9 +468,11 @@ const Status = {
this.error = error
},
clearError () {
+ this.$emit('interacted')
this.error = undefined
},
toggleReplying () {
+ this.$emit('interacted')
controlledOrUncontrolledToggle(this, 'replying')
},
gotoOriginal (id) {
@@ -469,6 +519,18 @@ const Status = {
window.scrollBy(0, rect.bottom - window.innerHeight + 50)
}
}
+ },
+ toggleDisplayQuote () {
+ if (this.shouldDisplayQuote) {
+ this.displayQuote = false
+ } else if (!this.quotedStatus) {
+ this.$store.dispatch('fetchStatus', this.status.quote_id)
+ .then(() => {
+ this.displayQuote = true
+ })
+ } else {
+ this.displayQuote = true
+ }
}
},
watch: {
diff --git a/src/components/status/status.scss b/src/components/status/status.scss
index 44812867..63809ff2 100644
--- a/src/components/status/status.scss
+++ b/src/components/status/status.scss
@@ -1,5 +1,3 @@
-@import "../../variables";
-
.Status {
min-width: 0;
white-space: normal;
@@ -12,24 +10,8 @@
--_still-image-label-visibility: hidden;
}
- &.-focused {
- background-color: $fallback--lightBg;
- background-color: var(--selectedPost, $fallback--lightBg);
- color: $fallback--text;
- color: var(--selectedPostText, $fallback--text);
-
- --lightText: var(--selectedPostLightText, $fallback--light);
- --faint: var(--selectedPostFaintText, $fallback--faint);
- --faintLink: var(--selectedPostFaintLink, $fallback--faint);
- --postLink: var(--selectedPostPostLink, $fallback--faint);
- --postFaintLink: var(--selectedPostFaintPostLink, $fallback--faint);
- --icon: var(--selectedPostIcon, $fallback--icon);
- }
-
.gravestone {
- padding: var(--status-margin, $status-margin);
- color: $fallback--faint;
- color: var(--faint, $fallback--faint);
+ padding: var(--status-margin);
display: flex;
.deleted-text {
@@ -40,7 +22,7 @@
.status-container {
display: flex;
- padding: var(--status-margin, $status-margin);
+ padding: var(--status-margin);
> * {
min-width: 0;
@@ -52,7 +34,7 @@
}
.pin {
- padding: var(--status-margin, $status-margin) var(--status-margin, $status-margin) 0;
+ padding: var(--status-margin) var(--status-margin) 0;
display: flex;
align-items: center;
justify-content: flex-end;
@@ -68,7 +50,7 @@
}
.left-side {
- margin-right: var(--status-margin, $status-margin);
+ margin-right: var(--status-margin);
}
.right-side {
@@ -77,7 +59,7 @@
}
.usercard {
- margin-bottom: var(--status-margin, $status-margin);
+ margin-bottom: var(--status-margin);
}
.status-username {
@@ -135,11 +117,6 @@
.button-unstyled {
padding: 5px;
margin: -5px;
-
- &:hover svg {
- color: $fallback--lightText;
- color: var(--lightText, $fallback--lightText);
- }
}
.svg-inline--fa {
@@ -243,16 +220,15 @@
}
.repeat-info {
- padding: 0.4em var(--status-margin, $status-margin);
+ padding: 0.4em var(--status-margin);
.repeat-icon {
- color: $fallback--cGreen;
- color: var(--cGreen, $fallback--cGreen);
+ color: var(--cGreen);
}
}
.repeater-avatar {
- border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius);
+ border-radius: var(--roundness);
margin-left: 28px;
width: 20px;
height: 20px;
@@ -289,7 +265,7 @@
position: relative;
width: 100%;
display: flex;
- margin-top: var(--status-margin, $status-margin);
+ margin-top: var(--status-margin);
> * {
max-width: 4em;
@@ -357,7 +333,7 @@
}
.favs-repeated-users {
- margin-top: var(--status-margin, $status-margin);
+ margin-top: var(--status-margin);
}
.stats {
@@ -368,10 +344,10 @@
.avatar-row {
flex: 1;
- overflow: hidden;
position: relative;
display: flex;
align-items: center;
+ overflow: hidden;
&::before {
content: "";
@@ -379,16 +355,16 @@
height: 100%;
width: 1px;
left: 0;
- background-color: var(--faint, $fallback--faint);
+ background-color: var(--textFaint);
}
}
.stat-count {
- margin-right: var(--status-margin, $status-margin);
+ margin-right: var(--status-margin);
user-select: none;
.stat-title {
- color: var(--faint, $fallback--faint);
+ color: var(--textFaint);
font-size: 0.85em;
text-transform: uppercase;
position: relative;
@@ -398,6 +374,7 @@
font-weight: bolder;
font-size: 1.1em;
line-height: 1em;
+ color: var(--text);
}
&:hover .stat-title {
@@ -422,4 +399,22 @@
}
}
}
+
+ .quoted-status {
+ margin-top: 0.5em;
+ border: 1px solid var(--border);
+ border-radius: var(--roundness);
+
+ &.-unavailable-prompt {
+ padding: 0.5em;
+ }
+ }
+
+ .display-quoted-status-button {
+ margin: 0.5em;
+
+ &-icon {
+ color: inherit;
+ }
+ }
}
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index 35b15362..61a58cda 100644
--- a/src/components/status/status.vue
+++ b/src/components/status/status.vue
@@ -31,6 +31,12 @@
/>
</small>
<small
+ v-if="muteSensitiveStatuses && status.nsfw"
+ class="mute-thread"
+ >
+ {{ $t('status.sensitive_muted') }}
+ </small>
+ <small
v-if="showReasonMutedThread"
class="mute-thread"
>
@@ -79,7 +85,7 @@
<UserAvatar
v-if="retweet"
class="left-side repeater-avatar"
- :bot="rtBotIndicator"
+ :show-actor-type-indicator="showActorTypeIndicator"
:better-shadow="betterShadow"
:user="statusoid.user"
/>
@@ -133,7 +139,7 @@
>
<UserAvatar
class="post-avatar"
- :bot="botIndicator"
+ :show-actor-type-indicator="showActorTypeIndicator"
:compact="compact"
:better-shadow="betterShadow"
:user="status.user"
@@ -180,7 +186,7 @@
<span class="heading-right">
<router-link
- class="timeago faint-link"
+ class="timeago faint"
:to="{ name: 'conversation', params: { id: status.id } }"
>
<Timeago
@@ -250,6 +256,47 @@
</span>
</div>
<div
+ v-if="scrobblePresent"
+ class="status-rich-presence"
+ >
+ <a
+ v-if="scrobble.externalLink"
+ :href="scrobble.externalLink"
+ target="_blank"
+ >
+ {{ scrobble.artist }} — {{ scrobble.title }}
+ <FAIcon
+ class="fa-scale-110 fa-old-padding"
+ icon="play"
+ />
+ <span class="status-rich-presence-time">
+ <Timeago
+ template-key="time.in_past"
+ :time="scrobble.created_at"
+ :auto-update="60"
+ />
+ </span>
+ </a>
+ <span v-if="!scrobble.externalLink">
+ <FAIcon
+ class="fa-scale-110 fa-old-padding"
+ icon="music"
+ />
+ {{ scrobble.artist }} — {{ scrobble.title }}
+ <FAIcon
+ class="fa-scale-110 fa-old-padding"
+ icon="play"
+ />
+ <span class="status-rich-presence-time">
+ <Timeago
+ template-key="time.in_past"
+ :time="scrobble.created_at"
+ :auto-update="60"
+ />
+ </span>
+ </span>
+ </div>
+ <div
v-if="isReply || hasMentionsLine"
class="heading-reply-row"
>
@@ -364,13 +411,52 @@
@parseReady="setHeadTailLinks"
/>
+ <article
+ v-if="hasVisibleQuote"
+ class="quoted-status"
+ >
+ <button
+ class="button-unstyled -link display-quoted-status-button"
+ :aria-expanded="shouldDisplayQuote"
+ @click="toggleDisplayQuote"
+ >
+ {{ shouldDisplayQuote ? $t('status.hide_quote') : $t('status.display_quote') }}
+ <FAIcon
+ class="display-quoted-status-button-icon"
+ :icon="shouldDisplayQuote ? 'chevron-up' : 'chevron-down'"
+ />
+ </button>
+ <Status
+ v-if="shouldDisplayQuote"
+ :statusoid="quotedStatus"
+ :in-quote="true"
+ />
+ </article>
+ <p
+ v-else-if="hasInvisibleQuote"
+ class="quoted-status -unavailable-prompt"
+ >
+ <i18n-t keypath="status.invisible_quote">
+ <template #link>
+ <bdi>
+ <a
+ :href="status.quote_url"
+ target="_blank"
+ >
+ {{ status.quote_url }}
+ </a>
+ </bdi>
+ </template>
+ </i18n-t>
+ </p>
+
<div
v-if="inConversation && !isPreview && replies && replies.length"
class="replies"
>
<button
v-if="showOtherRepliesAsButton && replies.length > 1"
- class="button-unstyled -link faint"
+ class="button-unstyled -link"
:title="$tc('status.ancestor_follow', replies.length - 1, { numReplies: replies.length - 1 })"
@click.prevent="dive"
>
@@ -398,7 +484,7 @@
<transition name="fade">
<div
- v-if="!hidePostStats && isFocused && combinedFavsAndRepeatsUsers.length > 0"
+ v-if="shouldDisplayFavsAndRepeats"
class="favs-repeated-users"
>
<div class="stats">
@@ -426,6 +512,19 @@
</div>
</div>
</UserListPopover>
+ <router-link
+ v-if="statusFromGlobalRepository.quotes_count > 0"
+ :to="{ name: 'quotes', params: { id: status.id } }"
+ >
+ <div
+ class="stat-count"
+ >
+ <a class="stat-title">{{ $t('status.quotes') }}</a>
+ <div class="stat-number">
+ {{ statusFromGlobalRepository.quotes_count }}
+ </div>
+ </div>
+ </router-link>
<div class="avatar-row">
<AvatarList :users="combinedFavsAndRepeatsUsers" />
</div>
@@ -451,14 +550,17 @@
:visibility="status.visibility"
:logged-in="loggedIn"
:status="status"
+ @click="$emit('interacted')"
/>
<favorite-button
:logged-in="loggedIn"
:status="status"
+ @click="$emit('interacted')"
/>
<ReactButton
v-if="loggedIn"
:status="status"
+ @click="$emit('interacted')"
/>
<extra-buttons
:status="status"
@@ -476,7 +578,7 @@
<UserAvatar
class="post-avatar"
:compact="compact"
- :bot="botIndicator"
+ :show-actor-type-indicator="showActorTypeIndicator"
/>
</div>
<div class="right-side">
diff --git a/src/components/status_body/status_body.scss b/src/components/status_body/status_body.scss
index 8fd60afc..0a467b4f 100644
--- a/src/components/status_body/status_body.scss
+++ b/src/components/status_body/status_body.scss
@@ -1,5 +1,3 @@
-@import "../../variables";
-
.StatusBody {
display: flex;
flex-direction: column;
@@ -14,7 +12,6 @@
& .text,
& .summary {
- font-family: var(--postFont, sans-serif);
white-space: pre-wrap;
overflow-wrap: break-word;
word-wrap: break-word;
@@ -41,7 +38,7 @@
margin-bottom: 0.5em;
border-style: solid;
border-width: 0 0 1px;
- border-color: var(--border, $fallback--border);
+ border-color: var(--border);
flex-grow: 0;
&.-tall {
@@ -112,15 +109,6 @@
}
}
- .greentext {
- color: $fallback--cGreen;
- color: var(--postGreentext, $fallback--cGreen);
- }
-
- .cyantext {
- color: var(--postCyantext, $fallback--cBlue);
- }
-
&.-compact {
align-items: top;
flex-direction: row;
diff --git a/src/components/status_body/status_body.vue b/src/components/status_body/status_body.vue
index fb356360..f21bcc80 100644
--- a/src/components/status_body/status_body.vue
+++ b/src/components/status_body/status_body.vue
@@ -11,6 +11,7 @@
>
<RichContent
class="media-body summary"
+ :faint="compact"
:html="status.summary_raw_html"
:emoji="status.emojis"
/>
@@ -48,6 +49,7 @@
:html="status.raw_html"
:emoji="status.emojis"
:handle-links="true"
+ :faint="compact"
:greentext="mergedConfig.greentext"
:attentions="status.attentions"
@parseReady="onParseReady"
diff --git a/src/components/status_content/status_content.js b/src/components/status_content/status_content.js
index 89f0aa51..8d8a91dc 100644
--- a/src/components/status_content/status_content.js
+++ b/src/components/status_content/status_content.js
@@ -73,6 +73,10 @@ const StatusContent = {
},
computed: {
...controlledOrUncontrolledGetters(['showingTall', 'expandingSubject', 'showingLongSubject']),
+ statusCard () {
+ if (!this.status.card) return null
+ return this.status.card.url === this.status.quote_url ? null : this.status.card
+ },
hideAttachments () {
return (this.mergedConfig.hideAttachments && !this.inConversation) ||
(this.mergedConfig.hideAttachmentsInConv && this.inConversation)
diff --git a/src/components/status_content/status_content.vue b/src/components/status_content/status_content.vue
index c0e5c0b9..e977d489 100644
--- a/src/components/status_content/status_content.vue
+++ b/src/components/status_content/status_content.vue
@@ -43,7 +43,7 @@
/>
<div
- v-if="status.card && !noHeading && !compact"
+ v-if="statusCard && !noHeading && !compact"
class="link-preview media-body"
>
<link-preview
diff --git a/src/components/status_popover/status_popover.vue b/src/components/status_popover/status_popover.vue
index 311ca099..839f82e9 100644
--- a/src/components/status_popover/status_popover.vue
+++ b/src/components/status_popover/status_popover.vue
@@ -40,19 +40,14 @@
<script src="./status_popover.js"></script>
<style lang="scss">
-@import "../../variables";
-
/* popover styles load on-demand, so we need to override */
.status-popover.popover {
font-size: 1rem;
min-width: 15em;
max-width: 95%;
- border-color: $fallback--border;
- border-color: var(--border, $fallback--border);
+ border-color: var(--border);
border-style: solid;
border-width: 1px;
- border-radius: $fallback--tooltipRadius;
- border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
/* TODO cleanup this */
.Status.Status {
diff --git a/src/components/sticker_picker/sticker_picker.vue b/src/components/sticker_picker/sticker_picker.vue
index 904853c0..6678132e 100644
--- a/src/components/sticker_picker/sticker_picker.vue
+++ b/src/components/sticker_picker/sticker_picker.vue
@@ -32,8 +32,6 @@
<script src="./sticker_picker.js"></script>
<style lang="scss">
-@import "../../variables";
-
.sticker-picker {
width: 100%;
@@ -56,7 +54,7 @@
height: 100%;
&:hover {
- filter: drop-shadow(0 0 5px var(--accent, $fallback--link));
+ filter: drop-shadow(0 0 5px var(--accent));
}
}
}
diff --git a/src/components/still-image/still-image.vue b/src/components/still-image/still-image.vue
index fc46fbe6..57633827 100644
--- a/src/components/still-image/still-image.vue
+++ b/src/components/still-image/still-image.vue
@@ -28,8 +28,6 @@
<script src="./still-image.js"></script>
<style lang="scss">
-@import "../../variables";
-
.still-image {
position: relative;
line-height: 0;
@@ -68,8 +66,7 @@
color: #fff;
display: block;
padding: 2px 4px;
- border-radius: $fallback--tooltipRadius;
- border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
+ border-radius: var(--roundness);
z-index: 2;
visibility: var(--_still-image-label-visibility, visible);
}
diff --git a/src/components/tab_switcher/tab.style.js b/src/components/tab_switcher/tab.style.js
new file mode 100644
index 00000000..eac8aaeb
--- /dev/null
+++ b/src/components/tab_switcher/tab.style.js
@@ -0,0 +1,78 @@
+export default {
+ name: 'Tab', // Name of the component
+ selector: '.tab', // CSS selector/prefix
+ states: {
+ active: '.active',
+ hover: ':hover:not(.disabled)',
+ disabled: '.disabled'
+ },
+ validInnerComponents: [
+ 'Text',
+ 'Icon'
+ ],
+ defaultRules: [
+ {
+ directives: {
+ background: '--fg',
+ shadow: ['--defaultButtonShadow', '--defaultButtonBevel'],
+ roundness: 3
+ }
+ },
+ {
+ state: ['hover'],
+ directives: {
+ shadow: ['--defaultButtonHoverGlow', '--defaultButtonBevel']
+ }
+ },
+ {
+ state: ['active'],
+ directives: {
+ opacity: 0
+ }
+ },
+ {
+ state: ['hover', 'active'],
+ directives: {
+ shadow: ['--defaultButtonShadow', '--defaultButtonBevel']
+ }
+ },
+ {
+ state: ['disabled'],
+ directives: {
+ background: '$blend(--inheritedBackground, 0.25, --parent)',
+ shadow: ['--defaultButtonBevel']
+ }
+ },
+ {
+ component: 'Text',
+ parent: {
+ component: 'Tab',
+ state: ['disabled']
+ },
+ directives: {
+ textOpacity: 0.25,
+ textOpacityMode: 'blend'
+ }
+ },
+ {
+ component: 'Icon',
+ parent: {
+ component: 'Tab',
+ state: ['active']
+ },
+ directives: {
+ textColor: '--text'
+ }
+ },
+ {
+ component: 'Icon',
+ parent: {
+ component: 'Tab',
+ state: ['active', 'hover']
+ },
+ directives: {
+ textColor: '--text'
+ }
+ }
+ ]
+}
diff --git a/src/components/tab_switcher/tab_switcher.jsx b/src/components/tab_switcher/tab_switcher.jsx
index a7ef8560..027a380a 100644
--- a/src/components/tab_switcher/tab_switcher.jsx
+++ b/src/components/tab_switcher/tab_switcher.jsx
@@ -60,13 +60,7 @@ export default {
const isWanted = slot => slot.props && slot.props['data-tab-name'] === tabName
return this.$slots.default().findIndex(isWanted) === this.activeIndex
}
- },
- settingsModalVisible () {
- return this.settingsModalState === 'visible'
- },
- ...mapState({
- settingsModalState: state => state.interface.settingsModalState
- })
+ }
},
beforeUpdate () {
const currentSlot = this.slots()[this.active]
@@ -103,7 +97,7 @@ export default {
.map((slot, index) => {
const props = slot.props
if (!props) return
- const classesTab = ['tab', 'button-default']
+ const classesTab = ['tab']
const classesWrapper = ['tab-wrapper']
if (this.activeIndex === index) {
classesTab.push('active')
diff --git a/src/components/tab_switcher/tab_switcher.scss b/src/components/tab_switcher/tab_switcher.scss
index 705925c8..4e242b91 100644
--- a/src/components/tab_switcher/tab_switcher.scss
+++ b/src/components/tab_switcher/tab_switcher.scss
@@ -1,5 +1,3 @@
-@import "../../variables";
-
/* stylelint-disable no-descending-specificity */
.tab-switcher {
display: flex;
@@ -25,8 +23,7 @@
content: "";
flex: 1 1 auto;
border-bottom: 1px solid;
- border-bottom-color: $fallback--border;
- border-bottom-color: var(--border, $fallback--border);
+ border-bottom-color: var(--border);
}
.tab-wrapper {
@@ -37,8 +34,7 @@
right: 0;
bottom: 0;
border-bottom: 1px solid;
- border-bottom-color: $fallback--border;
- border-bottom-color: var(--border, $fallback--border);
+ border-bottom-color: var(--border);
}
}
@@ -80,8 +76,7 @@
flex-basis: 0.5em;
content: "";
border-right: 1px solid;
- border-right-color: $fallback--border;
- border-right-color: var(--border, $fallback--border);
+ border-right-color: var(--border);
}
&::after {
@@ -106,16 +101,14 @@
right: 0;
bottom: 0;
border-right: 1px solid;
- border-right-color: $fallback--border;
- border-right-color: var(--border, $fallback--border);
+ border-right-color: var(--border);
}
&::before {
flex: 0 0 6px;
content: "";
border-right: 1px solid;
- border-right-color: $fallback--border;
- border-right-color: var(--border, $fallback--border);
+ border-right-color: var(--border);
}
&:last-child .tab {
@@ -173,6 +166,15 @@
}
.tab {
+ user-select: none;
+ color: var(--text);
+ border: none;
+ cursor: pointer;
+ box-shadow: var(--shadow);
+ font-size: 1em;
+ font-family: var(--font);
+ border-radius: var(--roundness);
+ background-color: var(--background);
position: relative;
white-space: nowrap;
padding: 6px 1em;
@@ -188,8 +190,6 @@
&.active {
background: transparent;
z-index: 5;
- color: $fallback--text;
- color: var(--tabActiveText, $fallback--text);
}
img {
@@ -231,7 +231,7 @@
margin-top: 0.5em;
margin-left: 0.2em;
margin-bottom: 0.25em;
- border-bottom: 1px solid var(--border, $fallback--border);
+ border-bottom: 1px solid var(--border);
@media all and (min-width: 800px) {
display: none;
diff --git a/src/components/text.style.js b/src/components/text.style.js
new file mode 100644
index 00000000..a254ceb4
--- /dev/null
+++ b/src/components/text.style.js
@@ -0,0 +1,22 @@
+export default {
+ name: 'Text',
+ selector: '/*text*/',
+ virtual: true,
+ states: {
+ faint: '.faint'
+ },
+ defaultRules: [
+ {
+ directives: {
+ textColor: '--text',
+ textAuto: 'no-preserve'
+ }
+ },
+ {
+ state: ['faint'],
+ directives: {
+ textOpacity: 0.5
+ }
+ }
+ ]
+}
diff --git a/src/components/thread_tree/thread_tree.vue b/src/components/thread_tree/thread_tree.vue
index 04727278..971b7001 100644
--- a/src/components/thread_tree/thread_tree.vue
+++ b/src/components/thread_tree/thread_tree.vue
@@ -119,15 +119,13 @@
<script src="./thread_tree.js"></script>
<style lang="scss">
-@import "../../variables";
-
.thread-tree-replies {
- margin-left: var(--status-margin, $status-margin);
- border-left: 2px solid var(--border, $fallback--border);
+ margin-left: var(--status-margin);
+ border-left: 2px solid var(--border);
}
.thread-tree-replies-hidden {
- padding: var(--status-margin, $status-margin);
+ padding: var(--status-margin);
/* Make the button stretch along the whole row */
display: flex;
diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js
index b7414610..59170f49 100644
--- a/src/components/timeline/timeline.js
+++ b/src/components/timeline/timeline.js
@@ -25,6 +25,7 @@ const Timeline = {
'title',
'userId',
'listId',
+ 'statusId',
'tag',
'embedded',
'count',
@@ -77,13 +78,13 @@ const Timeline = {
}
},
classes () {
- let rootClasses = !this.embedded ? ['panel', 'panel-default'] : ['-nonpanel']
+ let rootClasses = !this.embedded ? ['panel', 'panel-default'] : ['-embedded']
if (this.blockingClicks) rootClasses = rootClasses.concat(['-blocked', '_misclick-prevention'])
return {
root: rootClasses,
- header: ['timeline-heading'].concat(!this.embedded ? ['panel-heading', '-sticky'] : []),
- body: ['timeline-body'].concat(!this.embedded ? ['panel-body'] : []),
- footer: ['timeline-footer'].concat(!this.embedded ? ['panel-footer'] : [])
+ header: ['timeline-heading'].concat(!this.embedded ? ['panel-heading', '-sticky'] : ['panel-body']),
+ body: ['timeline-body'].concat(!this.embedded ? ['panel-body'] : ['panel-body']),
+ footer: ['timeline-footer'].concat(!this.embedded ? ['panel-footer'] : ['panel-body'])
}
},
// id map of statuses which need to be hidden in the main list due to pinning logic
@@ -121,6 +122,7 @@ const Timeline = {
showImmediately,
userId: this.userId,
listId: this.listId,
+ statusId: this.statusId,
tag: this.tag
})
},
@@ -160,6 +162,9 @@ const Timeline = {
if (this.timeline.flushMarker !== 0) {
this.$store.commit('clearTimeline', { timeline: this.timelineName, excludeUserId: true })
this.$store.commit('queueFlush', { timeline: this.timelineName, id: 0 })
+ if (this.timelineName === 'user') {
+ this.$store.dispatch('fetchPinnedStatuses', this.userId)
+ }
this.fetchOlderStatuses()
} else {
this.blockClicksTemporarily()
@@ -180,6 +185,7 @@ const Timeline = {
showImmediately: true,
userId: this.userId,
listId: this.listId,
+ statusId: this.statusId,
tag: this.tag
}).then(({ statuses }) => {
if (statuses && statuses.length === 0) {
diff --git a/src/components/timeline/timeline.scss b/src/components/timeline/timeline.scss
index 4371947d..0fc0d979 100644
--- a/src/components/timeline/timeline.scss
+++ b/src/components/timeline/timeline.scss
@@ -1,31 +1,20 @@
-@import "../../variables";
-
.Timeline {
- .alert-dot {
- border-radius: 100%;
- height: 8px;
- width: 8px;
- position: absolute;
- left: calc(50% - 4px);
- top: calc(50% - 4px);
- margin-left: 6px;
- margin-top: -6px;
- background-color: var(--badgeNeutral);
+ .timeline-body {
+ background: none;
+ backdrop-filter: none;
}
.alert-badge {
font-size: 0.75em;
line-height: 1;
text-align: right;
- border-radius: var(--tooltipRadius);
+ border-radius: var(--roundness);
position: absolute;
left: calc(50% - 0.5em);
top: calc(50% - 0.4em);
padding: 0.2em;
margin-left: 0.7em;
margin-top: -1em;
- background-color: var(--badgeNeutral);
- color: var(--badgeNeutralText);
}
.loadmore-button {
@@ -41,12 +30,17 @@
z-index: 2;
}
- &.-nonpanel {
+ &.-embedded {
.timeline-heading {
text-align: center;
line-height: 2.75em;
padding: 0 0.5em;
+ // Override the shrug empty filler
+ &:empty::before {
+ content: initial;
+ }
+
.button-default,
.alert {
line-height: 2em;
diff --git a/src/components/timeline/timeline.vue b/src/components/timeline/timeline.vue
index 2279f21a..862a1972 100644
--- a/src/components/timeline/timeline.vue
+++ b/src/components/timeline/timeline.vue
@@ -38,7 +38,7 @@
fixed-width
icon="circle-plus"
/>
- <div class="alert-badge">
+ <div class="badge -counter">
{{ mobileLoadButtonString }}
</div>
</button>
diff --git a/src/components/timeline_menu/timeline_menu.js b/src/components/timeline_menu/timeline_menu.js
index 5a2a86c2..c4586b32 100644
--- a/src/components/timeline_menu/timeline_menu.js
+++ b/src/components/timeline_menu/timeline_menu.js
@@ -19,7 +19,8 @@ export const timelineNames = () => {
bookmarks: 'nav.bookmarks',
dms: 'nav.dms',
'public-timeline': 'nav.public_tl',
- 'public-external-timeline': 'nav.twkn'
+ 'public-external-timeline': 'nav.twkn',
+ quotes: 'nav.quotes'
}
}
diff --git a/src/components/timeline_menu/timeline_menu.vue b/src/components/timeline_menu/timeline_menu.vue
index 5f1da1f7..d755b9dd 100644
--- a/src/components/timeline_menu/timeline_menu.vue
+++ b/src/components/timeline_menu/timeline_menu.vue
@@ -45,8 +45,6 @@
<script src="./timeline_menu.js"></script>
<style lang="scss">
-@import "../../variables";
-
.timeline-menu-popover {
min-width: 24rem;
max-width: 100vw;
@@ -60,65 +58,6 @@
margin: 0;
padding: 0;
}
-
- a {
- display: block;
- padding: 0 0.65em;
- height: 3.5em;
- line-height: 3.5em;
-
- &:hover {
- background-color: $fallback--lightBg;
- background-color: var(--selectedMenu, $fallback--lightBg);
- color: $fallback--link;
- color: var(--selectedMenuText, $fallback--link);
-
- --faint: var(--selectedMenuFaintText, $fallback--faint);
- --faintLink: var(--selectedMenuFaintLink, $fallback--faint);
- --lightText: var(--selectedMenuLightText, $fallback--lightText);
- --icon: var(--selectedMenuIcon, $fallback--icon);
- }
-
- &.router-link-active {
- font-weight: bolder;
- background-color: $fallback--lightBg;
- background-color: var(--selectedMenu, $fallback--lightBg);
- color: $fallback--text;
- color: var(--selectedMenuText, $fallback--text);
-
- --faint: var(--selectedMenuFaintText, $fallback--faint);
- --faintLink: var(--selectedMenuFaintLink, $fallback--faint);
- --lightText: var(--selectedMenuLightText, $fallback--lightText);
- --icon: var(--selectedMenuIcon, $fallback--icon);
-
- &:hover {
- text-decoration: underline;
- }
- }
-
- svg {
- margin-right: 0.4em;
- margin-left: -0.2em;
- }
- }
-
- li {
- border-bottom: 1px solid;
- border-color: $fallback--border;
- border-color: var(--border, $fallback--border);
- padding: 0;
-
- &:last-child a {
- border-bottom-right-radius: $fallback--panelRadius;
- border-bottom-right-radius: var(--panelRadius, $fallback--panelRadius);
- border-bottom-left-radius: $fallback--panelRadius;
- border-bottom-left-radius: var(--panelRadius, $fallback--panelRadius);
- }
-
- &:last-child {
- border: none;
- }
- }
}
.TimelineMenu {
@@ -159,8 +98,6 @@
}
&.open .timeline-menu-title svg {
- color: $fallback--text;
- color: var(--panelText, $fallback--text);
transform: rotate(180deg);
}
diff --git a/src/components/top_bar.style.js b/src/components/top_bar.style.js
new file mode 100644
index 00000000..46b3fb56
--- /dev/null
+++ b/src/components/top_bar.style.js
@@ -0,0 +1,28 @@
+export default {
+ name: 'TopBar',
+ selector: 'nav',
+ validInnerComponents: [
+ 'Link',
+ 'Text',
+ 'Icon',
+ 'Button',
+ 'ButtonUnstyled',
+ 'Input',
+ 'Badge'
+ ],
+ defaultRules: [
+ {
+ directives: {
+ background: '--fg',
+ shadow: [{
+ x: 0,
+ y: 0,
+ blur: 4,
+ spread: 0,
+ color: '#000000',
+ alpha: 0.6
+ }]
+ }
+ }
+ ]
+}
diff --git a/src/components/underlay.style.js b/src/components/underlay.style.js
new file mode 100644
index 00000000..3e0e1bf1
--- /dev/null
+++ b/src/components/underlay.style.js
@@ -0,0 +1,19 @@
+export default {
+ name: 'Underlay',
+ selector: '#content',
+ // Out of tree selector: Most components are laid over underlay, but underlay itself is not part of the DOM tree,
+ // i.e. it's a separate absolutely-positioned component, so we need to treat it differently depending on whether
+ // we are searching for underlay specifically or for whatever is laid on top of it.
+ outOfTreeSelector: '.underlay',
+ validInnerComponents: [
+ 'Panel'
+ ],
+ defaultRules: [
+ {
+ directives: {
+ background: '#000000',
+ opacity: 0.2
+ }
+ }
+ ]
+}
diff --git a/src/components/update_notification/update_notification.scss b/src/components/update_notification/update_notification.scss
index 4337acc4..fb2edaa4 100644
--- a/src/components/update_notification/update_notification.scss
+++ b/src/components/update_notification/update_notification.scss
@@ -1,5 +1,3 @@
-@import "src/variables";
-
.UpdateNotification {
overflow: hidden;
}
@@ -48,7 +46,7 @@
.panel-body {
border-width: 0 0 1px;
border-style: solid;
- border-color: var(--border, $fallback--border);
+ border-color: var(--border);
}
.panel-footer {
diff --git a/src/components/user_avatar/avatar.style.js b/src/components/user_avatar/avatar.style.js
new file mode 100644
index 00000000..812d45a4
--- /dev/null
+++ b/src/components/user_avatar/avatar.style.js
@@ -0,0 +1,22 @@
+export default {
+ name: 'Avatar',
+ selector: '.Avatar',
+ variants: {
+ compact: '.-compact'
+ },
+ defaultRules: [
+ {
+ directives: {
+ roundness: 3,
+ shadow: [{
+ x: 0,
+ y: 1,
+ blur: 8,
+ spread: 0,
+ color: '#000000',
+ alpha: 0.7
+ }]
+ }
+ }
+ ]
+}
diff --git a/src/components/user_avatar/user_avatar.js b/src/components/user_avatar/user_avatar.js
index 33d9a258..ffd81f87 100644
--- a/src/components/user_avatar/user_avatar.js
+++ b/src/components/user_avatar/user_avatar.js
@@ -3,11 +3,13 @@ import StillImage from '../still-image/still-image.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
- faRobot
+ faRobot,
+ faPeopleGroup
} from '@fortawesome/free-solid-svg-icons'
library.add(
- faRobot
+ faRobot,
+ faPeopleGroup
)
const UserAvatar = {
@@ -15,7 +17,7 @@ const UserAvatar = {
'user',
'betterShadow',
'compact',
- 'bot'
+ 'showActorTypeIndicator'
],
data () {
return {
diff --git a/src/components/user_avatar/user_avatar.vue b/src/components/user_avatar/user_avatar.vue
index 91c17611..83608c50 100644
--- a/src/components/user_avatar/user_avatar.vue
+++ b/src/components/user_avatar/user_avatar.vue
@@ -18,21 +18,24 @@
:class="{ '-compact': compact }"
/>
<FAIcon
- v-if="bot"
+ v-if="showActorTypeIndicator && user?.actor_type === 'Service'"
icon="robot"
- class="bot-indicator"
+ class="actor-type-indicator"
+ />
+ <FAIcon
+ v-if="showActorTypeIndicator && user?.actor_type === 'Group'"
+ icon="people-group"
+ class="actor-type-indicator"
/>
</span>
</template>
<script src="./user_avatar.js"></script>
<style lang="scss">
-@import "../../variables";
-
.Avatar {
- --_avatarShadowBox: var(--avatarStatusShadow);
- --_avatarShadowFilter: var(--avatarStatusShadowFilter);
- --_avatarShadowInset: var(--avatarStatusShadowInset);
+ --_avatarShadowBox: var(--shadow);
+ --_avatarShadowFilter: var(--shadowFilter);
+ --_avatarShadowInset: var(--shadowInset);
--_still-image-label-visibility: hidden;
display: inline-block;
@@ -43,16 +46,14 @@
&.-compact {
width: 32px;
height: 32px;
- border-radius: $fallback--avatarAltRadius;
- border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius);
+ border-radius: var(--roundness);
}
.avatar {
width: 100%;
height: 100%;
box-shadow: var(--_avatarShadowBox);
- border-radius: $fallback--avatarRadius;
- border-radius: var(--avatarRadius, $fallback--avatarRadius);
+ border-radius: var(--roundness);
&.-better-shadow {
box-shadow: var(--_avatarShadowInset);
@@ -64,13 +65,11 @@
}
&.-compact {
- border-radius: $fallback--avatarAltRadius;
- border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius);
+ border-radius: var(--roundness);
}
&.-placeholder {
- background-color: $fallback--fg;
- background-color: var(--fg, $fallback--fg);
+ background-color: var(--background);
}
}
@@ -79,7 +78,7 @@
height: 100%;
}
- .bot-indicator {
+ .actor-type-indicator {
position: absolute;
bottom: 0;
right: 0;
@@ -87,7 +86,7 @@
padding: 0.2em;
background: rgb(127 127 127 / 50%);
color: #fff;
- border-radius: var(--tooltipRadius);
+ border-radius: var(--roundness);
}
}
</style>
diff --git a/src/components/user_card/user_card.js b/src/components/user_card/user_card.js
index e17bf8eb..b1fe2e8f 100644
--- a/src/components/user_card/user_card.js
+++ b/src/components/user_card/user_card.js
@@ -225,7 +225,7 @@ export default {
this.$store.dispatch('setCurrentMedia', attachment)
},
mentionUser () {
- this.$store.dispatch('openPostStatusModal', { replyTo: true, repliedUser: this.user })
+ this.$store.dispatch('openPostStatusModal', { profileMention: true, repliedUser: this.user })
},
onAvatarClickHandler (e) {
if (this.onAvatarClick) {
diff --git a/src/components/user_card/user_card.scss b/src/components/user_card/user_card.scss
index 4ab93a8a..70a76d54 100644
--- a/src/components/user_card/user_card.scss
+++ b/src/components/user_card/user_card.scss
@@ -1,5 +1,3 @@
-@import "../../variables";
-
.user-card {
position: relative;
z-index: 1;
@@ -21,14 +19,6 @@
position: relative;
}
- .panel-body {
- word-wrap: break-word;
- border-bottom-right-radius: inherit;
- border-bottom-left-radius: inherit;
- // create new stacking context
- position: relative;
- }
-
.background-image {
position: absolute;
top: 0;
@@ -62,11 +52,6 @@
padding: 1em;
margin: 0;
- a {
- color: $fallback--link;
- color: var(--postLink, $fallback--link);
- }
-
img {
object-fit: contain;
vertical-align: middle;
@@ -76,53 +61,37 @@
}
&.-rounded-t {
- border-top-left-radius: $fallback--panelRadius;
- border-top-left-radius: var(--panelRadius, $fallback--panelRadius);
- border-top-right-radius: $fallback--panelRadius;
- border-top-right-radius: var(--panelRadius, $fallback--panelRadius);
+ border-top-left-radius: var(--roundness);
+ border-top-right-radius: var(--roundness);
- --__roundnessTop: var(--panelRadius);
+ --__roundnessTop: var(--roundness);
--__roundnessBottom: 0;
}
&.-rounded {
- border-radius: $fallback--panelRadius;
- border-radius: var(--panelRadius, $fallback--panelRadius);
+ border-radius: var(--roundness);
- --__roundnessTop: var(--panelRadius);
- --__roundnessBottom: var(--panelRadius);
+ --__roundnessTop: var(--roundness);
+ --__roundnessBottom: var(--roundness);
}
&.-popover {
- border-radius: $fallback--tooltipRadius;
- border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
+ border-radius: var(--roundness);
- --__roundnessTop: var(--tooltipRadius);
- --__roundnessBottom: var(--tooltipRadius);
+ --__roundnessTop: var(--roundness);
+ --__roundnessBottom: var(--roundness);
}
&.-bordered {
border-width: 1px;
border-style: solid;
- border-color: $fallback--border;
- border-color: var(--border, $fallback--border);
+ border-color: var(--border);
}
}
.user-info {
- color: $fallback--lightText;
- color: var(--lightText, $fallback--lightText);
padding: 0 26px;
- a {
- color: $fallback--lightText;
- color: var(--lightText, $fallback--lightText);
-
- &:hover {
- color: var(--icon);
- }
- }
-
.container {
min-width: 0;
padding: 16px 0 6px;
@@ -164,8 +133,7 @@
display: flex;
justify-content: center;
align-items: center;
- border-radius: $fallback--avatarRadius;
- border-radius: var(--avatarRadius, $fallback--avatarRadius);
+ border-radius: var(--roundness);
opacity: 0;
transition: opacity 0.2s ease;
@@ -188,8 +156,7 @@
padding: 0.5em 0;
&:not(:hover) .icon {
- color: $fallback--lightText;
- color: var(--lightText, $fallback--lightText);
+ color: var(--lightText);
}
}
@@ -203,6 +170,7 @@
}
.user-screen-name {
+ color: var(--text);
min-width: 1px;
flex: 0 1 auto;
text-overflow: ellipsis;
@@ -214,16 +182,11 @@
flex: 0 0 auto;
margin-left: 1em;
font-size: 0.7em;
- color: $fallback--text;
- color: var(--text, $fallback--text);
+ color: var(--text);
}
.user-role {
flex: none;
- color: $fallback--text;
- color: var(--alertNeutralText, $fallback--text);
- background-color: $fallback--fg;
- background-color: var(--alertNeutral, $fallback--fg);
}
}
@@ -241,6 +204,11 @@
--emoji-size: 1.7em;
+ .RichContent {
+ /* stylelint-disable-next-line declaration-no-important */
+ --link: var(--text) !important;
+ }
+
.top-line,
.bottom-line {
display: flex;
@@ -334,8 +302,6 @@
padding: 0.5em 1.5em 0;
text-align: center;
justify-content: space-between;
- color: $fallback--lightText;
- color: var(--lightText, $fallback--lightText);
flex-wrap: wrap;
}
diff --git a/src/components/user_card/user_card.style.js b/src/components/user_card/user_card.style.js
new file mode 100644
index 00000000..34eaa176
--- /dev/null
+++ b/src/components/user_card/user_card.style.js
@@ -0,0 +1,41 @@
+export default {
+ name: 'UserCard',
+ selector: '.user-card',
+ validInnerComponents: [
+ 'Text',
+ 'Link',
+ 'Icon',
+ 'Button',
+ 'ButtonUnstyled',
+ 'Input',
+ 'RichContent',
+ 'Alert'
+ ],
+ defaultRules: [
+ {
+ directives: {
+ background: '--bg',
+ opacity: 0,
+ roundness: 3,
+ shadow: [{
+ x: 1,
+ y: 1,
+ blur: 4,
+ spread: 0,
+ color: '#000000',
+ alpha: 0.6
+ }],
+ '--profileTint': 'color | $alpha(--background, 0.5)'
+ }
+ },
+ {
+ parent: {
+ component: 'UserCard'
+ },
+ component: 'RichContent',
+ directives: {
+ opacity: 0
+ }
+ }
+ ]
+}
diff --git a/src/components/user_card/user_card.vue b/src/components/user_card/user_card.vue
index 2de14063..70c4f67d 100644
--- a/src/components/user_card/user_card.vue
+++ b/src/components/user_card/user_card.vue
@@ -113,22 +113,28 @@
<template v-if="!hideBio">
<span
v-if="user.deactivated"
- class="alert user-role"
+ class="alert neutral user-role"
>
{{ $t('user_card.deactivated') }}
</span>
<span
v-if="!!visibleRole"
- class="alert user-role"
+ class="alert neutral user-role"
>
{{ $t(`general.role.${visibleRole}`) }}
</span>
<span
- v-if="user.bot"
- class="alert user-role"
+ v-if="user.actor_type === 'Service'"
+ class="alert neutral user-role"
>
{{ $t('user_card.bot') }}
</span>
+ <span
+ v-if="user.actor_type === 'Group'"
+ class="alert user-role"
+ >
+ {{ $t('user_card.group') }}
+ </span>
</template>
<span v-if="user.locked">
<FAIcon
@@ -160,14 +166,14 @@
v-if="userHighlightType !== 'disabled'"
:id="'userHighlightColorTx'+user.id"
v-model="userHighlightColor"
- class="userHighlightText"
+ class="input userHighlightText"
type="text"
>
<input
v-if="userHighlightType !== 'disabled'"
:id="'userHighlightColor'+user.id"
v-model="userHighlightColor"
- class="userHighlightCl"
+ class="input userHighlightCl"
type="color"
>
{{ ' ' }}
@@ -276,10 +282,7 @@
/>
</div>
</div>
- <div
- v-if="!hideBio"
- class="panel-body"
- >
+ <div v-if="!hideBio">
<div
v-if="!mergedConfig.hideUserStats && switcher"
class="user-counts"
diff --git a/src/components/user_list_menu/user_list_menu.vue b/src/components/user_list_menu/user_list_menu.vue
index 06947ab7..39ee2451 100644
--- a/src/components/user_list_menu/user_list_menu.vue
+++ b/src/components/user_list_menu/user_list_menu.vue
@@ -10,11 +10,11 @@
<button
v-for="list in lists"
:key="list.id"
- class="button-default dropdown-item"
+ class="menu-item dropdown-item"
@click="toggleList(list.id)"
>
<span
- class="menu-checkbox"
+ class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': list.inList }"
/>
{{ list.title }}
@@ -22,7 +22,7 @@
</div>
</template>
<template #trigger>
- <button class="btn button-default dropdown-item -has-submenu">
+ <button class="menu-item dropdown-item -has-submenu">
{{ $t('lists.manage_lists') }}
<FAIcon
class="chevron-icon"
diff --git a/src/components/user_list_popover/user_list_popover.vue b/src/components/user_list_popover/user_list_popover.vue
index 8307cc8a..cd134453 100644
--- a/src/components/user_list_popover/user_list_popover.vue
+++ b/src/components/user_list_popover/user_list_popover.vue
@@ -48,8 +48,6 @@
<script src="./user_list_popover.js"></script>
<style lang="scss">
-@import "../../variables";
-
.user-list-popover {
padding: 0.5em;
diff --git a/src/components/user_note/user_note.vue b/src/components/user_note/user_note.vue
index 4e05951f..e175ced0 100644
--- a/src/components/user_note/user_note.vue
+++ b/src/components/user_note/user_note.vue
@@ -33,7 +33,7 @@
<textarea
v-show="editing"
v-model="localNote"
- class="note-text"
+ class="input note-text"
/>
<span
v-show="!editing"
@@ -48,8 +48,6 @@
<script src="./user_note.js"></script>
<style lang="scss">
-@import "../../variables";
-
.user-note {
display: flex;
flex-direction: column;
@@ -82,7 +80,7 @@
.note-text.-blank {
font-style: italic;
- color: var(--faint, $fallback--faint);
+ color: var(--textFaint);
}
}
</style>
diff --git a/src/components/user_panel/user_panel.vue b/src/components/user_panel/user_panel.vue
index 95ec97af..9323b65e 100644
--- a/src/components/user_panel/user_panel.vue
+++ b/src/components/user_panel/user_panel.vue
@@ -22,8 +22,15 @@
<script src="./user_panel.js"></script>
<style lang="scss">
-.user-panel .signed-in {
- overflow: visible;
- z-index: 10;
+.user-panel {
+ .panel {
+ background: var(--background);
+ backdrop-filter: var(--backdrop-filter);
+ }
+
+ .signed-in {
+ overflow: visible;
+ z-index: 10;
+ }
}
</style>
diff --git a/src/components/user_popover/user_popover.vue b/src/components/user_popover/user_popover.vue
index 3b2bbc45..413b254c 100644
--- a/src/components/user_popover/user_popover.vue
+++ b/src/components/user_popover/user_popover.vue
@@ -24,8 +24,6 @@
<script src="./user_popover.js"></script>
<style lang="scss">
-@import "../../variables";
-
/* popover styles load on-demand, so we need to override */
/* stylelint-disable block-no-empty */
.user-popover.popover {
diff --git a/src/components/user_profile/user_profile.js b/src/components/user_profile/user_profile.js
index acb612ed..751bfd5a 100644
--- a/src/components/user_profile/user_profile.js
+++ b/src/components/user_profile/user_profile.js
@@ -80,6 +80,9 @@ const UserProfile = {
followersTabVisible () {
return this.isUs || !this.user.hide_followers
},
+ favoritesTabVisible () {
+ return this.isUs || !this.user.hide_favorites
+ },
formattedBirthday () {
const browserLocale = localeService.internalToBrowserLocale(this.$i18n.locale)
return this.user.birthday && new Date(Date.parse(this.user.birthday)).toLocaleDateString(browserLocale, { timeZone: 'UTC', day: 'numeric', month: 'long', year: 'numeric' })
@@ -103,6 +106,8 @@ const UserProfile = {
startFetchingTimeline('user', userId)
startFetchingTimeline('media', userId)
if (this.isUs) {
+ startFetchingTimeline('favorites')
+ } else if (!this.user.hide_favorites) {
startFetchingTimeline('favorites', userId)
}
// Fetch all pinned statuses immediately
diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue
index c63a303c..1ec12d0c 100644
--- a/src/components/user_profile/user_profile.vue
+++ b/src/components/user_profile/user_profile.vue
@@ -4,52 +4,54 @@
v-if="user"
class="user-profile panel panel-default"
>
- <UserCard
- :user-id="userId"
- :switcher="true"
- :selected="timeline.viewing"
- avatar-action="zoom"
- rounded="top"
- :has-note-editor="true"
- />
- <span
- v-if="!!user.birthday"
- class="user-birthday"
- >
- <FAIcon
- class="fa-old-padding"
- icon="birthday-cake"
+ <div class="panel-body">
+ <UserCard
+ :user-id="userId"
+ :switcher="true"
+ :selected="timeline.viewing"
+ avatar-action="zoom"
+ rounded="top"
+ :has-note-editor="true"
/>
- {{ $t('user_card.birthday', { birthday: formattedBirthday }) }}
- </span>
- <div
- v-if="user.fields_html && user.fields_html.length > 0"
- class="user-profile-fields"
- >
- <dl
- v-for="(field, index) in user.fields_html"
- :key="index"
- class="user-profile-field"
+ <span
+ v-if="!!user.birthday"
+ class="user-birthday"
>
- <dt
- :title="user.fields_text[index].name"
- class="user-profile-field-name"
- >
- <RichContent
- :html="field.name"
- :emoji="user.emoji"
- />
- </dt>
- <dd
- :title="user.fields_text[index].value"
- class="user-profile-field-value"
+ <FAIcon
+ class="fa-old-padding"
+ icon="birthday-cake"
+ />
+ {{ $t('user_card.birthday', { birthday: formattedBirthday }) }}
+ </span>
+ <div
+ v-if="user.fields_html && user.fields_html.length > 0"
+ class="user-profile-fields"
+ >
+ <dl
+ v-for="(field, index) in user.fields_html"
+ :key="index"
+ class="user-profile-field"
>
- <RichContent
- :html="field.value"
- :emoji="user.emoji"
- />
- </dd>
- </dl>
+ <dt
+ :title="user.fields_text[index].name"
+ class="user-profile-field-name"
+ >
+ <RichContent
+ :html="field.name"
+ :emoji="user.emoji"
+ />
+ </dt>
+ <dd
+ :title="user.fields_text[index].value"
+ class="user-profile-field-value"
+ >
+ <RichContent
+ :html="field.value"
+ :emoji="user.emoji"
+ />
+ </dd>
+ </dl>
+ </div>
</div>
<tab-switcher
:active-tab="tab"
@@ -72,10 +74,14 @@
<div
v-if="followsTabVisible"
key="followees"
+ class="panel-body"
:label="$t('user_card.followees')"
:disabled="!user.friends_count"
>
- <FriendList :user-id="userId">
+ <FriendList
+ :user-id="userId"
+ :non-interactive="true"
+ >
<template #item="{item}">
<FollowCard :user="item" />
</template>
@@ -84,10 +90,14 @@
<div
v-if="followersTabVisible"
key="followers"
+ class="panel-body"
:label="$t('user_card.followers')"
:disabled="!user.followers_count"
>
- <FollowerList :user-id="userId">
+ <FollowerList
+ :user-id="userId"
+ :non-interactive="true"
+ >
<template #item="{item}">
<FollowCard
:user="item"
@@ -109,7 +119,7 @@
:footer-slipgate="footerRef"
/>
<Timeline
- v-if="isUs"
+ v-if="favoritesTabVisible"
key="favorites"
:label="$t('user_card.favorites')"
:disabled="!favorites.visibleStatuses.length"
@@ -117,6 +127,7 @@
:title="$t('user_card.favorites')"
timeline-name="favorites"
:timeline="favorites"
+ :user-id="isUs ? undefined : userId"
:in-profile="true"
:footer-slipgate="footerRef"
/>
@@ -135,7 +146,7 @@
{{ $t('settings.profile_tab') }}
</div>
</div>
- <div class="panel-body">
+ <div>
<span v-if="error">{{ error }}</span>
<FAIcon
v-else
@@ -150,8 +161,6 @@
<script src="./user_profile.js"></script>
<style lang="scss">
-@import "../../variables";
-
.user-profile {
flex: 2;
flex-basis: 500px;
@@ -181,9 +190,8 @@
.user-profile-field {
display: flex;
margin: 0.25em;
- border: 1px solid var(--border, $fallback--border);
- border-radius: $fallback--inputRadius;
- border-radius: var(--inputRadius, $fallback--inputRadius);
+ border: 1px solid var(--border);
+ border-radius: var(--roundness);
.user-profile-field-name {
flex: 0 1 30%;
@@ -191,7 +199,7 @@
text-align: right;
color: var(--lightText);
min-width: 120px;
- border-right: 1px solid var(--border, $fallback--border);
+ border-right: 1px solid var(--border);
}
.user-profile-field-value {
@@ -228,4 +236,5 @@
padding: 7em;
}
}
+
</style>
diff --git a/src/components/user_reporting_modal/user_reporting_modal.vue b/src/components/user_reporting_modal/user_reporting_modal.vue
index 092c514e..2bda509a 100644
--- a/src/components/user_reporting_modal/user_reporting_modal.vue
+++ b/src/components/user_reporting_modal/user_reporting_modal.vue
@@ -19,7 +19,7 @@
<p>{{ $t('user_reporting.add_comment_description') }}</p>
<textarea
v-model="comment"
- class="form-control"
+ class="input form-control"
:placeholder="$t('user_reporting.additional_comments')"
rows="1"
@input="resize"
@@ -72,8 +72,6 @@
<script src="./user_reporting_modal.js"></script>
<style lang="scss">
-@import "../../variables";
-
.user-reporting-panel {
width: 90vw;
max-width: 700px;
@@ -84,8 +82,7 @@
display: flex;
flex-direction: column-reverse;
border-top: 1px solid;
- border-color: $fallback--border;
- border-color: var(--border, $fallback--border);
+ border-color: var(--border);
overflow: hidden;
}
@@ -155,8 +152,7 @@
width: 50%;
max-width: 320px;
border-right: 1px solid;
- border-color: $fallback--border;
- border-color: var(--border, $fallback--border);
+ border-color: var(--border);
padding: 1.1em;
> div {
diff --git a/src/components/video_attachment/video_attachment.vue b/src/components/video_attachment/video_attachment.vue
index 8a3ea1e3..df763143 100644
--- a/src/components/video_attachment/video_attachment.vue
+++ b/src/components/video_attachment/video_attachment.vue
@@ -2,7 +2,7 @@
<video
class="video"
preload="metadata"
- :src="attachment.url"
+ :src="attachment.url + '#t=0.00000000000001'"
:loop="loopVideo"
:controls="controls"
:alt="attachment.description"