aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--changelog.d/mute-nsfw.add1
-rw-r--r--changelog.d/notif-types.fix1
-rw-r--r--changelog.d/poll-ended-notifications.fix1
-rw-r--r--changelog.d/public-favorites.skip0
-rw-r--r--changelog.d/status-loading-indicator.add1
-rw-r--r--changelog.d/themes3-fixes.fix1
-rw-r--r--changelog.d/themes3.change1
-rw-r--r--src/App.scss423
-rw-r--r--src/App.vue1
-rw-r--r--src/_variables.scss36
-rw-r--r--src/boot/after_store.js18
-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.vue29
-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.scss32
-rw-r--r--src/components/dialog_modal/dialog_modal.vue13
-rw-r--r--src/components/emoji_input/emoji_input.vue57
-rw-r--r--src/components/emoji_picker/emoji_picker.scss21
-rw-r--r--src/components/emoji_picker/emoji_picker.vue10
-rw-r--r--src/components/emoji_reactions/emoji_reactions.vue17
-rw-r--r--src/components/extra_buttons/extra_buttons.vue30
-rw-r--r--src/components/extra_notifications/extra_notifications.vue5
-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.vue44
-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/interface_language_switcher/interface_language_switcher.vue2
-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.vue26
-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.vue2
-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.vue41
-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.vue27
-rw-r--r--src/components/notification/notification.scss26
-rw-r--r--src/components/notification/notification.style.js17
-rw-r--r--src/components/notification/notification.vue3
-rw-r--r--src/components/notifications/notification_filters.vue28
-rw-r--r--src/components/notifications/notifications.scss28
-rw-r--r--src/components/notifications/notifications.vue4
-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.vue21
-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.vue124
-rw-r--r--src/components/post_status_form/post_status_form.vue67
-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.js7
-rw-r--r--src/components/quick_view_settings/quick_view_settings.vue22
-rw-r--r--src/components/range_input/range_input.vue6
-rw-r--r--src/components/react_button/react_button.vue8
-rw-r--r--src/components/registration/registration.vue26
-rw-r--r--src/components/reply_button/reply_button.vue4
-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.jsx8
-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/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.scss6
-rw-r--r--src/components/settings_modal/admin_tabs/emoji_tab.vue172
-rw-r--r--src/components/settings_modal/admin_tabs/frontends_tab.vue19
-rw-r--r--src/components/settings_modal/admin_tabs/instance_tab.vue10
-rw-r--r--src/components/settings_modal/helpers/attachment_setting.vue2
-rw-r--r--src/components/settings_modal/helpers/emoji_editing_popover.vue97
-rw-r--r--src/components/settings_modal/helpers/number_setting.vue2
-rw-r--r--src/components/settings_modal/helpers/string_setting.vue2
-rw-r--r--src/components/settings_modal/helpers/unit_setting.vue2
-rw-r--r--src/components/settings_modal/settings_modal.scss2
-rw-r--r--src/components/settings_modal/settings_modal.vue8
-rw-r--r--src/components/settings_modal/tabs/filtering_tab.vue25
-rw-r--r--src/components/settings_modal/tabs/notifications_tab.vue9
-rw-r--r--src/components/settings_modal/tabs/profile_tab.scss8
-rw-r--r--src/components/settings_modal/tabs/profile_tab.vue10
-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.vue9
-rw-r--r--src/components/settings_modal/tabs/theme_tab/preview.vue6
-rw-r--r--src/components/settings_modal/tabs/theme_tab/theme_tab.js22
-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.vue96
-rw-r--r--src/components/status/post.style.js33
-rw-r--r--src/components/status/status.js10
-rw-r--r--src/components/status/status.scss56
-rw-r--r--src/components/status/status.vue10
-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_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.jsx2
-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.js8
-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.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.vue22
-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.vue15
-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.vue114
-rw-r--r--src/components/user_reporting_modal/user_reporting_modal.vue10
-rw-r--r--src/hocs/with_load_more/with_load_more.scss5
-rw-r--r--src/i18n/cs.json194
-rw-r--r--src/i18n/en.json13
-rw-r--r--src/i18n/fr.json314
-rw-r--r--src/i18n/ja_pedantic.json24
-rw-r--r--src/i18n/pt.json185
-rw-r--r--src/modules/config.js1
-rw-r--r--src/modules/instance.js2
-rw-r--r--src/modules/interface.js4
-rw-r--r--src/panel.scss111
-rw-r--r--src/services/color_convert/color_convert.js2
-rw-r--r--src/services/notifications_fetcher/notifications_fetcher.service.js3
-rw-r--r--src/services/style_setter/style_setter.js473
-rw-r--r--src/services/theme_data/css_utils.js163
-rw-r--r--src/services/theme_data/iss_utils.js129
-rw-r--r--src/services/theme_data/pleromafe.t3.js2
-rw-r--r--src/services/theme_data/theme2_keys.js177
-rw-r--r--src/services/theme_data/theme2_to_theme3.js536
-rw-r--r--src/services/theme_data/theme3_slot_functions.js103
-rw-r--r--src/services/theme_data/theme_data.service.js346
-rw-r--r--src/services/theme_data/theme_data_3.service.js468
-rw-r--r--test/unit/specs/services/theme_data/theme_data3.spec.js144
-rw-r--r--tools/check-changelog2
208 files changed, 5528 insertions, 2336 deletions
diff --git a/changelog.d/mute-nsfw.add b/changelog.d/mute-nsfw.add
new file mode 100644
index 00000000..b1794a0c
--- /dev/null
+++ b/changelog.d/mute-nsfw.add
@@ -0,0 +1 @@
+Added ability to mute sensitive posts (ported from eintei)
diff --git a/changelog.d/notif-types.fix b/changelog.d/notif-types.fix
new file mode 100644
index 00000000..fb0e5046
--- /dev/null
+++ b/changelog.d/notif-types.fix
@@ -0,0 +1 @@
+Synchronized requested notification types with backend, hopefully should fix missing notifications for polls and follow requests
diff --git a/changelog.d/poll-ended-notifications.fix b/changelog.d/poll-ended-notifications.fix
new file mode 100644
index 00000000..d04b8cb0
--- /dev/null
+++ b/changelog.d/poll-ended-notifications.fix
@@ -0,0 +1 @@
+Add poll end notifications to fetched types.
diff --git a/changelog.d/public-favorites.skip b/changelog.d/public-favorites.skip
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/changelog.d/public-favorites.skip
diff --git a/changelog.d/status-loading-indicator.add b/changelog.d/status-loading-indicator.add
new file mode 100644
index 00000000..d0725677
--- /dev/null
+++ b/changelog.d/status-loading-indicator.add
@@ -0,0 +1 @@
+Display loading and error indicator for conversation page
diff --git a/changelog.d/themes3-fixes.fix b/changelog.d/themes3-fixes.fix
new file mode 100644
index 00000000..15c31e82
--- /dev/null
+++ b/changelog.d/themes3-fixes.fix
@@ -0,0 +1 @@
+fix color inputs and some in-development themes3 issues
diff --git a/changelog.d/themes3.change b/changelog.d/themes3.change
new file mode 100644
index 00000000..5255f9b1
--- /dev/null
+++ b/changelog.d/themes3.change
@@ -0,0 +1 @@
+Overhauled the way themes work, migrating to new Pleroma Interface Style Sheets system.
diff --git a/src/App.scss b/src/App.scss
index ef68ac50..6e0aabca 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -1,9 +1,10 @@
// stylelint-disable rscss/class-format
/* stylelint-disable no-descending-specificity */
-@import "./variables";
@import "./panel";
:root {
+ --font-size: 14px;
+ --status-margin: 0.75em;
--navbar-height: 3.5rem;
--post-line-height: 1.4;
// Z-Index stuff
@@ -13,19 +14,21 @@
--ZI_navbar_popovers: 7500;
--ZI_navbar: 7000;
--ZI_popovers: 6000;
+
+ // Fallback for when stuff is loading
+ --background: var(--bg);
}
html {
- font-size: 14px;
+ font-size: var(--font-size);
// overflow-x: clip causes my browser's tab to crash with SIGILL lul
}
body {
font-family: sans-serif;
- font-family: var(--interfaceFont, sans-serif);
+ font-family: var(--font);
margin: 0;
- color: $fallback--text;
- color: var(--text, $fallback--text);
+ color: var(--text);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
overscroll-behavior-y: none;
@@ -42,17 +45,35 @@ body {
// have a cursor/pointer to operate them
@media (any-pointer: fine) {
* {
- scrollbar-color: var(--btn) transparent;
+ scrollbar-color: var(--fg) transparent;
&::-webkit-scrollbar {
background: transparent;
}
+ &::-webkit-scrollbar-corner {
+ background: transparent;
+ }
+
+ &::-webkit-resizer {
+ /* stylelint-disable-next-line declaration-no-important */
+ background-color: transparent !important;
+ background-image:
+ linear-gradient(
+ 135deg,
+ transparent calc(50% - 1px),
+ var(--textFaint) 50%,
+ transparent calc(50% + 1px),
+ transparent calc(75% - 1px),
+ var(--textFaint) 75%,
+ transparent calc(75% + 1px),
+ );
+ }
+
&::-webkit-scrollbar-button,
&::-webkit-scrollbar-thumb {
- background-color: var(--btn);
- box-shadow: var(--buttonShadow);
- border-radius: var(--btnRadius);
+ box-shadow: var(--shadow);
+ border-radius: var(--roundness);
}
// horizontal/vertical/increment/decrement are webkit-specific stuff
@@ -61,7 +82,7 @@ body {
&::-webkit-scrollbar-button {
--___bgPadding: 2px;
- color: var(--btnText);
+ color: var(--text);
background-repeat: no-repeat, no-repeat;
&:horizontal {
@@ -69,15 +90,15 @@ body {
&:increment {
background-image:
- linear-gradient(45deg, var(--btnText) 50%, transparent 51%),
- linear-gradient(-45deg, transparent 50%, var(--btnText) 51%);
+ linear-gradient(45deg, var(--text) 50%, transparent 51%),
+ linear-gradient(-45deg, transparent 50%, var(--text) 51%);
background-position: top var(--___bgPadding) left 50%, right 50% bottom var(--___bgPadding);
}
&:decrement {
background-image:
- linear-gradient(45deg, transparent 50%, var(--btnText) 51%),
- linear-gradient(-45deg, var(--btnText) 50%, transparent 51%);
+ linear-gradient(45deg, transparent 50%, var(--text) calc(50% + 1px)),
+ linear-gradient(-45deg, var(--text) 50%, transparent 51%);
background-position: bottom var(--___bgPadding) right 50%, left 50% top var(--___bgPadding);
}
}
@@ -87,15 +108,15 @@ body {
&:increment {
background-image:
- linear-gradient(-45deg, transparent 50%, var(--btnText) 51%),
- linear-gradient(45deg, transparent 50%, var(--btnText) 51%);
+ linear-gradient(-45deg, transparent 50%, var(--text) 51%),
+ linear-gradient(45deg, transparent 50%, var(--text) 51%);
background-position: right var(--___bgPadding) top 50%, left var(--___bgPadding) top 50%;
}
&:decrement {
background-image:
- linear-gradient(-45deg, var(--btnText) 50%, transparent 51%),
- linear-gradient(45deg, var(--btnText) 50%, transparent 51%);
+ linear-gradient(-45deg, var(--text) 50%, transparent 51%),
+ linear-gradient(45deg, var(--text) 50%, transparent 51%);
background-position: left var(--___bgPadding) top 50%, right var(--___bgPadding) top 50%;
}
}
@@ -104,15 +125,14 @@ body {
}
// Body should have background to scrollbar otherwise it will use white (body color?)
html {
- scrollbar-color: var(--selectedMenu) var(--wallpaper);
+ scrollbar-color: var(--fg) var(--wallpaper);
background: var(--wallpaper);
}
}
a {
text-decoration: none;
- color: $fallback--link;
- color: var(--link, $fallback--link);
+ color: var(--link);
}
h4 {
@@ -128,27 +148,12 @@ h4 {
i[class*="icon-"],
.svg-inline--fa,
.iconLetter {
- color: $fallback--icon;
- color: var(--icon, $fallback--icon);
-}
-
-.button-unstyled:hover,
-a:hover {
- > i[class*="icon-"],
- > .svg-inline--fa,
- > .iconLetter {
- color: var(--text);
- }
+ color: var(--icon);
}
nav {
z-index: var(--ZI_navbar);
- background-color: $fallback--fg;
- background-color: var(--topBar, $fallback--fg);
- color: $fallback--faint;
- color: var(--faint, $fallback--faint);
- box-shadow: 0 0 4px rgb(0 0 0 / 60%);
- box-shadow: var(--topBarShadow);
+ box-shadow: var(--shadow);
box-sizing: border-box;
height: var(--navbar-height);
position: fixed;
@@ -195,8 +200,7 @@ nav {
grid-column: 1 / span 3;
grid-row: 1 / 1;
pointer-events: none;
- background-color: rgb(0 0 0 / 15%);
- background-color: var(--underlay, rgb(0 0 0 / 15%));
+ background-color: var(--underlay);
z-index: -1000;
}
@@ -204,7 +208,6 @@ nav {
--miniColumn: 25rem;
--maxiColumn: 45rem;
--columnGap: 1em;
- --status-margin: 0.75em;
--effectiveSidebarColumnWidth: minmax(var(--miniColumn), var(--sidebarColumnWidth, var(--miniColumn)));
--effectiveNotifsColumnWidth: minmax(var(--miniColumn), var(--notifsColumnWidth, var(--miniColumn)));
--effectiveContentColumnWidth: minmax(var(--miniColumn), var(--contentColumnWidth, var(--maxiColumn)));
@@ -366,106 +369,113 @@ nav {
.button-default {
user-select: none;
- color: $fallback--text;
- color: var(--btnText, $fallback--text);
- background-color: $fallback--fg;
- background-color: var(--btn, $fallback--fg);
+ color: var(--text);
border: none;
- border-radius: $fallback--btnRadius;
- border-radius: var(--btnRadius, $fallback--btnRadius);
+ border-radius: var(--roundness);
cursor: pointer;
- box-shadow: $fallback--buttonShadow;
- box-shadow: var(--buttonShadow);
+ background-color: var(--background);
+ box-shadow: var(--shadow);
font-size: 1em;
font-family: sans-serif;
- font-family: var(--interfaceFont, sans-serif);
+ font-family: var(--font);
- &.-sublime {
- background: transparent;
+ &::-moz-focus-inner {
+ border: none;
}
- i[class*="icon-"],
- .svg-inline--fa {
- color: $fallback--text;
- color: var(--btnText, $fallback--text);
+ &:disabled {
+ cursor: not-allowed;
}
+}
- &::-moz-focus-inner {
- border: none;
+.menu-item,
+.list-item {
+ display: block;
+ box-sizing: border-box;
+ border: none;
+ outline: none;
+ text-align: initial;
+ font-size: inherit;
+ font-family: inherit;
+ font-weight: 400;
+ cursor: pointer;
+ color: inherit;
+ clear: both;
+ position: relative;
+ white-space: nowrap;
+ border-color: var(--border);
+ border-style: solid;
+ border-width: 0;
+ border-top-width: 1px;
+ width: 100%;
+ line-height: var(--__line-height);
+ padding: var(--__vertical-gap) var(--__horizontal-gap);
+ background: transparent;
+
+ --__line-height: 1.5em;
+ --__horizontal-gap: 0.75em;
+ --__vertical-gap: 0.5em;
+
+ &.-non-interactive {
+ cursor: auto;
}
+ &.-active,
&:hover {
- box-shadow: 0 0 4px rgb(255 255 255 / 30%);
- box-shadow: var(--buttonHoverShadow);
- }
-
- &:active {
- box-shadow:
- 0 0 4px 0 rgb(255 255 255 / 30%),
- 0 1px 0 0 rgb(0 0 0 / 20%) inset,
- 0 -1px 0 0 rgb(255 255 255 / 20%) inset;
- box-shadow: var(--buttonPressedShadow);
- color: $fallback--text;
- color: var(--btnPressedText, $fallback--text);
- background-color: $fallback--fg;
- background-color: var(--btnPressed, $fallback--fg);
-
- svg,
- i {
- color: $fallback--text;
- color: var(--btnPressedText, $fallback--text);
- }
+ border-top-width: 1px;
+ border-bottom-width: 1px;
}
- &:disabled {
- cursor: not-allowed;
- color: $fallback--text;
- color: var(--btnDisabledText, $fallback--text);
- background-color: $fallback--fg;
- background-color: var(--btnDisabled, $fallback--fg);
-
- svg,
- i {
- color: $fallback--text;
- color: var(--btnDisabledText, $fallback--text);
- }
+ &.-active + &,
+ &:hover + & {
+ border-top-width: 0;
}
- &.toggled {
- color: $fallback--text;
- color: var(--btnToggledText, $fallback--text);
- background-color: $fallback--fg;
- background-color: var(--btnToggled, $fallback--fg);
- box-shadow:
- 0 0 4px 0 rgb(255 255 255 / 30%),
- 0 1px 0 0 rgb(0 0 0 / 20%) inset,
- 0 -1px 0 0 rgb(255 255 255 / 20%) inset;
- box-shadow: var(--buttonPressedShadow);
-
- svg,
- i {
- color: $fallback--text;
- color: var(--btnToggledText, $fallback--text);
- }
+ &:hover + .menu-item-collapsible:not(.-expanded) + &,
+ &.-active + .menu-item-collapsible:not(.-expanded) + & {
+ border-top-width: 0;
+ }
+
+ &[aria-expanded="true"] {
+ border-bottom-width: 1px;
+ }
+
+ a,
+ button:not(.button-default) {
+ text-align: initial;
+ padding: 0;
+ background: none;
+ border: none;
+ outline: none;
+ display: inline;
+ font-size: 100%;
+ font-family: inherit;
+ line-height: unset;
+ color: var(--text);
}
- &.danger {
- // TODO: add better color variable
- color: $fallback--text;
- color: var(--alertErrorPanelText, $fallback--text);
- background-color: $fallback--alertError;
- background-color: var(--alertError, $fallback--alertError);
+ &:first-child {
+ border-top-right-radius: var(--roundness);
+ border-top-left-radius: var(--roundness);
+ border-top-width: 0;
+ }
+
+ &:last-child {
+ border-bottom-right-radius: var(--roundness);
+ border-bottom-left-radius: var(--roundness);
+ border-bottom-width: 0;
}
}
.button-unstyled {
- background: none;
border: none;
outline: none;
display: inline;
text-align: initial;
font-size: 100%;
font-family: inherit;
+ box-shadow: var(--shadow);
+ background-color: transparent;
padding: 0;
line-height: unset;
cursor: pointer;
@@ -473,28 +483,23 @@ nav {
color: inherit;
&.-link {
- color: $fallback--link;
- color: var(--link, $fallback--link);
- }
-
- &.-fullwidth {
- width: 100%;
- }
-
- &.-hover-highlight {
- &:hover svg {
- color: $fallback--lightText;
- color: var(--lightText, $fallback--lightText);
- }
+ /* stylelint-disable-next-line declaration-no-important */
+ color: var(--link) !important;
}
}
input,
-textarea,
+textarea {
+ border: none;
+ display: inline-block;
+ outline: none;
+}
+
.input {
&.unstyled {
border-radius: 0;
- background: none;
+ /* stylelint-disable-next-line declaration-no-important */
+ background: none !important;
box-shadow: none;
height: unset;
}
@@ -502,19 +507,11 @@ textarea,
--_padding: 0.5em;
border: none;
- border-radius: $fallback--inputRadius;
- border-radius: var(--inputRadius, $fallback--inputRadius);
- box-shadow:
- 0 1px 0 0 rgb(0 0 0 / 20%) inset,
- 0 -1px 0 0 rgb(255 255 255 / 20%) inset,
- 0 0 2px 0 rgb(0 0 0 / 100%) inset;
- box-shadow: var(--inputShadow);
- background-color: $fallback--fg;
- background-color: var(--input, $fallback--fg);
- color: $fallback--lightText;
- color: var(--inputText, $fallback--lightText);
- font-family: sans-serif;
- font-family: var(--inputFont, sans-serif);
+ border-radius: var(--roundness);
+ background-color: var(--background);
+ color: var(--text);
+ box-shadow: var(--shadow);
+ font-family: var(--font);
font-size: 1em;
margin: 0;
box-sizing: border-box;
@@ -528,7 +525,6 @@ textarea,
&[disabled="disabled"],
&.disabled {
cursor: not-allowed;
- opacity: 0.5;
}
&[type="range"] {
@@ -543,9 +539,9 @@ textarea,
display: none;
&:checked + label::before {
- box-shadow: 0 0 2px black inset, 0 0 0 4px $fallback--fg inset;
- box-shadow: var(--inputShadow), 0 0 0 4px var(--fg, $fallback--fg) inset;
- background-color: var(--accent, $fallback--link);
+ box-shadow: var(--shadow);
+ background-color: var(--background);
+ color: var(--text);
}
&:disabled {
@@ -559,16 +555,14 @@ textarea,
+ label::before {
flex-shrink: 0;
display: inline-block;
- content: "";
+ content: "•";
transition: box-shadow 200ms;
width: 1.1em;
height: 1.1em;
border-radius: 100%; // Radio buttons should always be circle
- box-shadow: 0 0 2px black inset;
- box-shadow: var(--inputShadow);
+ background-color: var(--background);
+ box-shadow: var(--shadow);
margin-right: 0.5em;
- background-color: $fallback--fg;
- background-color: var(--input, $fallback--fg);
vertical-align: top;
text-align: center;
line-height: 1.1;
@@ -581,8 +575,9 @@ textarea,
&[type="checkbox"] {
&:checked + label::before {
- color: $fallback--text;
- color: var(--inputText, $fallback--text);
+ color: var(--text);
+ background-color: var(--background);
+ box-shadow: var(--shadow);
}
&:disabled {
@@ -600,13 +595,9 @@ textarea,
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);
+ border-radius: var(--roundness);
+ box-shadow: var(--shadow);
margin-right: 0.5em;
- background-color: $fallback--fg;
- background-color: var(--input, $fallback--fg);
vertical-align: top;
text-align: center;
line-height: 1.1;
@@ -623,16 +614,14 @@ textarea,
}
// Textareas should have stock line-height + vertical padding instead of huge line-height
-textarea {
+textarea.input {
padding: var(--_padding);
line-height: var(--post-line-height);
}
option {
- color: $fallback--text;
- color: var(--text, $fallback--text);
- background-color: $fallback--bg;
- background-color: var(--bg, $fallback--bg);
+ color: var(--text);
+ background-color: var(--background);
}
.hide-number-spinner {
@@ -653,7 +642,7 @@ option {
li {
border: 1px solid var(--border);
- border-radius: var(--inputRadius);
+ border-radius: var(--roundness);
padding: 0.5em;
margin: 0.25em;
}
@@ -714,74 +703,58 @@ option {
overflow: hidden;
text-overflow: ellipsis;
- &.badge-notification {
- background-color: $fallback--cRed;
- background-color: var(--badgeNotification, $fallback--cRed);
- color: white;
- color: var(--badgeNotificationText, white);
- }
-}
-
-.alert {
- margin: 0 0.35em;
- padding: 0 0.25em;
- border-radius: $fallback--tooltipRadius;
- border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
-
- &.error {
- background-color: $fallback--alertError;
- background-color: var(--alertError, $fallback--alertError);
- color: $fallback--text;
- color: var(--alertErrorText, $fallback--text);
-
- .panel-heading & {
- color: $fallback--text;
- color: var(--alertErrorPanelText, $fallback--text);
- }
+ &.-dot,
+ &.-counter {
+ margin: 0;
+ position: absolute;
}
- &.warning {
- background-color: $fallback--alertWarning;
- background-color: var(--alertWarning, $fallback--alertWarning);
- color: $fallback--text;
- color: var(--alertWarningText, $fallback--text);
-
- .panel-heading & {
- color: $fallback--text;
- color: var(--alertWarningPanelText, $fallback--text);
- }
+ &.-dot {
+ min-height: 8px;
+ max-height: 8px;
+ min-width: 8px;
+ max-width: 8px;
+ padding: 0;
+ line-height: 0;
+ font-size: 0;
+ left: calc(50% - 4px);
+ top: calc(50% - 4px);
+ margin-left: 6px;
+ margin-top: -6px;
}
- &.success {
- background-color: var(--alertSuccess, $fallback--alertWarning);
- color: var(--alertSuccessText, $fallback--text);
-
- .panel-heading & {
- color: var(--alertSuccessPanelText, $fallback--text);
- }
+ &.-counter {
+ border-radius: var(--roundness);
+ font-size: 0.75em;
+ line-height: 1;
+ text-align: right;
+ padding: 0.2em;
+ min-width: 0;
+ left: calc(50% - 0.5em);
+ top: calc(50% - 0.4em);
+ margin-left: 0.7em;
+ margin-top: -1em;
}
}
-.faint {
- color: $fallback--faint;
- color: var(--faint, $fallback--faint);
+.alert {
+ margin: 0 0.35em;
+ padding: 0 0.25em;
+ border-radius: var(--roundness);
+ border: 1px solid var(--border);
}
-.faint-link {
- color: $fallback--faint;
- color: var(--faint, $fallback--faint);
+.faint {
+ --text: var(--textFaint);
+ --link: var(--linkFaint);
- &:hover {
- text-decoration: underline;
- }
+ color: var(--text);
}
.visibility-notice {
padding: 0.5em;
- border: 1px solid $fallback--faint;
- border: 1px solid var(--faint, $fallback--faint);
- border-radius: $fallback--inputRadius;
- border-radius: var(--inputRadius, $fallback--inputRadius);
+ border: 1px solid var(--textFaint);
+ border-radius: var(--roundness);
}
.notice-dismissible {
@@ -802,6 +775,10 @@ option {
&.iconLetter {
font-size: 1.1em;
}
+
+ &.svg-inline--fa {
+ vertical-align: -0.15em;
+ }
}
.fa-old-padding {
@@ -816,6 +793,11 @@ option {
opacity: 0.25;
}
+.timeago {
+ --link: var(--text);
+ --linkFaint: var(--textFaint);
+}
+
.login-hint {
text-align: center;
@@ -914,3 +896,8 @@ option {
padding: 0;
position: absolute;
}
+
+*::selection {
+ color: var(--selectionText);
+ background-color: var(--selectionBackground);
+}
diff --git a/src/App.vue b/src/App.vue
index fe214ce7..9d7ad912 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -1,5 +1,6 @@
<template>
<div
+ v-show="$store.state.interface.themeApplied"
id="app-loaded"
:style="bgStyle"
>
diff --git a/src/_variables.scss b/src/_variables.scss
deleted file mode 100644
index 751fc9a4..00000000
--- a/src/_variables.scss
+++ /dev/null
@@ -1,36 +0,0 @@
-$main-color: #f58d2c;
-$main-background: white;
-$darkened-background: whitesmoke;
-
-$fallback--bg: #121a24;
-$fallback--fg: #182230;
-$fallback--faint: rgb(185 185 186 / 50%);
-$fallback--text: #b9b9ba;
-$fallback--link: #d8a070;
-$fallback--icon: #666;
-$fallback--lightBg: rgb(21 30 42);
-$fallback--lightText: #b9b9ba;
-$fallback--border: #222;
-$fallback--cRed: #f00;
-$fallback--cBlue: #0095ff;
-$fallback--cGreen: #0fa00f;
-$fallback--cOrange: orange;
-
-$fallback--alertError: rgb(211 16 20 / 50%);
-$fallback--alertWarning: rgb(111 111 20 / 50%);
-
-$fallback--panelRadius: 10px;
-$fallback--checkboxRadius: 2px;
-$fallback--btnRadius: 4px;
-$fallback--inputRadius: 4px;
-$fallback--tooltipRadius: 5px;
-$fallback--avatarRadius: 4px;
-$fallback--avatarAltRadius: 10px;
-$fallback--attachmentRadius: 10px;
-$fallback--chatMessageRadius: 10px;
-
-$fallback--buttonShadow: 0 0 2px 0 rgb(0 0 0 / 100%),
- 0 1px 0 0 rgb(255 255 255 / 20%) inset,
- 0 -1px 0 0 rgb(0 0 0 / 20%) inset;
-
-$status-margin: 0.75em;
diff --git a/src/boot/after_store.js b/src/boot/after_store.js
index 7039f85a..bfb671ed 100644
--- a/src/boot/after_store.js
+++ b/src/boot/after_store.js
@@ -328,17 +328,14 @@ const setConfig = async ({ store }) => {
}
const checkOAuthToken = async ({ store }) => {
- // eslint-disable-next-line no-async-promise-executor
- return new Promise(async (resolve, reject) => {
- if (store.getters.getUserToken()) {
- try {
- await store.dispatch('loginUser', store.getters.getUserToken())
- } catch (e) {
- console.error(e)
- }
+ if (store.getters.getUserToken()) {
+ try {
+ await store.dispatch('loginUser', store.getters.getUserToken())
+ } catch (e) {
+ console.error(e)
}
- resolve()
- })
+ }
+ return Promise.resolve()
}
const afterStoreSetup = async ({ store, i18n }) => {
@@ -366,6 +363,7 @@ const afterStoreSetup = async ({ store, i18n }) => {
} else {
applyTheme(customTheme)
}
+ store.commit('setThemeApplied')
} else if (theme) {
// do nothing, it will load asynchronously
} else {
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 b8b77e7c..6261bf3a 100644
--- a/src/components/checkbox/checkbox.vue
+++ b/src/components/checkbox/checkbox.vue
@@ -12,7 +12,7 @@
@change="$emit('update:modelValue', $event.target.checked)"
>
<i
- class="checkbox-indicator"
+ class="input -checkbox checkbox-indicator"
:aria-hidden="true"
@transitionend.capture="onTransitionEnd"
/>
@@ -54,7 +54,6 @@ export default {
</script>
<style lang="scss">
-@import "../../variables";
@import "../../mixins";
.checkbox {
@@ -62,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 {
@@ -76,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;
@@ -98,21 +100,18 @@ 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);
}
}
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.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/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.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.scss b/src/components/emoji_picker/emoji_picker.scss
index aab9251d..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;
@@ -81,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;
}
@@ -109,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 1231ce2b..7c36deaa 100644
--- a/src/components/emoji_picker/emoji_picker.vue
+++ b/src/components/emoji_picker/emoji_picker.vue
@@ -23,9 +23,9 @@
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"
@@ -52,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"
>
@@ -77,7 +77,7 @@
ref="search"
v-model="keyword"
type="text"
- class="form-control"
+ class="input form-control"
:placeholder="$t('emoji.search_emoji')"
@input="$event.target.composing = false"
>
diff --git a/src/components/emoji_reactions/emoji_reactions.vue b/src/components/emoji_reactions/emoji_reactions.vue
index c11b338e..ad4a3c0b 100644
--- a/src/components/emoji_reactions/emoji_reactions.vue
+++ b/src/components/emoji_reactions/emoji_reactions.vue
@@ -72,7 +72,6 @@
<script src="./emoji_reactions.js"></script>
<style lang="scss">
-@import "../../variables";
@import "../../mixins";
.EmojiReactions {
@@ -92,7 +91,6 @@
padding: 0;
.emoji-reaction-count-button {
- background-color: var(--btn);
margin: 0;
height: 100%;
border-top-left-radius: 0;
@@ -102,11 +100,9 @@
display: inline-flex;
justify-content: center;
align-items: center;
- color: $fallback--text;
- color: var(--btnText, $fallback--text);
&.-picked-reaction {
- border: 1px solid var(--accent, $fallback--link);
+ border: 1px solid var(--accent);
margin-right: -1px;
}
}
@@ -149,18 +145,16 @@
}
.svg-inline--fa {
- color: $fallback--text;
- color: var(--btnText, $fallback--text);
+ 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: -1px;
.svg-inline--fa {
- color: $fallback--link;
- color: var(--accent, $fallback--link);
+ color: var(--accent);
}
}
@@ -176,8 +170,7 @@
@include focused-style {
.svg-inline--fa {
- color: $fallback--link;
- color: var(--accent, $fallback--link);
+ color: var(--accent);
}
.focus-marker {
diff --git a/src/components/extra_buttons/extra_buttons.vue b/src/components/extra_buttons/extra_buttons.vue
index b7d3b1d3..7b38d974 100644
--- a/src/components/extra_buttons/extra_buttons.vue
+++ b/src/components/extra_buttons/extra_buttons.vue
@@ -12,13 +12,13 @@
>
<template #content="{close}">
<div
+ :id="`popup-menu-${randomSeed}`"
class="dropdown-menu"
role="menu"
- :id="`popup-menu-${randomSeed}`"
>
<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"
>
@@ -29,7 +29,7 @@
</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"
>
@@ -40,7 +40,7 @@
</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"
@@ -52,7 +52,7 @@
</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"
@@ -65,7 +65,7 @@
<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"
@@ -77,7 +77,7 @@
</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"
@@ -90,7 +90,7 @@
</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"
@@ -102,7 +102,7 @@
</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"
@@ -114,7 +114,7 @@
</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"
@@ -125,7 +125,7 @@
/><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"
@@ -137,7 +137,7 @@
</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"
@@ -149,7 +149,7 @@
/><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"
@@ -201,7 +201,6 @@
<script src="./extra_buttons.js"></script>
<style lang="scss">
-@import "../../variables";
@import "../../mixins";
.ExtraButtons {
@@ -211,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.vue b/src/components/extra_notifications/extra_notifications.vue
index 6e0456a5..600c99bb 100644
--- a/src/components/extra_notifications/extra_notifications.vue
+++ b/src/components/extra_notifications/extra_notifications.vue
@@ -80,8 +80,6 @@
<script src="./extra_notifications.js" />
<style lang="scss">
-@import "../../variables";
-
.ExtraNotifications {
width: 100%;
display: flex;
@@ -91,8 +89,7 @@
.notification {
width: 100%;
border-bottom: 1px solid;
- border-color: $fallback--border;
- border-color: var(--border, $fallback--border);
+ border-color: var(--border);
display: flex;
flex-direction: column;
align-items: stretch;
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 9e9ec7aa..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,8 +25,6 @@
<script src="./global_notice_list.js"></script>
<style lang="scss">
-@import "../../variables";
-
.global-notice-list {
position: fixed;
top: calc(var(--navbar-height) + 0.5em);
@@ -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/interface_language_switcher/interface_language_switcher.vue b/src/components/interface_language_switcher/interface_language_switcher.vue
index 364791a1..f7d5ef7e 100644
--- a/src/components/interface_language_switcher/interface_language_switcher.vue
+++ b/src/components/interface_language_switcher/interface_language_switcher.vue
@@ -104,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 a3562c5d..c885bacd 100644
--- a/src/components/list/list.vue
+++ b/src/components/list/list.vue
@@ -7,6 +7,7 @@
v-for="item in items"
:key="getKey(item)"
class="list-item"
+ :class="[getClass(item), nonInteractive ? '-non-interactive' : '']"
role="listitem"
>
<slot
@@ -33,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.vue b/src/components/media_upload/media_upload.vue
index 2ea5567a..047e3483 100644
--- a/src/components/media_upload/media_upload.vue
+++ b/src/components/media_upload/media_upload.vue
@@ -36,8 +36,6 @@
<script src="./media_upload.js"></script>
<style lang="scss">
-@import "../../variables";
-
.media-upload {
.hidden-input-file {
display: none;
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.vue b/src/components/mobile_nav/mobile_nav.vue
index f20a509d..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,24 +37,24 @@
/>
<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">
+ <div class="panel-heading mobile-notifications-header">
<span class="title">
{{ $t('notifications.notifications') }}
<span
v-if="unseenCountBadgeText"
- class="badge badge-notification unseen-count"
+ class="badge -notification unseen-count"
>{{ unseenCountBadgeText }}</span>
</span>
<span class="spacer" />
@@ -123,8 +123,6 @@
<script src="./mobile_nav.js"></script>
<style lang="scss">
-@import "../../variables";
-
.MobileNav {
z-index: var(--ZI_navbar);
@@ -137,7 +135,7 @@
box-sizing: border-box;
a {
- color: var(--topBarLink, $fallback--link);
+ color: var(--link);
}
}
@@ -165,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;
@@ -185,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%);
@@ -208,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;
@@ -238,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.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.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 a8eceab0..632ae6e9 100644
--- a/src/components/notification/notification.vue
+++ b/src/components/notification/notification.vue
@@ -155,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"
@@ -247,7 +247,6 @@
/>
<template v-else>
<StatusContent
- :class="{ faint: !statusExpanded }"
:compact="!statusExpanded"
:status="notification.status"
/>
diff --git a/src/components/notifications/notification_filters.vue b/src/components/notifications/notification_filters.vue
index 1315b51a..cc506e78 100644
--- a/src/components/notifications/notification_filters.vue
+++ b/src/components/notifications/notification_filters.vue
@@ -8,65 +8,65 @@
<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('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.scss b/src/components/notifications/notifications.scss
index 5749e430..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;
}
diff --git a/src/components/notifications/notifications.vue b/src/components/notifications/notifications.vue
index a0025182..87e7b68d 100644
--- a/src/components/notifications/notifications.vue
+++ b/src/components/notifications/notifications.vue
@@ -18,7 +18,7 @@
{{ $t('notifications.notifications') }}
<span
v-if="unseenCountBadgeText"
- class="badge badge-notification unseen-count"
+ class="badge -notification unseen-count"
>{{ unseenCountBadgeText }}</span>
</div>
<div
@@ -85,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.vue b/src/components/poll/poll.vue
index f7e16665..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
@@ -103,8 +106,6 @@
<script src="./poll.js"></script>
<style lang="scss">
-@import "../../variables";
-
.poll {
.votes {
display: flex;
@@ -114,6 +115,10 @@
.poll-option {
margin: 0.75em 0.5em;
+
+ .input {
+ line-height: inherit;
+ }
}
.option-result {
@@ -121,8 +126,7 @@
display: flex;
flex-direction: row;
position: relative;
- color: $fallback--lightText;
- color: var(--lightText, $fallback--lightText);
+ color: var(--textLight);
}
.option-result-label {
@@ -141,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.vue b/src/components/popover/popover.vue
index 1a4bffd9..0c5b372e 100644
--- a/src/components/popover/popover.vue
+++ b/src/components/popover/popover.vue
@@ -42,8 +42,6 @@
<script src="./popover.js" />
<style lang="scss">
-@import "../../variables";
-
.popover-trigger-button {
display: inline-block;
}
@@ -53,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);
}
}
@@ -138,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;
@@ -188,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.vue b/src/components/post_status_form/post_status_form.vue
index 9b108a5a..cf411ff1 100644
--- a/src/components/post_status_form/post_status_form.vue
+++ b/src/components/post_status_form/post_status_form.vue
@@ -161,7 +161,7 @@
v-model="newStatus.spoilerText"
enable-emoji-picker
:suggest="emojiSuggestor"
- class="form-control"
+ class="input form-control"
>
<template #default="inputProps">
<input
@@ -171,7 +171,7 @@
:disabled="posting && !optimisticPosting"
v-bind="propsToNative(inputProps)"
size="1"
- class="form-post-subject"
+ class="input form-post-subject"
>
</template>
</EmojiInput>
@@ -180,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"
@@ -198,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)"
@@ -237,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
@@ -375,8 +375,6 @@
<script src="./post_status_form.js"></script>
<style lang="scss">
-@import "../../variables";
-
.post-status-form {
position: relative;
@@ -437,15 +435,12 @@
.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;
}
@@ -456,8 +451,7 @@
.text-format {
.only-format {
- color: $fallback--faint;
- color: var(--faint, $fallback--faint);
+ color: var(--textFaint);
}
}
@@ -503,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 {
@@ -580,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;
@@ -591,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;
@@ -609,8 +579,7 @@
margin: 0 0.5em;
&.error {
- color: $fallback--cRed;
- color: var(--cRed, $fallback--cRed);
+ color: var(--cRed);
}
}
@@ -633,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/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 67aa6713..e35fd579 100644
--- a/src/components/quick_view_settings/quick_view_settings.js
+++ b/src/components/quick_view_settings/quick_view_settings.js
@@ -61,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/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.vue b/src/components/react_button/react_button.vue
index 1b0674e6..e648e7e3 100644
--- a/src/components/react_button/react_button.vue
+++ b/src/components/react_button/react_button.vue
@@ -41,7 +41,6 @@
<script src="./react_button.js"></script>
<style lang="scss">
-@import "../../variables";
@import "../../mixins";
.ReactButton {
@@ -58,7 +57,7 @@
height: 1px;
width: 100%;
margin: 0.5em;
- background-color: var(--border, $fallback--border);
+ background-color: var(--border);
}
.reaction-picker {
@@ -99,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.vue b/src/components/registration/registration.vue
index 5c913f94..a2008d87 100644
--- a/src/components/registration/registration.vue
+++ b/src/components/registration/registration.vue
@@ -25,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')"
>
@@ -53,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')"
>
@@ -81,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"
>
@@ -106,7 +106,7 @@
id="bio"
v-model="user.bio"
:disabled="isPending"
- class="form-control"
+ class="input form-control"
:placeholder="bioPlaceholder"
/>
</div>
@@ -123,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"
>
@@ -151,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"
>
@@ -184,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"
@@ -229,7 +229,7 @@
id="reason"
v-model="user.reason"
:disabled="isPending"
- class="form-control"
+ class="input form-control"
:placeholder="reasonPlaceholder"
/>
</div>
@@ -256,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"
@@ -275,7 +275,7 @@
id="token"
v-model="token"
disabled="true"
- class="form-control"
+ class="input form-control"
type="text"
>
</div>
@@ -320,9 +320,6 @@
<script src="./registration.js"></script>
<style lang="scss">
-@import "../../variables";
-$validations-cRed: #f04124;
-
.registration-form {
display: flex;
flex-direction: column;
@@ -369,8 +366,7 @@ $validations-cRed: #f04124;
}
.form-group--error .form--label {
- color: $validations-cRed;
- color: var(--cRed, $validations-cRed);
+ color: var(--cRed);
}
.form-error {
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.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 ff14a58a..99d7daca 100644
--- a/src/components/rich_content/rich_content.jsx
+++ b/src/components/rich_content/rich_content.jsx
@@ -79,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
@@ -277,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..3e840a52
--- /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)'
+ }
+ }
+ ]
+}
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/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.scss b/src/components/settings_modal/admin_tabs/emoji_tab.scss
index cc918870..3e77e019 100644
--- a/src/components/settings_modal/admin_tabs/emoji_tab.scss
+++ b/src/components/settings_modal/admin_tabs/emoji_tab.scss
@@ -1,5 +1,3 @@
-@import "src/variables";
-
.emoji-tab {
.btn-group .btn:not(:first-child) {
margin-left: 0.5em;
@@ -25,7 +23,7 @@
}
.emoji-unsaved {
- box-shadow: 0 3px 5px var(--cBlue, $fallback--cBlue);
+ box-shadow: 0 3px 5px var(--cBlue);
}
.emoji-list {
@@ -56,6 +54,6 @@
}
.warning {
- color: var(--cOrange, $fallback--cOrange);
+ 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
index 5231f860..5742d2ce 100644
--- a/src/components/settings_modal/admin_tabs/emoji_tab.vue
+++ b/src/components/settings_modal/admin_tabs/emoji_tab.vue
@@ -13,13 +13,15 @@
<button
class="button button-default btn"
type="button"
- @click="reloadEmoji">
+ @click="reloadEmoji"
+ >
{{ $t('admin_dash.emoji.reload') }}
</button>
<button
class="button button-default btn"
type="button"
- @click="importFromFS">
+ @click="importFromFS"
+ >
{{ $t('admin_dash.emoji.importFS') }}
</button>
</li>
@@ -28,7 +30,8 @@
<button
class="button button-default btn"
type="button"
- @click="$refs.remotePackPopover.showPopover">
+ @click="$refs.remotePackPopover.showPopover"
+ >
{{ $t('admin_dash.emoji.remote_packs') }}
<Popover
@@ -43,11 +46,16 @@
<template #content>
<div class="emoji-tab-popover-input">
<h3>{{ $t('admin_dash.emoji.remote_pack_instance') }}</h3>
- <input v-model="remotePackInstance" :placeholder="$t('admin_dash.emoji.remote_pack_instance')">
+ <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">
+ @click="listRemotePacks"
+ >
{{ $t('admin_dash.emoji.do_list') }}
</button>
</div>
@@ -61,9 +69,22 @@
<li>
<h4>{{ $t('admin_dash.emoji.edit_pack') }}</h4>
- <Select class="form-control" v-model="packName">
- <option value="" disabled hidden>{{ $t('admin_dash.emoji.emoji_pack') }}</option>
- <option v-for="(pack, listPackName) in knownPacks" :label="listPackName" :key="listPackName">
+ <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>
@@ -71,7 +92,8 @@
<button
class="button button-default btn emoji-tab-popover-button"
type="button"
- @click="$refs.createPackPopover.showPopover">
+ @click="$refs.createPackPopover.showPopover"
+ >
{{ $t('admin_dash.emoji.create_pack') }}
</button>
<Popover
@@ -86,11 +108,16 @@
<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')">
+ <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">
+ @click="createEmojiPack"
+ >
{{ $t('admin_dash.emoji.create') }}
</button>
</div>
@@ -105,67 +132,96 @@
<li>
<label>
{{ $t('admin_dash.emoji.description') }}
- <ModifiedIndicator :changed="metaEdited('description')" message-key="admin_dash.emoji.metadata_changed" />
+ <ModifiedIndicator
+ :changed="metaEdited('description')"
+ message-key="admin_dash.emoji.metadata_changed"
+ />
<textarea
v-model="packMeta.description"
:disabled="pack.remote !== undefined"
- class="bio resize-height" />
+ 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" />
+ <ModifiedIndicator
+ :changed="metaEdited('homepage')"
+ message-key="admin_dash.emoji.metadata_changed"
+ />
- <input
- class="emoji-info-input" v-model="packMeta.homepage"
- :disabled="pack.remote !== undefined">
+ <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" />
+ <ModifiedIndicator
+ :changed="metaEdited('fallback-src')"
+ message-key="admin_dash.emoji.metadata_changed"
+ />
- <input class="emoji-info-input" v-model="packMeta['fallback-src']" :disabled="pack.remote !== undefined">
+ <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 :disabled="true" class="emoji-info-input" v-model="packMeta['fallback-src-sha256']">
+ <input
+ v-model="packMeta['fallback-src-sha256']"
+ :disabled="true"
+ class="emoji-info-input input"
+ >
</label>
</li>
<li>
- <Checkbox :disabled="pack.remote !== undefined" v-model="packMeta['share-files']">
+ <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" />
+ <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"
- v-if="pack.remote === undefined"
- @click="savePackMetadata">
+ @click="savePackMetadata"
+ >
{{ $t('admin_dash.emoji.save_meta') }}
</button>
<button
+ v-if="pack.remote === undefined"
class="button button-default btn"
type="button"
- v-if="pack.remote === undefined"
- @click="savePackMetadata">
+ @click="savePackMetadata"
+ >
{{ $t('admin_dash.emoji.revert_meta') }}
</button>
<button
- class="button button-default btn"
v-if="pack.remote === undefined"
+ class="button button-default btn"
type="button"
- @click="deleteModalVisible = true">
+ @click="deleteModalVisible = true"
+ >
{{ $t('admin_dash.emoji.delete_pack') }}
<ConfirmModal
@@ -174,16 +230,18 @@
:cancel-text="$t('status.delete_confirm_cancel_button')"
:confirm-text="$t('status.delete_confirm_accept_button')"
@cancelled="deleteModalVisible = false"
- @accepted="deleteEmojiPack" >
+ @accepted="deleteEmojiPack"
+ >
{{ $t('admin_dash.emoji.delete_confirm', [packName]) }}
</ConfirmModal>
</button>
<button
+ v-if="pack.remote !== undefined"
class="button button-default btn"
type="button"
- v-if="pack.remote !== undefined"
- @click="$refs.dlPackPopover.showPopover">
+ @click="$refs.dlPackPopover.showPopover"
+ >
{{ $t('admin_dash.emoji.download_pack') }}
<Popover
@@ -202,12 +260,17 @@
<div class="emoji-tab-popover-input">
<label>
{{ $t('admin_dash.emoji.download_as_name') }}
- <input class="emoji-data-input"
+ <input
v-model="remotePackDownloadAs"
- :placeholder="$t('admin_dash.emoji.download_as_name_full')">
+ class="emoji-data-input input"
+ :placeholder="$t('admin_dash.emoji.download_as_name_full')"
+ >
</label>
- <div v-if="downloadWillReplaceLocal" class="warning">
+ <div
+ v-if="downloadWillReplaceLocal"
+ class="warning"
+ >
<em>{{ $t('admin_dash.emoji.replace_warning') }}</em>
</div>
</div>
@@ -215,7 +278,8 @@
<button
class="button button-default btn"
type="button"
- @click="downloadRemotePack">
+ @click="downloadRemotePack"
+ >
{{ $t('admin_dash.emoji.download') }}
</button>
</div>
@@ -231,31 +295,47 @@
<h4>
{{ $t('admin_dash.emoji.files') }}
- <ModifiedIndicator v-if="pack"
+ <ModifiedIndicator
+ v-if="pack"
:changed="$refs.emojiPopovers && $refs.emojiPopovers.some(p => p.isEdited)"
- message-key="admin_dash.emoji.emoji_changed"/>
+ message-key="admin_dash.emoji.emoji_changed"
+ />
</h4>
- <div class="emoji-list" v-if="pack">
+ <div
+ v-if="pack"
+ class="emoji-list"
+ >
<EmojiEditingPopover
v-if="pack.remote === undefined"
- placement="bottom" new-upload
+ placement="bottom"
+ new-upload
:title="$t('admin_dash.emoji.adding_new')"
- :packName="packName"
- @updatePackFiles="updatePackFiles" @displayError="displayError"
+ :pack-name="packName"
+ @updatePackFiles="updatePackFiles"
+ @displayError="displayError"
>
<template #trigger>
- <FAIcon icon="plus" size="2x" :title="$t('admin_dash.emoji.add_file')" />
+ <FAIcon
+ icon="plus"
+ size="2x"
+ :title="$t('admin_dash.emoji.add_file')"
+ />
</template>
</EmojiEditingPopover>
<EmojiEditingPopover
- placement="top" ref="emojiPopovers"
- v-for="(file, shortcode) in pack.files" :key="shortcode"
+ 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" :packName="packName"
- @updatePackFiles="updatePackFiles" @displayError="displayError"
+ :shortcode="shortcode"
+ :file="file"
+ :pack-name="packName"
+ @updatePackFiles="updatePackFiles"
+ @displayError="displayError"
>
<template #trigger>
<StillImage
diff --git a/src/components/settings_modal/admin_tabs/frontends_tab.vue b/src/components/settings_modal/admin_tabs/frontends_tab.vue
index 097877bc..8fb3d399 100644
--- a/src/components/settings_modal/admin_tabs/frontends_tab.vue
+++ b/src/components/settings_modal/admin_tabs/frontends_tab.vue
@@ -6,7 +6,10 @@
<div class="setting-item">
<h2>{{ $t('admin_dash.tabs.frontends') }}</h2>
<p>{{ $t('admin_dash.frontend.wip_notice') }}</p>
- <ul class="setting-list" v-if="adminDraft">
+ <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>
@@ -23,12 +26,18 @@
</ul>
</li>
</ul>
- <div v-else class="setting-list">
+ <div
+ v-else
+ class="setting-list"
+ >
{{ $t('admin_dash.frontend.default_frontend_unavail') }}
</div>
<div class="setting-list relative">
- <PanelLoading class="overlay" v-if="working"/>
+ <PanelLoading
+ v-if="working"
+ class="overlay"
+ />
<h3>{{ $t('admin_dash.frontend.available_frontends') }}</h3>
<ul class="cards-list">
<li
@@ -107,7 +116,7 @@
<button
v-for="ref in frontend.refs"
:key="ref"
- class="button-default dropdown-item"
+ class="menu-item dropdown-item"
@click.prevent="update(frontend, ref)"
@click="close"
>
@@ -164,7 +173,7 @@
<button
v-for="ref in frontend.installedRefs || frontend.refs"
:key="ref"
- class="button-default dropdown-item"
+ class="menu-item dropdown-item"
@click.prevent="setDefault(frontend, ref)"
@click="close"
>
diff --git a/src/components/settings_modal/admin_tabs/instance_tab.vue b/src/components/settings_modal/admin_tabs/instance_tab.vue
index a0e3351e..32e8df25 100644
--- a/src/components/settings_modal/admin_tabs/instance_tab.vue
+++ b/src/components/settings_modal/admin_tabs/instance_tab.vue
@@ -8,7 +8,10 @@
</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" />
+ <AttachmentSetting
+ compact
+ path=":pleroma.:instance.:favicon"
+ />
</li>
<li>
<StringSetting path=":pleroma.:instance.:email" />
@@ -20,7 +23,10 @@
<StringSetting path=":pleroma.:instance.:short_description" />
</li>
<li>
- <AttachmentSetting compact path=":pleroma.:instance.:instance_thumbnail" />
+ <AttachmentSetting
+ compact
+ path=":pleroma.:instance.:instance_thumbnail"
+ />
</li>
<li>
<AttachmentSetting path=":pleroma.:instance.:background_image" />
diff --git a/src/components/settings_modal/helpers/attachment_setting.vue b/src/components/settings_modal/helpers/attachment_setting.vue
index b50231f2..96c80ab1 100644
--- a/src/components/settings_modal/helpers/attachment_setting.vue
+++ b/src/components/settings_modal/helpers/attachment_setting.vue
@@ -29,7 +29,7 @@
<label for="path">{{ $t('settings.url') }}</label>
<input
:id="path"
- class="string-input"
+ class="input string-input"
:disabled="shouldBeDisabled"
:value="realDraftMode ? draft : state"
@change="update"
diff --git a/src/components/settings_modal/helpers/emoji_editing_popover.vue b/src/components/settings_modal/helpers/emoji_editing_popover.vue
index cdd3e403..f0465dd5 100644
--- a/src/components/settings_modal/helpers/emoji_editing_popover.vue
+++ b/src/components/settings_modal/helpers/emoji_editing_popover.vue
@@ -1,10 +1,10 @@
<template>
<Popover
+ ref="emojiPopover"
trigger="click"
:placement="placement"
bound-to-selector=".emoji-list"
popover-class="emoji-tab-edit-popover popover-default"
- ref="emojiPopover"
:bound-to="{ x: 'container' }"
:offset="{ y: 5 }"
:disabled="disabled"
@@ -18,23 +18,36 @@
{{ title }}
</h3>
- <StillImage class="emoji" v-if="emojiPreview" :src="emojiPreview" />
- <div v-else class="emoji"></div>
-
- <div class="emoji-tab-popover-input" v-if="newUpload">
+ <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"
- @change="uploadFile = $event.target.files">
+ 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 class="emoji-data-input"
+ <input
v-model="editedShortcode"
- :placeholder="$t('admin_dash.emoji.new_shortcode')">
+ class="emoji-data-input input"
+ :placeholder="$t('admin_dash.emoji.new_shortcode')"
+ >
</label>
</div>
@@ -42,9 +55,11 @@
<label>
{{ $t('admin_dash.emoji.filename') }}
- <input class="emoji-data-input"
+ <input
v-model="editedFile"
- :placeholder="$t('admin_dash.emoji.new_filename')">
+ class="emoji-data-input input"
+ :placeholder="$t('admin_dash.emoji.new_filename')"
+ >
</label>
</div>
@@ -52,7 +67,8 @@
class="button button-default btn"
type="button"
:disabled="newUpload ? uploadFile.length == 0 : !isEdited"
- @click="newUpload ? uploadEmoji() : saveEditedEmoji()">
+ @click="newUpload ? uploadEmoji() : saveEditedEmoji()"
+ >
{{ $t('admin_dash.emoji.save') }}
</button>
@@ -60,13 +76,15 @@
<button
class="button button-default btn emoji-tab-popover-button"
type="button"
- @click="deleteModalVisible = true">
+ @click="deleteModalVisible = true"
+ >
{{ $t('admin_dash.emoji.delete') }}
</button>
<button
class="button button-default btn emoji-tab-popover-button"
type="button"
- @click="revertEmoji">
+ @click="revertEmoji"
+ >
{{ $t('admin_dash.emoji.revert') }}
</button>
<ConfirmModal
@@ -75,7 +93,8 @@
:cancel-text="$t('status.delete_confirm_cancel_button')"
:confirm-text="$t('status.delete_confirm_accept_button')"
@cancelled="deleteModalVisible = false"
- @accepted="deleteEmoji" >
+ @accepted="deleteEmoji"
+ >
{{ $t('admin_dash.emoji.delete_confirm', [shortcode]) }}
</ConfirmModal>
</template>
@@ -91,6 +110,30 @@ 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: [],
@@ -113,7 +156,6 @@ export default {
return !this.newUpload && (this.editedShortcode !== this.shortcode || this.editedFile !== this.file)
}
},
- inject: ['emojiAddr'],
methods: {
saveEditedEmoji () {
if (!this.isEdited) return
@@ -167,29 +209,6 @@ export default {
this.$emit('updatePackFiles', resp)
})
}
- },
- emits: ['updatePackFiles', 'displayError'],
- 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: ''
- }
}
}
</script>
diff --git a/src/components/settings_modal/helpers/number_setting.vue b/src/components/settings_modal/helpers/number_setting.vue
index 93f11331..23c1a5dd 100644
--- a/src/components/settings_modal/helpers/number_setting.vue
+++ b/src/components/settings_modal/helpers/number_setting.vue
@@ -17,7 +17,7 @@
</label>
<input
:id="path"
- class="number-input"
+ class="input number-input"
type="number"
:step="step || 1"
:disabled="shouldBeDisabled"
diff --git a/src/components/settings_modal/helpers/string_setting.vue b/src/components/settings_modal/helpers/string_setting.vue
index 0cfa61ce..7b30d1b9 100644
--- a/src/components/settings_modal/helpers/string_setting.vue
+++ b/src/components/settings_modal/helpers/string_setting.vue
@@ -17,7 +17,7 @@
</label>
<input
:id="path"
- class="string-input"
+ class="input string-input"
:disabled="shouldBeDisabled"
:value="realDraftMode ? draft : state"
@change="update"
diff --git a/src/components/settings_modal/helpers/unit_setting.vue b/src/components/settings_modal/helpers/unit_setting.vue
index d6aafe26..68f52b1c 100644
--- a/src/components/settings_modal/helpers/unit_setting.vue
+++ b/src/components/settings_modal/helpers/unit_setting.vue
@@ -11,7 +11,7 @@
</label>
<input
:id="path"
- class="number-input"
+ class="input number-input"
type="number"
step="1"
:disabled="disabled"
diff --git a/src/components/settings_modal/settings_modal.scss b/src/components/settings_modal/settings_modal.scss
index 6bc9459b..d01553db 100644
--- a/src/components/settings_modal/settings_modal.scss
+++ b/src/components/settings_modal/settings_modal.scss
@@ -1,5 +1,3 @@
-@import "src/variables";
-
.settings-modal {
overflow: hidden;
diff --git a/src/components/settings_modal/settings_modal.vue b/src/components/settings_modal/settings_modal.vue
index 4e7fd931..50859c94 100644
--- a/src/components/settings_modal/settings_modal.vue
+++ b/src/components/settings_modal/settings_modal.vue
@@ -14,7 +14,7 @@
<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') }}
@@ -70,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"
>
@@ -80,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"
>
@@ -90,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"
>
diff --git a/src/components/settings_modal/tabs/filtering_tab.vue b/src/components/settings_modal/tabs/filtering_tab.vue
index ab4f5b18..821ca750 100644
--- a/src/components/settings_modal/tabs/filtering_tab.vue
+++ b/src/components/settings_modal/tabs/filtering_tab.vue
@@ -45,6 +45,29 @@
</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>
@@ -67,7 +90,7 @@
<textarea
id="muteWords"
v-model="muteWordsString"
- class="resize-height"
+ class="input resize-height"
/>
<div>{{ $t('settings.filtering_explanation') }}</div>
</li>
diff --git a/src/components/settings_modal/tabs/notifications_tab.vue b/src/components/settings_modal/tabs/notifications_tab.vue
index 9ace4c36..ca2cf8ad 100644
--- a/src/components/settings_modal/tabs/notifications_tab.vue
+++ b/src/components/settings_modal/tabs/notifications_tab.vue
@@ -19,7 +19,10 @@
</div>
</li>
<li>
- <BooleanSetting path="unseenAtTop" expert="1">
+ <BooleanSetting
+ path="unseenAtTop"
+ expert="1"
+ >
{{ $t('settings.notification_setting_unseen_at_top') }}
</BooleanSetting>
</li>
@@ -38,7 +41,9 @@
</li>
<li>
<h3> {{ $t('settings.notification_visibility') }}</h3>
- <p v-if="expertLevel > 0">{{ $t('settings.notification_setting_filters_chrome_push') }}</p>
+ <p v-if="expertLevel > 0">
+ {{ $t('settings.notification_setting_filters_chrome_push') }}
+ </p>
<ul class="setting-list two-column">
<li>
<h4> {{ $t('settings.notification_visibility_mentions') }}</h4>
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 de5219a7..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>
@@ -205,6 +207,7 @@
<div>
<input
type="file"
+ class="input"
@change="uploadFile('banner', $event)"
>
</div>
@@ -247,6 +250,7 @@
<div>
<input
type="file"
+ class="input"
@change="uploadFile('background', $event)"
>
</div>
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 d36d478f..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
@@ -155,6 +160,7 @@
</i18n-t>
<input
v-model="addAliasTarget"
+ class="input"
>
</div>
<button
@@ -187,6 +193,7 @@
</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 58f8d44a..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,
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.vue b/src/components/side_drawer/side_drawer.vue
index 09588767..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"
@@ -181,7 +209,7 @@
@click="toggleDrawer"
>
<button
- class="button-unstyled -link -fullwidth"
+ class="menu-item"
@click.stop="openAdminModal"
>
<FAIcon
@@ -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 43181897..e4fb1f51 100644
--- a/src/components/status/status.js
+++ b/src/components/status/status.js
@@ -238,6 +238,9 @@ const Status = {
showActorTypeIndicator () {
return !this.hideBotIndication
},
+ sensitiveStatus () {
+ return this.status.nsfw
+ },
mentionsLine () {
if (!this.headTailLinks) return []
const writtenSet = new Set(this.headTailLinks.writtenMentions.map(_ => _.url))
@@ -265,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 () {
@@ -371,6 +376,9 @@ const Status = {
muteBotStatuses () {
return this.mergedConfig.muteBotStatuses
},
+ muteSensitiveStatuses () {
+ return this.mergedConfig.muteSensitiveStatuses
+ },
hideBotIndication () {
return this.mergedConfig.hideBotIndication
},
diff --git a/src/components/status/status.scss b/src/components/status/status.scss
index 760c6ac1..dfdc1189 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;
@@ -425,8 +401,8 @@
.quoted-status {
margin-top: 0.5em;
- border: 1px solid var(--border, $fallback--border);
- border-radius: var(--attachmentRadius, $fallback--attachmentRadius);
+ border: 1px solid var(--border);
+ border-radius: var(--roundness);
&.-unavailable-prompt {
padding: 0.5em;
diff --git a/src/components/status/status.vue b/src/components/status/status.vue
index 1c91c36c..2a17bb13 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"
>
@@ -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
@@ -450,7 +456,7 @@
>
<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"
>
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_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 b444da43..027a380a 100644
--- a/src/components/tab_switcher/tab_switcher.jsx
+++ b/src/components/tab_switcher/tab_switcher.jsx
@@ -97,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 1050b87a..47e4a45e 100644
--- a/src/components/timeline/timeline.js
+++ b/src/components/timeline/timeline.js
@@ -77,13 +77,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
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.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.vue b/src/components/user_avatar/user_avatar.vue
index 3cbccec3..83608c50 100644
--- a/src/components/user_avatar/user_avatar.vue
+++ b/src/components/user_avatar/user_avatar.vue
@@ -32,12 +32,10 @@
<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;
@@ -48,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);
@@ -69,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);
}
}
@@ -92,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.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 2c76a220..70c4f67d 100644
--- a/src/components/user_card/user_card.vue
+++ b/src/components/user_card/user_card.vue
@@ -113,19 +113,19 @@
<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.actor_type === 'Service'"
- class="alert user-role"
+ class="alert neutral user-role"
>
{{ $t('user_card.bot') }}
</span>
@@ -166,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"
>
{{ ' ' }}
@@ -282,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.vue b/src/components/user_profile/user_profile.vue
index d0618dbb..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"
@@ -117,7 +127,7 @@
:title="$t('user_card.favorites')"
timeline-name="favorites"
:timeline="favorites"
- :user-id="userId"
+ :user-id="isUs ? undefined : userId"
:in-profile="true"
:footer-slipgate="footerRef"
/>
@@ -136,7 +146,7 @@
{{ $t('settings.profile_tab') }}
</div>
</div>
- <div class="panel-body">
+ <div>
<span v-if="error">{{ error }}</span>
<FAIcon
v-else
@@ -151,8 +161,6 @@
<script src="./user_profile.js"></script>
<style lang="scss">
-@import "../../variables";
-
.user-profile {
flex: 2;
flex-basis: 500px;
@@ -182,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%;
@@ -192,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 {
@@ -229,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/hocs/with_load_more/with_load_more.scss b/src/hocs/with_load_more/with_load_more.scss
index bad852f4..524e4012 100644
--- a/src/hocs/with_load_more/with_load_more.scss
+++ b/src/hocs/with_load_more/with_load_more.scss
@@ -1,12 +1,9 @@
-@import "../../variables";
-
.with-load-more {
&-footer {
padding: 10px;
text-align: center;
border-top: 1px solid;
- border-top-color: $fallback--border;
- border-top-color: var(--border, $fallback--border);
+ border-top-color: var(--border);
.error {
font-size: 1rem;
diff --git a/src/i18n/cs.json b/src/i18n/cs.json
index ac1ff37a..9c04ede7 100644
--- a/src/i18n/cs.json
+++ b/src/i18n/cs.json
@@ -737,7 +737,8 @@
"frontend_version": "Frontend verze"
},
"commit_value_tooltip": "Hodnota není uložena, stiskněte toto tlačítko pro potvrzení změn",
- "hard_reset_value_tooltip": "Odstranit nastavení z úložiště a vynutit výchozí hodnotu"
+ "hard_reset_value_tooltip": "Odstranit nastavení z úložiště a vynutit výchozí hodnotu",
+ "accent": "Akcentní barva"
},
"time": {
"day": "{0} day",
@@ -748,7 +749,7 @@
"hours": "{0} hours",
"hour_short": "{0}h",
"hours_short": "{0}h",
- "in_future": "in {0}",
+ "in_future": "za {0}",
"in_past": "před {0}",
"minute": "{0} minute",
"minutes": "{0} minutes",
@@ -758,8 +759,8 @@
"months": "{0} měs",
"month_short": "{0} měs",
"months_short": "{0} měs",
- "now": "teď",
- "now_short": "teď",
+ "now": "právě teď",
+ "now_short": "nyní",
"second": "{0} second",
"seconds": "{0} seconds",
"second_short": "{0}s",
@@ -771,7 +772,23 @@
"year": "{0} r",
"years": "{0} l",
"year_short": "{0}r",
- "years_short": "{0}l"
+ "years_short": "{0}l",
+ "unit": {
+ "seconds_short": "{0}s",
+ "days": "{0} den | {0} dnů",
+ "days_short": "{0}d",
+ "hours": "{0} hodina | {0} hodin",
+ "hours_short": "{0}h",
+ "minutes": "{0} minuta | {0} minut",
+ "months": "{0} měsíc | {0} měsíců",
+ "months_short": "{0}mo",
+ "minutes_short": "{0}min",
+ "seconds": "{0} sekunda | {0} sekund",
+ "weeks": "{0} týden | {0} týdnů",
+ "weeks_short": "{0}w",
+ "years": "{0} rok | {0} roky",
+ "years_short": "{0}y"
+ }
},
"timeline": {
"collapse": "Zabalit",
@@ -783,11 +800,60 @@
"show_new": "Zobrazit nové",
"up_to_date": "Aktuální",
"no_more_statuses": "Žádné další příspěvky",
- "no_statuses": "Žádné příspěvky"
+ "no_statuses": "Žádné příspěvky",
+ "socket_reconnected": "Navázáno spojení v reálném čase",
+ "error": "Chyba při načítání časové osy: {0}",
+ "reload": "Načíst znovu",
+ "socket_broke": "Spojení v reálném čase ztraceno: CloseEvent code {0}"
},
"status": {
"reply_to": "Odpověď uživateli",
- "replies_list": "Odpovědi:"
+ "replies_list": "Odpovědi:",
+ "many_attachments": "Příspěvek má {number} příloh(u)",
+ "collapse_attachments": "Sbalit přílohy",
+ "unpin": "Odepnout z profilu",
+ "thread_muted": "Vlákno ztlumeno",
+ "show_attachment_description": "Popis náhledu (otevřete přílohu pro celý popis)",
+ "move_down": "Posunout přílohu doprava",
+ "thread_show": "Zobrazit toto vlákno",
+ "pin": "Připnout na profil",
+ "mute_conversation": "Ztlumit konverzaci",
+ "thread_hide": "Skrýt toto vlákno",
+ "show_full_subject": "Zobrazit celý předmět",
+ "edited_at": "(naposledy upraveno {time})",
+ "repeat_confirm_accept_button": "Zopakovat",
+ "repeat_confirm_title": "Potvrzení zopakování",
+ "delete_error": "Chyba při mazání příspěvku: {0}",
+ "delete_confirm": "Opravdu chcete smazat tento příspěvek?",
+ "delete_confirm_title": "Potvrzení smazání",
+ "delete_confirm_accept_button": "Smazat",
+ "delete_confirm_cancel_button": "Ponechat",
+ "you": "(Vy)",
+ "hide_attachment": "Skrýt přílohu",
+ "remove_attachment": "Odstranit přílohu",
+ "attachment_stop_flash": "Zastavit Flash player",
+ "nsfw": "NSFW",
+ "repeat_confirm_cancel_button": "Neopakovat",
+ "favorites": "Oblíbené",
+ "repeats": "Opakovaní",
+ "repeat_confirm": "Opravdu chcete zopakovat tento příspěvek?",
+ "delete": "Smazat příspěvek",
+ "copy_link": "Kopírovat odkaz k příspěvku",
+ "external_source": "Externí zdroj",
+ "edit": "Upravit příspěvek",
+ "bookmark": "Přidat do záložek",
+ "unbookmark": "Odebrat ze záložek",
+ "mentions": "Zmínky",
+ "hide_full_subject": "Skrýt celý předmět",
+ "show_content": "Zobrazit obsah",
+ "hide_content": "Skrýt obsah",
+ "unmute_conversation": "Zrušit ztlumení konverzace",
+ "status_unavailable": "Příspěvek je nedostupný",
+ "status_deleted": "Tento příspěvek byl smazán",
+ "expand": "Rozbalit",
+ "show_all_attachments": "Zobrazit všechny přílohy",
+ "move_up": "Posunout přílohu doleva",
+ "open_gallery": "Otevřít galerii"
},
"user_card": {
"approve": "Schválit",
@@ -995,13 +1061,121 @@
"nodb": "Žádné nastavení v databázi",
"frontends": "Frontendy",
"instance": "Instance",
- "limits": "Limity"
+ "limits": "Limity",
+ "emoji": "Emoji"
},
"nodb": {
- "heading": "Nastavení v databázi je vypnuto"
+ "heading": "Nastavení v databázi je vypnuto",
+ "documentation": "dokumentace",
+ "text2": "Většina konfiguračních možností nebude dostupná."
},
"wip_notice": "Tento administrační panel je experimentální a v aktivní vývoji, {adminFeLink}.",
"old_ui_link": "staré administrační rozhraní je dostupné zde",
- "reset_all": "Resetovat vše"
+ "reset_all": "Resetovat vše",
+ "frontend": {
+ "failure_installing_frontend": "Nepodařilo se nainstalovat frontend {version}: {reason}",
+ "reinstall": "Přeinstalovat",
+ "available_frontends": "Dostupné k instalaci",
+ "is_default": "(Výchozí)",
+ "versions": "Dostupné verze",
+ "build_url": "URL sestavení",
+ "install": "Instalovat",
+ "install_version": "Instalovat verzi {version}",
+ "more_install_options": "Více instalačních možností",
+ "more_default_options": "Více výchozích nastavení pro možnosti",
+ "set_default": "Nastavit výchozí",
+ "default_frontend": "Výchozí frontend",
+ "set_default_version": "Nastavit verzi {version} jako výchozí",
+ "repository": "Odkaz k repozitáři",
+ "is_default_custom": "(Výchozí, verze: {version})",
+ "success_installing_frontend": "Frontend {version} byl úspěšně nainstalován"
+ },
+ "captcha": {
+ "native": "Nativní",
+ "kocaptcha": "KoCaptcha"
+ },
+ "instance": {
+ "instance": "Informace o instanci",
+ "captcha_header": "CAPTCHA",
+ "restrict": {
+ "activities": "Přístup k příspěvkům a aktivitám",
+ "timelines": "Přístup k časovým osám",
+ "profiles": "Přístup k uživatelským profilům",
+ "header": "Omezit přístup pro anonymní návštěvníky"
+ },
+ "registrations": "Registrace uživatelů",
+ "kocaptcha": "KoCaptcha nastavení"
+ },
+ "limits": {
+ "posts": "Limity příspěvků",
+ "uploads": "Limity příloh",
+ "users": "Limity uživatelských profilů",
+ "arbitrary_limits": "Libovolné limity",
+ "profile_fields": "Limity profilových polí",
+ "user_uploads": "Limity médií profilů"
+ },
+ "emoji": {
+ "global_actions": "Globální akce",
+ "reload": "Znovu načíst emoji",
+ "importFS": "Importovat emoji ze souborového systému",
+ "error": "Chyba: {0}",
+ "create_pack": "Vytvořit balíček",
+ "delete_pack": "Smazat balíček",
+ "new_pack_name": "Nový název balíčku",
+ "create": "Vytvořit",
+ "emoji_packs": "Emoji balíčky",
+ "remote_packs": "Vzdálené balíčky",
+ "do_list": "List",
+ "emoji_pack": "Emoji balíček",
+ "edit_pack": "Upravit balíček",
+ "description": "Popis",
+ "homepage": "Domovská stránka",
+ "fallback_src": "Záložní zdroj",
+ "fallback_sha256": "Záložní SHA256",
+ "share": "Sdílet",
+ "save": "Uložit",
+ "save_meta": "Uložit metadata",
+ "revert_meta": "Vrátit zpět metadata",
+ "delete": "Smazat",
+ "add_file": "Přidat soubor",
+ "adding_new": "Přidávání nových emoji",
+ "shortcode": "Zkratka",
+ "filename": "Jméno souboru",
+ "new_shortcode": "Zkrat, ponechte prázdné pro odvození",
+ "delete_confirm": "Opravdu chcete smazat {0}?",
+ "download_pack": "Stáhnout balíček",
+ "downloading_pack": "Stahování {0}",
+ "download": "Stáhnout",
+ "download_as_name": "Nové jméno",
+ "download_as_name_full": "Nové jméno, pro opakované použití ponechte prázdné",
+ "files": "Soubory",
+ "editing": "Upravování {0}",
+ "delete_title": "Smazat?",
+ "emoji_changed": "Neuložené změny emoji souborů, zkontrolujte zvýrazněné emoji",
+ "replace_warning": "Tímto se NAHRADÍ místní balíček se stejným jménem",
+ "metadata_changed": "Metadata jsou rozdílné od uložených",
+ "revert": "Vrátit zpět",
+ "new_filename": "Jméno souboru, ponechte prázdné pro odvození"
+ },
+ "temp_overrides": {
+ ":pleroma": {
+ ":instance": {
+ ":background_image": {
+ "label": "Obrázek na pozadí",
+ "description": "Obrázek na pozadí (především používáno PleromaFE)"
+ },
+ ":description_limit": {
+ "label": "Limit",
+ "description": "Limit počtu znaků pro popisy příloh"
+ },
+ ":public": {
+ "label": "Instance je veřejná"
+ },
+ ":limit_to_local_content": {
+ "label": "Limitovat vyhledávání pouze na místní obsah"
+ }
+ }
+ }
+ }
}
}
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 2fd4668d..7a03b49d 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -186,7 +186,6 @@
"edit_pinned": "Edit pinned items",
"edit_finish": "Done editing",
"mobile_sidebar": "Toggle mobile sidebar",
- "mobile_notifications": "Open notifications",
"mobile_notifications": "Open notifications (there are unread ones)",
"mobile_notifications_close": "Close notifications",
"mobile_notifications_mark_as_seen": "Mark all as seen",
@@ -511,6 +510,7 @@
"hide_actor_type_indication": "Hide actor type (bots, groups, etc.) indication in posts",
"hide_scrobbles": "Hide scrobbles",
"hide_scrobbles_after": "Hide scrobbles older than",
+ "mute_sensitive_posts": "Mute sensitive posts",
"hide_all_muted_posts": "Hide muted posts",
"max_thumbnails": "Maximum amount of thumbnails per post (empty = no limit)",
"hide_isp": "Hide instance-specific panel",
@@ -915,7 +915,7 @@
"description": "Detailed setting for allowing/disallowing access to certain aspects of API. By default (indeterminate state) it will disallow if instance is not public, ticked checkbox means disallow access even if instance is public, unticked means allow access even if instance is private. Please note that unexpected behavior might happen if some settings are set, i.e. if profile access is disabled posts will show without profile information.",
"timelines": "Timelines access",
"profiles": "User profiles access",
- "activities": "Statues/activities access"
+ "activities": "Statuses/activities access"
}
},
"limits": {
@@ -940,8 +940,8 @@
"set_default": "Set default",
"set_default_version": "Set version {version} as default",
"wip_notice": "Please note that this section is a WIP and lacks certain features as backend implementation of front-end management is incomplete.",
- "default_frontend": "Default front-end",
- "default_frontend_tip": "Default front-end will be shown to all users. Currently there's no way to for a user to select personal front-end. If you switch away from PleromaFE you'll most likely have to use old and buggy AdminFE to do instance configuration until we replace it.",
+ "default_frontend": "Default frontend",
+ "default_frontend_tip": "Default frontend will be shown to all users. Currently there's no way to for a user to select personal frontend. If you switch away from PleromaFE you'll most likely have to use old and buggy AdminFE to do instance configuration until we replace it.",
"default_frontend_unavail": "Default frontend settings are not available, as this requires configuration in the database",
"available_frontends": "Available for install",
"failure_installing_frontend": "Failed to install frontend {version}: {reason}",
@@ -1084,6 +1084,7 @@
"external_source": "External source",
"thread_muted": "Thread muted",
"thread_muted_and_words": ", has words:",
+ "sensitive_muted": "Muting sensitive content",
"show_full_subject": "Show full subject",
"hide_full_subject": "Hide full subject",
"show_content": "Show content",
@@ -1120,7 +1121,9 @@
"hide_quote": "Hide the quoted status",
"display_quote": "Display the quoted status",
"invisible_quote": "Quoted status unavailable: {link}",
- "more_actions": "More actions on this status"
+ "more_actions": "More actions on this status",
+ "loading": "Loading...",
+ "load_error": "Unable to load status: {error}"
},
"user_card": {
"approve": "Approve",
diff --git a/src/i18n/fr.json b/src/i18n/fr.json
index f86d1821..e2396937 100644
--- a/src/i18n/fr.json
+++ b/src/i18n/fr.json
@@ -90,7 +90,11 @@
"heading": {
"totp": "Authentification à double-facteur",
"recovery": "Récupération de l'authentification à double-facteur"
- }
+ },
+ "logout_confirm_title": "Confirmation de déconnexion",
+ "logout_confirm": "Souhaitez-vous vous déconnecter ?",
+ "logout_confirm_accept_button": "Déconnexion",
+ "logout_confirm_cancel_button": "Ne pas se déconnecter"
},
"media_modal": {
"previous": "Précédent",
@@ -110,7 +114,7 @@
"timeline": "Flux personnel",
"twkn": "Réseau connu",
"user_search": "Recherche de comptes",
- "who_to_follow": "Suggestion de suivit",
+ "who_to_follow": "Suggestion de suivi",
"preferences": "Préférences",
"search": "Recherche",
"administration": "Administration",
@@ -124,7 +128,10 @@
"edit_pinned": "Éditer les éléments agrafés",
"edit_finish": "Édition terminée",
"mobile_sidebar": "(Dés)activer le panneau latéral",
- "mobile_notifications_close": "Fermer les notifications"
+ "mobile_notifications_close": "Fermer les notifications",
+ "search_close": "Fermer la barre de recherche",
+ "announcements": "Annonces",
+ "mobile_notifications_mark_as_seen": "Marquer tout comme vu"
},
"notifications": {
"broken_favorite": "Message inconnu, recherche en cours…",
@@ -140,7 +147,13 @@
"follow_request": "veut vous suivre",
"error": "Erreur de chargement des notifications : {0}",
"poll_ended": "Sondage terminé",
- "submitted_report": "Rapport envoyé"
+ "submitted_report": "Rapport envoyé",
+ "unread_announcements": "{num} annonce non lue | {num} annonces non lues",
+ "unread_chats": "{num} message non lu | {num} messages non lus",
+ "configuration_tip_settings": "les préférences",
+ "unread_follow_requests": "{num} nouvelle demande de suivi | {num} nouvelles demandes de suivi",
+ "configuration_tip": "Vous pouvez personnaliser ce qui est affiché ici dans {theSettings}. {dismiss}",
+ "configuration_tip_dismiss": "Ne plus montrer"
},
"interactions": {
"favs_repeats": "Partages et favoris",
@@ -154,7 +167,7 @@
"new_status": "Poster un nouveau statut",
"account_not_locked_warning": "Votre compte n'est pas {0}. N'importe qui peut vous suivre pour voir vos billets en Abonné·e·s uniquement.",
"account_not_locked_warning_link": "verrouillé",
- "attachments_sensitive": "Marquer les pièce-jointes comme sensible",
+ "attachments_sensitive": "Marquer les pièces jointes comme sensible",
"content_type": {
"text/plain": "Texte brut",
"text/html": "HTML",
@@ -183,9 +196,13 @@
"preview": "Prévisualisation",
"media_description": "Description de la pièce-jointe",
"post": "Post",
- "edit_status": "Éditer le status",
+ "edit_status": "Éditer le statut",
"edit_remote_warning": "Des instances distantes pourraient ne pas supporter l'édition et seront incapables de recevoir la nouvelle version de votre post.",
- "edit_unsupported_warning": "Pleroma ne supporte pas l'édition de mentions ni de sondages."
+ "edit_unsupported_warning": "Pleroma ne supporte pas l'édition de mentions ni de sondages.",
+ "reply_option": "Répondre à ce statut",
+ "quote_option": "Citer ce statut",
+ "scope_notice_dismiss": "Fermer ce message",
+ "content_type_selection": "Format du statut"
},
"registration": {
"bio": "Biographie",
@@ -205,14 +222,18 @@
"email_required": "ne peut pas être laissé vide",
"password_required": "ne peut pas être laissé vide",
"password_confirmation_required": "ne peut pas être laissé vide",
- "password_confirmation_match": "doit être identique au mot de passe"
+ "password_confirmation_match": "doit être identique au mot de passe",
+ "birthday_min_age": "doit être le ou avant le {date}",
+ "birthday_required": "ne peut pas être vide"
},
"reason_placeholder": "Cette instance modère les inscriptions manuellement.\nExpliquer ce qui motive votre inscription à l'administration.",
"reason": "Motivation d'inscription",
"register": "Enregistrer",
"email_language": "Dans quelle langue voulez-vous recevoir les emails du server ?",
"bio_optional": "Biographie (optionnelle)",
- "email_optional": "Courriel (optionnel)"
+ "email_optional": "Courriel (optionnel)",
+ "birthday": "Anniversaire :",
+ "birthday_optional": "Anniversaire (optionnel) :"
},
"selectable_list": {
"select_all": "Tout selectionner"
@@ -684,7 +705,64 @@
"use_websockets": "Utiliser les websockets (mises à jour en temps réel)",
"user_popover_avatar_action_zoom": "Zoomer sur l'avatar",
"user_popover_avatar_action_open": "Ouvrir le profil",
- "conversation_display_tree_quick": "Vue arborescente"
+ "conversation_display_tree_quick": "Vue arborescente",
+ "emoji_reactions_scale": "Taille des réactions",
+ "backup_running": "Cette sauvegarde est en cours, {number} enregistrement effectué. | Cette sauvegarde est en cours, {number} enregistrements effectués.",
+ "backup_failed": "Cette sauvegarde a échoué.",
+ "autocomplete_select_first": "Sélectionner automatiquement la première occurrence lorsque les résultats de l'autocomplétion sont disponibles",
+ "confirm_dialogs_unfollow": "arrête de suivre un utilisateur",
+ "confirm_dialogs_repeat": "reposte un statut",
+ "actor_type": "Ce compte est :",
+ "actor_type_Person": "un utilisateur normal",
+ "actor_type_Service": "un robot",
+ "actor_type_Group": "un groupe",
+ "confirm_dialogs_logout": "à la déconnexion",
+ "confirm_dialogs_approve_follow": "accepte un nouvel abonné",
+ "confirm_dialogs_deny_follow": "refuse un nouvel abonné",
+ "confirm_dialogs_remove_follower": "supprime un abonné",
+ "actor_type_description": "En marquant votre compte comme un groupe, vous répétez automatiquement les statuts qui le mentionnent.",
+ "add_language": "Ajouter une langue de remplacement",
+ "remove_language": "Supprimer",
+ "primary_language": "Langue principale :",
+ "fallback_language": "Langue de remplacement {index} :",
+ "confirm_dialogs": "Demande de confirmation quand",
+ "confirm_dialogs_block": "bloque un utilisateur",
+ "confirm_dialogs_mute": "mute un utilisateur",
+ "confirm_dialogs_delete": "supprime un statut",
+ "url": "URL",
+ "preview": "Aperçu",
+ "reset_value": "Réinitialiser",
+ "hard_reset_value_tooltip": "Supprime le réglage du stockage, force l'utilisation de la valeur par défaut",
+ "reset_value_tooltip": "Réinitialiser le brouillon",
+ "hard_reset_value": "Remise à zéro",
+ "hide_actor_type_indication": "Cacher le type (robots, groupes, etc.) dans les status",
+ "notification_extra_follow_requests": "Afficher les nouvelles demandes de suivi",
+ "user_popover_avatar_action": "Action du clic sur l'avatar",
+ "user_popover_avatar_action_close": "Fermer la fenêtre contextuelle",
+ "notification_setting_ignore_inactionable_seen": "Ignorer les status de lecture des notifications non actionnables (favoris, répétitions, etc)",
+ "notification_setting_ignore_inactionable_seen_tip": "Ceci ne marquera pas ces notifications comme lues, et vous recevrez encore les notifications de bureau si vous le décidez",
+ "notification_setting_unseen_at_top": "Afficher les notifications non lues au-dessus des autres",
+ "notification_setting_filters_chrome_push": "Sur certains navigateurs (chrome), il peut être impossible de filtrer complètement les notifications par type lorsqu'elles arrivent",
+ "enable_web_push_always_show": "Toujours afficher les notifications web",
+ "commit_value": "Sauvegarder",
+ "hide_scrobbles": "Masquer les scrobbles",
+ "notification_setting_annoyance": "Agacement",
+ "notification_setting_drawer_marks_as_seen": "Fermer le tiroir marque toutes les notifications comme lues (mobile)",
+ "commit_value_tooltip": "Les valeurs ne sont pas sauvegardées, appuyez sur ce bouton pour soumettre vos changements",
+ "birthday": {
+ "show_birthday": "Afficher mon anniversaire",
+ "label": "Anniversaire"
+ },
+ "notification_visibility_native_notifications": "Afficher une notification native",
+ "notification_visibility_follow_requests": "Demandes de suivi",
+ "notification_visibility_reports": "Rapports",
+ "notification_extra_chats": "Afficher les discussions non lues",
+ "notification_extra_announcements": "Afficher les annonces non lues",
+ "notification_extra_tip": "Afficher les astuces de personnalisation pour les notifications extras",
+ "enable_web_push_always_show_tip": "Certains navigateurs (Chromium, Chrome) exigent que les messages push donnent toujours lieu à une notification, sinon le message générique \"Le site web a été mis à jour en arrière-plan\" s'affiche ; activez cette option pour empêcher l'affichage de cette notification, car Chrome semble masquer les notifications push si l'onglet est au centre de l'attention. Cela peut entraîner l'affichage de notifications en double sur d'autres navigateurs.",
+ "user_popover_avatar_overlay": "Afficher la fenêtre contextuelle sur l'avatar de l'utilisateur",
+ "notification_visibility_in_column": "Afficher la colonne / le tiroir de notifications",
+ "notification_show_extra": "Afficher les extras dans la colonne de notifications"
},
"timeline": {
"collapse": "Fermer",
@@ -758,7 +836,20 @@
"show_all_conversation": "Montrer tout le fil ({numStatus} autre message) | Montrer tout le fil ({numStatus} autre messages)",
"edit": "Éditer le status",
"edited_at": "(dernière édition {time})",
- "status_history": "Historique du status"
+ "status_history": "Historique du status",
+ "delete_error": "Erreur de suppression du statut : {0}",
+ "repeat_confirm": "Voulez-vous réellement reposter ce statut ?",
+ "reaction_count_label": "{num} personne a réagi | {num} personnes ont réagi",
+ "repeat_confirm_cancel_button": "Ne pas reposter",
+ "hide_quote": "Masquer les status cités",
+ "display_quote": "Afficher les status cités",
+ "invisible_quote": "Citation de statut non disponible : {link}",
+ "delete_confirm_title": "Confirmer la suppression",
+ "more_actions": "Plus d'action sur ce statut",
+ "delete_confirm_cancel_button": "Conserver",
+ "repeat_confirm_title": "Confirmer reposte",
+ "repeat_confirm_accept_button": "Reposter",
+ "delete_confirm_accept_button": "Supprimer"
},
"user_card": {
"approve": "Accepter",
@@ -828,7 +919,39 @@
"edit_profile": "Éditer le profil",
"deactivated": "Désactivé",
"follow_cancel": "Annuler la requête",
- "remove_follower": "Retirer l'abonné·e"
+ "remove_follower": "Retirer l'abonné·e",
+ "remove_follower_confirm_accept_button": "Supprimer",
+ "approve_confirm_cancel_button": "Ne pas approuver",
+ "block_confirm_accept_button": "Bloquer",
+ "mute_confirm_title": "Confirmation de mise en sourdine",
+ "block_confirm_cancel_button": "Ne pas bloquer",
+ "unfollow_confirm": "Voulez-vous vraiment arrêter de suivre {user} ?",
+ "unfollow_confirm_accept_button": "Ne plus suivre",
+ "birthday": "Né(e) le {birthday}",
+ "edit_note": "Éditer note",
+ "edit_note_apply": "Appliquer",
+ "edit_note_cancel": "Abandonner",
+ "note": "Note",
+ "group": "Groupe",
+ "unfollow_confirm_title": "Confirmer l'arrêt de suivi",
+ "block_confirm_title": "Confirmer le blocage",
+ "deny_confirm_accept_button": "Refuser",
+ "deny_confirm_cancel_button": "Ne pas refuser",
+ "deny_confirm": "Voulez-vous refuser la demande de suivi de {user} ?",
+ "deny_confirm_title": "Refuser la confirmation",
+ "remove_follower_confirm_cancel_button": "Conserver",
+ "mute_duration_prompt": "Mettre cet utilisateur en sourdine pour (0 pour une durée indéterminée) :",
+ "remove_follower_confirm_title": "Confirmation de suppression d'utilisateur",
+ "note_blank": "(Aucun)",
+ "mute_confirm": "Voulez-vous vraiment mettre {user} en sourdine ?",
+ "mute_confirm_accept_button": "Mettre en sourdine",
+ "mute_confirm_cancel_button": "Ne pas mettre en sourdine",
+ "remove_follower_confirm": "Voulez-vous vraiment supprimer {user} de vos abonnés ?",
+ "approve_confirm_accept_button": "Approuver",
+ "approve_confirm": "Voulez-vous approuver la demande de suivi de {user} ?",
+ "block_confirm": "Voulez-vous vraiment bloquer {user} ?",
+ "approve_confirm_title": "Approuver confirmation",
+ "unfollow_confirm_cancel_button": "Ne pas arrêter le suivi"
},
"user_profile": {
"timeline_title": "Flux du compte",
@@ -857,7 +980,10 @@
"add_reaction": "Ajouter une réaction",
"accept_follow_request": "Accepter la demande de suivit",
"reject_follow_request": "Rejeter la demande de suivit",
- "bookmark": "Favori"
+ "bookmark": "Favori",
+ "autocomplete_available": "{number} résultat est disponible. Utilisez les touches haut et bas pour naviguer à l'intérieur. | {number} résultats sont disponibles. Utilisez les touches haut et bas pour naviguer à l'intérieur.",
+ "toggle_expand": "Développer ou réduire la notification pour afficher le message dans son intégralité",
+ "toggle_mute": "Développer ou réduire la notification pour révéler le contenu en sourdine"
},
"upload": {
"error": {
@@ -950,7 +1076,9 @@
"symbols": "Symboles",
"travel-and-places": "Voyages & lieux"
},
- "regional_indicator": "Indicateur régional {letter}"
+ "regional_indicator": "Indicateur régional {letter}",
+ "unpacked": "Émojis non catégorisés",
+ "hide_custom_emoji": "Masquer les émojis personnalisés"
},
"remote_user_resolver": {
"error": "Non trouvé.",
@@ -1012,7 +1140,7 @@
"person_talking": "{count} personnes discutant",
"hashtags": "Mot-dièses",
"people_talking": "{count} personnes discutant",
- "no_results": "Aucun résultats",
+ "no_results": "Aucun résultat",
"no_more_results": "Pas de résultats supplémentaires",
"load_more": "Charger plus de résultats"
},
@@ -1083,7 +1211,8 @@
"update_changelog_here": "Liste compète des changements",
"art_by": "Œuvre par {linkToArtist}",
"big_update_content": "Nous n'avons pas fait de nouvelle version depuis un moment, les choses peuvent vous paraitre différentes de vos habitudes.",
- "update_bugs": "Veuillez rapporter les problèmes sur {pleromaGitlab}, comme beaucoup de changements on été fait, même si nous testons entièrement et utilisons la version de dévelopement nous-même, nous avons pu en louper. Les retours et suggestions sont bienvenues sur ce que vous avez pu rencontrer, ou sur comment améliorer Pleroma (BE) et Pleroma-FE."
+ "update_bugs": "Veuillez rapporter les problèmes sur {pleromaGitlab}, comme beaucoup de changements on été fait, même si nous testons entièrement et utilisons la version de dévelopement nous-même, nous avons pu en louper. Les retours et suggestions sont bienvenues sur ce que vous avez pu rencontrer, ou sur comment améliorer Pleroma (BE) et Pleroma-FE.",
+ "big_update_title": "Soyez indulgent avec nous"
},
"unicode_domain_indicator": {
"tooltip": "Ce domaine contient des caractères non ascii."
@@ -1097,5 +1226,158 @@
"state_open": "Ouvert",
"state_closed": "Fermé",
"state_resolved": "Résolut"
+ },
+ "announcements": {
+ "page_header": "Annonces",
+ "title": "Annonce",
+ "mark_as_read_action": "Marquer comme lu",
+ "post_form_header": "Faire une annonce",
+ "post_placeholder": "Écrivez le contenu de l'annonce ici...",
+ "post_action": "Envoyer",
+ "post_error": "Erreur : {error}",
+ "close_error": "Fermer",
+ "delete_action": "Supprimer",
+ "start_time_prompt": "Heure de début : ",
+ "end_time_prompt": "Heure de fin : ",
+ "all_day_prompt": "L'événement dure toute la journée",
+ "inactive_message": "Cette annonce n'est pas active",
+ "published_time_display": "Publié le {time}",
+ "start_time_display": "Démarre à {time}",
+ "end_time_display": "Se termine à {time}",
+ "edit_action": "Modifier",
+ "submit_edit_action": "Envoyer",
+ "cancel_edit_action": "Annuler"
+ },
+ "admin_dash": {
+ "frontend": {
+ "success_installing_frontend": "Installation réussie de l'interface {version}",
+ "failure_installing_frontend": "Échec de l'installation de l'interface {version} : {reason}",
+ "default_frontend_unavail": "Les paramètres de l'interface ne sont pas disponibles, ils doivent être configurés dans la base de données",
+ "build_url": "Construction URL",
+ "reinstall": "Réinstaller",
+ "repository": "Lien du dépôt",
+ "versions": "Versions disponibles",
+ "default_frontend_tip": "L'interface par défaut sera affichée à tous les utilisateurs. Si vous décidez de quitter PleromaFE, vous devrez utiliser l'ancienne AdminFE buguée pour configurer votre instance jusqu'à ce que nous la remplacions.",
+ "is_default": "(Défaut)",
+ "is_default_custom": "(Défaut, version : {version})",
+ "install": "Installation",
+ "install_version": "Installation de la version {version}",
+ "more_install_options": "Plus d'options d'installation",
+ "more_default_options": "Plus d'options de paramétrages par défaut",
+ "set_default": "Définir la valeur par défaut",
+ "set_default_version": "Définir la version {version} comme version par défaut",
+ "wip_notice": "Veuillez noter que cette section est en cours de développement et que certaines fonctionnalités de l'interface ne sont pas implémentées côté serveur.",
+ "default_frontend": "Interface par défaut",
+ "available_frontends": "Disponible pour installation"
+ },
+ "temp_overrides": {
+ ":pleroma": {
+ ":instance": {
+ ":public": {
+ "label": "Cette instance est publique",
+ "description": "En désactivant cette option, toutes les API ne seront accessibles qu'aux utilisateurs connectés, ce qui rendra les chronologies publiques et fédérées inaccessibles aux visiteurs anonymes."
+ },
+ ":limit_to_local_content": {
+ "label": "Limitez la recherche au contenu local",
+ "description": "Désactive la recherche globale sur le réseau pour les utilisateurs non authentifiés (par défaut), tous les utilisateurs ou aucun"
+ },
+ ":description_limit": {
+ "label": "Limite",
+ "description": "Limite de nombre de caractères pour la description des fichiers joints"
+ },
+ ":background_image": {
+ "description": "Image de fond (principalement utilisé par PleromaFE)",
+ "label": "Image de fond d'écran"
+ }
+ }
+ }
+ },
+ "tabs": {
+ "emoji": "Émoji",
+ "limits": "Limites",
+ "frontends": "Interfaces",
+ "instance": "Instance",
+ "nodb": "Pas de configuration de base de données"
+ },
+ "instance": {
+ "kocaptcha": "Réglages KoCaptcha",
+ "access": "Accès à l'instance",
+ "restrict": {
+ "header": "Restreindre l'accès aux visiteurs anonymes",
+ "profiles": "Accès aux profils d'utilisateur",
+ "activities": "Accès aux status/activités",
+ "description": "Paramètre détaillé permettant d'autoriser/interdire l'accès à certains aspects de l'API. Par défaut (état indéterminé), l'accès est interdit si l'instance n'est pas publique ; si la case est cochée, l'accès est interdit même si l'instance est publique ; si la case n'est pas cochée, l'accès est autorisé même si l'instance est privée. Veuillez noter qu'un comportement inattendu peut se produire si certains paramètres sont définis, par exemple si l'accès au profil est désactivé, les messages s'afficheront sans les informations relatives au profil.",
+ "timelines": "Accès aux flux"
+ },
+ "registrations": "Inscription des utilisateurs",
+ "captcha_header": "CAPTCHA",
+ "instance": "Informations sur l'instance"
+ },
+ "emoji": {
+ "global_actions": "Actions globales",
+ "reload": "Recharger les émojis",
+ "importFS": "Importer les émojis depuis le système de fichiers",
+ "error": "Erreur : {0}",
+ "create_pack": "Créer un pack",
+ "delete_pack": "Supprimer un paquet",
+ "new_pack_name": "Renommer le pack",
+ "create": "Créer",
+ "emoji_packs": "Pack d'émojis",
+ "remote_packs": "Packs d'autres instances",
+ "do_list": "Liste",
+ "remote_pack_instance": "Instance du pack",
+ "emoji_pack": "Pack d'émoji",
+ "edit_pack": "Modifier le pack",
+ "description": "Description",
+ "homepage": "Page d'accueil",
+ "fallback_src": "Source de remplacement",
+ "fallback_sha256": "Remplacement SHA256",
+ "share": "Partager",
+ "save": "Sauvegarder",
+ "save_meta": "Sauvegarder les métadonnées",
+ "revert_meta": "Annuler métadonnées",
+ "delete": "Supprimer",
+ "revert": "Revenir en arrière",
+ "add_file": "Ajouter un fichier",
+ "adding_new": "Ajouter un nouvel émoji",
+ "shortcode": "Shortcode",
+ "filename": "Nom du fichier",
+ "new_filename": "Nom de fichier, laisser blanc pour inférer",
+ "delete_confirm": "Êtes-vous sûr de vouloir supprimer {0} ?",
+ "download_pack": "Télécharger pack",
+ "downloading_pack": "Télécharge {0}",
+ "download": "Téléchargement",
+ "download_as_name": "Nouveau nom",
+ "download_as_name_full": "Nouveau nom, laissez blanc pour réutiliser le précédent",
+ "files": "Fichiers",
+ "editing": "Édition de {0}",
+ "delete_title": "Supprimer ?",
+ "metadata_changed": "Métadonnées différentes de celles sauvegardées",
+ "emoji_changed": "Modifications du fichier émoji non sauvegardées, vérifier l'émoji surligné",
+ "replace_warning": "Vous allez REMPLACER le pack local qui porte ce nom"
+ },
+ "window_title": "Administration",
+ "nodb": {
+ "heading": "La configuration de base de données est désactivée",
+ "documentation": "documentation",
+ "text2": "La majorité des options de configuration ne seront pas disponibles.",
+ "text": "Vous devez modifier les fichiers de configuration du serveur pour que {property} soit définie à {value}, plus de détails dans la {documentation}."
+ },
+ "limits": {
+ "arbitrary_limits": "Limites arbitraires",
+ "posts": "Limites des statuts",
+ "uploads": "Limites des pièces jointes",
+ "users": "Limites du profil d'utilisateur",
+ "profile_fields": "Limites des champs du profile",
+ "user_uploads": "Limites des médias du profil"
+ },
+ "captcha": {
+ "native": "Natif",
+ "kocaptcha": "KoCaptcha"
+ },
+ "wip_notice": "Ce tableau de bord d'administration est expérimental et en cours de développement, {adminFeLink}.",
+ "old_ui_link": "L'ancien espace d'administration est disponible ici",
+ "reset_all": "Tout réinitialiser",
+ "commit_all": "Tout sauvegarder"
}
}
diff --git a/src/i18n/ja_pedantic.json b/src/i18n/ja_pedantic.json
index 1e9510bd..616905fe 100644
--- a/src/i18n/ja_pedantic.json
+++ b/src/i18n/ja_pedantic.json
@@ -1243,7 +1243,8 @@
"tabs": {
"limits": "制限",
"instance": "インスタンス",
- "frontends": "フロントエンド"
+ "frontends": "フロントエンド",
+ "emoji": "絵文字"
},
"limits": {
"arbitrary_limits": "変更可能な制限",
@@ -1252,6 +1253,27 @@
"profile_fields": "追加情報欄の制限",
"user_uploads": "プロフィール画像の制限",
"users": "ユーザープロフィールの設定"
+ },
+ "emoji": {
+ "create_pack": "パックを作成",
+ "delete_pack": "パックを削除",
+ "create": "作成",
+ "emoji_packs": "絵文字パック",
+ "remote_packs": "リモートのパック",
+ "emoji_pack": "絵文字パック",
+ "edit_pack": "パックを編集",
+ "homepage": "ホームページ",
+ "save": "保存",
+ "save_meta": "メタデータを保存",
+ "shortcode": "ショートコード",
+ "filename": "ファイル名",
+ "delete_confirm": "{0}を削除してもよろしいですか?",
+ "download_pack": "パックをダウンロード",
+ "downloading_pack": "{0}をダウンロード中",
+ "download": "ダウンロード",
+ "editing": "{0}を編集中",
+ "error": "エラー: {0}",
+ "delete": "削除"
}
},
"lists": {
diff --git a/src/i18n/pt.json b/src/i18n/pt.json
index b997701c..76119eda 100644
--- a/src/i18n/pt.json
+++ b/src/i18n/pt.json
@@ -11,7 +11,8 @@
"title": "Características",
"who_to_follow": "Quem seguir",
"upload_limit": "Limite de carregamento",
- "pleroma_chat_messages": "Chat do Pleroma"
+ "pleroma_chat_messages": "Chat do Pleroma",
+ "shout": "Shoutbox"
},
"finder": {
"error_fetching_user": "Erro ao pesquisar utilizador",
@@ -36,11 +37,27 @@
"error_retry": "Por favor, tenta novamente",
"loading": "A carregar…",
"dismiss": "Ignorar",
- "role":
- {
+ "role": {
"moderator": "Moderador",
"admin": "Admin"
- }
+ },
+ "undo": "Refazer",
+ "yes": "Sim",
+ "no": "Não",
+ "unpin": "Desafixar o item",
+ "scroll_to_top": "Rolar para o topo",
+ "flash_content": "Clique para mostrar conteúdo Flash usando o Ruffle (Experimental, talvez não funcione).",
+ "flash_security": "Note que isso pode ser potencialmente perigoso dado que o conteúdo Flash ainda é código arbitrário.",
+ "flash_fail": "Falha ao carregar conteúdo flash, veja o console para detalhes.",
+ "scope_in_timeline": {
+ "direct": "Direct",
+ "private": "Apenas-seguidores",
+ "public": "Público",
+ "unlisted": "Não-listado"
+ },
+ "pin": "Fixar o item",
+ "generic_error_message": "Um erro ocorreu: {0}",
+ "never_show_again": "Não mostrar mais"
},
"image_cropper": {
"crop_picture": "Cortar imagem",
@@ -64,11 +81,17 @@
"recovery_code": "Código de recuperação",
"authentication_code": "Código de autenticação",
"enter_two_factor_code": "Introduza o código de dois fatores",
- "enter_recovery_code": "Introduza um código de recuperação"
+ "enter_recovery_code": "Introduza um código de recuperação",
+ "logout_confirm_title": "Confirmação de logoff",
+ "logout_confirm": "Você realmente quer sair?",
+ "logout_confirm_accept_button": "Sair",
+ "logout_confirm_cancel_button": "Não sair"
},
"media_modal": {
"previous": "Anterior",
- "next": "Próximo"
+ "next": "Próximo",
+ "counter": "{current} / {total}",
+ "hide": "Fechar visualizador de mídia"
},
"nav": {
"about": "Sobre",
@@ -88,7 +111,18 @@
"administration": "Administração",
"chats": "Salas de Chat",
"timelines": "Cronologias",
- "bookmarks": "Itens Guardados"
+ "bookmarks": "Itens Guardados",
+ "home_timeline": "Timeline da home",
+ "lists": "Listas",
+ "edit_pinned": "Editar itens fixados",
+ "edit_nav_mobile": "Customizar barra de navegação",
+ "mobile_notifications_mark_as_seen": "Marcar todas como vistas",
+ "search_close": "Fechar barra de busca",
+ "mobile_notifications_close": "Fechar notificações",
+ "announcements": "Anúncios",
+ "edit_finish": "Edição finalizada",
+ "mobile_sidebar": "Alternar barra lateral móvel",
+ "mobile_notifications": "Abrir notificações (há notificações não lidas)"
},
"notifications": {
"broken_favorite": "Publicação desconhecida, a procurar…",
@@ -102,7 +136,15 @@
"reacted_with": "reagiu com {0}",
"migrated_to": "migrou para",
"follow_request": "quer seguir-te",
- "error": "Erro ao obter notificações: {0}"
+ "error": "Erro ao obter notificações: {0}",
+ "unread_announcements": "{num} anúncio não lido | {num} anúncios não lidos",
+ "unread_chats": "{num} mensagem não lida | {num} mensagens não lidas",
+ "configuration_tip": "Você pode customizar o que você deseja mostrar aqui em {theSettings}. {dismiss}",
+ "unread_follow_requests": "{num} novo pedido de seguidor | {num} novos pedidos de seguidores",
+ "configuration_tip_settings": "as configurações",
+ "configuration_tip_dismiss": "Não mostrar novamente",
+ "poll_ended": "enquete finalizada",
+ "submitted_report": "enviado um relatório"
},
"post_status": {
"new_status": "Publicar nova publicação",
@@ -136,7 +178,14 @@
"media_description": "Descrição da multimédia",
"media_description_error": "Falha ao atualizar ficheiro, tente novamente",
"direct_warning_to_first_only": "Esta publicação só será visível para os utilizadores mencionados no início da mensagem.",
- "direct_warning_to_all": "Esta publicação será visível para todos os utilizadores mencionados."
+ "direct_warning_to_all": "Esta publicação será visível para todos os utilizadores mencionados.",
+ "edit_status": "Editar status",
+ "reply_option": "Responder a esse status",
+ "quote_option": "Citar esse status",
+ "edit_remote_warning": "Outras instâncias remotas talvez não suportem edição e sejam incapazes de receber a última versão do seu post.",
+ "content_type_selection": "Formato do post",
+ "scope_notice_dismiss": "Fechar essa notificação",
+ "edit_unsupported_warning": "Pleroma não suporta editar menções ou enquetes."
},
"registration": {
"bio": "Biografia",
@@ -156,8 +205,18 @@
"email_required": "não pode ser deixado em branco",
"password_required": "não pode ser deixado em branco",
"password_confirmation_required": "não pode ser deixado em branco",
- "password_confirmation_match": "deve corresponder à palavra-passe"
- }
+ "password_confirmation_match": "deve corresponder à palavra-passe",
+ "birthday_required": "não pode ser deixado em branco",
+ "birthday_min_age": "deve ser em ou antes de {date}"
+ },
+ "birthday": "Data de nascimento:",
+ "reason": "Razão para registrar",
+ "register": "Registrar",
+ "reason_placeholder": "Essa instância aprova os registros manualmente.\nPermita ao administrador saber o porquê do seu registro.",
+ "birthday_optional": "Data de nascimento (opcional):",
+ "bio_optional": "Bio (opcional)",
+ "email_optional": "Email (opcional)",
+ "email_language": "Em qual linguagem você deseja receber emails do servidor?"
},
"settings": {
"app_name": "Nome da aplicação",
@@ -523,7 +582,56 @@
"autohide_floating_post_button": "Automaticamente ocultar o botão 'Nova Publicação' (telemóvel)",
"notification_visibility_moves": "Utilizador Migrado",
"accent": "Destaque",
- "pad_emoji": "Preencher espaços ao adicionar emojis do seletor"
+ "pad_emoji": "Preencher espaços ao adicionar emojis do seletor",
+ "confirm_dialogs_logout": "saindo",
+ "move_account_error": "Erro ao mover conta: {error}",
+ "confirm_dialogs_delete": "excluindo um status",
+ "save": "Salvar mudanças",
+ "lists_navigation": "Mostrar listas na navegação",
+ "email_language": "Linguagem para receber emails do servidor",
+ "account_backup_description": "Isso permite a você baixar um arquivo das informações da sua conta e os seus posts, mas eles ainda não podem ser importados para uma conta do Pleroma.",
+ "add_backup_error": "Erro ao adicionar um novo backup: {error}",
+ "confirm_dialogs": "Pedir por confirmação quando",
+ "confirm_dialogs_repeat": "repetindo um status",
+ "account_alias": "Apelidos de conta",
+ "account_alias_table_head": "Apelido",
+ "list_aliases_error": "Erro ao buscar por apelidos: {error}",
+ "hide_list_aliases_error_action": "Fechar",
+ "confirm_dialogs_deny_follow": "negando um seguidor",
+ "confirm_dialogs_approve_follow": "aprovando um seguidor",
+ "backup_running": "Esse backup está em andamento, {number} registro processado. | Esse backup está em progresso, {number} registros processados.",
+ "add_backup": "Criar um novo backup",
+ "added_backup": "Adicionado um novo backup.",
+ "backup_failed": "Esse backup falhou.",
+ "list_backups_error": "Erro ao buscar a lista de backup: {error}",
+ "move_account_notes": "Se você deseja mover a conta para outro lugar, você deve ir para sua conta de destino e adicionar um apelido apontando para cá.",
+ "add_alias_error": "Erro ao adicionar apelido: {error}",
+ "move_account": "Mover conta",
+ "actor_type": "Essa conta é:",
+ "actor_type_description": "Marcando a sua conta como um grupo irá fazer com que ela automaticamente repita os status que a mencionam.",
+ "actor_type_Person": "um usuário normal",
+ "actor_type_Service": "um bot",
+ "actor_type_Group": "um grupo",
+ "account_backup": "Backup da conta",
+ "confirm_dialogs_unfollow": "deixando de seguir usuário",
+ "confirm_dialogs_block": "bloqueando um usuário",
+ "confirm_dialogs_remove_follower": "removendo um seguidor",
+ "remove_alias": "Remover esse apelido",
+ "new_alias_target": "Adicionar um novo apelido (e.g. {example})",
+ "added_alias": "Apelido adicionado.",
+ "move_account_target": "Conta de destino (e.g. {example})",
+ "moved_account": "Conta movida.",
+ "remove_language": "Remover",
+ "primary_language": "Linguagem primária:",
+ "fallback_language": "Linguagem de reserva {index}:",
+ "add_language": "Adicionar linguagem de reserva",
+ "expert_mode": "Mostrar avançados",
+ "setting_changed": "As configurações são diferentes do padrão",
+ "setting_server_side": "Essas configurações estão atreladas ao seu perfil e afetarão todas as sessões e clientes",
+ "mention_links": "Links de menção",
+ "confirm_dialogs_mute": "mutando um usuário",
+ "backup_not_ready": "Esse backup não está pronto ainda.",
+ "remove_backup": "Remover"
},
"timeline": {
"collapse": "Esconder",
@@ -699,7 +807,20 @@
"load_all": "A carregar todos os {emojiAmount} emojis",
"load_all_hint": "Carregado o primeiro emoji {saneAmount}, carregar todos os emojis pode causar problemas de desempenho.",
"keep_open": "Manter o seletor aberto",
- "stickers": "Autocolantes"
+ "stickers": "Autocolantes",
+ "hide_custom_emoji": "Ocultar emojis customizados",
+ "unicode_groups": {
+ "symbols": "Símbolos",
+ "activities": "Atividades",
+ "animals-and-nature": "Animais & Natureza",
+ "people-and-body": "Pessoas & Corpo",
+ "smileys-and-emotion": "Sorriso & Emoção",
+ "travel-and-places": "Viagem & Lugares",
+ "food-and-drink": "Comida & Bebidas",
+ "objects": "Objetos"
+ },
+ "regional_indicator": "Indicador regional {letter}",
+ "unpacked": "Emoji desempacotado"
},
"polls": {
"single_choice": "Escolha única",
@@ -713,7 +834,9 @@
"expiry": "Tempo para finalizar sondagem",
"multiple_choices": "Escolha múltipla",
"type": "Tipo de sondagem",
- "add_poll": "Adicionar Sondagem"
+ "add_poll": "Adicionar Sondagem",
+ "votes_count": "{count} voto | {count} votos",
+ "people_voted_count": "{count} pessoa votou | {count} pessoas votaram"
},
"importer": {
"error": "Ocorreu um erro ao importar este ficheiro.",
@@ -737,7 +860,9 @@
"load_older": "Carregar interações mais antigas",
"follows": "Novos seguidores",
"favs_repeats": "Gostos e Partilhas",
- "moves": "O utilizador migra"
+ "moves": "O utilizador migra",
+ "emoji_reactions": "Reações de Emoji",
+ "reports": "Relatórios"
},
"errors": {
"storage_unavailable": "O Pleroma não conseguiu aceder ao armazenamento do navegador. A sua sessão ou definições locais não serão armazenadas e poderá encontrar problemas inesperados. Tente ativar as cookies."
@@ -828,5 +953,35 @@
"day_short": "{0}d",
"days": "{0} dias",
"day": "{0} dia"
+ },
+ "report": {
+ "state_closed": "Fechar",
+ "reported_statuses": "Estado das denúncias:",
+ "reported_user": "Usuário denunciado:",
+ "state_resolved": "Resolvido",
+ "state": "Estado:",
+ "state_open": "Abrir",
+ "notes": "Notas:"
+ },
+ "announcements": {
+ "start_time_display": "Inicia às {time}",
+ "post_form_header": "Enviar anúncio",
+ "post_placeholder": "Digite o conteúdo do seu anúncio aqui...",
+ "page_header": "Anúncios",
+ "title": "Anúncio",
+ "mark_as_read_action": "Marcar como lido",
+ "post_action": "Postar",
+ "post_error": "Erro: {error}",
+ "close_error": "Fechar",
+ "delete_action": "Apagar",
+ "start_time_prompt": "Tempo de início: ",
+ "end_time_prompt": "Tempo de término: ",
+ "all_day_prompt": "Esse é um evento para o dia todo",
+ "published_time_display": "Publicado às {time}",
+ "end_time_display": "Finaliza às {time}",
+ "edit_action": "Editar",
+ "submit_edit_action": "Enviar",
+ "cancel_edit_action": "Cancelar",
+ "inactive_message": "Esse anúncio está inativo"
}
}
diff --git a/src/modules/config.js b/src/modules/config.js
index 23ee152c..a59a80d8 100644
--- a/src/modules/config.js
+++ b/src/modules/config.js
@@ -36,6 +36,7 @@ export const defaultState = {
hideMutedThreads: undefined, // instance default
hideWordFilteredPosts: undefined, // instance default
muteBotStatuses: undefined, // instance default
+ muteSensitiveStatuses: undefined, // instance default
collapseMessageWithSubject: undefined, // instance default
padEmoji: true,
hideAttachments: false,
diff --git a/src/modules/instance.js b/src/modules/instance.js
index c7a2cad1..f12cc735 100644
--- a/src/modules/instance.js
+++ b/src/modules/instance.js
@@ -71,6 +71,7 @@ const defaultState = {
hideSitename: false,
hideUserStats: false,
muteBotStatuses: false,
+ muteSensitiveStatuses: false,
modalOnRepeat: false,
modalOnUnfollow: false,
modalOnBlock: true,
@@ -386,6 +387,7 @@ const instance = {
} else {
applyTheme(themeData.theme)
}
+ commit('setThemeApplied')
})
},
fetchEmoji ({ dispatch, state }) {
diff --git a/src/modules/interface.js b/src/modules/interface.js
index f8d62d87..39242b9d 100644
--- a/src/modules/interface.js
+++ b/src/modules/interface.js
@@ -1,4 +1,5 @@
const defaultState = {
+ themeApplied: false,
settingsModalState: 'hidden',
settingsModalLoadedUser: false,
settingsModalLoadedAdmin: false,
@@ -35,6 +36,9 @@ const interfaceMod = {
state.settings.currentSaveStateNotice = { error: true, errorData: error }
}
},
+ setThemeApplied (state) {
+ state.themeApplied = true
+ },
setNotificationPermission (state, permission) {
state.notificationPermission = permission
},
diff --git a/src/panel.scss b/src/panel.scss
index 9471da11..833e4208 100644
--- a/src/panel.scss
+++ b/src/panel.scss
@@ -1,15 +1,24 @@
/* stylelint-disable no-descending-specificity */
.panel {
+ --__panel-background: var(--background);
+ --__panel-backdrop-filter: var(--backdrop-filter);
+
+ .tab-switcher .tabs {
+ background: var(--__panel-background);
+ backdrop-filter: var(--__panel-backdrop-filter);
+ }
+
position: relative;
display: flex;
flex-direction: column;
- background-color: $fallback--bg;
- background-color: var(--bg, $fallback--bg);
+
+ .panel-heading {
+ background-color: inherit;
+ }
&::after,
& {
- border-radius: $fallback--panelRadius;
- border-radius: var(--panelRadius, $fallback--panelRadius);
+ border-radius: var(--roundness);
}
&::after {
@@ -20,19 +29,25 @@
left: 0;
right: 0;
z-index: 5;
- box-shadow: 1px 1px 4px rgb(0 0 0 / 60%);
- box-shadow: var(--panelShadow);
+ box-shadow: var(--shadow);
pointer-events: none;
}
}
.panel-body {
padding: var(--panel-body-padding, 0);
+ background: var(--background);
+ backdrop-filter: var(--__panel-backdrop-filter);
+
+ .tab-switcher .tabs {
+ background: none;
+ backdrop-filter: none;
+ }
&:empty::before {
content: "¯\\_(ツ)_/¯"; // Could use words but it'd require translations
display: block;
- margin: 1em;
+ padding: 1em;
text-align: center;
}
@@ -50,6 +65,7 @@
--__panel-heading-height: 3.2em;
--__panel-heading-height-inner: calc(var(--__panel-heading-height) - 2 * var(--panel-heading-height-padding, 0));
+ backdrop-filter: var(--__panel-backdrop-filter);
position: relative;
box-sizing: border-box;
display: grid;
@@ -76,8 +92,7 @@
&.-stub {
&,
&::after {
- border-radius: $fallback--panelRadius;
- border-radius: var(--panelRadius, $fallback--panelRadius);
+ border-radius: var(--roundness);
}
}
@@ -119,82 +134,33 @@
padding-bottom: 0;
align-self: stretch;
}
+
+ > .alert {
+ line-height: calc(var(--__panel-heading-height-inner) - 2px);
+ }
}
}
// TODO Should refactor panels into separate component and utilize slots
.panel-heading {
- border-radius: $fallback--panelRadius $fallback--panelRadius 0 0;
- border-radius: var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius) 0 0;
+ border-radius: var(--roundness) var(--roundness) 0 0;
border-width: 0 0 1px;
align-items: start;
- // panel theme
- color: var(--panelText);
- background-color: $fallback--bg;
- background-color: var(--bg, $fallback--bg);
+ background-image:
+ linear-gradient(to bottom, var(--background), var(--background)),
+ linear-gradient(to bottom, var(--__panel-background), var(--__panel-background));
&::after {
- background-color: $fallback--fg;
- background-color: var(--panel, $fallback--fg);
+ background-color: var(--background);
z-index: -2;
- border-radius: $fallback--panelRadius $fallback--panelRadius 0 0;
- border-radius: var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius) 0 0;
- box-shadow: var(--panelHeaderShadow);
- }
-
- a,
- .-link {
- color: $fallback--link;
- color: var(--panelLink, $fallback--link);
- }
-
- .button-unstyled:hover,
- a:hover {
- i[class*="icon-"],
- .svg-inline--fa,
- .iconLetter {
- color: var(--panelText);
- }
- }
-
- .faint {
- background-color: transparent;
- color: $fallback--faint;
- color: var(--panelFaint, $fallback--faint);
- }
-
- .faint-link {
- color: $fallback--faint;
- color: var(--faintLink, $fallback--faint);
+ border-radius: var(--roundness) var(--roundness) 0 0;
+ box-shadow: var(--shadow);
}
&:not(.-flexible-height) {
> .button-default {
flex-shrink: 0;
-
- &,
- i[class*="icon-"] {
- color: $fallback--text;
- color: var(--btnPanelText, $fallback--text);
- }
-
- &:active {
- background-color: $fallback--fg;
- background-color: var(--btnPressedPanel, $fallback--fg);
- color: $fallback--text;
- color: var(--btnPressedPanelText, $fallback--text);
- }
-
- &:disabled {
- color: $fallback--text;
- color: var(--btnDisabledPanelText, $fallback--text);
- }
-
- &.toggled {
- color: $fallback--text;
- color: var(--btnToggledPanelText, $fallback--text);
- }
}
}
@@ -232,11 +198,12 @@
}
.panel-footer {
- border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius;
- border-radius: 0 0 var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius);
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
align-items: center;
border-width: 1px 0 0;
border-style: solid;
- border-color: var(--border, $fallback--border);
+ border-color: var(--border);
+ background-color: var(--__panel-background);
}
/* stylelint-enable no-descending-specificity */
diff --git a/src/services/color_convert/color_convert.js b/src/services/color_convert/color_convert.js
index 47d6344e..d92bbbe0 100644
--- a/src/services/color_convert/color_convert.js
+++ b/src/services/color_convert/color_convert.js
@@ -173,7 +173,7 @@ export const mixrgb = (a, b) => {
* @returns {String} CSS rgba() color
*/
export const rgba2css = function (rgba) {
- return `rgba(${Math.floor(rgba.r)}, ${Math.floor(rgba.g)}, ${Math.floor(rgba.b)}, ${rgba.a})`
+ return `rgba(${Math.floor(rgba.r)}, ${Math.floor(rgba.g)}, ${Math.floor(rgba.b)}, ${rgba.a ?? 1})`
}
/**
diff --git a/src/services/notifications_fetcher/notifications_fetcher.service.js b/src/services/notifications_fetcher/notifications_fetcher.service.js
index c91a86c8..6403693c 100644
--- a/src/services/notifications_fetcher/notifications_fetcher.service.js
+++ b/src/services/notifications_fetcher/notifications_fetcher.service.js
@@ -8,8 +8,11 @@ const mastoApiNotificationTypes = [
'favourite',
'reblog',
'follow',
+ 'follow_request',
'move',
+ 'poll',
'pleroma:emoji_reaction',
+ 'pleroma:chat_mention',
'pleroma:report'
]
diff --git a/src/services/style_setter/style_setter.js b/src/services/style_setter/style_setter.js
index 43fe3c73..95198ec7 100644
--- a/src/services/style_setter/style_setter.js
+++ b/src/services/style_setter/style_setter.js
@@ -1,24 +1,118 @@
-import { convert } from 'chromatism'
-import { rgb2hex, hex2rgb, rgba2css, getCssColor, relativeLuminance } from '../color_convert/color_convert.js'
-import { getColors, computeDynamicColor, getOpacitySlot } from '../theme_data/theme_data.service.js'
+import { hex2rgb } from '../color_convert/color_convert.js'
+import { generatePreset } from '../theme_data/theme_data.service.js'
+import { init } from '../theme_data/theme_data_3.service.js'
+import { convertTheme2To3 } from '../theme_data/theme2_to_theme3.js'
+import { getCssRules } from '../theme_data/css_utils.js'
import { defaultState } from '../../modules/config.js'
+import { chunk } from 'lodash'
+
+export const generateTheme = async (input, callbacks) => {
+ const {
+ onNewRule = (rule, isLazy) => {},
+ onLazyFinished = () => {},
+ onEagerFinished = () => {}
+ } = callbacks
+
+ let extraRules
+ if (input.themeFileVersion === 1) {
+ extraRules = convertTheme2To3(input)
+ } else {
+ const { theme } = generatePreset(input)
+ extraRules = convertTheme2To3(theme)
+ }
-export const applyTheme = (input) => {
- const { rules } = generatePreset(input)
- const head = document.head
- const body = document.body
- body.classList.add('hidden')
+ // Assuming that "worst case scenario background" is panel background since it's the most likely one
+ const themes3 = init(extraRules, extraRules[0].directives['--bg'].split('|')[1].trim())
+
+ getCssRules(themes3.eager, themes3.staticVars).forEach(rule => {
+ // Hacks to support multiple selectors on same component
+ if (rule.match(/::-webkit-scrollbar-button/)) {
+ const parts = rule.split(/[{}]/g)
+ const newRule = [
+ parts[0],
+ ', ',
+ parts[0].replace(/button/, 'thumb'),
+ ', ',
+ parts[0].replace(/scrollbar-button/, 'resizer'),
+ ' {',
+ parts[1],
+ '}'
+ ].join('')
+ onNewRule(newRule, false)
+ } else {
+ onNewRule(rule, false)
+ }
+ })
+ onEagerFinished()
+
+ // Optimization - instead of processing all lazy rules in one go, process them in small chunks
+ // so that UI can do other things and be somewhat responsive while less important rules are being
+ // processed
+ let counter = 0
+ const chunks = chunk(themes3.lazy, 200)
+ // let t0 = performance.now()
+ const processChunk = () => {
+ const chunk = chunks[counter]
+ Promise.all(chunk.map(x => x())).then(result => {
+ getCssRules(result.filter(x => x), themes3.staticVars).forEach(rule => {
+ if (rule.match(/\.modal-view/)) {
+ const parts = rule.split(/[{}]/g)
+ const newRule = [
+ parts[0],
+ ', ',
+ parts[0].replace(/\.modal-view/, '#modal'),
+ ', ',
+ parts[0].replace(/\.modal-view/, '.shout-panel'),
+ ' {',
+ parts[1],
+ '}'
+ ].join('')
+ onNewRule(newRule, true)
+ } else {
+ onNewRule(rule, true)
+ }
+ })
+ // const t1 = performance.now()
+ // console.debug('Chunk ' + counter + ' took ' + (t1 - t0) + 'ms')
+ // t0 = t1
+ counter += 1
+ if (counter < chunks.length) {
+ setTimeout(processChunk, 0)
+ } else {
+ onLazyFinished()
+ }
+ })
+ }
- const styleEl = document.createElement('style')
- head.appendChild(styleEl)
- const styleSheet = styleEl.sheet
+ return { lazyProcessFunc: processChunk }
+}
- styleSheet.toString()
- styleSheet.insertRule(`:root { ${rules.radii} }`, 'index-max')
- styleSheet.insertRule(`:root { ${rules.colors} }`, 'index-max')
- styleSheet.insertRule(`:root { ${rules.shadows} }`, 'index-max')
- styleSheet.insertRule(`:root { ${rules.fonts} }`, 'index-max')
- body.classList.remove('hidden')
+export const applyTheme = async (input) => {
+ const styleSheet = new CSSStyleSheet()
+ const lazyStyleSheet = new CSSStyleSheet()
+
+ const { lazyProcessFunc } = await generateTheme(
+ input,
+ {
+ onNewRule (rule, isLazy) {
+ if (isLazy) {
+ lazyStyleSheet.insertRule(rule, 'index-max')
+ } else {
+ styleSheet.insertRule(rule, 'index-max')
+ }
+ },
+ onEagerFinished () {
+ document.adoptedStyleSheets = [styleSheet]
+ },
+ onLazyFinished () {
+ document.adoptedStyleSheets = [styleSheet, lazyStyleSheet]
+ }
+ }
+ )
+
+ setTimeout(lazyProcessFunc, 0)
+
+ return Promise.resolve()
}
const configColumns = ({ sidebarColumnWidth, contentColumnWidth, notifsColumnWidth, emojiReactionsScale }) =>
@@ -51,308 +145,6 @@ export const applyConfig = (config) => {
body.classList.remove('hidden')
}
-export const getCssShadow = (input, usesDropShadow) => {
- if (input.length === 0) {
- return 'none'
- }
-
- return input
- .filter(_ => usesDropShadow ? _.inset : _)
- .map((shad) => [
- shad.x,
- shad.y,
- shad.blur,
- shad.spread
- ].map(_ => _ + 'px').concat([
- getCssColor(shad.color, shad.alpha),
- shad.inset ? 'inset' : ''
- ]).join(' ')).join(', ')
-}
-
-const getCssShadowFilter = (input) => {
- if (input.length === 0) {
- return 'none'
- }
-
- return input
- // drop-shadow doesn't support inset or spread
- .filter((shad) => !shad.inset && Number(shad.spread) === 0)
- .map((shad) => [
- shad.x,
- shad.y,
- // drop-shadow's blur is twice as strong compared to box-shadow
- shad.blur / 2
- ].map(_ => _ + 'px').concat([
- getCssColor(shad.color, shad.alpha)
- ]).join(' '))
- .map(_ => `drop-shadow(${_})`)
- .join(' ')
-}
-
-export const generateColors = (themeData) => {
- const sourceColors = !themeData.themeEngineVersion
- ? colors2to3(themeData.colors || themeData)
- : themeData.colors || themeData
-
- const { colors, opacity } = getColors(sourceColors, themeData.opacity || {})
-
- const htmlColors = Object.entries(colors)
- .reduce((acc, [k, v]) => {
- if (!v) return acc
- acc.solid[k] = rgb2hex(v)
- acc.complete[k] = typeof v.a === 'undefined' ? rgb2hex(v) : rgba2css(v)
- return acc
- }, { complete: {}, solid: {} })
- return {
- rules: {
- colors: Object.entries(htmlColors.complete)
- .filter(([k, v]) => v)
- .map(([k, v]) => `--${k}: ${v}`)
- .join(';')
- },
- theme: {
- colors: htmlColors.solid,
- opacity
- }
- }
-}
-
-export const generateRadii = (input) => {
- let inputRadii = input.radii || {}
- // v1 -> v2
- if (typeof input.btnRadius !== 'undefined') {
- inputRadii = Object
- .entries(input)
- .filter(([k, v]) => k.endsWith('Radius'))
- .reduce((acc, e) => { acc[e[0].split('Radius')[0]] = e[1]; return acc }, {})
- }
- const radii = Object.entries(inputRadii).filter(([k, v]) => v).reduce((acc, [k, v]) => {
- acc[k] = v
- return acc
- }, {
- btn: 4,
- input: 4,
- checkbox: 2,
- panel: 10,
- avatar: 5,
- avatarAlt: 50,
- tooltip: 2,
- attachment: 5,
- chatMessage: inputRadii.panel
- })
-
- return {
- rules: {
- radii: Object.entries(radii).filter(([k, v]) => v).map(([k, v]) => `--${k}Radius: ${v}px`).join(';')
- },
- theme: {
- radii
- }
- }
-}
-
-export const generateFonts = (input) => {
- const fonts = Object.entries(input.fonts || {}).filter(([k, v]) => v).reduce((acc, [k, v]) => {
- acc[k] = Object.entries(v).filter(([k, v]) => v).reduce((acc, [k, v]) => {
- acc[k] = v
- return acc
- }, acc[k])
- return acc
- }, {
- interface: {
- family: 'sans-serif'
- },
- input: {
- family: 'inherit'
- },
- post: {
- family: 'inherit'
- },
- postCode: {
- family: 'monospace'
- }
- })
-
- return {
- rules: {
- fonts: Object
- .entries(fonts)
- .filter(([k, v]) => v)
- .map(([k, v]) => `--${k}Font: ${v.family}`).join(';')
- },
- theme: {
- fonts
- }
- }
-}
-
-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 hoverGlow = {
- x: 0,
- y: 0,
- blur: 4,
- spread: 0,
- color: '--faint',
- alpha: 1
-}
-
-export const DEFAULT_SHADOWS = {
- panel: [{
- x: 1,
- y: 1,
- blur: 4,
- spread: 0,
- color: '#000000',
- alpha: 0.6
- }],
- topBar: [{
- x: 0,
- y: 0,
- blur: 4,
- spread: 0,
- color: '#000000',
- alpha: 0.6
- }],
- popup: [{
- x: 2,
- y: 2,
- blur: 3,
- spread: 0,
- color: '#000000',
- alpha: 0.5
- }],
- avatar: [{
- x: 0,
- y: 1,
- blur: 8,
- spread: 0,
- color: '#000000',
- alpha: 0.7
- }],
- avatarStatus: [],
- panelHeader: [],
- button: [{
- x: 0,
- y: 0,
- blur: 2,
- spread: 0,
- color: '#000000',
- alpha: 1
- }, ...buttonInsetFakeBorders],
- buttonHover: [hoverGlow, ...buttonInsetFakeBorders],
- buttonPressed: [hoverGlow, ...inputInsetFakeBorders],
- input: [...inputInsetFakeBorders, {
- x: 0,
- y: 0,
- blur: 2,
- inset: true,
- spread: 0,
- color: '#000000',
- alpha: 1
- }]
-}
-export const generateShadows = (input, colors) => {
- // TODO this is a small hack for `mod` to work with shadows
- // this is used to get the "context" of shadow, i.e. for `mod` properly depend on background color of element
- const hackContextDict = {
- button: 'btn',
- panel: 'bg',
- top: 'topBar',
- popup: 'popover',
- avatar: 'bg',
- panelHeader: 'panel',
- input: 'input'
- }
-
- const cleanInputShadows = Object.fromEntries(
- Object.entries(input.shadows || {})
- .map(([name, shadowSlot]) => [
- name,
- // defaulting color to black to avoid potential problems
- shadowSlot.map(shadowDef => ({ color: '#000000', ...shadowDef }))
- ])
- )
- const inputShadows = cleanInputShadows && !input.themeEngineVersion
- ? shadows2to3(cleanInputShadows, input.opacity)
- : cleanInputShadows || {}
- const shadows = Object.entries({
- ...DEFAULT_SHADOWS,
- ...inputShadows
- }).reduce((shadowsAcc, [slotName, shadowDefs]) => {
- const slotFirstWord = slotName.replace(/[A-Z].*$/, '')
- const colorSlotName = hackContextDict[slotFirstWord]
- const isLightOnDark = relativeLuminance(convert(colors[colorSlotName]).rgb) < 0.5
- const mod = isLightOnDark ? 1 : -1
- const newShadow = shadowDefs.reduce((shadowAcc, def) => [
- ...shadowAcc,
- {
- ...def,
- color: rgb2hex(computeDynamicColor(
- def.color,
- (variableSlot) => convert(colors[variableSlot]).rgb,
- mod
- ))
- }
- ], [])
- return { ...shadowsAcc, [slotName]: newShadow }
- }, {})
-
- return {
- rules: {
- shadows: Object
- .entries(shadows)
- // TODO for v2.2: if shadow doesn't have non-inset shadows with spread > 0 - optionally
- // convert all non-inset shadows into filter: drop-shadow() to boost performance
- .map(([k, v]) => [
- `--${k}Shadow: ${getCssShadow(v)}`,
- `--${k}ShadowFilter: ${getCssShadowFilter(v)}`,
- `--${k}ShadowInset: ${getCssShadow(v, true)}`
- ].join(';'))
- .join(';')
- },
- theme: {
- shadows
- }
- }
-}
-
-export const composePreset = (colors, radii, shadows, fonts) => {
- return {
- rules: {
- ...shadows.rules,
- ...colors.rules,
- ...radii.rules,
- ...fonts.rules
- },
- theme: {
- ...shadows.theme,
- ...colors.theme,
- ...radii.theme,
- ...fonts.theme
- }
- }
-}
-
-export const generatePreset = (input) => {
- const colors = generateColors(input)
- return composePreset(
- colors,
- generateRadii(input),
- generateShadows(input, colors.theme.colors, colors.mod),
- generateFonts(input)
- )
-}
-
export const getThemes = () => {
const cache = 'no-store'
@@ -382,47 +174,6 @@ export const getThemes = () => {
}, {})
})
}
-export const colors2to3 = (colors) => {
- return Object.entries(colors).reduce((acc, [slotName, color]) => {
- const btnPositions = ['', 'Panel', 'TopBar']
- switch (slotName) {
- case 'lightBg':
- return { ...acc, highlight: color }
- case 'btnText':
- return {
- ...acc,
- ...btnPositions
- .reduce(
- (statePositionAcc, position) =>
- ({ ...statePositionAcc, ['btn' + position + 'Text']: color })
- , {}
- )
- }
- default:
- return { ...acc, [slotName]: color }
- }
- }, {})
-}
-
-/**
- * This handles compatibility issues when importing v2 theme's shadows to current format
- *
- * Back in v2 shadows allowed you to use dynamic colors however those used pure CSS3 variables
- */
-export const shadows2to3 = (shadows, opacity) => {
- return Object.entries(shadows).reduce((shadowsAcc, [slotName, shadowDefs]) => {
- const isDynamic = ({ color = '#000000' }) => color.startsWith('--')
- const getOpacity = ({ color }) => opacity[getOpacitySlot(color.substring(2).split(',')[0])]
- const newShadow = shadowDefs.reduce((shadowAcc, def) => [
- ...shadowAcc,
- {
- ...def,
- alpha: isDynamic(def) ? getOpacity(def) || 1 : def.alpha
- }
- ], [])
- return { ...shadowsAcc, [slotName]: newShadow }
- }, {})
-}
export const getPreset = (val) => {
return getThemes()
@@ -449,4 +200,4 @@ export const getPreset = (val) => {
})
}
-export const setPreset = (val) => getPreset(val).then(data => applyTheme(data.theme))
+export const setPreset = (val) => getPreset(val).then(data => applyTheme(data))
diff --git a/src/services/theme_data/css_utils.js b/src/services/theme_data/css_utils.js
new file mode 100644
index 00000000..a89eac3b
--- /dev/null
+++ b/src/services/theme_data/css_utils.js
@@ -0,0 +1,163 @@
+import { convert } from 'chromatism'
+
+import { hex2rgb, rgba2css } from '../color_convert/color_convert.js'
+
+// This changes what backgrounds are used to "stacked" solid colors so you can see
+// what theme engine "thinks" is actual background color is for purposes of text color
+// generation and for when --stacked variable is used
+const DEBUG = false
+
+export const parseCssShadow = (text) => {
+ const dimensions = /(\d[a-z]*\s?){2,4}/.exec(text)?.[0]
+ const inset = /inset/.exec(text)?.[0]
+ const color = text.replace(dimensions, '').replace(inset, '')
+
+ const [x, y, blur = 0, spread = 0] = dimensions.split(/ /).filter(x => x).map(x => x.trim())
+ const isInset = inset?.trim() === 'inset'
+ const colorString = color.split(/ /).filter(x => x).map(x => x.trim())[0]
+
+ return {
+ x,
+ y,
+ blur,
+ spread,
+ inset: isInset,
+ color: colorString
+ }
+}
+
+export const getCssColorString = (color, alpha = 1) => rgba2css({ ...convert(color).rgb, a: alpha })
+
+export const getCssShadow = (input, usesDropShadow) => {
+ if (input.length === 0) {
+ return 'none'
+ }
+
+ return input
+ .filter(_ => usesDropShadow ? _.inset : _)
+ .map((shad) => [
+ shad.x,
+ shad.y,
+ shad.blur,
+ shad.spread
+ ].map(_ => _ + 'px ').concat([
+ getCssColorString(shad.color, shad.alpha),
+ shad.inset ? 'inset' : ''
+ ]).join(' ')).join(', ')
+}
+
+export const getCssShadowFilter = (input) => {
+ if (input.length === 0) {
+ return 'none'
+ }
+
+ return input
+ // drop-shadow doesn't support inset or spread
+ .filter((shad) => !shad.inset && Number(shad.spread) === 0)
+ .map((shad) => [
+ shad.x,
+ shad.y,
+ // drop-shadow's blur is twice as strong compared to box-shadow
+ shad.blur / 2
+ ].map(_ => _ + 'px').concat([
+ getCssColorString(shad.color, shad.alpha)
+ ]).join(' '))
+ .map(_ => `drop-shadow(${_})`)
+ .join(' ')
+}
+
+export const getCssRules = (rules) => rules.map(rule => {
+ let selector = rule.selector
+ if (!selector) {
+ selector = 'html'
+ }
+ const header = selector + ' {'
+ const footer = '}'
+
+ const virtualDirectives = Object.entries(rule.virtualDirectives || {}).map(([k, v]) => {
+ return ' ' + k + ': ' + v
+ }).join(';\n')
+
+ const directives = Object.entries(rule.directives).map(([k, v]) => {
+ switch (k) {
+ case 'roundness': {
+ return ' ' + [
+ '--roundness: ' + v + 'px'
+ ].join(';\n ')
+ }
+ case 'shadow': {
+ return ' ' + [
+ '--shadow: ' + getCssShadow(rule.dynamicVars.shadow),
+ '--shadowFilter: ' + getCssShadowFilter(rule.dynamicVars.shadow),
+ '--shadowInset: ' + getCssShadow(rule.dynamicVars.shadow, true)
+ ].join(';\n ')
+ }
+ case 'background': {
+ if (DEBUG) {
+ return `
+ --background: ${getCssColorString(rule.dynamicVars.stacked)};
+ background-color: ${getCssColorString(rule.dynamicVars.stacked)};
+ `
+ }
+ if (v === 'transparent') {
+ if (rule.component === 'Root') return []
+ return [
+ rule.directives.backgroundNoCssColor !== 'yes' ? ('background-color: ' + v) : '',
+ ' --background: ' + v
+ ].filter(x => x).join(';\n')
+ }
+ const color = getCssColorString(rule.dynamicVars.background, rule.directives.opacity)
+ const cssDirectives = ['--background: ' + color]
+ if (rule.directives.backgroundNoCssColor !== 'yes') {
+ cssDirectives.push('background-color: ' + color)
+ }
+ return cssDirectives.filter(x => x).join(';\n')
+ }
+ case 'blur': {
+ const cssDirectives = []
+ if (rule.directives.opacity < 1) {
+ cssDirectives.push(`--backdrop-filter: blur(${v}) `)
+ if (rule.directives.backgroundNoCssColor !== 'yes') {
+ cssDirectives.push(`backdrop-filter: blur(${v}) `)
+ }
+ }
+ return cssDirectives.join(';\n')
+ }
+ case 'font': {
+ return 'font-family: ' + v
+ }
+ case 'textColor': {
+ if (rule.directives.textNoCssColor === 'yes') { return '' }
+ return 'color: ' + v
+ }
+ default:
+ if (k.startsWith('--')) {
+ const [type, value] = v.split('|').map(x => x.trim()) // woah, Extreme!
+ switch (type) {
+ case 'color': {
+ const color = rule.dynamicVars[k]
+ if (typeof color === 'string') {
+ return k + ': ' + rgba2css(hex2rgb(color))
+ } else {
+ return k + ': ' + rgba2css(color)
+ }
+ }
+ case 'generic':
+ return k + ': ' + value
+ default:
+ return ''
+ }
+ }
+ return ''
+ }
+ }).filter(x => x).map(x => ' ' + x).join(';\n')
+
+ return [
+ header,
+ directives + ';',
+ (rule.component === 'Text' && rule.state.indexOf('faint') < 0 && rule.directives.textNoCssColor !== 'yes') ? ' color: var(--text);' : '',
+ '',
+ virtualDirectives,
+ footer
+ ].join('\n')
+}).filter(x => x)
diff --git a/src/services/theme_data/iss_utils.js b/src/services/theme_data/iss_utils.js
new file mode 100644
index 00000000..2d9dd0b5
--- /dev/null
+++ b/src/services/theme_data/iss_utils.js
@@ -0,0 +1,129 @@
+// "Unrolls" a tree structure of item: { parent: { ...item2, parent: { ...item3, parent: {...} } }}
+// into an array [item2, item3] for iterating
+export const unroll = (item) => {
+ const out = []
+ let currentParent = item
+ while (currentParent) {
+ out.push(currentParent)
+ currentParent = currentParent.parent
+ }
+ return out
+}
+
+// This gives you an array of arrays of all possible unique (i.e. order-insensitive) combinations
+// Can only accept primitives. Duplicates are not supported and can cause unexpected behavior
+export const getAllPossibleCombinations = (array) => {
+ const combos = [array.map(x => [x])]
+ for (let comboSize = 2; comboSize <= array.length; comboSize++) {
+ const previous = combos[combos.length - 1]
+ const newCombos = previous.map(self => {
+ const selfSet = new Set()
+ self.forEach(x => selfSet.add(x))
+ const nonSelf = array.filter(x => !selfSet.has(x))
+ return nonSelf.map(x => [...self, x])
+ })
+ const flatCombos = newCombos.reduce((acc, x) => [...acc, ...x], [])
+ const uniqueComboStrings = new Set()
+ const uniqueCombos = flatCombos.map(x => x.toSorted()).filter(x => {
+ if (uniqueComboStrings.has(x.join())) {
+ return false
+ } else {
+ uniqueComboStrings.add(x.join())
+ return true
+ }
+ })
+ combos.push(uniqueCombos)
+ }
+ return combos.reduce((acc, x) => [...acc, ...x], [])
+}
+
+// Converts rule, parents and their criteria into a CSS (or path if ignoreOutOfTreeSelector == true) selector
+export const genericRuleToSelector = components => (rule, ignoreOutOfTreeSelector, isParent) => {
+ if (!rule && !isParent) return null
+ const component = components[rule.component]
+ const { states = {}, variants = {}, selector, outOfTreeSelector } = component
+
+ const applicableStates = ((rule.state || []).filter(x => x !== 'normal')).map(state => states[state])
+
+ const applicableVariantName = (rule.variant || 'normal')
+ let applicableVariant = ''
+ if (applicableVariantName !== 'normal') {
+ applicableVariant = variants[applicableVariantName]
+ } else {
+ applicableVariant = variants?.normal ?? ''
+ }
+
+ let realSelector
+ if (selector === ':root') {
+ realSelector = ''
+ } else if (isParent) {
+ realSelector = selector
+ } else {
+ if (outOfTreeSelector && !ignoreOutOfTreeSelector) realSelector = outOfTreeSelector
+ else realSelector = selector
+ }
+
+ const selectors = [realSelector, applicableVariant, ...applicableStates]
+ .toSorted((a, b) => {
+ if (a.startsWith(':')) return 1
+ if (/^[a-z]/.exec(a)) return -1
+ else return 0
+ })
+ .join('')
+
+ if (rule.parent) {
+ return (genericRuleToSelector(components)(rule.parent, ignoreOutOfTreeSelector, true) + ' ' + selectors).trim()
+ }
+ return selectors.trim()
+}
+
+export const combinationsMatch = (criteria, subject, strict) => {
+ if (criteria.component !== subject.component) return false
+
+ // All variants inherit from normal
+ if (subject.variant !== 'normal' || strict) {
+ if (criteria.variant !== subject.variant) return false
+ }
+
+ // Subject states > 1 essentially means state is "normal" and therefore matches
+ if (subject.state.length > 1 || strict) {
+ const subjectStatesSet = new Set(subject.state)
+ const criteriaStatesSet = new Set(criteria.state)
+
+ const setsAreEqual =
+ [...criteriaStatesSet].every(state => subjectStatesSet.has(state)) &&
+ [...subjectStatesSet].every(state => criteriaStatesSet.has(state))
+
+ if (!setsAreEqual) return false
+ }
+ return true
+}
+
+export const findRules = (criteria, strict) => subject => {
+ // If we searching for "general" rules - ignore "specific" ones
+ if (criteria.parent === null && !!subject.parent) return false
+ if (!combinationsMatch(criteria, subject, strict)) return false
+
+ if (criteria.parent !== undefined && criteria.parent !== null) {
+ if (!subject.parent && !strict) return true
+ const pathCriteria = unroll(criteria)
+ const pathSubject = unroll(subject)
+ if (pathCriteria.length < pathSubject.length) return false
+
+ // Search: .a .b .c
+ // Matches: .a .b .c; .b .c; .c; .z .a .b .c
+ // Does not match .a .b .c .d, .a .b .e
+ for (let i = 0; i < pathCriteria.length; i++) {
+ const criteriaParent = pathCriteria[i]
+ const subjectParent = pathSubject[i]
+ if (!subjectParent) return true
+ if (!combinationsMatch(criteriaParent, subjectParent, strict)) return false
+ }
+ }
+ return true
+}
+
+export const normalizeCombination = rule => {
+ rule.variant = rule.variant ?? 'normal'
+ rule.state = [...new Set(['normal', ...(rule.state || [])])]
+}
diff --git a/src/services/theme_data/pleromafe.t3.js b/src/services/theme_data/pleromafe.t3.js
new file mode 100644
index 00000000..db612a5b
--- /dev/null
+++ b/src/services/theme_data/pleromafe.t3.js
@@ -0,0 +1,2 @@
+export const sampleRules = [
+]
diff --git a/src/services/theme_data/theme2_keys.js b/src/services/theme_data/theme2_keys.js
new file mode 100644
index 00000000..ffc5627c
--- /dev/null
+++ b/src/services/theme_data/theme2_keys.js
@@ -0,0 +1,177 @@
+export default [
+ 'bg',
+ 'wallpaper',
+ 'fg',
+ 'text',
+ 'underlay',
+ 'link',
+ 'accent',
+ 'faint',
+ 'faintLink',
+ 'postFaintLink',
+
+ 'cBlue',
+ 'cRed',
+ 'cGreen',
+ 'cOrange',
+
+ 'profileBg',
+ 'profileTint',
+
+ 'highlight',
+ 'highlightLightText',
+ 'highlightPostLink',
+ 'highlightFaintText',
+ 'highlightFaintLink',
+ 'highlightPostFaintLink',
+ 'highlightText',
+ 'highlightLink',
+ 'highlightIcon',
+
+ 'popover',
+ 'popoverLightText',
+ 'popoverPostLink',
+ 'popoverFaintText',
+ 'popoverFaintLink',
+ 'popoverPostFaintLink',
+ 'popoverText',
+ 'popoverLink',
+ 'popoverIcon',
+
+ 'selectedPost',
+ 'selectedPostFaintText',
+ 'selectedPostLightText',
+ 'selectedPostPostLink',
+ 'selectedPostFaintLink',
+ 'selectedPostText',
+ 'selectedPostLink',
+ 'selectedPostIcon',
+
+ 'selectedMenu',
+ 'selectedMenuLightText',
+ 'selectedMenuFaintText',
+ 'selectedMenuFaintLink',
+ 'selectedMenuText',
+ 'selectedMenuLink',
+ 'selectedMenuIcon',
+
+ 'selectedMenuPopover',
+ 'selectedMenuPopoverLightText',
+ 'selectedMenuPopoverFaintText',
+ 'selectedMenuPopoverFaintLink',
+ 'selectedMenuPopoverText',
+ 'selectedMenuPopoverLink',
+ 'selectedMenuPopoverIcon',
+
+ 'lightText',
+
+ 'postLink',
+
+ 'postGreentext',
+
+ 'postCyantext',
+
+ 'border',
+
+ 'poll',
+ 'pollText',
+
+ 'icon',
+
+ // Foreground,
+ 'fgText',
+ 'fgLink',
+
+ // Panel header,
+ 'panel',
+ 'panelText',
+ 'panelFaint',
+ 'panelLink',
+
+ // Top bar,
+ 'topBar',
+ 'topBarText',
+ 'topBarLink',
+
+ // Tabs,
+ 'tab',
+ 'tabText',
+ 'tabActiveText',
+
+ // Buttons,
+ 'btn',
+ 'btnText',
+ 'btnPanelText',
+ 'btnTopBarText',
+
+ // Buttons: pressed,
+ 'btnPressed',
+ 'btnPressedText',
+ 'btnPressedPanel',
+ 'btnPressedPanelText',
+ 'btnPressedTopBar',
+ 'btnPressedTopBarText',
+
+ // Buttons: toggled,
+ 'btnToggled',
+ 'btnToggledText',
+ 'btnToggledPanelText',
+ 'btnToggledTopBarText',
+
+ // Buttons: disabled,
+ 'btnDisabled',
+ 'btnDisabledText',
+ 'btnDisabledPanelText',
+ 'btnDisabledTopBarText',
+
+ // Input fields,
+ 'input',
+ 'inputText',
+ 'inputPanelText',
+ 'inputTopbarText',
+
+ 'alertError',
+ 'alertErrorText',
+ 'alertErrorPanelText',
+
+ 'alertWarning',
+ 'alertWarningText',
+ 'alertWarningPanelText',
+
+ 'alertSuccess',
+ 'alertSuccessText',
+ 'alertSuccessPanelText',
+
+ 'alertNeutral',
+ 'alertNeutralText',
+ 'alertNeutralPanelText',
+
+ 'alertPopupError',
+ 'alertPopupErrorText',
+
+ 'alertPopupWarning',
+ 'alertPopupWarningText',
+
+ 'alertPopupSuccess',
+ 'alertPopupSuccessText',
+
+ 'alertPopupNeutral',
+ 'alertPopupNeutralText',
+
+ 'badgeNeutral',
+ 'badgeNeutralText',
+
+ 'badgeNotification',
+ 'badgeNotificationText',
+
+ 'chatBg',
+
+ 'chatMessageIncomingBg',
+ 'chatMessageIncomingText',
+ 'chatMessageIncomingLink',
+ 'chatMessageIncomingBorder',
+ 'chatMessageOutgoingBg',
+ 'chatMessageOutgoingText',
+ 'chatMessageOutgoingLink',
+ 'chatMessageOutgoingBorder'
+]
diff --git a/src/services/theme_data/theme2_to_theme3.js b/src/services/theme_data/theme2_to_theme3.js
new file mode 100644
index 00000000..6ea12836
--- /dev/null
+++ b/src/services/theme_data/theme2_to_theme3.js
@@ -0,0 +1,536 @@
+import { convert } from 'chromatism'
+import allKeys from './theme2_keys'
+
+// keys that are meant to be used globally, i.e. what's the rest of the theme is based upon.
+export const basePaletteKeys = new Set([
+ 'bg',
+ 'fg',
+ 'text',
+ 'link',
+ 'accent',
+
+ 'cBlue',
+ 'cRed',
+ 'cGreen',
+ 'cOrange'
+])
+
+export const fontsKeys = new Set([
+ 'interface',
+ 'input',
+ 'post',
+ 'postCode'
+])
+
+export const opacityKeys = new Set([
+ 'alert',
+ 'alertPopup',
+ 'bg',
+ 'border',
+ 'btn',
+ 'faint',
+ 'input',
+ 'panel',
+ 'popover',
+ 'profileTint',
+ 'underlay'
+])
+
+export const shadowsKeys = new Set([
+ 'panel',
+ 'topBar',
+ 'popup',
+ 'avatar',
+ 'avatarStatus',
+ 'panelHeader',
+ 'button',
+ 'buttonHover',
+ 'buttonPressed',
+ 'input'
+])
+
+export const radiiKeys = new Set([
+ 'btn',
+ 'input',
+ 'checkbox',
+ 'panel',
+ 'avatar',
+ 'avatarAlt',
+ 'tooltip',
+ 'attachment',
+ 'chatMessage'
+])
+
+// Keys that are not available in editor and never meant to be edited
+export const hiddenKeys = new Set([
+ 'profileBg',
+ 'profileTint'
+])
+
+export const extendedBasePrefixes = [
+ 'border',
+ 'icon',
+ 'highlight',
+ 'lightText',
+
+ 'popover',
+
+ 'panel',
+ 'topBar',
+ 'tab',
+ 'btn',
+ 'input',
+ 'selectedMenu',
+
+ 'alert',
+ 'alertPopup',
+ 'badge',
+
+ 'post',
+ 'selectedPost', // wrong nomenclature
+ 'poll',
+
+ 'chatBg',
+ 'chatMessage'
+]
+export const nonComponentPrefixes = new Set([
+ 'border',
+ 'icon',
+ 'highlight',
+ 'lightText',
+ 'chatBg'
+])
+
+export const extendedBaseKeys = Object.fromEntries(
+ extendedBasePrefixes.map(prefix => [
+ prefix,
+ allKeys.filter(k => {
+ if (prefix === 'alert') {
+ return k.startsWith(prefix) && !k.startsWith('alertPopup')
+ }
+ return k.startsWith(prefix)
+ })
+ ])
+)
+
+// Keysets that are only really used intermideately, i.e. to generate other colors
+export const temporary = new Set([
+ '',
+ 'highlight'
+])
+
+export const temporaryColors = {}
+
+export const convertTheme2To3 = (data) => {
+ data.colors.accent = data.colors.accent || data.colors.link
+ data.colors.link = data.colors.link || data.colors.accent
+ const generateRoot = () => {
+ const directives = {}
+ basePaletteKeys.forEach(key => { directives['--' + key] = 'color | ' + convert(data.colors[key]).hex })
+ return {
+ component: 'Root',
+ directives
+ }
+ }
+
+ const convertOpacity = () => {
+ const newRules = []
+ Object.keys(data.opacity || {}).forEach(key => {
+ if (!opacityKeys.has(key) || data.opacity[key] === undefined) return null
+ const originalOpacity = data.opacity[key]
+ const rule = {}
+
+ switch (key) {
+ case 'alert':
+ rule.component = 'Alert'
+ break
+ case 'alertPopup':
+ rule.component = 'Alert'
+ rule.parent = { component: 'Popover' }
+ break
+ case 'bg':
+ rule.component = 'Panel'
+ break
+ case 'border':
+ rule.component = 'Border'
+ break
+ case 'btn':
+ rule.component = 'Button'
+ break
+ case 'faint':
+ rule.component = 'Text'
+ rule.state = ['faint']
+ break
+ case 'input':
+ rule.component = 'Input'
+ break
+ case 'panel':
+ rule.component = 'PanelHeader'
+ break
+ case 'popover':
+ rule.component = 'Popover'
+ break
+ case 'profileTint':
+ return null
+ case 'underlay':
+ rule.component = 'Underlay'
+ break
+ }
+
+ switch (key) {
+ case 'alert':
+ case 'alertPopup':
+ case 'bg':
+ case 'btn':
+ case 'input':
+ case 'panel':
+ case 'popover':
+ case 'underlay':
+ rule.directives = { opacity: originalOpacity }
+ break
+ case 'faint':
+ case 'border':
+ rule.directives = { textOpacity: originalOpacity }
+ break
+ }
+
+ newRules.push(rule)
+
+ if (rule.component === 'Button') {
+ newRules.push({ ...rule, component: 'ScrollbarElement' })
+ newRules.push({ ...rule, component: 'Tab' })
+ newRules.push({ ...rule, component: 'Tab', state: ['active'], directives: { opacity: 0 } })
+ }
+ if (rule.component === 'Panel') {
+ newRules.push({ ...rule, component: 'Post' })
+ }
+ })
+ return newRules
+ }
+
+ const convertRadii = () => {
+ const newRules = []
+ Object.keys(data.radii || {}).forEach(key => {
+ if (!radiiKeys.has(key) || data.radii[key] === undefined) return null
+ const originalRadius = data.radii[key]
+ const rule = {}
+
+ switch (key) {
+ case 'btn':
+ rule.component = 'Button'
+ break
+ case 'tab':
+ rule.component = 'Tab'
+ break
+ case 'input':
+ rule.component = 'Input'
+ break
+ case 'checkbox':
+ rule.component = 'Input'
+ rule.variant = 'checkbox'
+ break
+ case 'panel':
+ rule.component = 'Panel'
+ break
+ case 'avatar':
+ rule.component = 'Avatar'
+ break
+ case 'avatarAlt':
+ rule.component = 'Avatar'
+ rule.variant = 'compact'
+ break
+ case 'tooltip':
+ rule.component = 'Popover'
+ break
+ case 'attachment':
+ rule.component = 'Attachment'
+ break
+ case 'ChatMessage':
+ rule.component = 'Button'
+ break
+ }
+ rule.directives = {
+ roundness: originalRadius
+ }
+ newRules.push(rule)
+ if (rule.component === 'Button') {
+ newRules.push({ ...rule, component: 'ScrollbarElement' })
+ newRules.push({ ...rule, component: 'Tab' })
+ }
+ })
+ return newRules
+ }
+
+ const convertFonts = () => {
+ const newRules = []
+ Object.keys(data.fonts || {}).forEach(key => {
+ if (!fontsKeys.has(key)) return
+ const originalFont = data.fonts[key].family
+ const rule = {}
+
+ switch (key) {
+ case 'interface':
+ case 'postCode':
+ rule.component = 'Root'
+ break
+ case 'input':
+ rule.component = 'Input'
+ break
+ case 'post':
+ rule.component = 'RichContent'
+ break
+ }
+ switch (key) {
+ case 'interface':
+ case 'input':
+ case 'post':
+ rule.directives = { '--font': 'generic | ' + originalFont }
+ break
+ case 'postCode':
+ rule.directives = { '--monoFont': 'generic | ' + originalFont }
+ newRules.push({ ...rule, component: 'RichContent' })
+ break
+ }
+ newRules.push(rule)
+ })
+ return newRules
+ }
+ const convertShadows = () => {
+ const newRules = []
+ Object.keys(data.shadows || {}).forEach(key => {
+ if (!shadowsKeys.has(key)) return
+ const originalShadow = data.shadows[key]
+ const rule = {}
+
+ switch (key) {
+ case 'panel':
+ rule.component = 'Panel'
+ break
+ case 'topBar':
+ rule.component = 'TopBar'
+ break
+ case 'popup':
+ rule.component = 'Popover'
+ break
+ case 'avatar':
+ rule.component = 'Avatar'
+ break
+ case 'avatarStatus':
+ rule.component = 'Avatar'
+ rule.parent = { component: 'Post' }
+ break
+ case 'panelHeader':
+ rule.component = 'PanelHeader'
+ break
+ case 'button':
+ rule.component = 'Button'
+ break
+ case 'buttonHover':
+ rule.component = 'Button'
+ rule.state = ['hover']
+ break
+ case 'buttonPressed':
+ rule.component = 'Button'
+ rule.state = ['pressed']
+ break
+ case 'input':
+ rule.component = 'Input'
+ break
+ }
+ rule.directives = {
+ shadow: originalShadow
+ }
+ newRules.push(rule)
+ if (key === 'topBar') {
+ newRules.push({ ...rule, component: 'PanelHeader', parent: { component: 'MobileDrawer' } })
+ }
+ if (key === 'avatarStatus') {
+ newRules.push({ ...rule, parent: { component: 'Notification' } })
+ }
+ if (key === 'buttonPressed') {
+ newRules.push({ ...rule, state: ['toggled'] })
+ newRules.push({ ...rule, state: ['toggled', 'focus'] })
+ newRules.push({ ...rule, state: ['pressed', 'focus'] })
+ }
+ if (key === 'buttonHover') {
+ newRules.push({ ...rule, state: ['toggled', 'hover'] })
+ newRules.push({ ...rule, state: ['pressed', 'hover'] })
+ newRules.push({ ...rule, state: ['toggled', 'focus', 'hover'] })
+ newRules.push({ ...rule, state: ['pressed', 'focus', 'hover'] })
+ }
+
+ if (rule.component === 'Button') {
+ newRules.push({ ...rule, component: 'ScrollbarElement' })
+ newRules.push({ ...rule, component: 'Tab' })
+ }
+ })
+ return newRules
+ }
+
+ const extendedRules = Object.entries(extendedBaseKeys).map(([prefix, keys]) => {
+ if (nonComponentPrefixes.has(prefix)) return null
+ const rule = {}
+ if (prefix === 'alertPopup') {
+ rule.component = 'Alert'
+ rule.parent = { component: 'Popover' }
+ } else if (prefix === 'selectedPost') {
+ rule.component = 'Post'
+ rule.state = ['selected']
+ } else if (prefix === 'selectedMenu') {
+ rule.component = 'MenuItem'
+ rule.state = ['hover']
+ } else if (prefix === 'chatMessageIncoming') {
+ rule.component = 'ChatMessage'
+ } else if (prefix === 'chatMessageOutgoing') {
+ rule.component = 'ChatMessage'
+ rule.variant = 'outgoing'
+ } else if (prefix === 'panel') {
+ rule.component = 'PanelHeader'
+ } else if (prefix === 'topBar') {
+ rule.component = 'TopBar'
+ } else if (prefix === 'chatMessage') {
+ rule.component = 'ChatMessage'
+ } else if (prefix === 'poll') {
+ rule.component = 'PollGraph'
+ } else if (prefix === 'btn') {
+ rule.component = 'Button'
+ } else {
+ rule.component = prefix[0].toUpperCase() + prefix.slice(1).toLowerCase()
+ }
+ return keys.map((key) => {
+ if (!data.colors[key]) return null
+ const leftoverKey = key.replace(prefix, '')
+ const parts = (leftoverKey || 'Bg').match(/[A-Z][a-z]*/g)
+ const last = parts.slice(-1)[0]
+ let newRule = { directives: {} }
+ let variantArray = []
+
+ switch (last) {
+ case 'Text':
+ case 'Faint': // typo
+ case 'Link':
+ case 'Icon':
+ case 'Greentext':
+ case 'Cyantext':
+ case 'Border':
+ newRule.parent = rule
+ newRule.directives.textColor = data.colors[key]
+ newRule.directives.textAuto = 'no-auto'
+ variantArray = parts.slice(0, -1)
+ break
+ default:
+ newRule = { ...rule, directives: {} }
+ newRule.directives.background = data.colors[key]
+ variantArray = parts
+ break
+ }
+
+ if (last === 'Text' || last === 'Link') {
+ const secondLast = parts.slice(-2)[0]
+ if (secondLast === 'Light') {
+ return null // unsupported
+ } else if (secondLast === 'Faint') {
+ newRule.state = ['faint']
+ variantArray = parts.slice(0, -2)
+ }
+ }
+
+ switch (last) {
+ case 'Text':
+ case 'Link':
+ case 'Icon':
+ case 'Border':
+ newRule.component = last
+ break
+ case 'Greentext':
+ case 'Cyantext':
+ newRule.component = 'FunText'
+ newRule.variant = last.toLowerCase()
+ break
+ case 'Faint':
+ newRule.component = 'Text'
+ newRule.state = ['faint']
+ break
+ }
+
+ variantArray = variantArray.filter(x => x !== 'Bg')
+
+ if (last === 'Link' && prefix === 'selectedPost') {
+ // selectedPost has typo - duplicate 'Post'
+ variantArray = variantArray.filter(x => x !== 'Post')
+ }
+
+ if (prefix === 'popover' && variantArray[0] === 'Post') {
+ newRule.component = 'Post'
+ newRule.parent = { component: 'Popover' }
+ variantArray = variantArray.filter(x => x !== 'Post')
+ }
+
+ if (prefix === 'selectedMenu' && variantArray[0] === 'Popover') {
+ newRule.parent = { component: 'Popover' }
+ variantArray = variantArray.filter(x => x !== 'Popover')
+ }
+
+ switch (prefix) {
+ case 'btn':
+ case 'input':
+ case 'alert': {
+ const hasPanel = variantArray.find(x => x === 'Panel')
+ if (hasPanel) {
+ newRule.parent = { component: 'PanelHeader' }
+ variantArray = variantArray.filter(x => x !== 'Panel')
+ }
+ const hasTop = variantArray.find(x => x === 'Top') // TopBar
+ if (hasTop) {
+ newRule.parent = { component: 'TopBar' }
+ variantArray = variantArray.filter(x => x !== 'Top' && x !== 'Bar')
+ }
+ break
+ }
+ }
+
+ if (variantArray.length > 0) {
+ if (prefix === 'btn') {
+ newRule.state = variantArray.map(x => x.toLowerCase())
+ } else {
+ newRule.variant = variantArray[0].toLowerCase()
+ }
+ }
+
+ if (newRule.component === 'Panel') {
+ return [newRule, { ...newRule, component: 'MobileDrawer' }]
+ } else if (newRule.component === 'Button') {
+ const rules = [
+ newRule,
+ { ...newRule, component: 'Tab' },
+ { ...newRule, component: 'ScrollbarElement' }
+ ]
+ if (newRule.state?.indexOf('toggled') >= 0) {
+ rules.push({ ...newRule, state: [...newRule.state, 'focused'] })
+ rules.push({ ...newRule, state: [...newRule.state, 'hover'] })
+ rules.push({ ...newRule, state: [...newRule.state, 'hover', 'focused'] })
+ }
+ if (newRule.state?.indexOf('hover') >= 0) {
+ rules.push({ ...newRule, state: [...newRule.state, 'focused'] })
+ }
+ return rules
+ } else if (newRule.component === 'Badge') {
+ if (newRule.variant === 'notification') {
+ return [newRule, { component: 'Root', directives: { '--badgeNotification': 'color | ' + newRule.directives.background } }]
+ } else if (newRule.variant === 'neutral') {
+ return [{ ...newRule, variant: 'normal' }]
+ } else {
+ return [newRule]
+ }
+ } else if (newRule.component === 'TopBar') {
+ return [newRule, { ...newRule, parent: { component: 'MobileDrawer' }, component: 'PanelHeader' }]
+ } else {
+ return [newRule]
+ }
+ })
+ })
+
+ const flatExtRules = extendedRules.filter(x => x).reduce((acc, x) => [...acc, ...x], []).filter(x => x).reduce((acc, x) => [...acc, ...x], [])
+
+ return [generateRoot(), ...convertShadows(), ...convertRadii(), ...convertOpacity(), ...convertFonts(), ...flatExtRules]
+}
diff --git a/src/services/theme_data/theme3_slot_functions.js b/src/services/theme_data/theme3_slot_functions.js
new file mode 100644
index 00000000..074a88f0
--- /dev/null
+++ b/src/services/theme_data/theme3_slot_functions.js
@@ -0,0 +1,103 @@
+import { convert, brightness } from 'chromatism'
+import { alphaBlend, getTextColor, relativeLuminance } from '../color_convert/color_convert.js'
+
+export const process = (text, functions, { findColor, findShadow }, { dynamicVars, staticVars }) => {
+ const { funcName, argsString } = /\$(?<funcName>\w+)\((?<argsString>[#a-zA-Z0-9-,.'"\s]*)\)/.exec(text).groups
+ const args = argsString.split(/,/g).map(a => a.trim())
+
+ const func = functions[funcName]
+ if (args.length < func.argsNeeded) {
+ throw new Error(`$${funcName} requires at least ${func.argsNeeded} arguments, but ${args.length} were provided`)
+ }
+ return func.exec(args, { findColor, findShadow }, { dynamicVars, staticVars })
+}
+
+export const colorFunctions = {
+ alpha: {
+ argsNeeded: 2,
+ exec: (args, { findColor }, { dynamicVars, staticVars }) => {
+ const [color, amountArg] = args
+
+ const colorArg = convert(findColor(color, { dynamicVars, staticVars })).rgb
+ const amount = Number(amountArg)
+ return { ...colorArg, a: amount }
+ }
+ },
+ textColor: {
+ argsNeeded: 2,
+ exec: (args, { findColor }, { dynamicVars, staticVars }) => {
+ const [backgroundArg, foregroundArg, preserve = 'preserve'] = args
+
+ const background = convert(findColor(backgroundArg, { dynamicVars, staticVars })).rgb
+ const foreground = convert(findColor(foregroundArg, { dynamicVars, staticVars })).rgb
+
+ return getTextColor(background, foreground, preserve === 'preserve')
+ }
+ },
+ blend: {
+ argsNeeded: 3,
+ exec: (args, { findColor }, { dynamicVars, staticVars }) => {
+ const [backgroundArg, amountArg, foregroundArg] = args
+
+ const background = convert(findColor(backgroundArg, { dynamicVars, staticVars })).rgb
+ const foreground = convert(findColor(foregroundArg, { dynamicVars, staticVars })).rgb
+ const amount = Number(amountArg)
+
+ return alphaBlend(background, amount, foreground)
+ }
+ },
+ mod: {
+ argsNeeded: 2,
+ exec: (args, { findColor }, { dynamicVars, staticVars }) => {
+ const [colorArg, amountArg] = args
+
+ const color = convert(findColor(colorArg, { dynamicVars, staticVars })).rgb
+ const amount = Number(amountArg)
+
+ const effectiveBackground = dynamicVars.lowerLevelBackground
+ const isLightOnDark = relativeLuminance(convert(effectiveBackground).rgb) < 0.5
+ const mod = isLightOnDark ? 1 : -1
+ return brightness(amount * mod, color).rgb
+ }
+ }
+}
+
+export const shadowFunctions = {
+ borderSide: {
+ argsNeeded: 3,
+ exec: (args, { findColor }) => {
+ const [color, side, alpha = '1', widthArg = '1', inset = 'inset'] = args
+
+ const width = Number(widthArg)
+ const isInset = inset === 'inset'
+
+ const targetShadow = {
+ x: 0,
+ y: 0,
+ blur: 0,
+ spread: 0,
+ color,
+ alpha: Number(alpha),
+ inset: isInset
+ }
+
+ side.split('-').forEach((position) => {
+ switch (position) {
+ case 'left':
+ targetShadow.x = width * (inset ? 1 : -1)
+ break
+ case 'right':
+ targetShadow.x = -1 * width * (inset ? 1 : -1)
+ break
+ case 'top':
+ targetShadow.y = width * (inset ? 1 : -1)
+ break
+ case 'bottom':
+ targetShadow.y = -1 * width * (inset ? 1 : -1)
+ break
+ }
+ })
+ return [targetShadow]
+ }
+ }
+}
diff --git a/src/services/theme_data/theme_data.service.js b/src/services/theme_data/theme_data.service.js
index b376ef4d..6e477674 100644
--- a/src/services/theme_data/theme_data.service.js
+++ b/src/services/theme_data/theme_data.service.js
@@ -1,5 +1,5 @@
import { convert, brightness, contrastRatio } from 'chromatism'
-import { alphaBlendLayers, getTextColor, relativeLuminance } from '../color_convert/color_convert.js'
+import { rgb2hex, rgba2css, alphaBlendLayers, getTextColor, relativeLuminance, getCssColor } from '../color_convert/color_convert.js'
import { LAYERS, DEFAULT_OPACITY, SLOT_INHERITANCE } from './pleromafe.js'
/*
@@ -407,3 +407,347 @@ export const getColors = (sourceColors, sourceOpacity) => SLOT_ORDERED.reduce(({
}
}
}, { colors: {}, opacity: {} })
+
+export const composePreset = (colors, radii, shadows, fonts) => {
+ return {
+ rules: {
+ ...shadows.rules,
+ ...colors.rules,
+ ...radii.rules,
+ ...fonts.rules
+ },
+ theme: {
+ ...shadows.theme,
+ ...colors.theme,
+ ...radii.theme,
+ ...fonts.theme
+ }
+ }
+}
+
+export const generatePreset = (input) => {
+ const colors = generateColors(input)
+ return composePreset(
+ colors,
+ generateRadii(input),
+ generateShadows(input, colors.theme.colors, colors.mod),
+ generateFonts(input)
+ )
+}
+
+export const getCssShadow = (input, usesDropShadow) => {
+ if (input.length === 0) {
+ return 'none'
+ }
+
+ return input
+ .filter(_ => usesDropShadow ? _.inset : _)
+ .map((shad) => [
+ shad.x,
+ shad.y,
+ shad.blur,
+ shad.spread
+ ].map(_ => _ + 'px').concat([
+ getCssColor(shad.color, shad.alpha),
+ shad.inset ? 'inset' : ''
+ ]).join(' ')).join(', ')
+}
+
+const getCssShadowFilter = (input) => {
+ if (input.length === 0) {
+ return 'none'
+ }
+
+ return input
+ // drop-shadow doesn't support inset or spread
+ .filter((shad) => !shad.inset && Number(shad.spread) === 0)
+ .map((shad) => [
+ shad.x,
+ shad.y,
+ // drop-shadow's blur is twice as strong compared to box-shadow
+ shad.blur / 2
+ ].map(_ => _ + 'px').concat([
+ getCssColor(shad.color, shad.alpha)
+ ]).join(' '))
+ .map(_ => `drop-shadow(${_})`)
+ .join(' ')
+}
+
+export const generateColors = (themeData) => {
+ const sourceColors = !themeData.themeEngineVersion
+ ? colors2to3(themeData.colors || themeData)
+ : themeData.colors || themeData
+
+ const { colors, opacity } = getColors(sourceColors, themeData.opacity || {})
+
+ const htmlColors = Object.entries(colors)
+ .reduce((acc, [k, v]) => {
+ if (!v) return acc
+ acc.solid[k] = rgb2hex(v)
+ acc.complete[k] = typeof v.a === 'undefined' ? rgb2hex(v) : rgba2css(v)
+ return acc
+ }, { complete: {}, solid: {} })
+ return {
+ rules: {
+ colors: Object.entries(htmlColors.complete)
+ .filter(([k, v]) => v)
+ .map(([k, v]) => `--${k}: ${v}`)
+ .join(';')
+ },
+ theme: {
+ colors: htmlColors.solid,
+ opacity
+ }
+ }
+}
+
+export const generateRadii = (input) => {
+ let inputRadii = input.radii || {}
+ // v1 -> v2
+ if (typeof input.btnRadius !== 'undefined') {
+ inputRadii = Object
+ .entries(input)
+ .filter(([k, v]) => k.endsWith('Radius'))
+ .reduce((acc, e) => { acc[e[0].split('Radius')[0]] = e[1]; return acc }, {})
+ }
+ const radii = Object.entries(inputRadii).filter(([k, v]) => v).reduce((acc, [k, v]) => {
+ acc[k] = v
+ return acc
+ }, {
+ btn: 4,
+ input: 4,
+ checkbox: 2,
+ panel: 10,
+ avatar: 5,
+ avatarAlt: 50,
+ tooltip: 2,
+ attachment: 5,
+ chatMessage: inputRadii.panel
+ })
+
+ return {
+ rules: {
+ radii: Object.entries(radii).filter(([k, v]) => v).map(([k, v]) => `--${k}Radius: ${v}px`).join(';')
+ },
+ theme: {
+ radii
+ }
+ }
+}
+
+export const generateFonts = (input) => {
+ const fonts = Object.entries(input.fonts || {}).filter(([k, v]) => v).reduce((acc, [k, v]) => {
+ acc[k] = Object.entries(v).filter(([k, v]) => v).reduce((acc, [k, v]) => {
+ acc[k] = v
+ return acc
+ }, acc[k])
+ return acc
+ }, {
+ interface: {
+ family: 'sans-serif'
+ },
+ input: {
+ family: 'inherit'
+ },
+ post: {
+ family: 'inherit'
+ },
+ postCode: {
+ family: 'monospace'
+ }
+ })
+
+ return {
+ rules: {
+ fonts: Object
+ .entries(fonts)
+ .filter(([k, v]) => v)
+ .map(([k, v]) => `--${k}Font: ${v.family}`).join(';')
+ },
+ theme: {
+ fonts
+ }
+ }
+}
+
+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 hoverGlow = {
+ x: 0,
+ y: 0,
+ blur: 4,
+ spread: 0,
+ color: '--faint',
+ alpha: 1
+}
+
+export const DEFAULT_SHADOWS = {
+ panel: [{
+ x: 1,
+ y: 1,
+ blur: 4,
+ spread: 0,
+ color: '#000000',
+ alpha: 0.6
+ }],
+ topBar: [{
+ x: 0,
+ y: 0,
+ blur: 4,
+ spread: 0,
+ color: '#000000',
+ alpha: 0.6
+ }],
+ popup: [{
+ x: 2,
+ y: 2,
+ blur: 3,
+ spread: 0,
+ color: '#000000',
+ alpha: 0.5
+ }],
+ avatar: [{
+ x: 0,
+ y: 1,
+ blur: 8,
+ spread: 0,
+ color: '#000000',
+ alpha: 0.7
+ }],
+ avatarStatus: [],
+ panelHeader: [],
+ button: [{
+ x: 0,
+ y: 0,
+ blur: 2,
+ spread: 0,
+ color: '#000000',
+ alpha: 1
+ }, ...buttonInsetFakeBorders],
+ buttonHover: [hoverGlow, ...buttonInsetFakeBorders],
+ buttonPressed: [hoverGlow, ...inputInsetFakeBorders],
+ input: [...inputInsetFakeBorders, {
+ x: 0,
+ y: 0,
+ blur: 2,
+ inset: true,
+ spread: 0,
+ color: '#000000',
+ alpha: 1
+ }]
+}
+export const generateShadows = (input, colors) => {
+ // TODO this is a small hack for `mod` to work with shadows
+ // this is used to get the "context" of shadow, i.e. for `mod` properly depend on background color of element
+ const hackContextDict = {
+ button: 'btn',
+ panel: 'bg',
+ top: 'topBar',
+ popup: 'popover',
+ avatar: 'bg',
+ panelHeader: 'panel',
+ input: 'input'
+ }
+
+ const cleanInputShadows = Object.fromEntries(
+ Object.entries(input.shadows || {})
+ .map(([name, shadowSlot]) => [
+ name,
+ // defaulting color to black to avoid potential problems
+ shadowSlot.map(shadowDef => ({ color: '#000000', ...shadowDef }))
+ ])
+ )
+ const inputShadows = cleanInputShadows && !input.themeEngineVersion
+ ? shadows2to3(cleanInputShadows, input.opacity)
+ : cleanInputShadows || {}
+ const shadows = Object.entries({
+ ...DEFAULT_SHADOWS,
+ ...inputShadows
+ }).reduce((shadowsAcc, [slotName, shadowDefs]) => {
+ const slotFirstWord = slotName.replace(/[A-Z].*$/, '')
+ const colorSlotName = hackContextDict[slotFirstWord]
+ const isLightOnDark = relativeLuminance(convert(colors[colorSlotName]).rgb) < 0.5
+ const mod = isLightOnDark ? 1 : -1
+ const newShadow = shadowDefs.reduce((shadowAcc, def) => [
+ ...shadowAcc,
+ {
+ ...def,
+ color: rgb2hex(computeDynamicColor(
+ def.color,
+ (variableSlot) => convert(colors[variableSlot]).rgb,
+ mod
+ ))
+ }
+ ], [])
+ return { ...shadowsAcc, [slotName]: newShadow }
+ }, {})
+
+ return {
+ rules: {
+ shadows: Object
+ .entries(shadows)
+ // TODO for v2.2: if shadow doesn't have non-inset shadows with spread > 0 - optionally
+ // convert all non-inset shadows into filter: drop-shadow() to boost performance
+ .map(([k, v]) => [
+ `--${k}Shadow: ${getCssShadow(v)}`,
+ `--${k}ShadowFilter: ${getCssShadowFilter(v)}`,
+ `--${k}ShadowInset: ${getCssShadow(v, true)}`
+ ].join(';'))
+ .join(';')
+ },
+ theme: {
+ shadows
+ }
+ }
+}
+
+/**
+ * This handles compatibility issues when importing v2 theme's shadows to current format
+ *
+ * Back in v2 shadows allowed you to use dynamic colors however those used pure CSS3 variables
+ */
+export const shadows2to3 = (shadows, opacity) => {
+ return Object.entries(shadows).reduce((shadowsAcc, [slotName, shadowDefs]) => {
+ const isDynamic = ({ color = '#000000' }) => color.startsWith('--')
+ const getOpacity = ({ color }) => opacity[getOpacitySlot(color.substring(2).split(',')[0])]
+ const newShadow = shadowDefs.reduce((shadowAcc, def) => [
+ ...shadowAcc,
+ {
+ ...def,
+ alpha: isDynamic(def) ? getOpacity(def) || 1 : def.alpha
+ }
+ ], [])
+ return { ...shadowsAcc, [slotName]: newShadow }
+ }, {})
+}
+
+export const colors2to3 = (colors) => {
+ return Object.entries(colors).reduce((acc, [slotName, color]) => {
+ const btnPositions = ['', 'Panel', 'TopBar']
+ switch (slotName) {
+ case 'lightBg':
+ return { ...acc, highlight: color }
+ case 'btnText':
+ return {
+ ...acc,
+ ...btnPositions
+ .reduce(
+ (statePositionAcc, position) =>
+ ({ ...statePositionAcc, ['btn' + position + 'Text']: color })
+ , {}
+ )
+ }
+ default:
+ return { ...acc, [slotName]: color }
+ }
+ }, {})
+}
diff --git a/src/services/theme_data/theme_data_3.service.js b/src/services/theme_data/theme_data_3.service.js
new file mode 100644
index 00000000..7457ab99
--- /dev/null
+++ b/src/services/theme_data/theme_data_3.service.js
@@ -0,0 +1,468 @@
+import { convert, brightness } from 'chromatism'
+import { flattenDeep } from 'lodash'
+import {
+ alphaBlend,
+ getTextColor,
+ rgba2css,
+ mixrgb,
+ relativeLuminance
+} from '../color_convert/color_convert.js'
+
+import {
+ colorFunctions,
+ shadowFunctions,
+ process
+} from './theme3_slot_functions.js'
+
+import {
+ unroll,
+ getAllPossibleCombinations,
+ genericRuleToSelector,
+ normalizeCombination,
+ findRules
+} from './iss_utils.js'
+import { parseCssShadow } from './css_utils.js'
+
+// Ensuring the order of components
+const components = {
+ Root: null,
+ Text: null,
+ FunText: null,
+ Link: null,
+ Icon: null,
+ Border: null,
+ Panel: null,
+ Chat: null,
+ ChatMessage: null
+}
+
+const findShadow = (shadows, { dynamicVars, staticVars }) => {
+ return (shadows || []).map(shadow => {
+ let targetShadow
+ if (typeof shadow === 'string') {
+ if (shadow.startsWith('$')) {
+ targetShadow = process(shadow, shadowFunctions, { findColor, findShadow }, { dynamicVars, staticVars })
+ } else if (shadow.startsWith('--')) {
+ const [variable] = shadow.split(/,/g).map(str => str.trim()) // discarding modifier since it's not supported
+ const variableSlot = variable.substring(2)
+ return findShadow(staticVars[variableSlot], { dynamicVars, staticVars })
+ } else {
+ targetShadow = parseCssShadow(shadow)
+ }
+ } else {
+ targetShadow = shadow
+ }
+
+ const shadowArray = Array.isArray(targetShadow) ? targetShadow : [targetShadow]
+ return shadowArray.map(s => ({
+ ...s,
+ color: findColor(s.color, { dynamicVars, staticVars })
+ }))
+ })
+}
+
+const findColor = (color, { dynamicVars, staticVars }) => {
+ if (typeof color !== 'string' || (!color.startsWith('--') && !color.startsWith('$'))) return color
+ let targetColor = null
+ if (color.startsWith('--')) {
+ const [variable, modifier] = color.split(/,/g).map(str => str.trim())
+ const variableSlot = variable.substring(2)
+ if (variableSlot === 'stack') {
+ const { r, g, b } = dynamicVars.stacked
+ targetColor = { r, g, b }
+ } else if (variableSlot.startsWith('parent')) {
+ if (variableSlot === 'parent') {
+ const { r, g, b } = dynamicVars.lowerLevelBackground
+ targetColor = { r, g, b }
+ } else {
+ const virtualSlot = variableSlot.replace(/^parent/, '')
+ targetColor = convert(dynamicVars.lowerLevelVirtualDirectivesRaw[virtualSlot]).rgb
+ }
+ } else {
+ switch (variableSlot) {
+ case 'inheritedBackground':
+ targetColor = convert(dynamicVars.inheritedBackground).rgb
+ break
+ case 'background':
+ targetColor = convert(dynamicVars.background).rgb
+ break
+ default:
+ targetColor = convert(staticVars[variableSlot]).rgb
+ }
+ }
+
+ if (modifier) {
+ const effectiveBackground = dynamicVars.lowerLevelBackground ?? targetColor
+ const isLightOnDark = relativeLuminance(convert(effectiveBackground).rgb) < 0.5
+ const mod = isLightOnDark ? 1 : -1
+ targetColor = brightness(Number.parseFloat(modifier) * mod, targetColor).rgb
+ }
+ }
+
+ if (color.startsWith('$')) {
+ try {
+ targetColor = process(color, colorFunctions, { findColor }, { dynamicVars, staticVars })
+ } catch (e) {
+ console.error('Failure executing color function', e)
+ targetColor = '#FF00FF'
+ }
+ }
+ // Color references other color
+ return targetColor
+}
+
+const getTextColorAlpha = (directives, intendedTextColor, dynamicVars, staticVars) => {
+ const opacity = directives.textOpacity
+ const backgroundColor = convert(dynamicVars.lowerLevelBackground).rgb
+ const textColor = convert(findColor(intendedTextColor, { dynamicVars, staticVars })).rgb
+ if (opacity === null || opacity === undefined || opacity >= 1) {
+ return convert(textColor).hex
+ }
+ if (opacity === 0) {
+ return convert(backgroundColor).hex
+ }
+ const opacityMode = directives.textOpacityMode
+ switch (opacityMode) {
+ case 'fake':
+ return convert(alphaBlend(textColor, opacity, backgroundColor)).hex
+ case 'mixrgb':
+ return convert(mixrgb(backgroundColor, textColor)).hex
+ default:
+ return rgba2css({ a: opacity, ...textColor })
+ }
+}
+
+// Loading all style.js[on] files dynamically
+const componentsContext = require.context('src', true, /\.style.js(on)?$/)
+componentsContext.keys().forEach(key => {
+ const component = componentsContext(key).default
+ if (components[component.name] != null) {
+ console.warn(`Component in file ${key} is trying to override existing component ${component.name}! You have collisions/duplicates!`)
+ }
+ components[component.name] = component
+})
+
+const ruleToSelector = genericRuleToSelector(components)
+
+export const init = (extraRuleset, ultimateBackgroundColor) => {
+ const staticVars = {}
+ const stacked = {}
+ const computed = {}
+
+ const rulesetUnsorted = [
+ ...Object.values(components)
+ .map(c => (c.defaultRules || []).map(r => ({ component: c.name, ...r })))
+ .reduce((acc, arr) => [...acc, ...arr], []),
+ ...extraRuleset
+ ].map(rule => {
+ normalizeCombination(rule)
+ let currentParent = rule.parent
+ while (currentParent) {
+ normalizeCombination(currentParent)
+ currentParent = currentParent.parent
+ }
+
+ return rule
+ })
+
+ const ruleset = rulesetUnsorted
+ .map((data, index) => ({ data, index }))
+ .sort(({ data: a, index: ai }, { data: b, index: bi }) => {
+ const parentsA = unroll(a).length
+ const parentsB = unroll(b).length
+
+ if (parentsA === parentsB) {
+ if (a.component === 'Text') return -1
+ if (b.component === 'Text') return 1
+ return ai - bi
+ }
+ if (parentsA === 0 && parentsB !== 0) return -1
+ if (parentsB === 0 && parentsA !== 0) return 1
+ return parentsA - parentsB
+ })
+ .map(({ data }) => data)
+
+ const virtualComponents = new Set(Object.values(components).filter(c => c.virtual).map(c => c.name))
+
+ const processCombination = (combination) => {
+ const selector = ruleToSelector(combination, true)
+ const cssSelector = ruleToSelector(combination)
+
+ const parentSelector = selector.split(/ /g).slice(0, -1).join(' ')
+ const soloSelector = selector.split(/ /g).slice(-1)[0]
+
+ const lowerLevelSelector = parentSelector
+ const lowerLevelBackground = computed[lowerLevelSelector]?.background
+ const lowerLevelVirtualDirectives = computed[lowerLevelSelector]?.virtualDirectives
+ const lowerLevelVirtualDirectivesRaw = computed[lowerLevelSelector]?.virtualDirectivesRaw
+
+ const dynamicVars = computed[selector] || {
+ lowerLevelBackground,
+ lowerLevelVirtualDirectives,
+ lowerLevelVirtualDirectivesRaw
+ }
+
+ // Inheriting all of the applicable rules
+ const existingRules = ruleset.filter(findRules(combination))
+ const computedDirectives = existingRules.map(r => r.directives).reduce((acc, directives) => ({ ...acc, ...directives }), {})
+ const computedRule = {
+ ...combination,
+ directives: computedDirectives
+ }
+
+ computed[selector] = computed[selector] || {}
+ computed[selector].computedRule = computedRule
+ computed[selector].dynamicVars = dynamicVars
+
+ if (virtualComponents.has(combination.component)) {
+ const virtualName = [
+ '--',
+ combination.component.toLowerCase(),
+ combination.variant === 'normal'
+ ? ''
+ : combination.variant[0].toUpperCase() + combination.variant.slice(1).toLowerCase(),
+ ...combination.state.filter(x => x !== 'normal').toSorted().map(state => state[0].toUpperCase() + state.slice(1).toLowerCase())
+ ].join('')
+
+ let inheritedTextColor = computedDirectives.textColor
+ let inheritedTextAuto = computedDirectives.textAuto
+ let inheritedTextOpacity = computedDirectives.textOpacity
+ let inheritedTextOpacityMode = computedDirectives.textOpacityMode
+ const lowerLevelTextSelector = [...selector.split(/ /g).slice(0, -1), soloSelector].join(' ')
+ const lowerLevelTextRule = computed[lowerLevelTextSelector]
+
+ if (inheritedTextColor == null || inheritedTextOpacity == null || inheritedTextOpacityMode == null) {
+ inheritedTextColor = computedDirectives.textColor ?? lowerLevelTextRule.textColor
+ inheritedTextAuto = computedDirectives.textAuto ?? lowerLevelTextRule.textAuto
+ inheritedTextOpacity = computedDirectives.textOpacity ?? lowerLevelTextRule.textOpacity
+ inheritedTextOpacityMode = computedDirectives.textOpacityMode ?? lowerLevelTextRule.textOpacityMode
+ }
+
+ const newTextRule = {
+ ...computedRule,
+ directives: {
+ ...computedRule.directives,
+ textColor: inheritedTextColor,
+ textAuto: inheritedTextAuto ?? 'preserve',
+ textOpacity: inheritedTextOpacity,
+ textOpacityMode: inheritedTextOpacityMode
+ }
+ }
+
+ dynamicVars.inheritedBackground = lowerLevelBackground
+ dynamicVars.stacked = convert(stacked[lowerLevelSelector]).rgb
+
+ const intendedTextColor = convert(findColor(inheritedTextColor, { dynamicVars, staticVars })).rgb
+ const textColor = newTextRule.directives.textAuto === 'no-auto'
+ ? intendedTextColor
+ : getTextColor(
+ convert(stacked[lowerLevelSelector]).rgb,
+ intendedTextColor,
+ newTextRule.directives.textAuto === 'preserve'
+ )
+ const virtualDirectives = computed[lowerLevelSelector].virtualDirectives || {}
+ const virtualDirectivesRaw = computed[lowerLevelSelector].virtualDirectivesRaw || {}
+
+ // Storing color data in lower layer to use as custom css properties
+ virtualDirectives[virtualName] = getTextColorAlpha(newTextRule.directives, textColor, dynamicVars)
+ virtualDirectivesRaw[virtualName] = textColor
+
+ computed[lowerLevelSelector].virtualDirectives = virtualDirectives
+ computed[lowerLevelSelector].virtualDirectivesRaw = virtualDirectivesRaw
+
+ return {
+ dynamicVars,
+ selector: cssSelector.split(/ /g).slice(0, -1).join(' '),
+ ...combination,
+ directives: {},
+ virtualDirectives: {
+ [virtualName]: getTextColorAlpha(newTextRule.directives, textColor, dynamicVars)
+ },
+ virtualDirectivesRaw: {
+ [virtualName]: textColor
+ }
+ }
+ } else {
+ computed[selector] = computed[selector] || {}
+
+ // TODO: DEFAULT TEXT COLOR
+ const lowerLevelStackedBackground = stacked[lowerLevelSelector] || convert(ultimateBackgroundColor).rgb
+
+ if (computedDirectives.background) {
+ let inheritRule = null
+ const variantRules = ruleset.filter(
+ findRules({
+ component: combination.component,
+ variant: combination.variant,
+ parent: combination.parent
+ })
+ )
+ const lastVariantRule = variantRules[variantRules.length - 1]
+ if (lastVariantRule) {
+ inheritRule = lastVariantRule
+ } else {
+ const normalRules = ruleset.filter(findRules({
+ component: combination.component,
+ parent: combination.parent
+ }))
+ const lastNormalRule = normalRules[normalRules.length - 1]
+ inheritRule = lastNormalRule
+ }
+
+ const inheritSelector = ruleToSelector({ ...inheritRule, parent: combination.parent }, true)
+ const inheritedBackground = computed[inheritSelector].background
+
+ dynamicVars.inheritedBackground = inheritedBackground
+
+ const rgb = convert(findColor(computedDirectives.background, { dynamicVars, staticVars })).rgb
+
+ if (!stacked[selector]) {
+ let blend
+ const alpha = computedDirectives.opacity ?? 1
+ if (alpha >= 1) {
+ blend = rgb
+ } else if (alpha <= 0) {
+ blend = lowerLevelStackedBackground
+ } else {
+ blend = alphaBlend(rgb, computedDirectives.opacity, lowerLevelStackedBackground)
+ }
+ stacked[selector] = blend
+ computed[selector].background = { ...rgb, a: computedDirectives.opacity ?? 1 }
+ }
+ }
+
+ if (computedDirectives.shadow) {
+ dynamicVars.shadow = flattenDeep(findShadow(flattenDeep(computedDirectives.shadow), { dynamicVars, staticVars }))
+ }
+
+ if (!stacked[selector]) {
+ computedDirectives.background = 'transparent'
+ computedDirectives.opacity = 0
+ stacked[selector] = lowerLevelStackedBackground
+ computed[selector].background = { ...lowerLevelStackedBackground, a: 0 }
+ }
+
+ dynamicVars.stacked = stacked[selector]
+ dynamicVars.background = computed[selector].background
+
+ const dynamicSlots = Object.entries(computedDirectives).filter(([k, v]) => k.startsWith('--'))
+
+ dynamicSlots.forEach(([k, v]) => {
+ const [type, ...value] = v.split('|').map(x => x.trim()) // woah, Extreme!
+ switch (type) {
+ case 'color': {
+ const color = findColor(value[0], { dynamicVars, staticVars })
+ dynamicVars[k] = color
+ if (combination.component === 'Root') {
+ staticVars[k.substring(2)] = color
+ }
+ break
+ }
+ case 'shadow': {
+ const shadow = value
+ dynamicVars[k] = shadow
+ if (combination.component === 'Root') {
+ staticVars[k.substring(2)] = shadow
+ }
+ break
+ }
+ case 'generic': {
+ dynamicVars[k] = value
+ if (combination.component === 'Root') {
+ staticVars[k.substring(2)] = value
+ }
+ break
+ }
+ }
+ })
+
+ const rule = {
+ dynamicVars,
+ selector: cssSelector,
+ ...combination,
+ directives: computedDirectives
+ }
+
+ return rule
+ }
+ }
+
+ const processInnerComponent = (component, parent) => {
+ const combinations = []
+ const {
+ validInnerComponents = [],
+ states: originalStates = {},
+ variants: originalVariants = {}
+ } = component
+
+ // Normalizing states and variants to always include "normal"
+ const states = { normal: '', ...originalStates }
+ const variants = { normal: '', ...originalVariants }
+ const innerComponents = (validInnerComponents).map(name => {
+ const result = components[name]
+ if (result === undefined) console.error(`Component ${component.name} references a component ${name} which does not exist!`)
+ return result
+ })
+
+ // Optimization: we only really need combinations without "normal" because all states implicitly have it
+ const permutationStateKeys = Object.keys(states).filter(s => s !== 'normal')
+ const stateCombinations = [
+ ['normal'],
+ ...getAllPossibleCombinations(permutationStateKeys)
+ .map(combination => ['normal', ...combination])
+ .filter(combo => {
+ // Optimization: filter out some hard-coded combinations that don't make sense
+ if (combo.indexOf('disabled') >= 0) {
+ return !(
+ combo.indexOf('hover') >= 0 ||
+ combo.indexOf('focused') >= 0 ||
+ combo.indexOf('pressed') >= 0
+ )
+ }
+ return true
+ })
+ ]
+
+ const stateVariantCombination = Object.keys(variants).map(variant => {
+ return stateCombinations.map(state => ({ variant, state }))
+ }).reduce((acc, x) => [...acc, ...x], [])
+
+ stateVariantCombination.forEach(combination => {
+ combination.component = component.name
+ combination.lazy = component.lazy || parent?.lazy
+ combination.parent = parent
+ if (combination.state.indexOf('hover') >= 0) {
+ combination.lazy = true
+ }
+
+ combinations.push(combination)
+
+ innerComponents.forEach(innerComponent => {
+ combinations.push(...processInnerComponent(innerComponent, combination))
+ })
+ })
+
+ return combinations
+ }
+
+ const t0 = performance.now()
+ const combinations = processInnerComponent(components.Root)
+ const t1 = performance.now()
+ console.debug('Tree traveral took ' + (t1 - t0) + ' ms')
+
+ const result = combinations.map((combination) => {
+ if (combination.lazy) {
+ return async () => processCombination(combination)
+ } else {
+ return processCombination(combination)
+ }
+ }).filter(x => x)
+ const t2 = performance.now()
+ console.debug('Eager processing took ' + (t2 - t1) + ' ms')
+
+ return {
+ lazy: result.filter(x => typeof x === 'function'),
+ eager: result.filter(x => typeof x !== 'function'),
+ staticVars
+ }
+}
diff --git a/test/unit/specs/services/theme_data/theme_data3.spec.js b/test/unit/specs/services/theme_data/theme_data3.spec.js
new file mode 100644
index 00000000..bb8d785c
--- /dev/null
+++ b/test/unit/specs/services/theme_data/theme_data3.spec.js
@@ -0,0 +1,144 @@
+// import { topoSort } from 'src/services/theme_data/theme_data.service.js'
+import {
+ getAllPossibleCombinations
+} from 'src/services/theme_data/iss_utils.js'
+import {
+ init
+} from 'src/services/theme_data/theme_data_3.service.js'
+import {
+ basePaletteKeys
+} from 'src/services/theme_data/theme2_to_theme3.js'
+
+describe('Theme Data 3', () => {
+ describe('getAllPossibleCombinations', () => {
+ it('test simple 3 values case', () => {
+ const out = getAllPossibleCombinations([1, 2, 3]).map(x => x.sort((a, b) => a - b))
+ expect(out).to.eql([
+ [1], [2], [3],
+ [1, 2], [1, 3], [2, 3],
+ [1, 2, 3]
+ ])
+ })
+
+ it('test simple 4 values case', () => {
+ const out = getAllPossibleCombinations([1, 2, 3, 4]).map(x => x.sort((a, b) => a - b))
+ expect(out).to.eql([
+ [1], [2], [3], [4],
+ [1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4],
+ [1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4],
+ [1, 2, 3, 4]
+ ])
+ })
+
+ it('test massive 5 values case, using strings', () => {
+ const out = getAllPossibleCombinations(['a', 'b', 'c', 'd', 'e']).map(x => x.sort((a, b) => a - b))
+ expect(out).to.eql([
+ // 1
+ ['a'], ['b'], ['c'], ['d'], ['e'],
+ // 2
+ ['a', 'b'], ['a', 'c'], ['a', 'd'], ['a', 'e'],
+ ['b', 'c'], ['b', 'd'], ['b', 'e'],
+ ['c', 'd'], ['c', 'e'],
+ ['d', 'e'],
+ // 3
+ ['a', 'b', 'c'], ['a', 'b', 'd'], ['a', 'b', 'e'],
+ ['a', 'c', 'd'], ['a', 'c', 'e'],
+ ['a', 'd', 'e'],
+
+ ['b', 'c', 'd'], ['b', 'c', 'e'],
+ ['b', 'd', 'e'],
+
+ ['c', 'd', 'e'],
+ // 4
+ ['a', 'b', 'c', 'd'], ['a', 'b', 'c', 'e'],
+ ['a', 'b', 'd', 'e'],
+
+ ['a', 'c', 'd', 'e'],
+
+ ['b', 'c', 'd', 'e'],
+ // 5
+ ['a', 'b', 'c', 'd', 'e']
+ ])
+ })
+ })
+
+ describe('init', function () {
+ this.timeout(5000)
+
+ it('Test initialization without anything', () => {
+ const out = init([], '#DEADAF')
+
+ expect(out).to.have.property('eager')
+ expect(out).to.have.property('lazy')
+ expect(out).to.have.property('staticVars')
+
+ expect(out.lazy).to.be.an('array')
+ expect(out.lazy).to.have.lengthOf.above(1)
+ expect(out.eager).to.be.an('array')
+ expect(out.eager).to.have.lengthOf.above(1)
+ expect(out.staticVars).to.be.an('object')
+
+ // check backwards compat/generic stuff
+ basePaletteKeys.forEach(key => {
+ expect(out.staticVars).to.have.property(key)
+ })
+ })
+
+ it('Test initialization with a basic palette', () => {
+ const out = init([{
+ component: 'Root',
+ directives: {
+ '--bg': 'color | #008080',
+ '--fg': 'color | #00C0A0'
+ }
+ }], '#DEADAF')
+
+ expect(out.staticVars).to.have.property('bg').equal('#008080')
+ expect(out.staticVars).to.have.property('fg').equal('#00C0A0')
+
+ const panelRule = out.eager.filter(x => {
+ if (x.component !== 'Panel') return false
+ return true
+ })[0]
+
+ expect(panelRule).to.have.nested.deep.property('dynamicVars.stacked', { r: 0, g: 128, b: 128 })
+ })
+
+ it('Test initialization with opacity', () => {
+ const out = init([{
+ component: 'Root',
+ directives: {
+ '--bg': 'color | #008080'
+ }
+ }, {
+ component: 'Panel',
+ directives: {
+ opacity: 0.5
+ }
+ }], '#DEADAF')
+
+ expect(out.staticVars).to.have.property('bg').equal('#008080')
+
+ const panelRule = out.eager.filter(x => {
+ if (x.component !== 'Panel') return false
+ return true
+ })[0]
+
+ expect(panelRule).to.have.nested.deep.property('dynamicVars.background', { r: 0, g: 128, b: 128, a: 0.5 })
+ expect(panelRule).to.have.nested.deep.property('dynamicVars.stacked')
+ // Somewhat incorrect since we don't do gamma correction
+ // real expectancy should be this:
+ /*
+
+ expect(panelRule).to.have.nested.deep.property('dynamicVars.stacked.r').that.is.closeTo(147.0, 0.01)
+ expect(panelRule).to.have.nested.deep.property('dynamicVars.stacked.g').that.is.closeTo(143.2, 0.01)
+ expect(panelRule).to.have.nested.deep.property('dynamicVars.stacked.b').that.is.closeTo(144.0, 0.01)
+
+ */
+
+ expect(panelRule).to.have.nested.deep.property('dynamicVars.stacked.r').that.is.closeTo(88.8, 0.01)
+ expect(panelRule).to.have.nested.deep.property('dynamicVars.stacked.g').that.is.closeTo(133.2, 0.01)
+ expect(panelRule).to.have.nested.deep.property('dynamicVars.stacked.b').that.is.closeTo(134, 0.01)
+ })
+ })
+})
diff --git a/tools/check-changelog b/tools/check-changelog
index 848a9743..9b3ff37f 100644
--- a/tools/check-changelog
+++ b/tools/check-changelog
@@ -6,7 +6,7 @@ git remote add upstream https://git.pleroma.social/pleroma/pleroma-fe.git
git fetch upstream ${CI_MERGE_REQUEST_TARGET_BRANCH_NAME}:refs/remotes/upstream/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME
git diff --raw --no-renames upstream/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME HEAD -- changelog.d | \
- grep ' A\t' | grep '\.\(skip\|add\|remove\|fix\|security\)$'
+ grep ' A\t' | grep '\.\(skip\|add\|remove\|change\|fix\|security\)$'
ret=$?
if [ $ret -eq 0 ]; then