commit: d7f744d2819343eced508a589d6fad3842686a3e
parent 23edfe7b918844f8cc2ff5b9b245952092ee1cbf
Author: HJ <30-hj@users.noreply.git.pleroma.social>
Date: Tue, 26 Mar 2024 17:13:41 +0000
Merge branch 'themes3' into 'develop'
Themes 3.0 / Pleroma ISS (Interface Style Sheets) [Side A]
See merge request pleroma/pleroma-fe!1892
Diffstat:
189 files changed, 4628 insertions(+), 2266 deletions(-)
diff --git a/changelog.d/themes3.change 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
@@ -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
@@ -16,16 +17,15 @@
}
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 +42,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 +79,7 @@ body {
&::-webkit-scrollbar-button {
--___bgPadding: 2px;
- color: var(--btnText);
+ color: var(--text);
background-repeat: no-repeat, no-repeat;
&:horizontal {
@@ -69,15 +87,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 +105,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 +122,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 +145,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 +197,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 +205,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 +366,110 @@ 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);
+ 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);
+
+ --__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);
padding: 0;
line-height: unset;
cursor: pointer;
@@ -473,28 +477,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 +501,9 @@ 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);
+ box-shadow: var(--shadow);
+ font-family: var(--font);
font-size: 1em;
margin: 0;
box-sizing: border-box;
@@ -528,7 +517,6 @@ textarea,
&[disabled="disabled"],
&.disabled {
cursor: not-allowed;
- opacity: 0.5;
}
&[type="range"] {
@@ -543,9 +531,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 +547,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 +567,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 +587,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 +606,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 +634,7 @@ option {
li {
border: 1px solid var(--border);
- border-radius: var(--inputRadius);
+ border-radius: var(--roundness);
padding: 0.5em;
margin: 0.25em;
}
@@ -714,74 +695,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 +767,10 @@ option {
&.iconLetter {
font-size: 1.1em;
}
+
+ &.svg-inline--fa {
+ vertical-align: -0.15em;
+ }
}
.fa-old-padding {
@@ -816,6 +785,11 @@ option {
opacity: 0.25;
}
+.timeago {
+ --link: var(--text);
+ --linkFaint: var(--textFaint);
+}
+
.login-hint {
text-align: center;
@@ -914,3 +888,8 @@ option {
padding: 0;
position: absolute;
}
+
+*::selection {
+ color: var(--selectionText);
+ background-color: var(--selectionBackground);
+}
diff --git a/src/_variables.scss b/src/_variables.scss
@@ -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
@@ -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 }) => {
diff --git 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -1,5 +1,3 @@
-@import "../../variables";
-
.color-input {
display: inline-flex;
@@ -12,8 +10,6 @@
input {
background: none;
- color: $fallback--lightText;
- color: var(--inputText, $fallback--lightText);
border: none;
padding: 0;
margin: 0;
diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue
@@ -28,7 +28,10 @@
class="rightside-button"
/>
</div>
- <div class="conversation-body panel-body">
+ <div
+ class="conversation-body"
+ :class="{ 'panel-body': isExpanded }"
+ >
<div
v-if="isTreeView"
class="thread-body"
@@ -78,7 +81,7 @@
:replies="getReplies(status.id)"
:in-profile="inProfile"
:profile-user-id="profileUserId"
- class="conversation-status status-fadein panel-body"
+ class="conversation-status status-fadein"
:simple-tree="treeViewIsSimple"
:toggle-thread-display="toggleThreadDisplay"
@@ -183,7 +186,7 @@
:replies="getReplies(status.id)"
:in-profile="inProfile"
:profile-user-id="profileUserId"
- class="conversation-status status-fadein panel-body"
+ class="conversation-status status-fadein"
:toggle-thread-display="toggleThreadDisplay"
:thread-display-status="threadDisplayStatus"
@@ -203,6 +206,7 @@
</div>
<div
v-else
+ class="Conversation -hidden"
:style="hiddenStyle"
/>
</template>
@@ -210,14 +214,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 +234,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 +262,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 +280,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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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("admin")"
>
{{ $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("moderator")"
>
{{ $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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -42,8 +42,6 @@
<script src="./popover.js" />
<style lang="scss">
-@import "../../variables";
-
.popover-trigger-button {
display: inline-block;
}
@@ -53,42 +51,31 @@
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;
}
.dropdown-menu {
display: block;
- padding: 0.5rem 0;
+ padding: 0;
font-size: 1em;
text-align: left;
list-style: none;
@@ -100,34 +87,16 @@
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 +107,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 +135,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
@@ -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.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,43 @@
/>
</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="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.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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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,12 @@
vertical-align: middle;
object-fit: contain;
}
+
+ .greentext {
+ color: var(--funtextGreentext);
+ }
+
+ .cyantext {
+ color: var(--funtextCyantext);
+ }
}
diff --git a/src/components/rich_content/rich_content.style.js 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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/size_setting.vue b/src/components/settings_modal/helpers/size_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/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/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
@@ -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/settings_modal_admin_content.scss b/src/components/settings_modal/settings_modal_admin_content.scss
@@ -1,10 +1,8 @@
-@import "src/variables";
-
.settings_tab-switcher {
height: 100%;
.setting-item {
- border-bottom: 2px solid var(--fg, $fallback--fg);
+ border-bottom: 2px solid var(--border);
margin: 1em 1em 1.4em;
padding-bottom: 1.4em;
@@ -45,8 +43,7 @@
.unavailable,
.unavailable svg {
- color: var(--cRed, $fallback--cRed);
- color: $fallback--cRed;
+ color: var(--cRed);
}
}
}
diff --git a/src/components/settings_modal/settings_modal_user_content.scss b/src/components/settings_modal/settings_modal_user_content.scss
@@ -1,10 +1,8 @@
-@import "src/variables";
-
.settings_tab-switcher {
height: 100%;
.setting-item {
- border-bottom: 2px solid var(--fg, $fallback--fg);
+ border-bottom: 2px solid var(--border);
margin: 1em 1em 1.4em;
padding-bottom: 1.4em;
@@ -45,8 +43,7 @@
.unavailable,
.unavailable svg {
- color: var(--cRed, $fallback--cRed);
- color: $fallback--cRed;
+ color: var(--cRed);
}
}
}
diff --git a/src/components/settings_modal/tabs/filtering_tab.vue b/src/components/settings_modal/tabs/filtering_tab.vue
@@ -67,7 +67,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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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.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
@@ -180,7 +180,7 @@
<span class="heading-right">
<router-link
- class="timeago faint-link"
+ class="timeago faint"
:to="{ name: 'conversation', params: { id: status.id } }"
>
<Timeago
@@ -450,7 +450,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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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,14 @@
}
.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);
position: relative;
white-space: nowrap;
padding: 6px 1em;
@@ -188,8 +189,6 @@
&.active {
background: transparent;
z-index: 5;
- color: $fallback--text;
- color: var(--tabActiveText, $fallback--text);
}
img {
@@ -231,7 +230,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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
}
}
@@ -334,8 +297,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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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"
@@ -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
@@ -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
@@ -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/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
@@ -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/style_setter/style_setter.js b/src/services/style_setter/style_setter.js
@@ -1,24 +1,87 @@
-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 applyTheme = async (input) => {
+ 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
+ // 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())
const body = document.body
+
+ const styleSheet = new CSSStyleSheet()
+ document.adoptedStyleSheets = [styleSheet]
body.classList.add('hidden')
- const styleEl = document.createElement('style')
- head.appendChild(styleEl)
- const styleSheet = styleEl.sheet
+ 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('')
+ styleSheet.insertRule(newRule, 'index-max')
+ } else {
+ styleSheet.insertRule(rule, 'index-max')
+ }
+ })
- 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')
+
+ // 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[1],
+ '}'
+ ].join('')
+ styleSheet.insertRule(newRule, 'index-max')
+ } else {
+ styleSheet.insertRule(rule, 'index-max')
+ }
+ })
+ // const t1 = performance.now()
+ // console.debug('Chunk ' + counter + ' took ' + (t1 - t0) + 'ms')
+ // t0 = t1
+ counter += 1
+ if (counter < chunks.length) {
+ setTimeout(processChunk, 0)
+ }
+ })
+ }
+ setTimeout(processChunk, 0)
+
+ return Promise.resolve()
}
const configColumns = ({ sidebarColumnWidth, contentColumnWidth, notifsColumnWidth, emojiReactionsScale }) =>
@@ -51,308 +114,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 +143,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 +169,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
@@ -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
@@ -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
@@ -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
@@ -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',
+
+ 'badgeNotification',
+ 'badgeNotificationText',
+
+ 'badgeNeutral',
+ 'badgeNeutralText',
+
+ '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
@@ -0,0 +1,534 @@
+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 {
+ 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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