Merge branch 'release/2.7.x' into 'master'

Release 2.7.0

See merge request pleroma/pleroma-fe!1928
This commit is contained in:
HJ 2024-07-31 16:31:06 +00:00
commit 6bc020c733
277 changed files with 12646 additions and 3940 deletions

1
.gitignore vendored
View file

@ -8,3 +8,4 @@ selenium-debug.log
.idea/ .idea/
config/local.json config/local.json
static/emoji.json static/emoji.json
logs/

View file

@ -43,6 +43,8 @@ lint:
test: test:
stage: test stage: test
tags:
- amd64
variables: variables:
APT_CACHE_DIR: apt-cache APT_CACHE_DIR: apt-cache
script: script:
@ -54,6 +56,8 @@ test:
build: build:
stage: build stage: build
tags:
- amd64
script: script:
- yarn - yarn
- npm run build - npm run build

View file

@ -3,6 +3,61 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## 2.7.0
### Known issues
We got some reports related to emoji picker performance, this hopefully will be fixed in 2.7.1.
### Notes
This release overhauls how themes work, themes now need to be "compiled", which can cause some delay when loading for the first time and temporarily look "wrong" in some places (popups, menus, dialogs). Please do report any issues, especially if your theme looks wrong or breaks interface when loading. Also report issues if you're experiencing constant performance issues.
To admins: remember that you can update PleromaFE to recent `master` or `develop` in admin dashboard in "Front-ends" tab, scroll down to find PleromaFE box and click "Reinstall `master`" or dropdown and then "Reinstall `develop`". Currently there is no mechanism to check if there is an update or not.
### Changed
- Overhauled the way themes work, migrating to new Pleroma Interface Style Sheets system aka "Themes 3".
- Notifications are no longer sorted by "seen" status since interacting with them can change their read status and makes UI jumpy. Old behavior can be restored in settings.
- Notifications are now shown through a ServiceWorker (since mobile chrome does not allow them otherwise), it's always enabled, even if previously we only enabled it for WebPush notifications only. If you don't like websites "running" while closed, check how to disable them in your browser. Old way to show notifications will be used as a fallback but might not have all the new features.
- Reorganized Settings modal to move out visual stuff into Appearance tab
### Added
- Emoji pack management to the admin panel
- Support `status` notification type (subscriptions/bell, fixes PleromaFE on newer PleromaBE versions)
- Poll end notifications.
- Added option to not mark all notifications when closing notifications drawer on mobile, this creates a new button to mark all as seen.
- Option to always "show" notifications when using web push for better compatibility with some browsers (chrome, edge, safari)
- Option to toggle what notification types appear in native notifications, by default less important ones (likes, repeats, etc) will no longer show up in native notifications.
- Option to treat non-interactive notifications (likes, repeats et all) as seen for visual purposes (no read mark, ignored in counters, still can show in native notifications)
- Ability to resize UI (and certain components) scale independent of browser/text scale
- Ability to override certain aspects of UI style independent of theme used (UI roundness, fonts, underlay)
- Theme selector with visual previews of the theme
- Display loading and error indicator for conversation page
- Option to only show scrobbles that are recent enough
- Interacting (opening reply box etc) or simply clicking on non-interactive notifications now marks them as read. Clicking on native notifications for non-interactive ones also marks them as seen.
- Support group actors
- Focusing into a tab clears all current desktop notifications
- Ability to change size of emoji
- Ability to view APNG (Animated PNG) attachments.
- Support showing extra notifications in the notifications column
- Create a link to the URL of the scrobble when it's present
- Allow hiding custom emojis in picker.
- Ability to mute sensitive posts (ported from eintei).
- Native notifications now also have "badge" property that matches instance's favicon (visible in Android Chromium at least)
- Display public favorites on user profiles
- Display quotes count on posts and add quotes list page
- Show a dedicated registration notice page when further action is required after registering
### Fixed
- Synchronized requested notification types with backend, hopefully should fix missing notifications for polls and follow requests
- Error that appeared on mobile Chromium (and derivatives) when native notifications are allowed
- Being unable to set notification visibility for reports and follow requests
- Native notifications appearing as many times as there are open tabs. Clicking on notification will focus last focused tab.
- The expiry date indication won't be shown if the poll never expires
- Profile mentions causing a 422 error on newer PleromaBE versions.
- Color inputs are less ugly now
- Unread notifications should now properly catch up between sessions (eventually) in polling mode
- Video posters on Safari
## 2.6.1 ## 2.6.1
### Fixed ### Fixed
- fix admin dashboard not having any feedback on frontend installation - fix admin dashboard not having any feedback on frontend installation

View file

@ -4,6 +4,8 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1,user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1,user-scalable=no">
<link rel="icon" type="image/png" href="/favicon.png"> <link rel="icon" type="image/png" href="/favicon.png">
<style id="pleroma-eager-styles" type="text/css"></style>
<style id="pleroma-lazy-styles" type="text/css"></style>
<!--server-generated-meta--> <!--server-generated-meta-->
</head> </head>
<body class="hidden"> <body class="hidden">

View file

@ -1,6 +1,6 @@
{ {
"name": "pleroma_fe", "name": "pleroma_fe",
"version": "2.6.1", "version": "2.7.0",
"description": "Pleroma frontend, the default frontend of Pleroma social network server", "description": "Pleroma frontend, the default frontend of Pleroma social network server",
"author": "Pleroma contributors <https://git.pleroma.social/pleroma/pleroma-fe/-/blob/develop/CONTRIBUTORS.md>", "author": "Pleroma contributors <https://git.pleroma.social/pleroma/pleroma-fe/-/blob/develop/CONTRIBUTORS.md>",
"private": false, "private": false,
@ -24,15 +24,16 @@
"@fortawesome/vue-fontawesome": "3.0.3", "@fortawesome/vue-fontawesome": "3.0.3",
"@kazvmoe-infra/pinch-zoom-element": "1.2.0", "@kazvmoe-infra/pinch-zoom-element": "1.2.0",
"@kazvmoe-infra/unicode-emoji-json": "0.4.0", "@kazvmoe-infra/unicode-emoji-json": "0.4.0",
"@ruffle-rs/ruffle": "0.1.0-nightly.2022.7.12", "@ruffle-rs/ruffle": "0.1.0-nightly.2024.3.17",
"@vuelidate/core": "2.0.2", "@vuelidate/core": "2.0.3",
"@vuelidate/validators": "2.0.0", "@vuelidate/validators": "2.0.4",
"body-scroll-lock": "3.1.5", "body-scroll-lock": "3.1.5",
"chromatism": "3.0.0", "chromatism": "3.0.0",
"click-outside-vue3": "4.0.1", "click-outside-vue3": "4.0.1",
"cropperjs": "1.5.13", "cropperjs": "1.5.13",
"escape-html": "1.0.3", "escape-html": "1.0.3",
"js-cookie": "3.0.1", "hash-sum": "^2.0.0",
"js-cookie": "3.0.5",
"localforage": "1.10.0", "localforage": "1.10.0",
"parse-link-header": "2.0.0", "parse-link-header": "2.0.0",
"phoenix": "1.7.7", "phoenix": "1.7.7",
@ -55,13 +56,13 @@
"@babel/preset-env": "7.21.5", "@babel/preset-env": "7.21.5",
"@babel/register": "7.21.0", "@babel/register": "7.21.0",
"@intlify/vue-i18n-loader": "5.0.1", "@intlify/vue-i18n-loader": "5.0.1",
"@ungap/event-target": "0.2.3", "@ungap/event-target": "0.2.4",
"@vue/babel-helper-vue-jsx-merge-props": "1.4.0", "@vue/babel-helper-vue-jsx-merge-props": "1.4.0",
"@vue/babel-plugin-jsx": "1.1.1", "@vue/babel-plugin-jsx": "1.2.1",
"@vue/compiler-sfc": "3.2.45", "@vue/compiler-sfc": "3.2.45",
"@vue/test-utils": "2.2.8", "@vue/test-utils": "2.2.8",
"autoprefixer": "10.4.14", "autoprefixer": "10.4.19",
"babel-loader": "9.1.2", "babel-loader": "9.1.3",
"babel-plugin-lodash": "3.3.4", "babel-plugin-lodash": "3.3.4",
"chai": "4.3.7", "chai": "4.3.7",
"chalk": "1.1.3", "chalk": "1.1.3",
@ -69,7 +70,7 @@
"connect-history-api-fallback": "2.0.0", "connect-history-api-fallback": "2.0.0",
"copy-webpack-plugin": "11.0.0", "copy-webpack-plugin": "11.0.0",
"cross-spawn": "7.0.3", "cross-spawn": "7.0.3",
"css-loader": "6.7.3", "css-loader": "6.10.0",
"css-minimizer-webpack-plugin": "4.2.2", "css-minimizer-webpack-plugin": "4.2.2",
"custom-event-polyfill": "1.0.7", "custom-event-polyfill": "1.0.7",
"eslint": "8.33.0", "eslint": "8.33.0",
@ -99,7 +100,7 @@
"lodash": "4.17.21", "lodash": "4.17.21",
"mini-css-extract-plugin": "2.7.6", "mini-css-extract-plugin": "2.7.6",
"mocha": "10.2.0", "mocha": "10.2.0",
"nightwatch": "2.6.20", "nightwatch": "2.6.25",
"opn": "5.5.0", "opn": "5.5.0",
"ora": "0.4.1", "ora": "0.4.1",
"postcss": "8.4.23", "postcss": "8.4.23",

0
preview.style.js Normal file
View file

View file

@ -1,10 +1,9 @@
// stylelint-disable rscss/class-format // stylelint-disable rscss/class-format
/* stylelint-disable no-descending-specificity */ /* stylelint-disable no-descending-specificity */
@import "./variables";
@import "./panel"; @import "./panel";
:root { :root {
--navbar-height: 3.5rem; --status-margin: 0.75em;
--post-line-height: 1.4; --post-line-height: 1.4;
// Z-Index stuff // Z-Index stuff
--ZI_media_modal: 9000; --ZI_media_modal: 9000;
@ -13,19 +12,25 @@
--ZI_navbar_popovers: 7500; --ZI_navbar_popovers: 7500;
--ZI_navbar: 7000; --ZI_navbar: 7000;
--ZI_popovers: 6000; --ZI_popovers: 6000;
// Fallback for when stuff is loading
--background: var(--bg);
} }
html { html {
font-size: 14px; font-size: var(--textSize, 14px);
--navbar-height: var(--navbarSize, 3.5rem);
--emoji-size: var(--emojiSize, 32px);
--panel-header-height: var(--panelHeaderSize, 3.2rem);
// overflow-x: clip causes my browser's tab to crash with SIGILL lul // overflow-x: clip causes my browser's tab to crash with SIGILL lul
} }
body { body {
font-family: sans-serif; font-family: sans-serif;
font-family: var(--interfaceFont, sans-serif); font-family: var(--font);
margin: 0; margin: 0;
color: $fallback--text; color: var(--text);
color: var(--text, $fallback--text);
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
overscroll-behavior-y: none; overscroll-behavior-y: none;
@ -42,17 +47,35 @@ body {
// have a cursor/pointer to operate them // have a cursor/pointer to operate them
@media (any-pointer: fine) { @media (any-pointer: fine) {
* { * {
scrollbar-color: var(--btn) transparent; scrollbar-color: var(--fg) transparent;
&::-webkit-scrollbar { &::-webkit-scrollbar {
background: transparent; 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-button,
&::-webkit-scrollbar-thumb { &::-webkit-scrollbar-thumb {
background-color: var(--btn); box-shadow: var(--shadow);
box-shadow: var(--buttonShadow); border-radius: var(--roundness);
border-radius: var(--btnRadius);
} }
// horizontal/vertical/increment/decrement are webkit-specific stuff // horizontal/vertical/increment/decrement are webkit-specific stuff
@ -61,7 +84,7 @@ body {
&::-webkit-scrollbar-button { &::-webkit-scrollbar-button {
--___bgPadding: 2px; --___bgPadding: 2px;
color: var(--btnText); color: var(--text);
background-repeat: no-repeat, no-repeat; background-repeat: no-repeat, no-repeat;
&:horizontal { &:horizontal {
@ -69,15 +92,15 @@ body {
&:increment { &:increment {
background-image: background-image:
linear-gradient(45deg, var(--btnText) 50%, transparent 51%), linear-gradient(45deg, var(--text) 50%, transparent 51%),
linear-gradient(-45deg, transparent 50%, var(--btnText) 51%); linear-gradient(-45deg, transparent 50%, var(--text) 51%);
background-position: top var(--___bgPadding) left 50%, right 50% bottom var(--___bgPadding); background-position: top var(--___bgPadding) left 50%, right 50% bottom var(--___bgPadding);
} }
&:decrement { &:decrement {
background-image: background-image:
linear-gradient(45deg, transparent 50%, var(--btnText) 51%), linear-gradient(45deg, transparent 50%, var(--text) calc(50% + 1px)),
linear-gradient(-45deg, var(--btnText) 50%, transparent 51%); linear-gradient(-45deg, var(--text) 50%, transparent 51%);
background-position: bottom var(--___bgPadding) right 50%, left 50% top var(--___bgPadding); background-position: bottom var(--___bgPadding) right 50%, left 50% top var(--___bgPadding);
} }
} }
@ -87,15 +110,15 @@ body {
&:increment { &:increment {
background-image: background-image:
linear-gradient(-45deg, transparent 50%, var(--btnText) 51%), linear-gradient(-45deg, transparent 50%, var(--text) 51%),
linear-gradient(45deg, transparent 50%, var(--btnText) 51%); linear-gradient(45deg, transparent 50%, var(--text) 51%);
background-position: right var(--___bgPadding) top 50%, left var(--___bgPadding) top 50%; background-position: right var(--___bgPadding) top 50%, left var(--___bgPadding) top 50%;
} }
&:decrement { &:decrement {
background-image: background-image:
linear-gradient(-45deg, var(--btnText) 50%, transparent 51%), linear-gradient(-45deg, var(--text) 50%, transparent 51%),
linear-gradient(45deg, var(--btnText) 50%, transparent 51%); linear-gradient(45deg, var(--text) 50%, transparent 51%);
background-position: left var(--___bgPadding) top 50%, right var(--___bgPadding) top 50%; background-position: left var(--___bgPadding) top 50%, right var(--___bgPadding) top 50%;
} }
} }
@ -104,15 +127,14 @@ body {
} }
// Body should have background to scrollbar otherwise it will use white (body color?) // Body should have background to scrollbar otherwise it will use white (body color?)
html { html {
scrollbar-color: var(--selectedMenu) var(--wallpaper); scrollbar-color: var(--fg) var(--wallpaper);
background: var(--wallpaper); background: var(--wallpaper);
} }
} }
a { a {
text-decoration: none; text-decoration: none;
color: $fallback--link; color: var(--link);
color: var(--link, $fallback--link);
} }
h4 { h4 {
@ -128,29 +150,15 @@ h4 {
i[class*="icon-"], i[class*="icon-"],
.svg-inline--fa, .svg-inline--fa,
.iconLetter { .iconLetter {
color: $fallback--icon; color: var(--icon);
color: var(--icon, $fallback--icon);
}
.button-unstyled:hover,
a:hover {
> i[class*="icon-"],
> .svg-inline--fa,
> .iconLetter {
color: var(--text);
}
} }
nav { nav {
z-index: var(--ZI_navbar); z-index: var(--ZI_navbar);
background-color: $fallback--fg; box-shadow: var(--shadow);
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-sizing: border-box; box-sizing: border-box;
height: var(--navbar-height); height: var(--navbar-height);
font-size: calc(var(--navbar-height) / 3.5);
position: fixed; position: fixed;
} }
@ -195,16 +203,14 @@ nav {
grid-column: 1 / span 3; grid-column: 1 / span 3;
grid-row: 1 / 1; grid-row: 1 / 1;
pointer-events: none; pointer-events: none;
background-color: rgb(0 0 0 / 15%); background-color: var(--underlay);
background-color: var(--underlay, rgb(0 0 0 / 15%));
z-index: -1000; z-index: -1000;
} }
.app-layout { .app-layout {
--miniColumn: 25rem; --miniColumn: 25rem;
--maxiColumn: 45rem; --maxiColumn: 45rem;
--columnGap: 1em; --columnGap: 1rem;
--status-margin: 0.75em;
--effectiveSidebarColumnWidth: minmax(var(--miniColumn), var(--sidebarColumnWidth, var(--miniColumn))); --effectiveSidebarColumnWidth: minmax(var(--miniColumn), var(--sidebarColumnWidth, var(--miniColumn)));
--effectiveNotifsColumnWidth: minmax(var(--miniColumn), var(--notifsColumnWidth, var(--miniColumn))); --effectiveNotifsColumnWidth: minmax(var(--miniColumn), var(--notifsColumnWidth, var(--miniColumn)));
--effectiveContentColumnWidth: minmax(var(--miniColumn), var(--contentColumnWidth, var(--maxiColumn))); --effectiveContentColumnWidth: minmax(var(--miniColumn), var(--contentColumnWidth, var(--maxiColumn)));
@ -366,106 +372,112 @@ nav {
.button-default { .button-default {
user-select: none; user-select: none;
color: $fallback--text; color: var(--text);
color: var(--btnText, $fallback--text);
background-color: $fallback--fg;
background-color: var(--btn, $fallback--fg);
border: none; border: none;
border-radius: $fallback--btnRadius;
border-radius: var(--btnRadius, $fallback--btnRadius);
cursor: pointer; cursor: pointer;
box-shadow: $fallback--buttonShadow; background-color: var(--background);
box-shadow: var(--buttonShadow); box-shadow: var(--shadow);
font-size: 1em; font-size: 1em;
font-family: sans-serif; font-family: sans-serif;
font-family: var(--interfaceFont, sans-serif); font-family: var(--font);
&.-sublime {
background: transparent;
}
i[class*="icon-"],
.svg-inline--fa {
color: $fallback--text;
color: var(--btnText, $fallback--text);
}
&::-moz-focus-inner { &::-moz-focus-inner {
border: none; border: none;
} }
&: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);
}
}
&:disabled { &:disabled {
cursor: not-allowed; cursor: not-allowed;
color: $fallback--text; }
color: var(--btnDisabledText, $fallback--text); }
background-color: $fallback--fg;
background-color: var(--btnDisabled, $fallback--fg);
svg, .menu-item,
i { .list-item {
color: $fallback--text; display: block;
color: var(--btnDisabledText, $fallback--text); box-sizing: border-box;
} border: none;
outline: none;
text-align: initial;
font-size: inherit;
font-family: inherit;
font-weight: 400;
cursor: pointer;
color: inherit;
clear: both;
position: relative;
white-space: nowrap;
border-color: var(--border);
border-style: solid;
border-width: 0;
border-top-width: 1px;
width: 100%;
line-height: var(--__line-height);
padding: var(--__vertical-gap) var(--__horizontal-gap);
background: transparent;
--__line-height: 1.5em;
--__horizontal-gap: 0.75em;
--__vertical-gap: 0.5em;
&.-non-interactive {
cursor: auto;
} }
&.toggled { &.-active,
color: $fallback--text; &:hover {
color: var(--btnToggledText, $fallback--text); border-top-width: 1px;
background-color: $fallback--fg; border-bottom-width: 1px;
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);
}
} }
&.danger { &.-active + &,
// TODO: add better color variable &:hover + & {
color: $fallback--text; border-top-width: 0;
color: var(--alertErrorPanelText, $fallback--text); }
background-color: $fallback--alertError;
background-color: var(--alertError, $fallback--alertError); &: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);
}
&: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 { .button-unstyled {
background: none;
border: none; border: none;
outline: none; outline: none;
display: inline; display: inline;
text-align: initial; text-align: initial;
font-size: 100%; font-size: 100%;
font-family: inherit; font-family: inherit;
box-shadow: var(--shadow);
background-color: transparent;
padding: 0; padding: 0;
line-height: unset; line-height: unset;
cursor: pointer; cursor: pointer;
@ -473,28 +485,23 @@ nav {
color: inherit; color: inherit;
&.-link { &.-link {
color: $fallback--link; /* stylelint-disable-next-line declaration-no-important */
color: var(--link, $fallback--link); color: var(--link) !important;
}
&.-fullwidth {
width: 100%;
}
&.-hover-highlight {
&:hover svg {
color: $fallback--lightText;
color: var(--lightText, $fallback--lightText);
}
} }
} }
input, input,
textarea, textarea {
border: none;
display: inline-block;
outline: none;
}
.input { .input {
&.unstyled { &.unstyled {
border-radius: 0; border-radius: 0;
background: none; /* stylelint-disable-next-line declaration-no-important */
background: none !important;
box-shadow: none; box-shadow: none;
height: unset; height: unset;
} }
@ -502,19 +509,10 @@ textarea,
--_padding: 0.5em; --_padding: 0.5em;
border: none; border: none;
border-radius: $fallback--inputRadius; background-color: var(--background);
border-radius: var(--inputRadius, $fallback--inputRadius); color: var(--text);
box-shadow: box-shadow: var(--shadow);
0 1px 0 0 rgb(0 0 0 / 20%) inset, font-family: var(--font);
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);
font-size: 1em; font-size: 1em;
margin: 0; margin: 0;
box-sizing: border-box; box-sizing: border-box;
@ -528,7 +526,6 @@ textarea,
&[disabled="disabled"], &[disabled="disabled"],
&.disabled { &.disabled {
cursor: not-allowed; cursor: not-allowed;
opacity: 0.5;
} }
&[type="range"] { &[type="range"] {
@ -543,9 +540,9 @@ textarea,
display: none; display: none;
&:checked + label::before { &:checked + label::before {
box-shadow: 0 0 2px black inset, 0 0 0 4px $fallback--fg inset; box-shadow: var(--shadow);
box-shadow: var(--inputShadow), 0 0 0 4px var(--fg, $fallback--fg) inset; background-color: var(--background);
background-color: var(--accent, $fallback--link); color: var(--text);
} }
&:disabled { &:disabled {
@ -559,16 +556,14 @@ textarea,
+ label::before { + label::before {
flex-shrink: 0; flex-shrink: 0;
display: inline-block; display: inline-block;
content: ""; content: "";
transition: box-shadow 200ms; transition: box-shadow 200ms;
width: 1.1em; width: 1.1em;
height: 1.1em; height: 1.1em;
border-radius: 100%; // Radio buttons should always be circle border-radius: 100%; // Radio buttons should always be circle
box-shadow: 0 0 2px black inset; background-color: var(--background);
box-shadow: var(--inputShadow); box-shadow: var(--shadow);
margin-right: 0.5em; margin-right: 0.5em;
background-color: $fallback--fg;
background-color: var(--input, $fallback--fg);
vertical-align: top; vertical-align: top;
text-align: center; text-align: center;
line-height: 1.1; line-height: 1.1;
@ -581,8 +576,9 @@ textarea,
&[type="checkbox"] { &[type="checkbox"] {
&:checked + label::before { &:checked + label::before {
color: $fallback--text; color: var(--text);
color: var(--inputText, $fallback--text); background-color: var(--background);
box-shadow: var(--shadow);
} }
&:disabled { &:disabled {
@ -600,13 +596,9 @@ textarea,
transition: color 200ms; transition: color 200ms;
width: 1.1em; width: 1.1em;
height: 1.1em; height: 1.1em;
border-radius: $fallback--checkboxRadius; border-radius: var(--roundness);
border-radius: var(--checkboxRadius, $fallback--checkboxRadius); box-shadow: var(--shadow);
box-shadow: 0 0 2px black inset;
box-shadow: var(--inputShadow);
margin-right: 0.5em; margin-right: 0.5em;
background-color: $fallback--fg;
background-color: var(--input, $fallback--fg);
vertical-align: top; vertical-align: top;
text-align: center; text-align: center;
line-height: 1.1; line-height: 1.1;
@ -622,17 +614,26 @@ textarea,
} }
} }
.input,
.button-default {
--_roundness-left: var(--roundness);
--_roundness-right: var(--roundness);
border-top-left-radius: var(--_roundness-left);
border-bottom-left-radius: var(--_roundness-left);
border-top-right-radius: var(--_roundness-right);
border-bottom-right-radius: var(--_roundness-right);
}
// Textareas should have stock line-height + vertical padding instead of huge line-height // Textareas should have stock line-height + vertical padding instead of huge line-height
textarea { textarea.input {
padding: var(--_padding); padding: var(--_padding);
line-height: var(--post-line-height); line-height: var(--post-line-height);
} }
option { option {
color: $fallback--text; color: var(--text);
color: var(--text, $fallback--text); background-color: var(--background);
background-color: $fallback--bg;
background-color: var(--bg, $fallback--bg);
} }
.hide-number-spinner { .hide-number-spinner {
@ -653,7 +654,7 @@ option {
li { li {
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: var(--inputRadius); border-radius: var(--roundness);
padding: 0.5em; padding: 0.5em;
margin: 0.25em; margin: 0.25em;
} }
@ -669,22 +670,23 @@ option {
display: inline-flex; display: inline-flex;
vertical-align: middle; vertical-align: middle;
button, > *,
.button-dropdown { > * .button-default {
--_roundness-left: 0;
--_roundness-right: 0;
position: relative; position: relative;
flex: 1 1 auto; flex: 1 1 auto;
}
&:not(:last-child), > *:first-child,
&:not(:last-child) .button-default { > *:first-child .button-default {
border-top-right-radius: 0; --_roundness-left: var(--roundness);
border-bottom-right-radius: 0; }
}
&:not(:first-child), > *:last-child,
&:not(:first-child) .button-default { > *:last-child .button-default {
border-top-left-radius: 0; --_roundness-right: var(--roundness);
border-bottom-left-radius: 0;
}
} }
} }
@ -714,74 +716,58 @@ option {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
&.badge-notification { &.-dot,
background-color: $fallback--cRed; &.-counter {
background-color: var(--badgeNotification, $fallback--cRed); margin: 0;
color: white; position: absolute;
color: var(--badgeNotificationText, white); }
&.-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;
}
&.-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;
} }
} }
.alert { .alert {
margin: 0 0.35em; margin: 0 0.35em;
padding: 0 0.25em; padding: 0 0.25em;
border-radius: $fallback--tooltipRadius; border-radius: var(--roundness);
border-radius: var(--tooltipRadius, $fallback--tooltipRadius); border: 1px solid var(--border);
&.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);
}
}
&.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);
}
}
&.success {
background-color: var(--alertSuccess, $fallback--alertWarning);
color: var(--alertSuccessText, $fallback--text);
.panel-heading & {
color: var(--alertSuccessPanelText, $fallback--text);
}
}
} }
.faint { .faint {
color: $fallback--faint; --text: var(--textFaint);
color: var(--faint, $fallback--faint); --link: var(--linkFaint);
}
.faint-link { color: var(--text);
color: $fallback--faint;
color: var(--faint, $fallback--faint);
&:hover {
text-decoration: underline;
}
} }
.visibility-notice { .visibility-notice {
padding: 0.5em; padding: 0.5em;
border: 1px solid $fallback--faint; border: 1px solid var(--textFaint);
border: 1px solid var(--faint, $fallback--faint); border-radius: var(--roundness);
border-radius: $fallback--inputRadius;
border-radius: var(--inputRadius, $fallback--inputRadius);
} }
.notice-dismissible { .notice-dismissible {
@ -802,6 +788,10 @@ option {
&.iconLetter { &.iconLetter {
font-size: 1.1em; font-size: 1.1em;
} }
&.svg-inline--fa {
vertical-align: -0.15em;
}
} }
.fa-old-padding { .fa-old-padding {
@ -816,6 +806,11 @@ option {
opacity: 0.25; opacity: 0.25;
} }
.timeago {
--link: var(--text);
--linkFaint: var(--textFaint);
}
.login-hint { .login-hint {
text-align: center; text-align: center;
@ -914,3 +909,8 @@ option {
padding: 0; padding: 0;
position: absolute; position: absolute;
} }
*::selection {
color: var(--selectionText);
background-color: var(--selectionBackground);
}

View file

@ -1,5 +1,6 @@
<template> <template>
<div <div
v-show="$store.state.interface.themeApplied"
id="app-loaded" id="app-loaded"
:style="bgStyle" :style="bgStyle"
> >

View file

@ -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;

View file

@ -13,9 +13,9 @@ import VBodyScrollLock from 'src/directives/body_scroll_lock'
import { windowWidth, windowHeight } from '../services/window_utils/window_utils' import { windowWidth, windowHeight } from '../services/window_utils/window_utils'
import { getOrCreateApp, getClientToken } from '../services/new_api/oauth.js' import { getOrCreateApp, getClientToken } from '../services/new_api/oauth.js'
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js' import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
import { CURRENT_VERSION } from '../services/theme_data/theme_data.service.js' import { applyConfig } from '../services/style_setter/style_setter.js'
import { applyTheme, applyConfig } from '../services/style_setter/style_setter.js'
import FaviconService from '../services/favicon_service/favicon_service.js' import FaviconService from '../services/favicon_service/favicon_service.js'
import { initServiceWorker, updateFocus } from '../services/sw/sw.js'
let staticInitialResults = null let staticInitialResults = null
@ -159,8 +159,6 @@ const setSettings = async ({ apiConfig, staticConfig, store }) => {
copyInstanceOption('showFeaturesPanel') copyInstanceOption('showFeaturesPanel')
copyInstanceOption('hideSitename') copyInstanceOption('hideSitename')
copyInstanceOption('sidebarRight') copyInstanceOption('sidebarRight')
return store.dispatch('setTheme', config.theme)
} }
const getTOS = async ({ store }) => { const getTOS = async ({ store }) => {
@ -260,6 +258,7 @@ const getNodeInfo = async ({ store }) => {
store.dispatch('setInstanceOption', { name: 'pollLimits', value: metadata.pollLimits }) store.dispatch('setInstanceOption', { name: 'pollLimits', value: metadata.pollLimits })
store.dispatch('setInstanceOption', { name: 'mailerEnabled', value: metadata.mailerEnabled }) store.dispatch('setInstanceOption', { name: 'mailerEnabled', value: metadata.mailerEnabled })
store.dispatch('setInstanceOption', { name: 'quotingAvailable', value: features.includes('quote_posting') }) store.dispatch('setInstanceOption', { name: 'quotingAvailable', value: features.includes('quote_posting') })
store.dispatch('setInstanceOption', { name: 'groupActorAvailable', value: features.includes('pleroma:group_actors') })
const uploadLimits = metadata.uploadLimits const uploadLimits = metadata.uploadLimits
store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadLimits.general) }) store.dispatch('setInstanceOption', { name: 'uploadlimit', value: parseInt(uploadLimits.general) })
@ -326,17 +325,14 @@ const setConfig = async ({ store }) => {
} }
const checkOAuthToken = async ({ store }) => { const checkOAuthToken = async ({ store }) => {
// eslint-disable-next-line no-async-promise-executor if (store.getters.getUserToken()) {
return new Promise(async (resolve, reject) => { try {
if (store.getters.getUserToken()) { await store.dispatch('loginUser', store.getters.getUserToken())
try { } catch (e) {
await store.dispatch('loginUser', store.getters.getUserToken()) console.error(e)
} catch (e) {
console.error(e)
}
} }
resolve() }
}) return Promise.resolve()
} }
const afterStoreSetup = async ({ store, i18n }) => { const afterStoreSetup = async ({ store, i18n }) => {
@ -344,28 +340,16 @@ const afterStoreSetup = async ({ store, i18n }) => {
store.dispatch('setLayoutHeight', windowHeight()) store.dispatch('setLayoutHeight', windowHeight())
FaviconService.initFaviconService() FaviconService.initFaviconService()
initServiceWorker(store)
window.addEventListener('focus', () => updateFocus())
const overrides = window.___pleromafe_dev_overrides || {} const overrides = window.___pleromafe_dev_overrides || {}
const server = (typeof overrides.target !== 'undefined') ? overrides.target : window.location.origin const server = (typeof overrides.target !== 'undefined') ? overrides.target : window.location.origin
store.dispatch('setInstanceOption', { name: 'server', value: server }) store.dispatch('setInstanceOption', { name: 'server', value: server })
await setConfig({ store }) await setConfig({ store })
await store.dispatch('setTheme')
const { customTheme, customThemeSource } = store.state.config
const { theme } = store.state.instance
const customThemePresent = customThemeSource || customTheme
if (customThemePresent) {
if (customThemeSource && customThemeSource.themeEngineVersion === CURRENT_VERSION) {
applyTheme(customThemeSource)
} else {
applyTheme(customTheme)
}
} else if (theme) {
// do nothing, it will load asynchronously
} else {
console.error('Failed to load any theme!')
}
applyConfig(store.state.config) applyConfig(store.state.config)

View file

@ -25,6 +25,7 @@ import ListsTimeline from 'components/lists_timeline/lists_timeline.vue'
import ListsEdit from 'components/lists_edit/lists_edit.vue' import ListsEdit from 'components/lists_edit/lists_edit.vue'
import NavPanel from 'src/components/nav_panel/nav_panel.vue' import NavPanel from 'src/components/nav_panel/nav_panel.vue'
import AnnouncementsPage from 'components/announcements_page/announcements_page.vue' import AnnouncementsPage from 'components/announcements_page/announcements_page.vue'
import QuotesTimeline from '../components/quotes_timeline/quotes_timeline.vue'
export default (store) => { export default (store) => {
const validateAuthenticatedRoute = (to, from, next) => { const validateAuthenticatedRoute = (to, from, next) => {
@ -51,6 +52,7 @@ export default (store) => {
{ name: 'tag-timeline', path: '/tag/:tag', component: TagTimeline }, { name: 'tag-timeline', path: '/tag/:tag', component: TagTimeline },
{ name: 'bookmarks', path: '/bookmarks', component: BookmarkTimeline }, { name: 'bookmarks', path: '/bookmarks', component: BookmarkTimeline },
{ name: 'conversation', path: '/notice/:id', component: ConversationPage, meta: { dontScroll: true } }, { name: 'conversation', path: '/notice/:id', component: ConversationPage, meta: { dontScroll: true } },
{ name: 'quotes', path: '/notice/:id/quotes', component: QuotesTimeline },
{ {
name: 'remote-user-profile-acct', name: 'remote-user-profile-acct',
path: '/remote-users/:_(@)?:username([^/@]+)@:hostname([^/@]+)', path: '/remote-users/:_(@)?:username([^/@]+)@:hostname([^/@]+)',

View file

@ -11,14 +11,14 @@
<template v-if="relationship.following"> <template v-if="relationship.following">
<button <button
v-if="relationship.showing_reblogs" v-if="relationship.showing_reblogs"
class="btn button-default dropdown-item" class="dropdown-item menu-item"
@click="hideRepeats" @click="hideRepeats"
> >
{{ $t('user_card.hide_repeats') }} {{ $t('user_card.hide_repeats') }}
</button> </button>
<button <button
v-if="!relationship.showing_reblogs" v-if="!relationship.showing_reblogs"
class="btn button-default dropdown-item" class="dropdown-item menu-item"
@click="showRepeats" @click="showRepeats"
> >
{{ $t('user_card.show_repeats') }} {{ $t('user_card.show_repeats') }}
@ -31,34 +31,34 @@
<UserListMenu :user="user" /> <UserListMenu :user="user" />
<button <button
v-if="relationship.followed_by" v-if="relationship.followed_by"
class="btn button-default btn-block dropdown-item" class="dropdown-item menu-item"
@click="removeUserFromFollowers" @click="removeUserFromFollowers"
> >
{{ $t('user_card.remove_follower') }} {{ $t('user_card.remove_follower') }}
</button> </button>
<button <button
v-if="relationship.blocking" v-if="relationship.blocking"
class="btn button-default btn-block dropdown-item" class="dropdown-item menu-item"
@click="unblockUser" @click="unblockUser"
> >
{{ $t('user_card.unblock') }} {{ $t('user_card.unblock') }}
</button> </button>
<button <button
v-else v-else
class="btn button-default btn-block dropdown-item" class="dropdown-item menu-item"
@click="blockUser" @click="blockUser"
> >
{{ $t('user_card.block') }} {{ $t('user_card.block') }}
</button> </button>
<button <button
class="btn button-default btn-block dropdown-item" class="dropdown-item menu-item"
@click="reportUser" @click="reportUser"
> >
{{ $t('user_card.report') }} {{ $t('user_card.report') }}
</button> </button>
<button <button
v-if="pleromaChatMessagesAvailable" v-if="pleromaChatMessagesAvailable"
class="btn button-default btn-block dropdown-item" class="dropdown-item menu-item"
@click="openChat" @click="openChat"
> >
{{ $t('user_card.message') }} {{ $t('user_card.message') }}
@ -122,19 +122,12 @@
<script src="./account_actions.js"></script> <script src="./account_actions.js"></script>
<style lang="scss"> <style lang="scss">
@import "../../variables";
.AccountActions { .AccountActions {
.ellipsis-button { .ellipsis-button {
width: 2.5em; width: 2.5em;
margin: -0.5em 0; margin: -0.5em 0;
padding: 0.5em 0; padding: 0.5em 0;
text-align: center; text-align: center;
&:not(:hover) .icon {
color: $fallback--lightText;
color: var(--lightText, $fallback--lightText);
}
} }
} }
</style> </style>

View file

@ -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'
}
}
]
}

View file

@ -99,16 +99,14 @@
<script src="./announcement.js"></script> <script src="./announcement.js"></script>
<style lang="scss"> <style lang="scss">
@import "../../variables";
.announcement { .announcement {
border-bottom: 1px solid var(--border, $fallback--border); border-bottom: 1px solid var(--border);
border-radius: 0; border-radius: 0;
padding: var(--status-margin, $status-margin); padding: var(--status-margin);
.heading, .heading,
.body { .body {
margin-bottom: var(--status-margin, $status-margin); margin-bottom: var(--status-margin);
} }
.footer { .footer {

View file

@ -3,7 +3,7 @@
<textarea <textarea
ref="textarea" ref="textarea"
v-model="announcement.content" v-model="announcement.content"
class="post-textarea" class="input post-textarea"
rows="1" rows="1"
cols="1" cols="1"
:placeholder="$t('announcements.post_placeholder')" :placeholder="$t('announcements.post_placeholder')"
@ -14,6 +14,7 @@
<input <input
id="announcement-start-time" id="announcement-start-time"
v-model="announcement.startsAt" v-model="announcement.startsAt"
class="input"
:type="announcement.allDay ? 'date' : 'datetime-local'" :type="announcement.allDay ? 'date' : 'datetime-local'"
:disabled="disabled" :disabled="disabled"
> >
@ -23,6 +24,7 @@
<input <input
id="announcement-end-time" id="announcement-end-time"
v-model="announcement.endsAt" v-model="announcement.endsAt"
class="input"
:type="announcement.allDay ? 'date' : 'datetime-local'" :type="announcement.allDay ? 'date' : 'datetime-local'"
:disabled="disabled" :disabled="disabled"
> >

View file

@ -61,15 +61,13 @@
<script src="./announcements_page.js"></script> <script src="./announcements_page.js"></script>
<style lang="scss"> <style lang="scss">
@import "../../variables";
.announcements-page { .announcements-page {
.post-form { .post-form {
padding: var(--status-margin, $status-margin); padding: var(--status-margin);
.heading, .heading,
.body { .body {
margin-bottom: var(--status-margin, $status-margin); margin-bottom: var(--status-margin);
} }
.post-button { .post-button {

View file

@ -1,5 +1,3 @@
@import "../../variables";
.Attachment { .Attachment {
display: inline-flex; display: inline-flex;
flex-direction: column; flex-direction: column;
@ -9,10 +7,8 @@
height: 100%; height: 100%;
border-style: solid; border-style: solid;
border-width: 1px; border-width: 1px;
border-radius: $fallback--attachmentRadius; border-radius: var(--roundness);
border-radius: var(--attachmentRadius, $fallback--attachmentRadius); border-color: var(--border);
border-color: $fallback--border;
border-color: var(--border, $fallback--border);
.attachment-wrapper { .attachment-wrapper {
flex: 1 1 auto; flex: 1 1 auto;
@ -84,6 +80,13 @@
} }
} }
.video-container {
border: none;
outline: none;
color: inherit;
background: transparent;
}
.audio-container { .audio-container {
display: flex; display: flex;
align-items: flex-end; align-items: flex-end;
@ -126,23 +129,12 @@
.attachment-button { .attachment-button {
padding: 0; padding: 0;
border-radius: $fallback--tooltipRadius; border-radius: var(--roundness);
border-radius: var(--tooltipRadius, $fallback--tooltipRadius);
text-align: center; text-align: center;
width: 2em; width: 2em;
height: 2em; height: 2em;
margin-left: 0.5em; margin-left: 0.5em;
font-size: 1.25em; 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 { &.-placeholder {
display: inline-block; display: inline-block;
color: $fallback--link; color: var(--link);
color: var(--postLink, $fallback--link);
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
height: auto; height: auto;

View file

@ -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
}
}
]
}

View file

@ -38,7 +38,7 @@
v-if="edit" v-if="edit"
v-model="localDescription" v-model="localDescription"
type="text" type="text"
class="description-field" class="input description-field"
:placeholder="$t('post_status.media_description')" :placeholder="$t('post_status.media_description')"
@keydown.enter.prevent="" @keydown.enter.prevent=""
> >
@ -175,7 +175,6 @@
:is="videoTag" :is="videoTag"
v-if="type === 'video' && !hidden" v-if="type === 'video' && !hidden"
class="video-container" class="video-container"
:class="{ 'button-unstyled': 'isModal' }"
:href="attachment.url" :href="attachment.url"
@click.stop.prevent="openModal" @click.stop.prevent="openModal"
> >
@ -253,7 +252,7 @@
v-if="edit" v-if="edit"
v-model="localDescription" v-model="localDescription"
type="text" type="text"
class="description-field" class="input description-field"
:placeholder="$t('post_status.media_description')" :placeholder="$t('post_status.media_description')"
@keydown.enter.prevent="" @keydown.enter.prevent=""
> >

View file

@ -1,3 +1,4 @@
<!-- FIXME THIS NEEDS TO BE REFACTORED TO USE POPOVER -->
<template> <template>
<div <div
v-click-outside="onClickOutside" v-click-outside="onClickOutside"
@ -6,12 +7,12 @@
<input <input
v-model="term" v-model="term"
:placeholder="placeholder" :placeholder="placeholder"
class="autosuggest-input" class="input autosuggest-input"
@click="onInputClick" @click="onInputClick"
> >
<div <div
v-if="resultsVisible && filtered.length > 0" v-if="resultsVisible && filtered.length > 0"
class="autosuggest-results" class="panel autosuggest-results"
> >
<slot <slot
v-for="item in filtered" v-for="item in filtered"
@ -24,8 +25,6 @@
<script src="./autosuggest.js"></script> <script src="./autosuggest.js"></script>
<style lang="scss"> <style lang="scss">
@import "../../variables";
.autosuggest { .autosuggest {
position: relative; position: relative;
@ -40,18 +39,15 @@
top: 100%; top: 100%;
right: 0; right: 0;
max-height: 400px; max-height: 400px;
background-color: $fallback--bg; background-color: var(--bg);
background-color: var(--bg, $fallback--bg);
border-style: solid; border-style: solid;
border-width: 1px; border-width: 1px;
border-color: $fallback--border; border-color: var(--border);
border-color: var(--border, $fallback--border); border-radius: var(--roundness);
border-radius: $fallback--inputRadius;
border-radius: var(--inputRadius, $fallback--inputRadius);
border-top-left-radius: 0; border-top-left-radius: 0;
border-top-right-radius: 0; border-top-right-radius: 0;
box-shadow: 1px 1px 4px rgb(0 0 0 / 60%); box-shadow: 1px 1px 4px rgb(0 0 0 / 60%);
box-shadow: var(--panelShadow); box-shadow: var(--shadow);
overflow-y: auto; overflow-y: auto;
z-index: 1; z-index: 1;
} }

View file

@ -17,8 +17,6 @@
<script src="./avatar_list.js"></script> <script src="./avatar_list.js"></script>
<style lang="scss"> <style lang="scss">
@import "../../variables";
.avatars { .avatars {
display: flex; display: flex;
margin: 0; margin: 0;
@ -36,8 +34,7 @@
} }
.avatar-small { .avatar-small {
border-radius: $fallback--avatarAltRadius; border-radius: var(--roundness);
border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius);
height: 24px; height: 24px;
width: 24px; width: 24px;
} }

View file

@ -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'
}
}
]
}

View file

@ -47,7 +47,6 @@
display: flex; display: flex;
flex: 1 0; flex: 1 0;
margin: 0; margin: 0;
padding: 0.6em 1em;
--emoji-size: 14px; --emoji-size: 14px;

View file

@ -0,0 +1,13 @@
export default {
name: 'Border',
selector: '/*border*/',
virtual: true,
defaultRules: [
{
directives: {
textColor: '$mod(--parent, 10)',
textAuto: 'no-auto'
}
}
]
}

View file

@ -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'
}
}
]
}

View file

@ -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'
}
}
]
}

View file

@ -11,15 +11,15 @@
.chat-view-body { .chat-view-body {
box-sizing: border-box; box-sizing: border-box;
background-color: var(--chatBg, $fallback--bg);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: 100%; width: 100%;
overflow: visible; overflow: visible;
min-height: calc(100vh - var(--navbar-height)); min-height: calc(100vh - var(--navbar-height));
margin: 0; margin: 0;
border-radius: 10px 10px 0 0; border-radius: var(--roundness);
border-radius: var(--panelRadius, 10px) var(--panelRadius, 10px) 0 0; border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
&::after { &::after {
border-radius: 0; border-radius: 0;
@ -37,8 +37,6 @@
.footer { .footer {
position: sticky; position: sticky;
bottom: 0; bottom: 0;
background-color: $fallback--bg;
background-color: var(--bg, $fallback--bg);
z-index: 1; z-index: 1;
} }
@ -61,8 +59,6 @@
position: absolute; position: absolute;
right: 1.3em; right: 1.3em;
top: -3.2em; top: -3.2em;
background-color: $fallback--fg;
background-color: var(--btn, $fallback--fg);
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
@ -79,12 +75,6 @@
visibility: visible; visibility: visible;
} }
i {
font-size: 1em;
color: $fallback--text;
color: var(--text, $fallback--text);
}
.unread-message-count { .unread-message-count {
font-size: 0.8em; font-size: 0.8em;
left: 50%; left: 50%;

View file

@ -0,0 +1,19 @@
export default {
name: 'Chat',
selector: '.chat-message-list',
validInnerComponents: [
'Text',
'Link',
'Icon',
'Avatar',
'ChatMessage'
],
defaultRules: [
{
directives: {
background: '--bg',
blur: '5px'
}
}
]
}

View file

@ -26,7 +26,7 @@
</div> </div>
</div> </div>
<div <div
class="message-list" class="chat-message-list message-list"
:style="{ height: scrollableContainerHeight }" :style="{ height: scrollableContainerHeight }"
> >
<template v-if="!errorLoadingChat"> <template v-if="!errorLoadingChat">
@ -61,7 +61,7 @@
<FAIcon icon="chevron-down" /> <FAIcon icon="chevron-down" />
<div <div
v-if="newMessageCount" v-if="newMessageCount"
class="badge badge-notification unread-chat-count unread-message-count" class="badge -notification unread-chat-count unread-message-count"
> >
{{ newMessageCount }} {{ newMessageCount }}
</div> </div>
@ -95,6 +95,5 @@
<script src="./chat.js"></script> <script src="./chat.js"></script>
<style lang="scss"> <style lang="scss">
@import "../../variables";
@import "./chat"; @import "./chat";
</style> </style>

View file

@ -45,8 +45,6 @@
<script src="./chat_list.js"></script> <script src="./chat_list.js"></script>
<style lang="scss"> <style lang="scss">
@import "../../variables";
.chat-list { .chat-list {
min-height: 25em; min-height: 25em;
margin-bottom: 0; margin-bottom: 0;
@ -57,8 +55,7 @@
font-size: 1.2em; font-size: 1.2em;
display: flex; display: flex;
justify-content: center; justify-content: center;
color: $fallback--text; color: var(--textFaint);
color: var(--faint, $fallback--text);
} }
</style> </style>

View file

@ -1,8 +1,6 @@
.chat-list-item { .chat-list-item {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
padding: 0.75em;
height: 5em;
overflow: hidden; overflow: hidden;
box-sizing: border-box; box-sizing: border-box;
cursor: pointer; cursor: pointer;
@ -11,11 +9,6 @@
outline: none; outline: none;
} }
&:hover {
background-color: var(--selectedPost, $fallback--lightBg);
box-shadow: 0 0 3px 1px rgb(0 0 0 / 10%);
}
.chat-list-item-left { .chat-list-item-left {
margin-right: 1em; margin-right: 1em;
} }
@ -29,7 +22,7 @@
.heading { .heading {
width: 100%; width: 100%;
display: inline-flex; display: flex;
justify-content: space-between; justify-content: space-between;
line-height: 1em; line-height: 1em;
} }
@ -47,18 +40,17 @@
} }
.chat-preview { .chat-preview {
display: inline-flex; display: flex;
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
margin: 0.35em 0; margin: 0.35em 0;
color: $fallback--text; color: var(--textFaint);
color: var(--faint, $fallback--text);
width: 100%; width: 100%;
} }
a { a {
color: var(--faintLink, $fallback--link); color: var(--linkFaint);
text-decoration: none; text-decoration: none;
pointer-events: none; pointer-events: none;
} }
@ -73,11 +65,6 @@
} }
} }
.Avatar {
border-radius: $fallback--avatarAltRadius;
border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius);
}
.chat-preview-body { .chat-preview-body {
--emoji-size: 1.4em; --emoji-size: 1.4em;

View file

@ -36,7 +36,7 @@
/> />
<div <div
v-if="chat.unread > 0" v-if="chat.unread > 0"
class="badge badge-notification unread-chat-count" class="badge -notification unread-chat-count"
> >
{{ chat.unread }} {{ chat.unread }}
</div> </div>
@ -48,6 +48,5 @@
<script src="./chat_list_item.js"></script> <script src="./chat_list_item.js"></script>
<style lang="scss"> <style lang="scss">
@import "../../variables";
@import "./chat_list_item"; @import "./chat_list_item";
</style> </style>

View file

@ -1,5 +1,3 @@
@import "../../variables";
.chat-message-wrapper { .chat-message-wrapper {
&.hovered-message-chain { &.hovered-message-chain {
.animated.Avatar { .animated.Avatar {
@ -27,12 +25,6 @@
.menu-icon { .menu-icon {
cursor: pointer; cursor: pointer;
&:hover,
.extra-button-popover.open & {
color: $fallback--text;
color: var(--text, $fallback--text);
}
} }
.popover { .popover {
@ -61,10 +53,12 @@
} }
.status { .status {
border-radius: $fallback--chatMessageRadius; background-color: var(--background);
border-radius: var(--chatMessageRadius, $fallback--chatMessageRadius); color: var(--text);
border-radius: var(--roundness);
display: flex; display: flex;
padding: 0.75em; padding: 0.75em;
border: 1px solid var(--border);
} }
.created-at { .created-at {
@ -97,8 +91,7 @@
.error { .error {
.status-content.media-body, .status-content.media-body,
.created-at { .created-at {
color: $fallback--cRed; color: var(--badgeNotification);
color: var(--badgeNotification, $fallback--cRed);
} }
} }
@ -117,16 +110,6 @@
align-content: end; align-content: end;
justify-content: flex-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 { .chat-message-inner {
align-items: flex-end; align-items: flex-end;
} }
@ -137,22 +120,6 @@
} }
.incoming { .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 { .chat-message-menu {
left: 0.4rem; left: 0.4rem;
} }
@ -176,6 +143,5 @@
margin: 1.4em 0; margin: 1.4em 0;
font-size: 0.9em; font-size: 0.9em;
user-select: none; user-select: none;
color: $fallback--text; color: var(--textFaint);
color: var(--faintedText, $fallback--text);
} }

View file

@ -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'
}
}
]
}

View file

@ -53,7 +53,7 @@
<template #content> <template #content>
<div class="dropdown-menu"> <div class="dropdown-menu">
<button <button
class="button-default dropdown-item dropdown-item-icon" class="menu-item dropdown-item dropdown-item-icon"
@click="deleteMessage" @click="deleteMessage"
> >
<FAIcon icon="times" /> {{ $t("chats.delete") }} <FAIcon icon="times" /> {{ $t("chats.delete") }}

View file

@ -16,11 +16,6 @@
padding-bottom: 0.7rem; padding-bottom: 0.7rem;
} }
.basic-user-card:hover {
cursor: pointer;
background-color: var(--selectedPost, $fallback--lightBg);
}
.go-back-button { .go-back-button {
text-align: center; text-align: center;
line-height: 1; line-height: 1;

View file

@ -16,27 +16,29 @@
/> />
</button> </button>
</div> </div>
<div class="input-wrap"> <div class="panel-body">
<div class="input-search"> <div class="input-wrap">
<FAIcon <div class="input-search">
class="search-icon fa-scale-110 fa-old-padding" <FAIcon
icon="search" 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> </div>
<input <div class="member-list">
ref="search" <div
v-model="query" v-for="user in availableUsers"
placeholder="Search people" :key="user.id"
@input="onInput" class="list-item"
> @click.capture.prevent="goToChat(user)"
</div> >
<div class="member-list">
<div
v-for="user in availableUsers"
:key="user.id"
class="member"
>
<div @click.capture.prevent="goToChat(user)">
<BasicUserCard :user="user" /> <BasicUserCard :user="user" />
</div> </div>
</div> </div>
@ -46,6 +48,5 @@
<script src="./chat_new.js"></script> <script src="./chat_new.js"></script>
<style lang="scss"> <style lang="scss">
@import "../../variables";
@import "./chat_new"; @import "./chat_new";
</style> </style>

View file

@ -26,8 +26,6 @@
<script src="./chat_title.js"></script> <script src="./chat_title.js"></script>
<style lang="scss"> <style lang="scss">
@import "../../variables";
.chat-title { .chat-title {
display: flex; display: flex;
overflow: hidden; overflow: hidden;
@ -54,8 +52,7 @@
margin-right: 0.5em; margin-right: 0.5em;
height: 1.5em; height: 1.5em;
width: 1.5em; width: 1.5em;
border-radius: $fallback--avatarAltRadius; border-radius: var(--roundness);
border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius);
&.animated::before { &.animated::before {
display: none; display: none;

View file

@ -12,7 +12,7 @@
@change="$emit('update:modelValue', $event.target.checked)" @change="$emit('update:modelValue', $event.target.checked)"
> >
<i <i
class="checkbox-indicator" class="input -checkbox checkbox-indicator"
:aria-hidden="true" :aria-hidden="true"
@transitionend.capture="onTransitionEnd" @transitionend.capture="onTransitionEnd"
/> />
@ -54,7 +54,6 @@ export default {
</script> </script>
<style lang="scss"> <style lang="scss">
@import "../../variables";
@import "../../mixins"; @import "../../mixins";
.checkbox { .checkbox {
@ -62,9 +61,15 @@ export default {
display: inline-block; display: inline-block;
min-height: 1.2em; min-height: 1.2em;
&-indicator { & > &-indicator {
/* Reset .input stuff */
padding: 0;
margin: 0;
position: relative; position: relative;
line-height: inherit;
display: inline;
padding-left: 1.2em; padding-left: 1.2em;
box-shadow: none;
} }
&-indicator::before { &-indicator::before {
@ -76,12 +81,9 @@ export default {
transition: color 200ms; transition: color 200ms;
width: 1.1em; width: 1.1em;
height: 1.1em; height: 1.1em;
border-radius: $fallback--checkboxRadius; border-radius: var(--roundness);
border-radius: var(--checkboxRadius, $fallback--checkboxRadius); box-shadow: var(--shadow);
box-shadow: 0 0 2px black inset; background-color: var(--background);
box-shadow: var(--inputShadow);
background-color: $fallback--fg;
background-color: var(--input, $fallback--fg);
vertical-align: top; vertical-align: top;
text-align: center; text-align: center;
line-height: 1.1em; line-height: 1.1em;
@ -98,21 +100,18 @@ export default {
} }
.label { .label {
color: $fallback--faint; color: var(--text);
color: var(--faint, $fallback--faint);
} }
} }
input[type="checkbox"] { input[type="checkbox"] {
&:checked + .checkbox-indicator::before { &:checked + .checkbox-indicator::before {
color: $fallback--text; color: var(--text);
color: var(--inputText, $fallback--text);
} }
&:indeterminate + .checkbox-indicator::before { &:indeterminate + .checkbox-indicator::before {
content: ""; content: "";
color: $fallback--text; color: var(--text);
color: var(--inputText, $fallback--text);
} }
} }

View file

@ -1,5 +1,3 @@
@import "../../variables";
.color-input { .color-input {
display: inline-flex; display: inline-flex;
@ -11,9 +9,8 @@
padding: 0.2em 8px; padding: 0.2em 8px;
input { input {
color: var(--text);
background: none; background: none;
color: $fallback--lightText;
color: var(--inputText, $fallback--lightText);
border: none; border: none;
padding: 0; padding: 0;
margin: 0; margin: 0;
@ -23,21 +20,38 @@
min-width: 3em; min-width: 3em;
padding: 0; padding: 0;
} }
}
&.nativeColor { .nativeColor {
flex: 0 0 2em; cursor: pointer;
min-width: 2em; flex: 0 0 auto;
align-self: stretch;
min-height: 100%; input {
appearance: none;
max-width: 0;
min-width: 0;
max-height: 0;
/* stylelint-disable-next-line declaration-no-important */
opacity: 0 !important;
} }
} }
.computedIndicator, .computedIndicator,
.validIndicator,
.invalidIndicator,
.transparentIndicator { .transparentIndicator {
flex: 0 0 2em; flex: 0 0 2em;
margin: 0 0.5em;
min-width: 2em; min-width: 2em;
align-self: stretch; align-self: stretch;
min-height: 100%; min-height: 1.5em;
border-radius: var(--roundness);
}
.invalidIndicator {
background: transparent;
box-sizing: border-box;
border: 2px solid var(--cRed);
} }
.transparentIndicator { .transparentIndicator {
@ -58,11 +72,13 @@
&::after { &::after {
top: 0; top: 0;
left: 0; left: 0;
border-top-left-radius: var(--roundness);
} }
&::before { &::before {
bottom: 0; bottom: 0;
right: 0; right: 0;
border-bottom-right-radius: var(--roundness);
} }
} }
} }

View file

@ -25,30 +25,51 @@
:disabled="!present || disabled" :disabled="!present || disabled"
@input="$emit('update:modelValue', $event.target.value)" @input="$emit('update:modelValue', $event.target.value)"
> >
<input
v-if="validColor"
:id="name"
class="nativeColor unstyled"
type="color"
:value="modelValue || fallback"
:disabled="!present || disabled"
@input="$emit('update:modelValue', $event.target.value)"
>
<div <div
v-if="transparentColor" v-if="validColor"
class="validIndicator"
:style="{backgroundColor: modelValue || fallback}"
/>
<div
v-else-if="transparentColor"
class="transparentIndicator" class="transparentIndicator"
/> />
<div <div
v-if="computedColor" v-else-if="computedColor"
class="computedIndicator" class="computedIndicator"
:style="{backgroundColor: fallback}" :style="{backgroundColor: fallback}"
/> />
<div
v-else
class="invalidIndicator"
/>
<label class="nativeColor">
<FAIcon icon="eye-dropper" />
<input
:id="name"
class="unstyled"
type="color"
:value="modelValue || fallback"
:disabled="!present || disabled"
@input="$emit('update:modelValue', $event.target.value)"
>
</label>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import Checkbox from '../checkbox/checkbox.vue' import Checkbox from '../checkbox/checkbox.vue'
import { hex2rgb } from '../../services/color_convert/color_convert.js' import { hex2rgb } from '../../services/color_convert/color_convert.js'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faEyeDropper
} from '@fortawesome/free-solid-svg-icons'
library.add(
faEyeDropper
)
export default { export default {
components: { components: {
Checkbox Checkbox
@ -108,12 +129,3 @@ export default {
} }
</script> </script>
<style lang="scss" src="./color_input.scss"></style> <style lang="scss" src="./color_input.scss"></style>
<style lang="scss">
.color-control {
input.text-input {
max-width: 7em;
flex: 1;
}
}
</style>

View file

@ -56,7 +56,8 @@ const conversation = {
expanded: false, expanded: false,
threadDisplayStatusObject: {}, // id => 'showing' | 'hidden' threadDisplayStatusObject: {}, // id => 'showing' | 'hidden'
statusContentPropertiesObject: {}, statusContentPropertiesObject: {},
inlineDivePosition: null inlineDivePosition: null,
loadStatusError: null
} }
}, },
props: [ props: [
@ -392,11 +393,15 @@ const conversation = {
this.setHighlight(this.originalStatusId) this.setHighlight(this.originalStatusId)
}) })
} else { } else {
this.loadStatusError = null
this.$store.state.api.backendInteractor.fetchStatus({ id: this.statusId }) this.$store.state.api.backendInteractor.fetchStatus({ id: this.statusId })
.then((status) => { .then((status) => {
this.$store.dispatch('addNewStatuses', { statuses: [status] }) this.$store.dispatch('addNewStatuses', { statuses: [status] })
this.fetchConversation() this.fetchConversation()
}) })
.catch((error) => {
this.loadStatusError = error
})
} }
}, },
getReplies (id) { getReplies (id) {

View file

@ -28,7 +28,27 @@
class="rightside-button" class="rightside-button"
/> />
</div> </div>
<div class="conversation-body panel-body"> <div
v-if="isPage && !status"
class="conversation-body"
:class="{ 'panel-body': isExpanded }"
>
<p v-if="!loadStatusError">
<FAIcon
spin
icon="circle-notch"
/>
{{ $t('status.loading') }}
</p>
<p v-else>
{{ $t('status.load_error', { error: loadStatusError }) }}
</p>
</div>
<div
v-else
class="conversation-body"
:class="{ 'panel-body': isExpanded }"
>
<div <div
v-if="isTreeView" v-if="isTreeView"
class="thread-body" class="thread-body"
@ -203,6 +223,7 @@
</div> </div>
<div <div
v-else v-else
class="Conversation -hidden"
:style="hiddenStyle" :style="hiddenStyle"
/> />
</template> </template>
@ -210,14 +231,17 @@
<script src="./conversation.js"></script> <script src="./conversation.js"></script>
<style lang="scss"> <style lang="scss">
@import "../../variables";
.Conversation { .Conversation {
z-index: 1; z-index: 1;
&.-hidden {
background: var(--__panel-background);
backdrop-filter: var(--__panel-backdrop-filter);
}
.conversation-dive-to-top-level-box { .conversation-dive-to-top-level-box {
padding: var(--status-margin, $status-margin); padding: var(--status-margin);
border-bottom: 1px solid var(--border, $fallback--border); border-bottom: 1px solid var(--border);
border-radius: 0; border-radius: 0;
/* Make the button stretch along the whole row */ /* Make the button stretch along the whole row */
@ -227,20 +251,22 @@
} }
.thread-ancestors { .thread-ancestors {
margin-left: var(--status-margin, $status-margin); margin-left: var(--status-margin);
border-left: 2px solid var(--border, $fallback--border); border-left: 2px solid var(--border);
} }
.thread-ancestor.-faded .StatusContent { .thread-ancestor.-faded .RichContent {
--link: var(--faintLink); /* stylelint-disable declaration-no-important */
--text: var(--faint); --text: var(--textFaint) !important;
--link: var(--linkFaint) !important;
color: var(--text); --funtextGreentext: var(--funtextGreentextFaint) !important;
--funtextCyantext: var(--funtextCyantextFaint) !important;
/* stylelint-enable declaration-no-important */
} }
.thread-ancestor-dive-box { .thread-ancestor-dive-box {
padding-left: var(--status-margin, $status-margin); padding-left: var(--status-margin);
border-bottom: 1px solid var(--border, $fallback--border); border-bottom: 1px solid var(--border);
border-radius: 0; border-radius: 0;
/* Make the button stretch along the whole row */ /* Make the button stretch along the whole row */
@ -253,16 +279,17 @@
} }
.thread-ancestor-dive-box-inner { .thread-ancestor-dive-box-inner {
padding: var(--status-margin, $status-margin); padding: var(--status-margin);
} }
.conversation-status { .conversation-status {
border-bottom: 1px solid var(--border, $fallback--border); border-bottom: 1px solid var(--border);
border-radius: 0; border-radius: 0;
} }
.thread-ancestor-has-other-replies .conversation-status, .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 .conversation-status,
.thread-ancestor:last-child .thread-ancestor-dive-box, .thread-ancestor:last-child .thread-ancestor-dive-box,
&.-expanded .thread-tree .conversation-status { &.-expanded .thread-tree .conversation-status {
@ -270,20 +297,36 @@
} }
.thread-ancestors + .thread-tree > .conversation-status { .thread-ancestors + .thread-tree > .conversation-status {
border-top: 1px solid var(--border, $fallback--border); border-top: 1px solid var(--border);
} }
/* expanded conversation in timeline */ /* expanded conversation in timeline */
&.status-fadein.-expanded .thread-body { &.status-fadein.-expanded .thread-body {
border-left: 4px solid $fallback--cRed; border-left: 4px solid var(--cRed);
border-left-color: var(--cRed, $fallback--cRed); border-radius: var(--roundness);
border-radius: 0 0 $fallback--panelRadius $fallback--panelRadius; border-top-left-radius: 0;
border-radius: 0 0 var(--panelRadius, $fallback--panelRadius) var(--panelRadius, $fallback--panelRadius); border-top-right-radius: 0;
border-bottom: 1px solid var(--border, $fallback--border); border-bottom: 1px solid var(--border);
} }
&.-expanded.status-fadein { &.-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> </style>

View file

@ -1,5 +1,3 @@
@import "../../variables";
.DesktopNav { .DesktopNav {
width: 100%; width: 100%;
z-index: var(--ZI_navbar); z-index: var(--ZI_navbar);
@ -9,7 +7,7 @@
} }
a { a {
color: var(--topBarLink, $fallback--link); color: var(--link);
} }
.inner-nav { .inner-nav {
@ -54,27 +52,7 @@
.button-default { .button-default {
&, &,
svg { svg {
color: $fallback--text; color: var(--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);
} }
} }
@ -94,8 +72,7 @@
mask-repeat: no-repeat; mask-repeat: no-repeat;
mask-position: center; mask-position: center;
mask-size: contain; mask-size: contain;
background-color: $fallback--fg; background-color: var(--text);
background-color: var(--topBarText, $fallback--fg);
position: absolute; position: absolute;
top: 0; top: 0;
bottom: 0; bottom: 0;
@ -116,8 +93,7 @@
text-align: center; text-align: center;
.svg-inline--fa { .svg-inline--fa {
color: $fallback--link; color: var(--link);
color: var(--topBarLink, $fallback--link);
} }
} }

View file

@ -12,7 +12,7 @@
<slot name="header" /> <slot name="header" />
</div> </div>
</div> </div>
<div class="dialog-modal-content"> <div class="panel-body dialog-modal-content">
<slot name="default" /> <slot name="default" />
</div> </div>
<div class="dialog-modal-footer user-interactions panel-footer"> <div class="dialog-modal-footer user-interactions panel-footer">
@ -25,8 +25,6 @@
<script src="./dialog_modal.js"></script> <script src="./dialog_modal.js"></script>
<style lang="scss"> <style lang="scss">
@import "../../variables";
// TODO: unify with other modals. // TODO: unify with other modals.
.dark-overlay { .dark-overlay {
&::before { &::before {
@ -54,8 +52,6 @@
z-index: 2001; z-index: 2001;
cursor: default; cursor: default;
display: block; display: block;
background-color: $fallback--bg;
background-color: var(--bg, $fallback--bg);
.dialog-modal-heading { .dialog-modal-heading {
.title { .title {
@ -66,18 +62,13 @@
.dialog-modal-content { .dialog-modal-content {
margin: 0; margin: 0;
padding: 1rem; padding: 1rem;
background-color: $fallback--bg;
background-color: var(--bg, $fallback--bg);
white-space: normal; white-space: normal;
} }
.dialog-modal-footer { .dialog-modal-footer {
margin: 0; margin: 0;
padding: 0.5em; padding: 0.5em;
background-color: $fallback--bg; border-top: 1px solid var(--border);
background-color: var(--bg, $fallback--bg);
border-top: 1px solid $fallback--border;
border-top: 1px solid var(--border, $fallback--border);
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;

View file

@ -1,7 +1,7 @@
<template> <template>
<div <div
ref="root" ref="root"
class="emoji-input" class="input emoji-input"
:class="{ 'with-picker': !hideEmojiButton }" :class="{ 'with-picker': !hideEmojiButton }"
> >
<slot <slot
@ -68,9 +68,9 @@
v-for="(suggestion, index) in suggestions" v-for="(suggestion, index) in suggestions"
:id="suggestionItemId(index)" :id="suggestionItemId(index)"
:key="index" :key="index"
class="autocomplete-item" class="menu-item autocomplete-item"
role="option" role="option"
:class="{ highlighted: index === highlighted }" :class="{ '-active': index === highlighted }"
:aria-label="autoCompleteItemLabel(suggestion)" :aria-label="autoCompleteItemLabel(suggestion)"
:aria-selected="index === highlighted" :aria-selected="index === highlighted"
@click.stop.prevent="onClick($event, suggestion)" @click.stop.prevent="onClick($event, suggestion)"
@ -110,9 +110,8 @@
<script src="./emoji_input.js"></script> <script src="./emoji_input.js"></script>
<style lang="scss"> <style lang="scss">
@import "../../variables"; .input.emoji-input {
padding: 0;
.emoji-input {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
position: relative; position: relative;
@ -127,8 +126,7 @@
line-height: 24px; line-height: 24px;
&:hover i { &:hover i {
color: $fallback--text; color: var(--text);
color: var(--text, $fallback--text);
} }
} }
@ -145,6 +143,12 @@
input, input,
textarea { textarea {
flex: 1 0 auto; 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 { &.with-picker input {
@ -179,26 +183,28 @@
position: absolute; position: absolute;
} }
&-item { &-item.menu-item {
display: flex; display: flex;
cursor: pointer; padding-top: 0;
padding: 0.2em 0.4em; padding-bottom: 0;
border-bottom: 1px solid rgb(0 0 0 / 40%);
height: 32px;
.image { .image {
width: 32px; width: calc(var(--__line-height) + var(--__vertical-gap) * 2);
height: 32px; height: calc(var(--__line-height) + var(--__vertical-gap) * 2);
line-height: 32px; line-height: var(--__line-height);
text-align: center; text-align: center;
font-size: 32px; margin-right: var(--__horizontal-gap);
margin-right: 4px;
img { img {
width: 32px; width: calc(var(--__line-height) + var(--__vertical-gap) * 2);
height: 32px; height: calc(var(--__line-height) + var(--__vertical-gap) * 2);
object-fit: contain; object-fit: contain;
} }
span {
font-size: var(--__line-height);
line-height: var(--__line-height);
}
} }
.label { .label {
@ -216,17 +222,6 @@
line-height: 9px; 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> </style>

View file

@ -114,11 +114,13 @@ const EmojiPicker = {
groupsScrolledClass: 'scrolled-top', groupsScrolledClass: 'scrolled-top',
keepOpen: false, keepOpen: false,
customEmojiTimeout: null, customEmojiTimeout: null,
hideCustomEmojiInPicker: false,
// Lazy-load only after the first time `showing` becomes true. // Lazy-load only after the first time `showing` becomes true.
contentLoaded: false, contentLoaded: false,
groupRefs: {}, groupRefs: {},
emojiRefs: {}, emojiRefs: {},
filteredEmojiGroups: [], filteredEmojiGroups: [],
emojiSize: 0,
width: 0 width: 0
} }
}, },
@ -129,6 +131,23 @@ const EmojiPicker = {
Popover Popover
}, },
methods: { methods: {
updateEmojiSize () {
const css = window.getComputedStyle(this.$refs.popover.$el)
const emojiSize = css.getPropertyValue('--emojiSize')
const emojiSizeUnit = emojiSize.replace(/[0-9,.]+/, '')
const emojiSizeValue = Number(emojiSize.replace(/[^0-9,.]+/, ''))
const fontSize = css.getPropertyValue('font-size').replace(/[^0-9,.]+/, '')
let emojiSizeReal
if (emojiSizeUnit.endsWith('em')) {
emojiSizeReal = emojiSizeValue * fontSize
} else {
emojiSizeReal = emojiSizeValue
}
const fullEmojiSize = emojiSizeReal + (2 * 0.2 * fontSize)
this.emojiSize = fullEmojiSize
},
showPicker () { showPicker () {
this.$refs.popover.showPopover() this.$refs.popover.showPopover()
this.onShowing() this.onShowing()
@ -223,6 +242,7 @@ const EmojiPicker = {
}, },
onShowing () { onShowing () {
const oldContentLoaded = this.contentLoaded const oldContentLoaded = this.contentLoaded
this.updateEmojiSize()
this.recalculateItemPerRow() this.recalculateItemPerRow()
this.$nextTick(() => { this.$nextTick(() => {
this.$refs.search.focus() this.$refs.search.focus()
@ -265,16 +285,20 @@ const EmojiPicker = {
}, },
computed: { computed: {
minItemSize () { minItemSize () {
return this.emojiHeight return this.emojiSize
},
// used to watch it
fontSize () {
this.$nextTick(() => {
this.updateEmojiSize()
})
return this.$store.getters.mergedConfig.fontSize
}, },
emojiHeight () { emojiHeight () {
return 32 + 4 return this.emojiSize
},
emojiWidth () {
return 32 + 4
}, },
itemPerRow () { itemPerRow () {
return this.width ? Math.floor(this.width / this.emojiWidth - 1) : 6 return this.width ? Math.floor(this.width / this.emojiSize) : 6
}, },
activeGroupView () { activeGroupView () {
return this.showingStickers ? '' : this.activeGroup return this.showingStickers ? '' : this.activeGroup
@ -286,7 +310,7 @@ const EmojiPicker = {
return 0 return 0
}, },
allCustomGroups () { allCustomGroups () {
if (this.hideCustomEmoji) { if (this.hideCustomEmoji || this.hideCustomEmojiInPicker) {
return {} return {}
} }
const emojis = this.$store.getters.groupedCustomEmojis const emojis = this.$store.getters.groupedCustomEmojis

View file

@ -1,62 +1,55 @@
@import "../../variables";
$emoji-picker-header-height: 36px;
$emoji-picker-header-picture-width: 32px;
$emoji-picker-header-picture-height: 32px;
$emoji-picker-emoji-size: 32px;
.emoji-picker { .emoji-picker {
--__emoji-picker-header: 2.2em;
width: 25em; width: 25em;
max-width: calc(100vw - 20px); // popover gives 10px margin from window edge max-width: calc(100vw - 20px); // popover gives 10px margin from window edge
display: flex; display: flex;
flex-direction: column; 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 { &-header-image {
display: inline-flex; display: inline-flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
width: $emoji-picker-header-picture-width; width: var(--__emoji-picker-header);
max-width: $emoji-picker-header-picture-width; max-width: var(--__emoji-picker-header);
height: $emoji-picker-header-picture-height; height: var(--__emoji-picker-header);
max-height: $emoji-picker-header-picture-height; max-height: var(--__emoji-picker-header);
.still-image { .still-image {
max-width: 100%; width: var(--__emoji-picker-header);
max-height: 100%; max-width: var(--__emoji-picker-header);
height: 100%; height: var(--__emoji-picker-header);
width: 100%; max-height: var(--__emoji-picker-header);
object-fit: contain; object-fit: contain;
--_still_image-label-scale: 0.5;
} }
} }
.keep-open, .keep-open,
.too-many-emoji { .too-many-emoji,
padding: 7px; .hide-custom-emoji {
padding: 0.5em;
line-height: normal; line-height: normal;
} }
.hide-custom-emoji {
padding-top: 0;
}
.too-many-emoji { .too-many-emoji {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.keep-open-label { .keep-open-label {
padding: 0 7px; padding: 0 0.5em;
display: flex; display: flex;
} }
.heading { .heading {
display: flex; display: flex;
padding: 10px 7px 5px; padding: 0.7em 0.5em 0;
} }
.content { .content {
@ -71,14 +64,14 @@ $emoji-picker-emoji-size: 32px;
display: flex; display: flex;
flex-flow: row nowrap; flex-flow: row nowrap;
overflow-x: auto; overflow-x: auto;
overflow-y: hidden;
} }
.additional-tabs { .additional-tabs {
display: flex; display: flex;
border-left: 1px solid; border-left: 1px solid;
border-left-color: $fallback--icon; border-left-color: var(--border);
border-left-color: var(--icon, $fallback--icon); padding-left: 0.5em;
padding-left: 7px;
flex: 0 0 auto; flex: 0 0 auto;
} }
@ -87,30 +80,29 @@ $emoji-picker-emoji-size: 32px;
flex-basis: auto; flex-basis: auto;
display: flex; display: flex;
align-content: center; align-content: center;
scrollbar-width: thin;
&-item { &-item {
padding: 0 7px; padding: 0 0.5em;
cursor: pointer; cursor: pointer;
font-size: 1.85em; width: var(--__emoji-picker-header);
width: $emoji-picker-header-picture-width; max-width: var(--__emoji-picker-header);
max-width: $emoji-picker-header-picture-width; height: var(--__emoji-picker-header);
height: $emoji-picker-header-picture-height; max-height: var(--__emoji-picker-header);
max-height: $emoji-picker-header-picture-height;
display: flex; display: flex;
align-items: center; align-items: center;
.svg-inline--fa {
font-size: 1.85em;
}
&.disabled { &.disabled {
opacity: 0.5; opacity: 0.5;
pointer-events: none; pointer-events: none;
} }
&.active { &.toggled {
border-bottom: 4px solid; border-bottom: 0.2em solid;
svg {
color: $fallback--lightText;
color: var(--lightText, $fallback--lightText);
}
} }
} }
} }
@ -137,7 +129,7 @@ $emoji-picker-emoji-size: 32px;
.emoji { .emoji {
&-search { &-search {
padding: 5px; padding: 0.3em;
flex: 0 0 auto; flex: 0 0 auto;
input { input {
@ -151,6 +143,7 @@ $emoji-picker-emoji-size: 32px;
flex: 1 1 1px; flex: 1 1 1px;
position: relative; position: relative;
overflow: auto; overflow: auto;
scrollbar-gutter: stable both-edges;
user-select: none; user-select: none;
mask: mask:
linear-gradient(to top, white 0, transparent 100%) bottom no-repeat, linear-gradient(to top, white 0, transparent 100%) bottom no-repeat,
@ -177,13 +170,13 @@ $emoji-picker-emoji-size: 32px;
display: flex; display: flex;
align-items: center; align-items: center;
flex-wrap: wrap; flex-wrap: wrap;
padding-left: 5px;
justify-content: left; justify-content: left;
&-title { &-title {
font-size: 0.85em; font-size: 0.85em;
width: 100%; width: 100%;
margin: 0; margin: 0;
padding-left: 0.3em;
&.disabled { &.disabled {
display: none; display: none;
@ -192,24 +185,28 @@ $emoji-picker-emoji-size: 32px;
} }
&-item { &-item {
width: $emoji-picker-emoji-size; width: var(--emoji-size);
height: $emoji-picker-emoji-size; height: var(--emoji-size);
box-sizing: border-box; box-sizing: border-box;
display: flex; display: flex;
line-height: $emoji-picker-emoji-size; line-height: var(--emoji-size);
align-items: center; align-items: center;
justify-content: center; justify-content: center;
margin: 4px; margin: 0.2em;
cursor: pointer; cursor: pointer;
.emoji-picker-emoji.-custom { .emoji-picker-emoji.-custom {
object-fit: contain; object-fit: contain;
max-width: 100%; width: var(--emoji-size);
max-height: 100%; max-width: var(--emoji-size);
height: var(--emoji-size);
max-height: var(--emoji-size);
--_still_image-label-scale: 0.5;
} }
.emoji-picker-emoji.-unicode { .emoji-picker-emoji.-unicode {
font-size: 24px; font-size: 1.6em;
overflow: hidden; overflow: hidden;
} }
} }

View file

@ -23,9 +23,9 @@
v-for="group in filteredEmojiGroups" v-for="group in filteredEmojiGroups"
:ref="setGroupRef('group-header-' + group.id)" :ref="setGroupRef('group-header-' + group.id)"
:key="group.id" :key="group.id"
class="emoji-tabs-item" class="button-unstyled emoji-tabs-item"
:class="{ :class="{
active: activeGroupView === group.id toggled: activeGroupView === group.id
}" }"
:title="group.text" :title="group.text"
role="button" role="button"
@ -52,8 +52,8 @@
class="additional-tabs" class="additional-tabs"
> >
<span <span
class="stickers-tab-icon additional-tabs-item" class="button-unstyled stickers-tab-icon additional-tabs-item"
:class="{active: showingStickers}" :class="{toggled: showingStickers}"
:title="$t('emoji.stickers')" :title="$t('emoji.stickers')"
@click.prevent="toggleStickers" @click.prevent="toggleStickers"
> >
@ -77,7 +77,7 @@
ref="search" ref="search"
v-model="keyword" v-model="keyword"
type="text" type="text"
class="form-control" class="input form-control"
:placeholder="$t('emoji.search_emoji')" :placeholder="$t('emoji.search_emoji')"
@input="$event.target.composing = false" @input="$event.target.composing = false"
> >
@ -142,6 +142,17 @@
{{ $t('emoji.keep_open') }} {{ $t('emoji.keep_open') }}
</Checkbox> </Checkbox>
</div> </div>
<div
v-if="!hideCustomEmoji"
class="hide-custom-emoji"
>
<Checkbox
v-model="hideCustomEmojiInPicker"
@change="onShowing"
>
{{ $t('emoji.hide_custom_emoji') }}
</Checkbox>
</div>
</div> </div>
<div <div
v-if="showingStickers" v-if="showingStickers"

View file

@ -72,7 +72,6 @@
<script src="./emoji_reactions.js"></script> <script src="./emoji_reactions.js"></script>
<style lang="scss"> <style lang="scss">
@import "../../variables";
@import "../../mixins"; @import "../../mixins";
.EmojiReactions { .EmojiReactions {
@ -80,7 +79,7 @@
margin-top: 0.25em; margin-top: 0.25em;
flex-wrap: wrap; flex-wrap: wrap;
--emoji-size: calc(1.25em * var(--emojiReactionsScale, 1)); --emoji-size: calc(var(--emojiSize, 1.25em) * var(--emojiReactionsScale, 1));
.emoji-reaction-container { .emoji-reaction-container {
display: flex; display: flex;
@ -92,7 +91,6 @@
padding: 0; padding: 0;
.emoji-reaction-count-button { .emoji-reaction-count-button {
background-color: var(--btn);
margin: 0; margin: 0;
height: 100%; height: 100%;
border-top-left-radius: 0; border-top-left-radius: 0;
@ -102,11 +100,9 @@
display: inline-flex; display: inline-flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
color: $fallback--text;
color: var(--btnText, $fallback--text);
&.-picked-reaction { &.-picked-reaction {
border: 1px solid var(--accent, $fallback--link); border: 1px solid var(--accent);
margin-right: -1px; margin-right: -1px;
} }
} }
@ -149,18 +145,16 @@
} }
.svg-inline--fa { .svg-inline--fa {
color: $fallback--text; color: var(--text);
color: var(--btnText, $fallback--text);
} }
&.-picked-reaction { &.-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-left: -1px; // offset the border, can't use inset shadows either
margin-right: -1px; margin-right: -1px;
.svg-inline--fa { .svg-inline--fa {
color: $fallback--link; color: var(--accent);
color: var(--accent, $fallback--link);
} }
} }
@ -176,8 +170,7 @@
@include focused-style { @include focused-style {
.svg-inline--fa { .svg-inline--fa {
color: $fallback--link; color: var(--accent);
color: var(--accent, $fallback--link);
} }
.focus-marker { .focus-marker {

View file

@ -12,13 +12,13 @@
> >
<template #content="{close}"> <template #content="{close}">
<div <div
:id="`popup-menu-${randomSeed}`"
class="dropdown-menu" class="dropdown-menu"
role="menu" role="menu"
:id="`popup-menu-${randomSeed}`"
> >
<button <button
v-if="canMute && !status.thread_muted" v-if="canMute && !status.thread_muted"
class="button-default dropdown-item dropdown-item-icon" class="menu-item dropdown-item dropdown-item-icon"
role="menuitem" role="menuitem"
@click.prevent="muteConversation" @click.prevent="muteConversation"
> >
@ -29,7 +29,7 @@
</button> </button>
<button <button
v-if="canMute && status.thread_muted" v-if="canMute && status.thread_muted"
class="button-default dropdown-item dropdown-item-icon" class="menu-item dropdown-item dropdown-item-icon"
role="menuitem" role="menuitem"
@click.prevent="unmuteConversation" @click.prevent="unmuteConversation"
> >
@ -40,7 +40,7 @@
</button> </button>
<button <button
v-if="!status.pinned && canPin" v-if="!status.pinned && canPin"
class="button-default dropdown-item dropdown-item-icon" class="menu-item dropdown-item dropdown-item-icon"
role="menuitem" role="menuitem"
@click.prevent="pinStatus" @click.prevent="pinStatus"
@click="close" @click="close"
@ -52,7 +52,7 @@
</button> </button>
<button <button
v-if="status.pinned && canPin" v-if="status.pinned && canPin"
class="button-default dropdown-item dropdown-item-icon" class="menu-item dropdown-item dropdown-item-icon"
role="menuitem" role="menuitem"
@click.prevent="unpinStatus" @click.prevent="unpinStatus"
@click="close" @click="close"
@ -65,7 +65,7 @@
<template v-if="canBookmark"> <template v-if="canBookmark">
<button <button
v-if="!status.bookmarked" v-if="!status.bookmarked"
class="button-default dropdown-item dropdown-item-icon" class="menu-item dropdown-item dropdown-item-icon"
role="menuitem" role="menuitem"
@click.prevent="bookmarkStatus" @click.prevent="bookmarkStatus"
@click="close" @click="close"
@ -77,7 +77,7 @@
</button> </button>
<button <button
v-if="status.bookmarked" v-if="status.bookmarked"
class="button-default dropdown-item dropdown-item-icon" class="menu-item dropdown-item dropdown-item-icon"
role="menuitem" role="menuitem"
@click.prevent="unbookmarkStatus" @click.prevent="unbookmarkStatus"
@click="close" @click="close"
@ -90,7 +90,7 @@
</template> </template>
<button <button
v-if="ownStatus && editingAvailable" v-if="ownStatus && editingAvailable"
class="button-default dropdown-item dropdown-item-icon" class="menu-item dropdown-item dropdown-item-icon"
role="menuitem" role="menuitem"
@click.prevent="editStatus" @click.prevent="editStatus"
@click="close" @click="close"
@ -102,7 +102,7 @@
</button> </button>
<button <button
v-if="isEdited && editingAvailable" v-if="isEdited && editingAvailable"
class="button-default dropdown-item dropdown-item-icon" class="menu-item dropdown-item dropdown-item-icon"
role="menuitem" role="menuitem"
@click.prevent="showStatusHistory" @click.prevent="showStatusHistory"
@click="close" @click="close"
@ -114,7 +114,7 @@
</button> </button>
<button <button
v-if="canDelete" v-if="canDelete"
class="button-default dropdown-item dropdown-item-icon" class="menu-item dropdown-item dropdown-item-icon"
role="menuitem" role="menuitem"
@click.prevent="deleteStatus" @click.prevent="deleteStatus"
@click="close" @click="close"
@ -125,7 +125,7 @@
/><span>{{ $t("status.delete") }}</span> /><span>{{ $t("status.delete") }}</span>
</button> </button>
<button <button
class="button-default dropdown-item dropdown-item-icon" class="menu-item dropdown-item dropdown-item-icon"
role="menuitem" role="menuitem"
@click.prevent="copyLink" @click.prevent="copyLink"
@click="close" @click="close"
@ -137,7 +137,7 @@
</button> </button>
<a <a
v-if="!status.is_local" v-if="!status.is_local"
class="button-default dropdown-item dropdown-item-icon" class="menu-item dropdown-item dropdown-item-icon"
role="menuitem" role="menuitem"
title="Source" title="Source"
:href="status.external_url" :href="status.external_url"
@ -149,7 +149,7 @@
/><span>{{ $t("status.external_source") }}</span> /><span>{{ $t("status.external_source") }}</span>
</a> </a>
<button <button
class="button-default dropdown-item dropdown-item-icon" class="menu-item dropdown-item dropdown-item-icon"
role="menuitem" role="menuitem"
@click.prevent="reportStatus" @click.prevent="reportStatus"
@click="close" @click="close"
@ -201,7 +201,6 @@
<script src="./extra_buttons.js"></script> <script src="./extra_buttons.js"></script>
<style lang="scss"> <style lang="scss">
@import "../../variables";
@import "../../mixins"; @import "../../mixins";
.ExtraButtons { .ExtraButtons {
@ -211,8 +210,7 @@
margin: -10px; margin: -10px;
&:hover .svg-inline--fa { &:hover .svg-inline--fa {
color: $fallback--text; color: var(--text);
color: var(--text, $fallback--text);
} }
} }

View file

@ -0,0 +1,48 @@
import { mapGetters } from 'vuex'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faUserPlus,
faComments,
faBullhorn
} from '@fortawesome/free-solid-svg-icons'
library.add(
faUserPlus,
faComments,
faBullhorn
)
const ExtraNotifications = {
computed: {
shouldShowChats () {
return this.mergedConfig.showExtraNotifications && this.mergedConfig.showChatsInExtraNotifications && this.unreadChatCount
},
shouldShowAnnouncements () {
return this.mergedConfig.showExtraNotifications && this.mergedConfig.showAnnouncementsInExtraNotifications && this.unreadAnnouncementCount
},
shouldShowFollowRequests () {
return this.mergedConfig.showExtraNotifications && this.mergedConfig.showFollowRequestsInExtraNotifications && this.followRequestCount
},
hasAnythingToShow () {
return this.shouldShowChats || this.shouldShowAnnouncements || this.shouldShowFollowRequests
},
shouldShowCustomizationTip () {
return this.mergedConfig.showExtraNotificationsTip && this.hasAnythingToShow
},
currentUser () {
return this.$store.state.users.currentUser
},
...mapGetters(['unreadChatCount', 'unreadAnnouncementCount', 'followRequestCount', 'mergedConfig'])
},
methods: {
openNotificationSettings () {
return this.$store.dispatch('openSettingsModalTab', 'notifications')
},
dismissConfigurationTip () {
return this.$store.dispatch('setOption', { name: 'showExtraNotificationsTip', value: false })
}
}
}
export default ExtraNotifications

View file

@ -0,0 +1,110 @@
<template>
<div class="ExtraNotifications">
<div
v-if="shouldShowChats"
class="notification unseen"
>
<div class="notification-overlay" />
<router-link
class="button-unstyled -link extra-notification"
:to="{ name: 'chats', params: { username: currentUser.screen_name } }"
>
<FAIcon
fixed-width
class="fa-scale-110 icon"
icon="comments"
/>
{{ $tc('notifications.unread_chats', unreadChatCount, { num: unreadChatCount }) }}
</router-link>
</div>
<div
v-if="shouldShowAnnouncements"
class="notification unseen"
>
<div class="notification-overlay" />
<router-link
class="button-unstyled -link extra-notification"
:to="{ name: 'announcements' }"
>
<FAIcon
fixed-width
class="fa-scale-110 icon"
icon="bullhorn"
/>
{{ $tc('notifications.unread_announcements', unreadAnnouncementCount, { num: unreadAnnouncementCount }) }}
</router-link>
</div>
<div
v-if="shouldShowFollowRequests"
class="notification unseen"
>
<div class="notification-overlay" />
<router-link
class="button-unstyled -link extra-notification"
:to="{ name: 'friend-requests' }"
>
<FAIcon
fixed-width
class="fa-scale-110 icon"
icon="user-plus"
/>
{{ $tc('notifications.unread_follow_requests', followRequestCount, { num: followRequestCount }) }}
</router-link>
</div>
<i18n-t
v-if="shouldShowCustomizationTip"
tag="span"
class="notification tip extra-notification"
keypath="notifications.configuration_tip"
>
<template #theSettings>
<button
class="button-unstyled -link"
@click="openNotificationSettings"
>
{{ $t('notifications.configuration_tip_settings') }}
</button>
</template>
<template #dismiss>
<button
class="button-unstyled -link"
@click="dismissConfigurationTip"
>
{{ $t('notifications.configuration_tip_dismiss') }}
</button>
</template>
</i18n-t>
</div>
</template>
<script src="./extra_notifications.js" />
<style lang="scss">
.ExtraNotifications {
width: 100%;
display: flex;
flex-direction: column;
align-items: stretch;
.notification {
width: 100%;
border-bottom: 1px solid;
border-color: var(--border);
display: flex;
flex-direction: column;
align-items: stretch;
}
.extra-notification {
padding: 1em;
}
.icon {
margin-right: 0.5em;
}
.tip {
display: inline;
}
}
</style>

View file

@ -65,7 +65,6 @@
<script src="./favorite_button.js"></script> <script src="./favorite_button.js"></script>
<style lang="scss"> <style lang="scss">
@import "../../variables";
@import "../../mixins"; @import "../../mixins";
.FavoriteButton { .FavoriteButton {
@ -88,8 +87,7 @@
&:hover .svg-inline--fa, &:hover .svg-inline--fa,
&.-favorited .svg-inline--fa { &.-favorited .svg-inline--fa {
color: $fallback--cOrange; color: var(--cOrange);
color: var(--cOrange, $fallback--cOrange);
} }
@include unfocused-style { @include unfocused-style {

View file

@ -42,8 +42,6 @@
<script src="./flash.js"></script> <script src="./flash.js"></script>
<style lang="scss"> <style lang="scss">
@import "../../variables";
.Flash { .Flash {
display: inline-block; display: inline-block;
width: 100%; width: 100%;

View file

@ -1,63 +1,59 @@
import { set } from 'lodash'
import Select from '../select/select.vue' import Select from '../select/select.vue'
import Checkbox from 'src/components/checkbox/checkbox.vue'
import Popover from 'src/components/popover/popover.vue'
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faExclamationTriangle,
faKeyboard,
faFont
} from '@fortawesome/free-solid-svg-icons'
library.add(
faExclamationTriangle,
faKeyboard,
faFont
)
export default { export default {
components: { components: {
Select Select,
Checkbox,
Popover
}, },
props: [ props: [
'name', 'label', 'modelValue', 'fallback', 'options', 'no-inherit' 'name', 'label', 'modelValue', 'fallback', 'options', 'no-inherit'
], ],
mounted () {
this.$store.dispatch('queryLocalFonts')
},
emits: ['update:modelValue'], emits: ['update:modelValue'],
data () { data () {
return { return {
lValue: this.modelValue, manualEntry: false,
availableOptions: [ availableOptions: [
this.noInherit ? '' : 'inherit', this.noInherit ? '' : 'inherit',
'custom',
...(this.options || []),
'serif', 'serif',
'sans-serif',
'monospace', 'monospace',
'sans-serif' ...(this.options || [])
].filter(_ => _) ].filter(_ => _)
} }
}, },
beforeUpdate () { methods: {
this.lValue = this.modelValue toggleManualEntry () {
this.manualEntry = !this.manualEntry
}
}, },
computed: { computed: {
present () { present () {
return typeof this.lValue !== 'undefined' return typeof this.modelValue !== 'undefined'
}, },
dValue () { localFontsList () {
return this.lValue || this.fallback || {} return this.$store.state.interface.localFonts
}, },
family: { localFontsSize () {
get () { return this.$store.state.interface.localFonts?.length
return this.dValue.family
},
set (v) {
set(this.lValue, 'family', v)
this.$emit('update:modelValue', this.lValue)
}
},
isCustom () {
return this.preset === 'custom'
},
preset: {
get () {
if (this.family === 'serif' ||
this.family === 'sans-serif' ||
this.family === 'monospace' ||
this.family === 'inherit') {
return this.family
} else {
return 'custom'
}
},
set (v) {
this.family = v === 'custom' ? '' : v
}
} }
} }
} }

View file

@ -1,6 +1,6 @@
<template> <template>
<div <div
class="font-control style-control" class="font-control"
:class="{ custom: isCustom }" :class="{ custom: isCustom }"
> >
<label <label
@ -10,67 +10,137 @@
> >
{{ label }} {{ label }}
</label> </label>
<input {{ ' ' }}
<Checkbox
v-if="typeof fallback !== 'undefined'" v-if="typeof fallback !== 'undefined'"
:id="name + '-o'" :id="name + '-o'"
:aria-labelledby="name + '-label'" :modelValue="present"
class="opt exlcude-disabled visible-for-screenreader-only"
type="checkbox"
:checked="present"
@change="$emit('update:modelValue', typeof modelValue === 'undefined' ? fallback : undefined)" @change="$emit('update:modelValue', typeof modelValue === 'undefined' ? fallback : undefined)"
> >
<label {{ $t('settings.style.themes3.define') }}
v-if="typeof fallback !== 'undefined'" </Checkbox>
class="opt-l" <p v-if="modelValue?.family">
:for="name + '-o'" <label
:aria-hidden="true" v-if="manualEntry"
/> :id="name + '-label'"
{{ ' ' }} :for="preset === 'custom' ? name : name + '-font-switcher'"
<Select class="label"
:id="name + '-font-switcher'"
v-model="preset"
:disabled="!present"
class="font-switcher"
>
<option
v-for="option in availableOptions"
:key="option"
:value="option"
> >
{{ option === 'custom' ? $t('settings.style.fonts.custom') : option }} <i18n-t
</option> keypath="settings.style.themes3.font.entry"
</Select> tag="span"
<input >
v-if="isCustom" <template #fontFamily>
:id="name" <code>font-family</code>
v-model="family" </template>
class="custom-font" </i18n-t>
type="text" </label>
> <label
v-else
:id="name + '-label'"
:for="preset === 'custom' ? name : name + '-font-switcher'"
class="label"
>
{{ $t('settings.style.themes3.font.select') }}
</label>
{{ ' ' }}
<span
v-if="manualEntry"
class="btn-group"
>
<button
class="btn button-default"
@click="toggleManualEntry"
:title="$t('settings.style.themes3.font.lookup_local_fonts')"
>
<FAIcon
fixed-width
icon="font"
/>
</button>
<input
:id="name"
:model-value="modelValue.family"
class="input custom-font"
type="text"
@update:modelValue="$emit('update:modelValue', { ...(modelValue || {}), family: $event.target.value })"
>
</span>
<span
v-else
class="btn-group"
>
<button
class="btn button-default"
@click="toggleManualEntry"
:title="$t('settings.style.themes3.font.enter_manually')"
>
<FAIcon
fixed-width
icon="keyboard"
/>
</button>
<Select
:id="name + '-local-font-switcher'"
:model-value="modelValue?.family"
class="custom-font"
@update:modelValue="v => $emit('update:modelValue', { ...(modelValue || {}), family: v })"
>
<optgroup
:label="$t('settings.style.themes3.font.group-builtin')"
>
<option
v-for="option in availableOptions"
:key="option"
:value="option"
:style="{ fontFamily: option === 'inherit' ? null : option }"
>
{{ $t('settings.style.themes3.font.builtin.' + option) }}
</option>
</optgroup>
<optgroup
v-if="localFontsSize > 0"
:label="$t('settings.style.themes3.font.group-local')"
>
<option
v-for="option in localFontsList"
:key="option"
:value="option"
:style="{ fontFamily: option }"
>
{{ option }}
</option>
</optgroup>
<optgroup
v-else
:label="$t('settings.style.themes3.font.group-local')"
>
<option disabled>
{{ $t('settings.style.themes3.font.local-unavailable1') }}
</option>
<option disabled>
{{ $t('settings.style.themes3.font.local-unavailable2') }}
</option>
</optgroup>
</Select>
</span>
</p>
</div> </div>
</template> </template>
<script src="./font_control.js"></script> <script src="./font_control.js"></script>
<style lang="scss"> <style lang="scss">
@import "../../variables";
.font-control { .font-control {
input.custom-font { .custom-font {
min-width: 10em; min-width: 20em;
} max-width: 20em;
&.custom {
/* TODO Should make proper joiners... */
.font-switcher {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
.custom-font {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
} }
} }
.invalid-tooltip {
margin: 0.5em 1em;
min-width: 10em;
text-align: center;
}
</style> </style>

View file

@ -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'
}
}
]
}

View file

@ -87,8 +87,6 @@
<script src='./gallery.js'></script> <script src='./gallery.js'></script>
<style lang="scss"> <style lang="scss">
@import "../../variables";
.Gallery { .Gallery {
.gallery-rows { .gallery-rows {
display: flex; display: flex;

View file

@ -4,7 +4,7 @@
v-for="(notice, index) in notices" v-for="(notice, index) in notices"
:key="index" :key="index"
class="alert global-notice" class="alert global-notice"
:class="{ ['global-' + notice.level]: true }" :class="{ [notice.level]: true }"
> >
<div class="notice-message"> <div class="notice-message">
{{ $t(notice.messageKey, notice.messageArgs) }} {{ $t(notice.messageKey, notice.messageArgs) }}
@ -25,8 +25,6 @@
<script src="./global_notice_list.js"></script> <script src="./global_notice_list.js"></script>
<style lang="scss"> <style lang="scss">
@import "../../variables";
.global-notice-list { .global-notice-list {
position: fixed; position: fixed;
top: calc(var(--navbar-height) + 0.5em); 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 { .close-notice {
padding-right: 0.2em; padding-right: 0.2em;
.svg-inline--fa:hover {
opacity: 0.6;
}
} }
} }
</style> </style>

View file

@ -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'
}
}
]
}

View file

@ -41,7 +41,7 @@
<input <input
ref="input" ref="input"
type="file" type="file"
class="image-cropper-img-input" class="input image-cropper-img-input"
:accept="mimes" :accept="mimes"
> >
</div> </div>

View file

@ -3,6 +3,7 @@
<form> <form>
<input <input
ref="input" ref="input"
class="input"
type="file" type="file"
@change="change" @change="change"
> >

View file

@ -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']
}
}
]
}

View file

@ -3,6 +3,7 @@ import TabSwitcher from 'src/components/tab_switcher/tab_switcher.jsx'
const tabModeDict = { const tabModeDict = {
mentions: ['mention'], mentions: ['mention'],
statuses: ['status'],
'likes+repeats': ['repeat', 'like'], 'likes+repeats': ['repeat', 'like'],
follows: ['follow'], follows: ['follow'],
reactions: ['pleroma:emoji_reaction'], reactions: ['pleroma:emoji_reaction'],

View file

@ -10,9 +10,13 @@
:on-switch="onModeSwitch" :on-switch="onModeSwitch"
> >
<span <span
key="mentions" key="statuses"
:label="$t('nav.mentions')" :label="$t('nav.mentions')"
/> />
<span
key="statuses"
:label="$t('interactions.statuses')"
/>
<span <span
key="likes+repeats" key="likes+repeats"
:label="$t('interactions.favs_repeats')" :label="$t('interactions.favs_repeats')"
@ -39,6 +43,7 @@
<Notifications <Notifications
ref="notifications" ref="notifications"
:no-heading="true" :no-heading="true"
:no-extra="true"
:minimal-mode="true" :minimal-mode="true"
:filter-mode="filterMode" :filter-mode="filterMode"
/> />

View file

@ -104,8 +104,6 @@ export default {
</script> </script>
<style lang="scss"> <style lang="scss">
@import "../../variables";
.interface-language-switcher { .interface-language-switcher {
.language-select { .language-select {
margin-right: 1em; margin-right: 1em;

View file

@ -33,8 +33,6 @@
<script src="./link-preview.js"></script> <script src="./link-preview.js"></script>
<style lang="scss"> <style lang="scss">
@import "../../variables";
.link-preview-card { .link-preview-card {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@ -51,8 +49,7 @@
width: 100%; width: 100%;
height: 100%; height: 100%;
object-fit: cover; object-fit: cover;
border-radius: $fallback--attachmentRadius; border-radius: var(--roundness);
border-radius: var(--attachmentRadius, $fallback--attachmentRadius);
} }
} }
@ -82,13 +79,10 @@
margin: 2em 0; margin: 2em 0;
} }
color: $fallback--text; color: var(--text);
color: var(--text, $fallback--text);
border-style: solid; border-style: solid;
border-width: 1px; border-width: 1px;
border-radius: $fallback--attachmentRadius; border-radius: var(--roundness);
border-radius: var(--attachmentRadius, $fallback--attachmentRadius); border-color: var(--border);
border-color: $fallback--border;
border-color: var(--border, $fallback--border);
} }
</style> </style>

View file

@ -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'
}
}
]
}

View file

@ -7,6 +7,7 @@
v-for="item in items" v-for="item in items"
:key="getKey(item)" :key="getKey(item)"
class="list-item" class="list-item"
:class="[getClass(item), nonInteractive ? '-non-interactive' : '']"
role="listitem" role="listitem"
> >
<slot <slot
@ -33,24 +34,15 @@ export default {
getKey: { getKey: {
type: Function, type: Function,
default: item => item.id default: item => item.id
},
getClass: {
type: Function,
default: item => ''
},
nonInteractive: {
type: Boolean,
default: false
} }
} }
} }
</script> </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>

View file

@ -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
}
}
]
}

View file

@ -21,8 +21,6 @@
<script src="./lists_card.js"></script> <script src="./lists_card.js"></script>
<style lang="scss"> <style lang="scss">
@import "../../variables";
.list-card { .list-card {
display: flex; display: flex;
} }
@ -35,18 +33,6 @@
.button-list-edit { .button-list-edit {
margin: 0; margin: 0;
padding: 1em; padding: 1em;
color: $fallback--link; color: var(--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);
}
} }
</style> </style>

View file

@ -36,6 +36,7 @@
id="list-edit-title" id="list-edit-title"
ref="title" ref="title"
v-model="titleDraft" v-model="titleDraft"
class="input"
> >
<button <button
v-if="id" v-if="id"
@ -164,8 +165,6 @@
<script src="./lists_edit.js"></script> <script src="./lists_edit.js"></script>
<style lang="scss"> <style lang="scss">
@import "../../variables";
.ListEdit { .ListEdit {
--panel-body-padding: 0.5em; --panel-body-padding: 0.5em;

View file

@ -10,6 +10,7 @@
<input <input
ref="search" ref="search"
v-model="query" v-model="query"
class="input"
:placeholder="$t('lists.search')" :placeholder="$t('lists.search')"
@input="onInput" @input="onInput"
> >
@ -27,8 +28,6 @@
<script src="./lists_user_search.js"></script> <script src="./lists_user_search.js"></script>
<style lang="scss"> <style lang="scss">
@import "../../variables";
.ListsUserSearch { .ListsUserSearch {
.input-wrap { .input-wrap {
display: flex; display: flex;

View file

@ -18,7 +18,7 @@
id="username" id="username"
v-model="user.username" v-model="user.username"
:disabled="loggingIn" :disabled="loggingIn"
class="form-control" class="input form-control"
:placeholder="$t('login.placeholder')" :placeholder="$t('login.placeholder')"
> >
</div> </div>
@ -29,7 +29,7 @@
ref="passwordInput" ref="passwordInput"
v-model="user.password" v-model="user.password"
:disabled="loggingIn" :disabled="loggingIn"
class="form-control" class="input form-control"
type="password" type="password"
> >
</div> </div>
@ -93,8 +93,6 @@
<script src="./login_form.js"></script> <script src="./login_form.js"></script>
<style lang="scss"> <style lang="scss">
@import "../../variables";
.login-form { .login-form {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View file

@ -36,8 +36,6 @@
<script src="./media_upload.js"></script> <script src="./media_upload.js"></script>
<style lang="scss"> <style lang="scss">
@import "../../variables";
.media-upload { .media-upload {
.hidden-input-file { .hidden-input-file {
display: none; display: none;

View file

@ -1,10 +1,7 @@
@import "../../variables";
.MentionLink { .MentionLink {
position: relative; position: relative;
white-space: normal; white-space: normal;
display: inline; display: inline;
color: var(--link);
word-break: normal; word-break: normal;
& .new, & .new,
@ -14,7 +11,7 @@
} }
.mention-avatar { .mention-avatar {
border-radius: var(--avatarAltRadius, $fallback--avatarAltRadius); border-radius: var(--roundness);
width: 1.5em; width: 1.5em;
height: 1.5em; height: 1.5em;
vertical-align: middle; vertical-align: middle;
@ -61,8 +58,10 @@
} }
&.-has-selection { &.-has-selection {
color: var(--alertNeutralText, $fallback--text); --color: var(--selectionText);
background-color: var(--alertNeutral, $fallback--fg); --link: var(--selectionText);
background-color: var(--selectionBackground);
} }
.at { .at {
@ -102,7 +101,7 @@
} }
.serverName.-faded { .serverName.-faded {
color: var(--faintLink, $fallback--link); color: var(--linkFaint);
} }
} }

View file

@ -22,7 +22,7 @@
:class="classnames" :class="classnames"
> >
<a <a
class="short button-unstyled" class="short"
:class="{ '-with-tooltip': shouldShowTooltip }" :class="{ '-with-tooltip': shouldShowTooltip }"
:href="url" :href="url"
@click.prevent="onClick" @click.prevent="onClick"

View file

@ -22,13 +22,13 @@
/> />
</span><button </span><button
v-if="!expanded" v-if="!expanded"
class="button-unstyled showMoreLess" class="button-unstyled -link showMoreLess"
@click="toggleShowMore" @click="toggleShowMore"
> >
{{ $t('status.plus_more', { number: extraMentions.length }) }} {{ $t('status.plus_more', { number: extraMentions.length }) }}
</button><button </button><button
v-if="expanded" v-if="expanded"
class="button-unstyled showMoreLess" class="button-unstyled -link showMoreLess"
@click="toggleShowMore" @click="toggleShowMore"
> >
{{ $t('general.show_less') }} {{ $t('general.show_less') }}

View file

@ -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'
}
}
]
}

View file

@ -16,7 +16,7 @@
<input <input
id="code" id="code"
v-model="code" v-model="code"
class="form-control" class="input form-control"
> >
</div> </div>

View file

@ -18,7 +18,7 @@
<input <input
id="code" id="code"
v-model="code" v-model="code"
class="form-control" class="input form-control"
> >
</div> </div>

View file

@ -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
}]
}
}
]
}

View file

@ -1,7 +1,10 @@
import SideDrawer from '../side_drawer/side_drawer.vue' import SideDrawer from '../side_drawer/side_drawer.vue'
import Notifications from '../notifications/notifications.vue' import Notifications from '../notifications/notifications.vue'
import ConfirmModal from '../confirm_modal/confirm_modal.vue' import ConfirmModal from '../confirm_modal/confirm_modal.vue'
import { unseenNotificationsFromStore } from '../../services/notification_utils/notification_utils' import {
unseenNotificationsFromStore,
countExtraNotifications
} from '../../services/notification_utils/notification_utils'
import GestureService from '../../services/gesture_service/gesture_service' import GestureService from '../../services/gesture_service/gesture_service'
import NavigationPins from 'src/components/navigation/navigation_pins.vue' import NavigationPins from 'src/components/navigation/navigation_pins.vue'
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
@ -11,7 +14,8 @@ import {
faBell, faBell,
faBars, faBars,
faArrowUp, faArrowUp,
faMinus faMinus,
faCheckDouble
} from '@fortawesome/free-solid-svg-icons' } from '@fortawesome/free-solid-svg-icons'
library.add( library.add(
@ -19,7 +23,8 @@ library.add(
faBell, faBell,
faBars, faBars,
faArrowUp, faArrowUp,
faMinus faMinus,
faCheckDouble
) )
const MobileNav = { const MobileNav = {
@ -50,8 +55,14 @@ const MobileNav = {
return unseenNotificationsFromStore(this.$store) return unseenNotificationsFromStore(this.$store)
}, },
unseenNotificationsCount () { unseenNotificationsCount () {
return this.unseenNotifications.length + countExtraNotifications(this.$store)
},
unseenCount () {
return this.unseenNotifications.length return this.unseenNotifications.length
}, },
unseenCountBadgeText () {
return `${this.unseenCount ? this.unseenCount : ''}`
},
hideSitename () { return this.$store.state.instance.hideSitename }, hideSitename () { return this.$store.state.instance.hideSitename },
sitename () { return this.$store.state.instance.name }, sitename () { return this.$store.state.instance.name },
isChat () { isChat () {
@ -64,6 +75,9 @@ const MobileNav = {
shouldConfirmLogout () { shouldConfirmLogout () {
return this.$store.getters.mergedConfig.modalOnLogout return this.$store.getters.mergedConfig.modalOnLogout
}, },
closingDrawerMarksAsSeen () {
return this.$store.getters.mergedConfig.closingDrawerMarksAsSeen
},
...mapGetters(['unreadChatCount']) ...mapGetters(['unreadChatCount'])
}, },
methods: { methods: {
@ -78,7 +92,7 @@ const MobileNav = {
// make sure to mark notifs seen only when the notifs were open and not // make sure to mark notifs seen only when the notifs were open and not
// from close-calls. // from close-calls.
this.notificationsOpen = false this.notificationsOpen = false
if (markRead) { if (markRead && this.closingDrawerMarksAsSeen) {
this.markNotificationsAsSeen() this.markNotificationsAsSeen()
} }
} }
@ -114,7 +128,6 @@ const MobileNav = {
this.hideConfirmLogout() this.hideConfirmLogout()
}, },
markNotificationsAsSeen () { markNotificationsAsSeen () {
// this.$refs.notifications.markAsSeen()
this.$store.dispatch('markNotificationsAsSeen') this.$store.dispatch('markNotificationsAsSeen')
}, },
onScroll ({ target: { scrollTop, clientHeight, scrollHeight } }) { onScroll ({ target: { scrollTop, clientHeight, scrollHeight } }) {

View file

@ -20,7 +20,7 @@
/> />
<div <div
v-if="(unreadChatCount && !chatsPinned) || unreadAnnouncementCount" v-if="(unreadChatCount && !chatsPinned) || unreadAnnouncementCount"
class="alert-dot" class="badge -dot -notification"
/> />
</button> </button>
<NavigationPins class="pins" /> <NavigationPins class="pins" />
@ -37,20 +37,26 @@
/> />
<div <div
v-if="unseenNotificationsCount" v-if="unseenNotificationsCount"
class="alert-dot" class="badge -dot -notification"
/> />
</button> </button>
</div> </div>
</nav> </nav>
<aside <aside
v-if="currentUser" v-if="currentUser"
class="mobile-notifications-drawer" class="mobile-notifications-drawer mobile-drawer"
:class="{ '-closed': !notificationsOpen }" :class="{ '-closed': !notificationsOpen }"
@touchstart.stop="notificationsTouchStart" @touchstart.stop="notificationsTouchStart"
@touchmove.stop="notificationsTouchMove" @touchmove.stop="notificationsTouchMove"
> >
<div class="mobile-notifications-header"> <div class="panel-heading mobile-notifications-header">
<span class="title">{{ $t('notifications.notifications') }}</span> <span class="title">
{{ $t('notifications.notifications') }}
<span
v-if="unseenCountBadgeText"
class="badge -notification unseen-count"
>{{ unseenCountBadgeText }}</span>
</span>
<span class="spacer" /> <span class="spacer" />
<button <button
v-if="notificationsAtTop" v-if="notificationsAtTop"
@ -66,6 +72,17 @@
/> />
</FALayers> </FALayers>
</button> </button>
<button
v-if="!closingDrawerMarksAsSeen"
class="button-unstyled mobile-nav-button"
:title="$t('nav.mobile_notifications_mark_as_seen')"
@click.stop.prevent="markNotificationsAsSeen()"
>
<FAIcon
class="fa-scale-110 fa-old-padding"
icon="check-double"
/>
</button>
<button <button
class="button-unstyled mobile-nav-button" class="button-unstyled mobile-nav-button"
:title="$t('nav.mobile_notifications_close')" :title="$t('nav.mobile_notifications_close')"
@ -106,21 +123,19 @@
<script src="./mobile_nav.js"></script> <script src="./mobile_nav.js"></script>
<style lang="scss"> <style lang="scss">
@import "../../variables";
.MobileNav { .MobileNav {
z-index: var(--ZI_navbar); z-index: var(--ZI_navbar);
.mobile-nav { .mobile-nav {
display: grid; display: grid;
line-height: var(--navbar-height); line-height: var(--navbar-height);
grid-template-rows: 50px; grid-template-rows: var(--navbar-height);
grid-template-columns: 2fr auto; grid-template-columns: 2fr auto;
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;
a { a {
color: var(--topBarLink, $fallback--link); color: var(--link);
} }
} }
@ -148,19 +163,6 @@
display: flex; 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 { .mobile-notifications-drawer {
width: 100%; width: 100%;
height: 100vh; height: 100vh;
@ -168,13 +170,13 @@
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
box-shadow: 1px 1px 4px rgb(0 0 0 / 60%); box-shadow: var(--shadow);
box-shadow: var(--panelShadow);
transition-property: transform; transition-property: transform;
transition-duration: 0.25s; transition-duration: 0.25s;
transform: translateX(0); transform: translateX(0);
z-index: var(--ZI_navbar); z-index: var(--ZI_navbar);
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
background: var(--background);
&.-closed { &.-closed {
transform: translateX(100%); transform: translateX(100%);
@ -188,14 +190,10 @@
justify-content: space-between; justify-content: space-between;
z-index: calc(var(--ZI_navbar) + 100); z-index: calc(var(--ZI_navbar) + 100);
width: 100%; width: 100%;
height: 50px; height: 3.5em;
line-height: 50px; line-height: 3.5em;
position: absolute; position: absolute;
color: var(--topBarText); box-shadow: var(--shadow);
background-color: $fallback--fg;
background-color: var(--topBar, $fallback--fg);
box-shadow: 0 0 4px rgb(0 0 0 / 60%);
box-shadow: var(--topBarShadow);
.spacer { .spacer {
flex: 1; flex: 1;
@ -216,15 +214,11 @@
} }
.mobile-notifications { .mobile-notifications {
margin-top: 50px; margin-top: 3.5em;
width: 100vw; width: 100vw;
height: calc(100vh - var(--navbar-height)); height: calc(100vh - var(--navbar-height));
overflow-x: hidden; overflow-x: hidden;
overflow-y: scroll; overflow-y: scroll;
color: $fallback--text;
color: var(--text, $fallback--text);
background-color: $fallback--bg;
background-color: var(--bg, $fallback--bg);
.notifications { .notifications {
padding: 0; padding: 0;

View file

@ -13,8 +13,6 @@
<script src="./mobile_post_status_button.js"></script> <script src="./mobile_post_status_button.js"></script>
<style lang="scss"> <style lang="scss">
@import "../../variables";
.MobilePostButton { .MobilePostButton {
&.button-default { &.button-default {
width: 5em; width: 5em;
@ -25,8 +23,6 @@
right: 1.5em; right: 1.5em;
// TODO: this needs its own color, it has to stand out enough and link color // TODO: this needs its own color, it has to stand out enough and link color
// is not very optimal for this particular use. // is not very optimal for this particular use.
background-color: $fallback--fg;
background-color: var(--btn, $fallback--fg);
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
@ -42,8 +38,7 @@
svg { svg {
font-size: 1.5em; font-size: 1.5em;
color: $fallback--text; color: var(--text);
color: var(--text, $fallback--text);
} }
} }

View file

@ -0,0 +1,9 @@
export default {
name: 'Modals',
selector: '.modal-view',
lazy: true,
validInnerComponents: [
'Panel'
],
defaultRules: []
}

View file

@ -12,13 +12,13 @@
<div class="dropdown-menu"> <div class="dropdown-menu">
<span v-if="canGrantRole"> <span v-if="canGrantRole">
<button <button
class="button-default dropdown-item" class="menu-item dropdown-item menu-item"
@click="toggleRight(&quot;admin&quot;)" @click="toggleRight(&quot;admin&quot;)"
> >
{{ $t(!!user.rights.admin ? 'user_card.admin_menu.revoke_admin' : 'user_card.admin_menu.grant_admin') }} {{ $t(!!user.rights.admin ? 'user_card.admin_menu.revoke_admin' : 'user_card.admin_menu.grant_admin') }}
</button> </button>
<button <button
class="button-default dropdown-item" class="menu-item dropdown-item menu-item"
@click="toggleRight(&quot;moderator&quot;)" @click="toggleRight(&quot;moderator&quot;)"
> >
{{ $t(!!user.rights.moderator ? 'user_card.admin_menu.revoke_moderator' : 'user_card.admin_menu.grant_moderator') }} {{ $t(!!user.rights.moderator ? 'user_card.admin_menu.revoke_moderator' : 'user_card.admin_menu.grant_moderator') }}
@ -31,14 +31,14 @@
</span> </span>
<button <button
v-if="canChangeActivationState" v-if="canChangeActivationState"
class="button-default dropdown-item" class="menu-item dropdown-item menu-item"
@click="toggleActivationStatus()" @click="toggleActivationStatus()"
> >
{{ $t(!!user.deactivated ? 'user_card.admin_menu.activate_account' : 'user_card.admin_menu.deactivate_account') }} {{ $t(!!user.deactivated ? 'user_card.admin_menu.activate_account' : 'user_card.admin_menu.deactivate_account') }}
</button> </button>
<button <button
v-if="canDeleteAccount" v-if="canDeleteAccount"
class="button-default dropdown-item" class="menu-item dropdown-item menu-item"
@click="deleteUserDialog(true)" @click="deleteUserDialog(true)"
> >
{{ $t('user_card.admin_menu.delete_account') }} {{ $t('user_card.admin_menu.delete_account') }}
@ -50,74 +50,74 @@
/> />
<span v-if="canUseTagPolicy"> <span v-if="canUseTagPolicy">
<button <button
class="button-default dropdown-item" class="menu-item dropdown-item menu-item"
@click="toggleTag(tags.FORCE_NSFW)" @click="toggleTag(tags.FORCE_NSFW)"
> >
<span <span
class="menu-checkbox" class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.FORCE_NSFW) }" :class="{ 'menu-checkbox-checked': hasTag(tags.FORCE_NSFW) }"
/> />
{{ $t('user_card.admin_menu.force_nsfw') }} {{ $t('user_card.admin_menu.force_nsfw') }}
</button> </button>
<button <button
class="button-default dropdown-item" class="menu-item dropdown-item menu-item"
@click="toggleTag(tags.STRIP_MEDIA)" @click="toggleTag(tags.STRIP_MEDIA)"
> >
<span <span
class="menu-checkbox" class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.STRIP_MEDIA) }" :class="{ 'menu-checkbox-checked': hasTag(tags.STRIP_MEDIA) }"
/> />
{{ $t('user_card.admin_menu.strip_media') }} {{ $t('user_card.admin_menu.strip_media') }}
</button> </button>
<button <button
class="button-default dropdown-item" class="menu-item dropdown-item menu-item"
@click="toggleTag(tags.FORCE_UNLISTED)" @click="toggleTag(tags.FORCE_UNLISTED)"
> >
<span <span
class="menu-checkbox" class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.FORCE_UNLISTED) }" :class="{ 'menu-checkbox-checked': hasTag(tags.FORCE_UNLISTED) }"
/> />
{{ $t('user_card.admin_menu.force_unlisted') }} {{ $t('user_card.admin_menu.force_unlisted') }}
</button> </button>
<button <button
class="button-default dropdown-item" class="menu-item dropdown-item menu-item"
@click="toggleTag(tags.SANDBOX)" @click="toggleTag(tags.SANDBOX)"
> >
<span <span
class="menu-checkbox" class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.SANDBOX) }" :class="{ 'menu-checkbox-checked': hasTag(tags.SANDBOX) }"
/> />
{{ $t('user_card.admin_menu.sandbox') }} {{ $t('user_card.admin_menu.sandbox') }}
</button> </button>
<button <button
v-if="user.is_local" v-if="user.is_local"
class="button-default dropdown-item" class="menu-item dropdown-item menu-item"
@click="toggleTag(tags.DISABLE_REMOTE_SUBSCRIPTION)" @click="toggleTag(tags.DISABLE_REMOTE_SUBSCRIPTION)"
> >
<span <span
class="menu-checkbox" class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.DISABLE_REMOTE_SUBSCRIPTION) }" :class="{ 'menu-checkbox-checked': hasTag(tags.DISABLE_REMOTE_SUBSCRIPTION) }"
/> />
{{ $t('user_card.admin_menu.disable_remote_subscription') }} {{ $t('user_card.admin_menu.disable_remote_subscription') }}
</button> </button>
<button <button
v-if="user.is_local" v-if="user.is_local"
class="button-default dropdown-item" class="menu-item dropdown-item menu-item"
@click="toggleTag(tags.DISABLE_ANY_SUBSCRIPTION)" @click="toggleTag(tags.DISABLE_ANY_SUBSCRIPTION)"
> >
<span <span
class="menu-checkbox" class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.DISABLE_ANY_SUBSCRIPTION) }" :class="{ 'menu-checkbox-checked': hasTag(tags.DISABLE_ANY_SUBSCRIPTION) }"
/> />
{{ $t('user_card.admin_menu.disable_any_subscription') }} {{ $t('user_card.admin_menu.disable_any_subscription') }}
</button> </button>
<button <button
v-if="user.is_local" v-if="user.is_local"
class="button-default dropdown-item" class="menu-item dropdown-item menu-item"
@click="toggleTag(tags.QUARANTINE)" @click="toggleTag(tags.QUARANTINE)"
> >
<span <span
class="menu-checkbox" class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': hasTag(tags.QUARANTINE) }" :class="{ 'menu-checkbox-checked': hasTag(tags.QUARANTINE) }"
/> />
{{ $t('user_card.admin_menu.quarantine') }} {{ $t('user_card.admin_menu.quarantine') }}
@ -166,8 +166,6 @@
<script src="./moderation_tools.js"></script> <script src="./moderation_tools.js"></script>
<style lang="scss"> <style lang="scss">
@import "../../variables";
.moderation-tools-popover { .moderation-tools-popover {
height: 100%; height: 100%;

View file

@ -227,6 +227,5 @@
<script src="./mrf_transparency_panel.js"></script> <script src="./mrf_transparency_panel.js"></script>
<style lang="scss"> <style lang="scss">
@import "../../variables";
@import "./mrf_transparency_panel"; @import "./mrf_transparency_panel";
</style> </style>

View file

@ -37,7 +37,8 @@
</NavigationEntry> </NavigationEntry>
<div <div
v-show="showTimelines" v-show="showTimelines"
class="timelines-background" class="timelines-background menu-item-collapsible"
:class="{ '-expanded': showTimelines }"
> >
<div class="timelines"> <div class="timelines">
<NavigationEntry <NavigationEntry
@ -57,12 +58,11 @@
> >
<router-link <router-link
:title="$t('lists.manage_lists')" :title="$t('lists.manage_lists')"
class="extra-button" class="button-unstyled extra-button"
:to="{ name: 'lists' }" :to="{ name: 'lists' }"
@click.stop @click.stop
> >
<FAIcon <FAIcon
class="extra-button"
fixed-width fixed-width
icon="wrench" icon="wrench"
/> />
@ -75,7 +75,8 @@
</NavigationEntry> </NavigationEntry>
<div <div
v-show="showLists" v-show="showLists"
class="timelines-background" class="timelines-background menu-item-collapsible"
:class="{ '-expanded': showLists }"
> >
<ListsMenuContent <ListsMenuContent
:show-pin="editMode || forceEditMode" :show-pin="editMode || forceEditMode"
@ -102,12 +103,10 @@
<script src="./nav_panel.js"></script> <script src="./nav_panel.js"></script>
<style lang="scss"> <style lang="scss">
@import "../../variables";
.NavPanel { .NavPanel {
.panel { .panel {
overflow: hidden; overflow: hidden;
box-shadow: var(--panelShadow); box-shadow: var(--shadow);
} }
ul { ul {
@ -116,33 +115,6 @@
padding: 0; 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 { .navigation-chevron {
margin-left: 0.8em; margin-left: 0.8em;
margin-right: 0.8em; margin-right: 0.8em;
@ -156,16 +128,6 @@
.timelines-background { .timelines-background {
padding: 0 0 0 0.6em; 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 { .nav-panel-heading {

View file

@ -1,7 +1,6 @@
<template> <template>
<OptionalRouterLink <OptionalRouterLink
v-slot="{ isActive, href, navigate } = {}" v-slot="{ isActive, href, navigate } = {}"
ass="ass"
:to="routeTo" :to="routeTo"
> >
<li <li
@ -11,7 +10,7 @@
> >
<component <component
:is="routeTo ? 'a' : 'button'" :is="routeTo ? 'a' : 'button'"
class="main-link button-unstyled" class="main-link"
:href="href" :href="href"
@click="navigate" @click="navigate"
> >
@ -35,7 +34,7 @@
<slot /> <slot />
<div <div
v-if="item.badgeGetter && getters[item.badgeGetter]" v-if="item.badgeGetter && getters[item.badgeGetter]"
class="badge badge-notification" class="badge -notification"
> >
{{ getters[item.badgeGetter] }} {{ getters[item.badgeGetter] }}
</div> </div>
@ -63,73 +62,53 @@
<script src="./navigation_entry.js"></script> <script src="./navigation_entry.js"></script>
<style lang="scss"> <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; display: flex;
box-sizing: border-box;
align-items: baseline; 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 { &[aria-expanded] {
margin-right: 0; padding-right: var(--__horizontal-gap);
} }
.main-link { .main-link {
line-height: var(--__line-height);
box-sizing: border-box;
flex: 1; flex: 1;
padding: var(--__vertical-gap) var(--__horizontal-gap);
} }
.menu-icon { .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 { .extra-button {
width: 3em; line-height: var(--__line-height);
padding: 0;
width: var(--__line-height);
text-align: center; text-align: center;
&:last-child { &:last-child {
margin-right: -0.8em; margin-right: calc(-1 * var(--__horizontal-gap));
} }
} }
&:hover { .badge {
background-color: $fallback--lightBg; margin: 0 var(--__horizontal-gap);
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);
}
}
&.-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;
}
} }
} }
</style> </style>

View file

@ -3,7 +3,8 @@
<router-link <router-link
v-for="item in pinnedList" v-for="item in pinnedList"
:key="item.name" :key="item.name"
class="pinned-item" class="button-unstyled pinned-item"
active-class="toggled"
:to="getRouteTo(item)" :to="getRouteTo(item)"
:title="item.labelRaw || $t(item.label)" :title="item.labelRaw || $t(item.label)"
> >
@ -18,7 +19,7 @@
>{{ item.iconLetter }}</span> >{{ item.iconLetter }}</span>
<div <div
v-if="item.badgeGetter && getters[item.badgeGetter]" v-if="item.badgeGetter && getters[item.badgeGetter]"
class="alert-dot" class="badge -dot -notification"
/> />
</router-link> </router-link>
</span> </span>
@ -27,25 +28,12 @@
<script src="./navigation_pins.js"></script> <script src="./navigation_pins.js"></script>
<style lang="scss"> <style lang="scss">
@import "../../variables";
.NavigationPins { .NavigationPins {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
overflow: hidden; overflow: hidden;
height: 100%; 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 { .pinned-item {
position: relative; position: relative;
flex: 1 0 3em; flex: 1 0 3em;
@ -60,15 +48,9 @@
margin: 0; margin: 0;
} }
&.router-link-active { &.toggled {
color: $fallback--text; margin-bottom: -4px;
color: var(--panelText, $fallback--text);
border-bottom: 4px solid; border-bottom: 4px solid;
& .svg-inline--fa,
& .iconLetter {
color: inherit;
}
} }
} }
} }

View file

@ -50,6 +50,7 @@ const Notification = {
} }
}, },
props: ['notification'], props: ['notification'],
emits: ['interacted'],
components: { components: {
StatusContent, StatusContent,
UserAvatar, UserAvatar,
@ -72,6 +73,9 @@ const Notification = {
getUser (notification) { getUser (notification) {
return this.$store.state.users.usersObject[notification.from_profile.id] return this.$store.state.users.usersObject[notification.from_profile.id]
}, },
interacted () {
this.$emit('interacted')
},
toggleMute () { toggleMute () {
this.unmuted = !this.unmuted this.unmuted = !this.unmuted
}, },
@ -95,6 +99,7 @@ const Notification = {
} }
}, },
doApprove () { doApprove () {
this.$emit('interacted')
this.$store.state.api.backendInteractor.approveUser({ id: this.user.id }) this.$store.state.api.backendInteractor.approveUser({ id: this.user.id })
this.$store.dispatch('removeFollowRequest', this.user) this.$store.dispatch('removeFollowRequest', this.user)
this.$store.dispatch('markSingleNotificationAsSeen', { id: this.notification.id }) this.$store.dispatch('markSingleNotificationAsSeen', { id: this.notification.id })
@ -114,6 +119,7 @@ const Notification = {
} }
}, },
doDeny () { doDeny () {
this.$emit('interacted')
this.$store.state.api.backendInteractor.denyUser({ id: this.user.id }) this.$store.state.api.backendInteractor.denyUser({ id: this.user.id })
.then(() => { .then(() => {
this.$store.dispatch('dismissNotificationLocal', { id: this.notification.id }) this.$store.dispatch('dismissNotificationLocal', { id: this.notification.id })

View file

@ -1,13 +1,15 @@
@import "../../variables";
// TODO Copypaste from Status, should unify it somehow // TODO Copypaste from Status, should unify it somehow
.Notification { .Notification {
border-bottom: 1px solid; border-bottom: 1px solid;
border-color: $fallback--border; border-color: var(--border);
border-color: var(--border, $fallback--border);
word-wrap: break-word; word-wrap: break-word;
word-break: break-word; word-break: break-word;
&.Status {
/* stylelint-disable-next-line declaration-no-important */
background-color: transparent !important;
}
--emoji-size: 14px; --emoji-size: 14px;
&:hover { &:hover {
@ -71,28 +73,22 @@
} }
&.-type--repeat .type-icon { &.-type--repeat .type-icon {
color: $fallback--cGreen; color: var(--cGreen);
color: var(--cGreen, $fallback--cGreen);
} }
&.-type--follow .type-icon { &.-type--follow .type-icon {
color: $fallback--cBlue; color: var(--cBlue);
color: var(--cBlue, $fallback--cBlue);
} }
&.-type--follow-request .type-icon { &.-type--follow-request .type-icon {
color: $fallback--cBlue; color: var(--cBlue);
color: var(--cBlue, $fallback--cBlue);
} }
&.-type--like .type-icon { &.-type--like .type-icon {
color: orange; color: var(--cOrange);
color: $fallback--cOrange;
color: var(--cOrange, $fallback--cOrange);
} }
&.-type--move .type-icon { &.-type--move .type-icon {
color: $fallback--cBlue; color: var(--cBlue);
color: var(--cBlue, $fallback--cBlue);
} }
} }

View file

@ -0,0 +1,18 @@
export default {
name: 'Notification',
selector: '.Notification',
validInnerComponents: [
'Text',
'Link',
'Icon',
'Border',
'Button',
'ButtonUnstyled',
'RichContent',
'Input',
'Avatar',
'Attachment',
'PollGraph'
],
defaultRules: []
}

View file

@ -1,11 +1,12 @@
<template> <template>
<article <article
v-if="notification.type === 'mention'" v-if="notification.type === 'mention' || notification.type === 'status'"
> >
<Status <Status
class="Notification" class="Notification"
:compact="true" :compact="true"
:statusoid="notification.status" :statusoid="notification.status"
@interacted="interacted"
/> />
</article> </article>
<article v-else> <article v-else>
@ -154,7 +155,7 @@
<router-link <router-link
v-if="notification.status" v-if="notification.status"
:to="{ name: 'conversation', params: { id: notification.status.id } }" :to="{ name: 'conversation', params: { id: notification.status.id } }"
class="timeago-link faint-link" class="timeago-link faint"
> >
<Timeago <Timeago
:time="notification.created_at" :time="notification.created_at"
@ -246,9 +247,8 @@
/> />
<template v-else> <template v-else>
<StatusContent <StatusContent
:class="{ faint: !statusExpanded }"
:compact="!statusExpanded" :compact="!statusExpanded"
:status="notification.action" :status="notification.status"
/> />
</template> </template>
</div> </div>

View file

@ -8,65 +8,74 @@
<template #content> <template #content>
<div class="dropdown-menu"> <div class="dropdown-menu">
<button <button
class="button-default dropdown-item" class="menu-item dropdown-item"
@click="toggleNotificationFilter('likes')" @click="toggleNotificationFilter('likes')"
> >
<span <span
class="menu-checkbox" class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': filters.likes }" :class="{ 'menu-checkbox-checked': filters.likes }"
/>{{ $t('settings.notification_visibility_likes') }} />{{ $t('settings.notification_visibility_likes') }}
</button> </button>
<button <button
class="button-default dropdown-item" class="menu-item dropdown-item"
@click="toggleNotificationFilter('repeats')" @click="toggleNotificationFilter('repeats')"
> >
<span <span
class="menu-checkbox" class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': filters.repeats }" :class="{ 'menu-checkbox-checked': filters.repeats }"
/>{{ $t('settings.notification_visibility_repeats') }} />{{ $t('settings.notification_visibility_repeats') }}
</button> </button>
<button <button
class="button-default dropdown-item" class="menu-item dropdown-item"
@click="toggleNotificationFilter('follows')" @click="toggleNotificationFilter('follows')"
> >
<span <span
class="menu-checkbox" class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': filters.follows }" :class="{ 'menu-checkbox-checked': filters.follows }"
/>{{ $t('settings.notification_visibility_follows') }} />{{ $t('settings.notification_visibility_follows') }}
</button> </button>
<button <button
class="button-default dropdown-item" class="menu-item dropdown-item"
@click="toggleNotificationFilter('mentions')" @click="toggleNotificationFilter('mentions')"
> >
<span <span
class="menu-checkbox" class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': filters.mentions }" :class="{ 'menu-checkbox-checked': filters.mentions }"
/>{{ $t('settings.notification_visibility_mentions') }} />{{ $t('settings.notification_visibility_mentions') }}
</button> </button>
<button <button
class="button-default dropdown-item" class="menu-item dropdown-item"
@click="toggleNotificationFilter('statuses')"
>
<span
class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': filters.statuses }"
/>{{ $t('settings.notification_visibility_statuses') }}
</button>
<button
class="menu-item dropdown-item"
@click="toggleNotificationFilter('emojiReactions')" @click="toggleNotificationFilter('emojiReactions')"
> >
<span <span
class="menu-checkbox" class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': filters.emojiReactions }" :class="{ 'menu-checkbox-checked': filters.emojiReactions }"
/>{{ $t('settings.notification_visibility_emoji_reactions') }} />{{ $t('settings.notification_visibility_emoji_reactions') }}
</button> </button>
<button <button
class="button-default dropdown-item" class="menu-item dropdown-item"
@click="toggleNotificationFilter('moves')" @click="toggleNotificationFilter('moves')"
> >
<span <span
class="menu-checkbox" class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': filters.moves }" :class="{ 'menu-checkbox-checked': filters.moves }"
/>{{ $t('settings.notification_visibility_moves') }} />{{ $t('settings.notification_visibility_moves') }}
</button> </button>
<button <button
class="button-default dropdown-item" class="menu-item dropdown-item"
@click="toggleNotificationFilter('polls')" @click="toggleNotificationFilter('polls')"
> >
<span <span
class="menu-checkbox" class="input menu-checkbox"
:class="{ 'menu-checkbox-checked': filters.polls }" :class="{ 'menu-checkbox-checked': filters.polls }"
/>{{ $t('settings.notification_visibility_polls') }} />{{ $t('settings.notification_visibility_polls') }}
</button> </button>

View file

@ -1,12 +1,15 @@
import { computed } from 'vue' import { computed } from 'vue'
import { mapGetters } from 'vuex' import { mapGetters } from 'vuex'
import Notification from '../notification/notification.vue' import Notification from '../notification/notification.vue'
import ExtraNotifications from '../extra_notifications/extra_notifications.vue'
import NotificationFilters from './notification_filters.vue' import NotificationFilters from './notification_filters.vue'
import notificationsFetcher from '../../services/notifications_fetcher/notifications_fetcher.service.js' import notificationsFetcher from '../../services/notifications_fetcher/notifications_fetcher.service.js'
import { import {
notificationsFromStore, notificationsFromStore,
filteredNotificationsFromStore, filteredNotificationsFromStore,
unseenNotificationsFromStore unseenNotificationsFromStore,
countExtraNotifications,
ACTIONABLE_NOTIFICATION_TYPES
} from '../../services/notification_utils/notification_utils.js' } from '../../services/notification_utils/notification_utils.js'
import FaviconService from '../../services/favicon_service/favicon_service.js' import FaviconService from '../../services/favicon_service/favicon_service.js'
import { library } from '@fortawesome/fontawesome-svg-core' import { library } from '@fortawesome/fontawesome-svg-core'
@ -23,14 +26,20 @@ const DEFAULT_SEEN_TO_DISPLAY_COUNT = 30
const Notifications = { const Notifications = {
components: { components: {
Notification, Notification,
NotificationFilters NotificationFilters,
ExtraNotifications
}, },
props: { props: {
// Disables panel styles, unread mark, potentially other notification-related actions // Disables panel styles, unread mark, potentially other notification-related actions
// meant for "Interactions" timeline // meant for "Interactions" timeline
minimalMode: Boolean, minimalMode: Boolean,
// Custom filter mode, an array of strings, possible values 'mention', 'repeat', 'like', 'follow', used to override global filter for use in "Interactions" timeline // Custom filter mode, an array of strings, possible values 'mention', 'status', 'repeat', 'like', 'follow', used to override global filter for use in "Interactions" timeline
filterMode: Array, filterMode: Array,
// Do not show extra notifications
noExtra: {
type: Boolean,
default: false
},
// Disable teleporting (i.e. for /users/user/notifications) // Disable teleporting (i.e. for /users/user/notifications)
disableTeleport: Boolean disableTeleport: Boolean
}, },
@ -57,22 +66,36 @@ const Notifications = {
return notificationsFromStore(this.$store) return notificationsFromStore(this.$store)
}, },
error () { error () {
return this.$store.state.statuses.notifications.error return this.$store.state.notifications.error
}, },
unseenNotifications () { unseenNotifications () {
return unseenNotificationsFromStore(this.$store) return unseenNotificationsFromStore(this.$store)
}, },
filteredNotifications () { filteredNotifications () {
return filteredNotificationsFromStore(this.$store, this.filterMode) if (this.unseenAtTop) {
return [
...filteredNotificationsFromStore(this.$store).filter(n => this.shouldShowUnseen(n)),
...filteredNotificationsFromStore(this.$store).filter(n => !this.shouldShowUnseen(n))
]
} else {
return filteredNotificationsFromStore(this.$store, this.filterMode)
}
},
unseenCountBadgeText () {
return `${this.unseenCount ? this.unseenCount : ''}${this.extraNotificationsCount ? '*' : ''}`
}, },
unseenCount () { unseenCount () {
return this.unseenNotifications.length return this.unseenNotifications.length
}, },
ignoreInactionableSeen () { return this.$store.getters.mergedConfig.ignoreInactionableSeen },
extraNotificationsCount () {
return countExtraNotifications(this.$store)
},
unseenCountTitle () { unseenCountTitle () {
return this.unseenCount + (this.unreadChatCount) + this.unreadAnnouncementCount return this.unseenNotifications.length + (this.unreadChatCount) + this.unreadAnnouncementCount
}, },
loading () { loading () {
return this.$store.state.statuses.notifications.loading return this.$store.state.notifications.loading
}, },
noHeading () { noHeading () {
const { layoutType } = this.$store.state.interface const { layoutType } = this.$store.state.interface
@ -94,6 +117,10 @@ const Notifications = {
return this.filteredNotifications.slice(0, this.unseenCount + this.seenToDisplayCount) return this.filteredNotifications.slice(0, this.unseenCount + this.seenToDisplayCount)
}, },
noSticky () { return this.$store.getters.mergedConfig.disableStickyHeaders }, noSticky () { return this.$store.getters.mergedConfig.disableStickyHeaders },
unseenAtTop () { return this.$store.getters.mergedConfig.unseenAtTop },
showExtraNotifications () {
return !this.noExtra
},
...mapGetters(['unreadChatCount', 'unreadAnnouncementCount']) ...mapGetters(['unreadChatCount', 'unreadAnnouncementCount'])
}, },
mounted () { mounted () {
@ -137,11 +164,28 @@ const Notifications = {
scrollToTop () { scrollToTop () {
const scrollable = this.scrollerRef const scrollable = this.scrollerRef
scrollable.scrollTo({ top: this.$refs.root.offsetTop }) scrollable.scrollTo({ top: this.$refs.root.offsetTop })
// this.$refs.root.scrollIntoView({ behavior: 'smooth', block: 'start' })
}, },
updateScrollPosition () { updateScrollPosition () {
this.showScrollTop = this.$refs.root.offsetTop < this.scrollerRef.scrollTop this.showScrollTop = this.$refs.root.offsetTop < this.scrollerRef.scrollTop
}, },
shouldShowUnseen (notification) {
if (notification.seen) return false
const actionable = ACTIONABLE_NOTIFICATION_TYPES.has(notification.type)
return this.ignoreInactionableSeen ? actionable : true
},
/* "Interacted" really refers to "actionable" notifications that require user input,
* everything else (likes/repeats/reacts) cannot be acted and therefore we just clear
* the "seen" status upon any clicks on them
*/
notificationClicked (notification) {
const { id } = notification
this.$store.dispatch('notificationClicked', { id })
},
notificationInteracted (notification) {
const { id } = notification
this.$store.dispatch('markSingleNotificationAsSeen', { id })
},
markAsSeen () { markAsSeen () {
this.$store.dispatch('markNotificationsAsSeen') this.$store.dispatch('markNotificationsAsSeen')
this.seenToDisplayCount = DEFAULT_SEEN_TO_DISPLAY_COUNT this.seenToDisplayCount = DEFAULT_SEEN_TO_DISPLAY_COUNT

View file

@ -1,5 +1,3 @@
@import "../../variables";
.Notifications { .Notifications {
&:not(.minimal) { &:not(.minimal) {
// a bit of a hack to allow scrolling below notifications // a bit of a hack to allow scrolling below notifications
@ -7,8 +5,7 @@
} }
.loadmore-error { .loadmore-error {
color: $fallback--text; color: var(--text);
color: var(--text, $fallback--text);
} }
.notification { .notification {
@ -25,7 +22,7 @@
&.unseen { &.unseen {
.notification-overlay { .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 { .notification {
box-sizing: border-box; box-sizing: border-box;
/* TODO cleanup this */
.Status {
flex: 1;
}
&:hover .animated.Avatar { &:hover .animated.Avatar {
canvas { canvas {
display: none; display: none;
@ -60,24 +62,17 @@
width: 32px; width: 32px;
height: 32px; height: 32px;
} }
.faint {
--link: var(--faintLink);
--text: var(--faint);
}
} }
.follow-request-accept { .follow-request-accept {
&:hover { &:hover {
color: $fallback--text; color: var(--text);
color: var(--text, $fallback--text);
} }
} }
.follow-request-reject { .follow-request-reject {
&:hover { &:hover {
color: $fallback--cRed; color: var(--cRed);
color: var(--cRed, $fallback--cRed);
} }
} }
@ -97,11 +92,6 @@
} }
} }
/* TODO cleanup this */
.Status {
flex: 1;
}
time { time {
white-space: nowrap; white-space: nowrap;
} }

View file

@ -17,9 +17,9 @@
<div class="title"> <div class="title">
{{ $t('notifications.notifications') }} {{ $t('notifications.notifications') }}
<span <span
v-if="unseenCount" v-if="unseenCountBadgeText"
class="badge badge-notification unseen-count" class="badge -notification unseen-count"
>{{ unseenCount }}</span> >{{ unseenCountBadgeText }}</span>
</div> </div>
<div <div
v-if="showScrollTop" v-if="showScrollTop"
@ -54,15 +54,26 @@
class="panel-body" class="panel-body"
role="feed" role="feed"
> >
<div
v-if="showExtraNotifications"
role="listitem"
class="notification"
>
<extra-notifications />
</div>
<div <div
v-for="notification in notificationsToDisplay" v-for="notification in notificationsToDisplay"
:key="notification.id" :key="notification.id"
role="listitem" role="listitem"
class="notification" class="notification"
:class="{unseen: !minimalMode && !notification.seen}" :class="{unseen: !minimalMode && shouldShowUnseen(notification)}"
@click="e => notificationClicked(notification)"
> >
<div class="notification-overlay" /> <div class="notification-overlay" />
<notification :notification="notification" /> <notification
:notification="notification"
@interacted="e => notificationInteracted(notification)"
/>
</div> </div>
</div> </div>
<div class="panel-footer"> <div class="panel-footer">
@ -74,7 +85,7 @@
</div> </div>
<button <button
v-else-if="!loading" v-else-if="!loading"
class="button-unstyled -link -fullwidth" class="button-unstyled -link text-center"
@click.prevent="fetchOlderNotifications()" @click.prevent="fetchOlderNotifications()"
> >
<div class="new-status-notification text-center"> <div class="new-status-notification text-center">

Some files were not shown because too many files have changed in this diff Show more