Page MenuHomePhorge

No OneTemporary

Authored By
Unknown
Size
85 KB
Referenced Files
None
Subscribers
None
diff --git a/.stylelintrc.json b/.stylelintrc.json
index c91107595b..afdfd5f5ba 100644
--- a/.stylelintrc.json
+++ b/.stylelintrc.json
@@ -1,31 +1,33 @@
{
"extends": [
"stylelint-config-standard",
"stylelint-config-recommended-scss",
"stylelint-config-html",
"stylelint-config-recommended-vue/scss"
],
"rules": {
"declaration-no-important": true,
"selector-class-pattern": null,
"import-notation": null,
"custom-property-pattern": null,
"keyframes-name-pattern": null,
"scss/operator-no-newline-after": null,
+ "declaration-property-value-no-unknown": true,
+ "scss/declaration-property-value-no-unknown": true,
"declaration-block-no-redundant-longhand-properties": [
true,
{
"ignoreShorthands": [
"grid-template",
"margin",
"padding",
"border",
"border-width",
"border-style",
"border-color",
"border-radius"
]
}
]
}
}
diff --git a/changelog.d/linter.skip b/changelog.d/linter.skip
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/App.scss b/src/App.scss
index d56306c9e1..59995664cc 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -1,1101 +1,1101 @@
// stylelint-disable rscss/class-format
/* stylelint-disable no-descending-specificity */
@use "panel";
@import '@fortawesome/fontawesome-svg-core/styles.css';
@import '@kazvmoe-infra/pinch-zoom-element/dist/pinch-zoom.css';
:root {
--status-margin: 0.75em;
--post-line-height: 1.4;
// Z-Index stuff
--ZI_media_modal: 9000;
--ZI_modals_popovers: 8500;
--ZI_modals: 8000;
--ZI_navbar_popovers: 7500;
--ZI_navbar: 7000;
--ZI_popovers: 6000;
// Fallback for when stuff is loading
--background: var(--bg);
}
html {
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
}
body {
font-family: sans-serif;
font-family: var(--font);
margin: 0;
padding: 0;
color: var(--text);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
overscroll-behavior-y: none;
overflow: clip scroll;
&.hidden {
display: none;
}
}
// ## Custom scrollbars
// Only show custom scrollbars on devices which
// have a cursor/pointer to operate them
@media (any-pointer: fine) {
* {
scrollbar-color: var(--fg) transparent;
&::-webkit-scrollbar {
background: transparent;
}
&::-webkit-scrollbar-corner {
background: transparent;
}
&::-webkit-resizer {
/* stylelint-disable-next-line declaration-no-important */
background-color: transparent !important;
background-image:
linear-gradient(
135deg,
transparent calc(50% - 1px),
var(--textFaint) 50%,
transparent calc(50% + 1px),
transparent calc(75% - 1px),
var(--textFaint) 75%,
transparent calc(75% + 1px),
);
}
&::-webkit-scrollbar-button,
&::-webkit-scrollbar-thumb {
box-shadow: var(--shadow);
border-radius: var(--roundness);
}
// horizontal/vertical/increment/decrement are webkit-specific stuff
// that indicates whether we're affecting vertical scrollbar, increase button etc
// stylelint-disable selector-pseudo-class-no-unknown
&::-webkit-scrollbar-button {
--___bgPadding: 2px;
color: var(--text);
background-repeat: no-repeat, no-repeat;
&:horizontal {
background-size: 50% calc(50% - var(--___bgPadding)), 50% calc(50% - var(--___bgPadding));
&:increment {
background-image:
linear-gradient(45deg, var(--text) 50%, transparent 51%),
linear-gradient(-45deg, transparent 50%, var(--text) 51%);
background-position: top var(--___bgPadding) left 50%, right 50% bottom var(--___bgPadding);
}
&:decrement {
background-image:
linear-gradient(45deg, transparent 50%, var(--text) calc(50% + 1px)),
linear-gradient(-45deg, var(--text) 50%, transparent 51%);
background-position: bottom var(--___bgPadding) right 50%, left 50% top var(--___bgPadding);
}
}
&:vertical {
background-size: calc(50% - var(--___bgPadding)) 50%, calc(50% - var(--___bgPadding)) 50%;
&:increment {
background-image:
linear-gradient(-45deg, transparent 50%, var(--text) 51%),
linear-gradient(45deg, transparent 50%, var(--text) 51%);
background-position: right var(--___bgPadding) top 50%, left var(--___bgPadding) top 50%;
}
&:decrement {
background-image:
linear-gradient(-45deg, var(--text) 50%, transparent 51%),
linear-gradient(45deg, var(--text) 50%, transparent 51%);
background-position: left var(--___bgPadding) top 50%, right var(--___bgPadding) top 50%;
}
}
}
// stylelint-enable selector-pseudo-class-no-unknown
}
// Body should have background to scrollbar otherwise it will use white (body color?)
html {
scrollbar-color: var(--fg) var(--wallpaper);
background: var(--wallpaper);
}
}
a {
text-decoration: none;
color: var(--link);
}
h4 {
margin: 0;
}
.iconLetter {
display: inline-block;
text-align: center;
font-weight: 1000;
}
i[class*="icon-"],
.svg-inline--fa,
.iconLetter {
color: var(--icon);
}
nav {
z-index: var(--ZI_navbar);
box-shadow: var(--shadow);
box-sizing: border-box;
height: var(--navbar-height);
font-size: calc(var(--navbar-height) / 3.5);
position: fixed;
}
#sidebar {
grid-area: sidebar;
}
#modal {
position: absolute;
z-index: var(--ZI_modals);
}
.column.-scrollable {
top: var(--navbar-height);
position: sticky;
}
#main-scroller {
grid-area: content;
position: relative;
}
#notifs-column {
grid-area: notifs;
}
.app-bg-wrapper {
position: fixed;
height: 100%;
top: var(--navbar-height);
z-index: -1000;
left: 0;
right: -20px;
background-size: cover;
background-repeat: no-repeat;
background-color: var(--wallpaper);
background-image: var(--body-background-image);
background-position: 50%;
}
.underlay {
grid-column: 1 / span 3;
grid-row: 1 / 1;
pointer-events: none;
background-color: var(--underlay);
z-index: -1000;
}
.app-layout {
--miniColumn: 25rem;
--maxiColumn: 45rem;
--columnGap: 1rem;
--effectiveSidebarColumnWidth: minmax(var(--miniColumn), var(--sidebarColumnWidth, var(--miniColumn)));
--effectiveNotifsColumnWidth: minmax(var(--miniColumn), var(--notifsColumnWidth, var(--miniColumn)));
--effectiveContentColumnWidth: minmax(var(--miniColumn), var(--contentColumnWidth, var(--maxiColumn)));
position: relative;
display: grid;
grid-template-columns:
var(--effectiveSidebarColumnWidth)
var(--effectiveContentColumnWidth);
grid-template-areas: "sidebar content";
grid-template-rows: 1fr;
box-sizing: border-box;
margin: 0 auto;
place-content: flex-start center;
flex-wrap: wrap;
min-height: 100vh;
overflow-x: clip;
.column {
--___columnMargin: var(--columnGap);
display: grid;
grid-template-columns: 100%;
box-sizing: border-box;
grid-row: 1 / 1;
margin: 0 calc(var(--___columnMargin) / 2);
padding: calc(var(--___columnMargin)) 0;
row-gap: var(--___columnMargin);
align-content: start;
&:not(.-scrollable) {
margin-top: var(--navbar-height);
}
&:hover {
z-index: 2;
}
&.-full-height {
margin-bottom: 0;
padding-top: 0;
padding-bottom: 0;
}
&.-scrollable {
--___paddingIncrease: calc(var(--columnGap) / 2);
position: sticky;
top: var(--navbar-height);
max-height: calc(100vh - var(--navbar-height));
overflow: hidden auto;
margin-left: calc(var(--___paddingIncrease) * -1);
padding-left: calc(var(--___paddingIncrease) + var(--___columnMargin) / 2);
// On browsers that don't support hiding scrollbars we enforce "show scrolbars" mode
// might implement old style of hiding scrollbars later if there's demand
@supports (scrollbar-width: none) or (-webkit-text-fill-color: initial) {
&:not(.-show-scrollbar) {
scrollbar-width: none;
margin-right: calc(var(--___paddingIncrease) * -1);
padding-right: calc(var(--___paddingIncrease) + var(--___columnMargin) / 2);
&::-webkit-scrollbar {
display: block;
width: 0;
}
}
}
.panel-heading.-sticky {
top: calc(var(--columnGap) / -1);
}
}
}
&.-has-new-post-button {
.column {
padding-bottom: 10rem;
}
}
&.-no-sticky-headers {
.column {
.panel-heading.-sticky {
position: relative;
top: 0;
}
}
}
.column-inner {
display: grid;
grid-template-columns: 100%;
box-sizing: border-box;
row-gap: 1em;
align-content: start;
}
&.-reverse:not(.-wide, .-mobile) {
grid-template-columns:
var(--effectiveContentColumnWidth)
var(--effectiveSidebarColumnWidth);
grid-template-areas: "content sidebar";
}
&.-wide {
grid-template-columns:
var(--effectiveSidebarColumnWidth)
var(--effectiveContentColumnWidth)
var(--effectiveNotifsColumnWidth);
grid-template-areas: "sidebar content notifs";
&.-reverse {
grid-template-columns:
var(--effectiveNotifsColumnWidth)
var(--effectiveContentColumnWidth)
var(--effectiveSidebarColumnWidth);
grid-template-areas: "notifs content sidebar";
}
}
&.-mobile {
grid-template-columns: 100vw;
grid-template-areas: "content";
padding: 0;
.column {
padding-top: 0;
margin: var(--navbar-height) 0 0 0;
}
.panel-heading,
.panel-heading::after,
.panel-heading::before,
.panel,
.panel::after {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
#sidebar,
#notifs-column {
display: none;
}
}
&.-normal {
#notifs-column {
display: none;
}
}
}
.text-center {
text-align: center;
}
.button-default {
user-select: none;
color: var(--text);
border: none;
cursor: pointer;
background-color: var(--background);
box-shadow: var(--shadow);
font-size: 1em;
font-family: sans-serif;
font-family: var(--font);
&::-moz-focus-inner {
border: none;
}
&:disabled {
cursor: not-allowed;
}
&:active {
transform: translate(1px, 1px);
}
}
.menu-item {
line-height: var(--__line-height);
font-family: inherit;
font-weight: 400;
font-size: 100%;
cursor: pointer;
a,
button:not(.button-default) {
color: var(--text);
font-size: 100%;
}
&.disabled {
cursor: not-allowed;
}
}
.list-item {
border-color: var(--border);
border-style: solid;
border-width: 0;
border-top-width: 1px;
&.-active,
&:hover {
border-top-width: 1px;
border-bottom-width: 1px;
}
&.-active + &,
&:hover + & {
border-top-width: 0;
}
&:hover + .menu-item-collapsible:not(.-expanded) + &,
&.-active + .menu-item-collapsible:not(.-expanded) + & {
border-top-width: 0;
}
&[aria-expanded="true"] {
border-bottom-width: 1px;
}
&: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;
}
}
.menu-item,
.list-item {
display: block;
box-sizing: border-box;
border: none;
outline: none;
text-align: initial;
color: inherit;
clear: both;
position: relative;
white-space: nowrap;
width: 100%;
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;
}
a,
button:not(.button-default) {
text-align: initial;
padding: 0;
background: none;
border: none;
outline: none;
display: inline;
font-family: inherit;
line-height: unset;
}
}
.button-unstyled {
border: none;
outline: none;
display: inline;
text-align: initial;
font-size: 100%;
font-family: inherit;
box-shadow: var(--shadow);
background-color: transparent;
padding: 0;
line-height: unset;
cursor: pointer;
box-sizing: content-box;
color: inherit;
&.-link {
/* stylelint-disable-next-line declaration-no-important */
color: var(--link) !important;
}
}
input,
textarea {
border: none;
display: inline-block;
outline: none;
}
.input {
&.unstyled {
border-radius: 0;
/* stylelint-disable-next-line declaration-no-important */
background: none !important;
box-shadow: none;
height: unset;
}
--_padding: 0.5em;
border: none;
background-color: var(--background);
color: var(--text);
box-shadow: var(--shadow);
font-family: var(--font);
font-size: 1em;
margin: 0;
box-sizing: border-box;
display: inline-block;
position: relative;
line-height: 2;
hyphens: none;
padding: 0 var(--_padding);
&:disabled,
&[disabled="disabled"],
&.disabled {
cursor: not-allowed;
}
&[type="range"] {
background: none;
border: none;
margin: 0;
box-shadow: none;
flex: 1;
}
&[type="radio"] {
display: none;
&:checked + label::before {
box-shadow: var(--shadow);
background-color: var(--background);
color: var(--text);
}
&:disabled {
&,
& + label,
& + label::before {
opacity: 0.5;
}
}
+ label::before {
flex-shrink: 0;
display: inline-block;
content: "•";
transition: box-shadow 200ms;
width: 1.1em;
height: 1.1em;
border-radius: 100%; // Radio buttons should always be circle
background-color: var(--background);
box-shadow: var(--shadow);
margin-right: 0.5em;
vertical-align: top;
text-align: center;
line-height: 1.1;
font-size: 1.1em;
box-sizing: border-box;
color: transparent;
overflow: hidden;
}
}
&[type="checkbox"] {
&:checked + label::before {
color: var(--text);
background-color: var(--background);
box-shadow: var(--shadow);
}
&:disabled {
&,
& + label,
& + label::before {
opacity: 0.5;
}
}
+ label::before {
flex-shrink: 0;
display: inline-block;
content: "✓";
transition: color 200ms;
width: 1.1em;
height: 1.1em;
border-radius: var(--roundness);
box-shadow: var(--shadow);
margin-right: 0.5em;
vertical-align: top;
text-align: center;
line-height: 1.1;
font-size: 1.1em;
box-sizing: border-box;
color: transparent;
overflow: hidden;
}
}
&.resize-height {
resize: vertical;
}
}
.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
textarea.input {
padding: var(--_padding);
line-height: var(--post-line-height);
}
option {
color: var(--text);
background-color: var(--background);
}
.hide-number-spinner {
appearance: textfield;
&[type="number"]::-webkit-inner-spin-button,
&[type="number"]::-webkit-outer-spin-button {
opacity: 0;
display: none;
}
}
.cards-list {
list-style: none;
display: grid;
grid-auto-flow: row dense;
grid-template-columns: 1fr 1fr;
li {
border: 1px solid var(--border);
border-radius: var(--roundness);
padding: 0.5em;
margin: 0.25em;
}
}
.btn-group {
position: relative;
display: inline-flex;
vertical-align: middle;
> *,
> * .button-default {
--_roundness-left: 0;
--_roundness-right: 0;
position: relative;
flex: 1 1 auto;
}
> *:first-child,
> *:first-child .button-default {
--_roundness-left: var(--roundness);
}
> *:last-child,
> *:last-child .button-default {
--_roundness-right: var(--roundness);
}
}
.fa {
color: grey;
}
.mobile-shown {
display: none;
}
.badge {
box-sizing: border-box;
display: inline-block;
border-radius: 99px;
max-width: 10em;
min-width: 1.7em;
height: 1.3em;
padding: 0.15em;
vertical-align: middle;
font-weight: normal;
font-style: normal;
font-size: 0.9em;
line-height: 1;
text-align: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
&.-dot,
&.-counter {
margin: 0;
position: absolute;
}
&.-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 {
margin: 0 0.35em;
padding: 0 0.25em;
border-radius: var(--roundness);
border: 1px solid var(--border);
}
.faint {
--text: var(--textFaint);
--link: var(--linkFaint);
color: var(--text);
}
.visibility-notice {
padding: 0.5em;
border: 1px solid var(--textFaint);
border-radius: var(--roundness);
}
.notice-dismissible {
padding-right: 4rem;
position: relative;
.dismiss {
position: absolute;
top: 0;
right: 0;
padding: 0.5em;
color: inherit;
}
}
.fa-scale-110 {
&.svg-inline--fa,
&.iconLetter {
font-size: 1.1em;
}
&.svg-inline--fa {
vertical-align: -0.15em;
}
}
.fa-old-padding {
&.iconLetter,
&.svg-inline--fa,
&-layer {
padding: 0 0.3em;
}
}
.veryfaint {
opacity: 0.25;
}
.timeago {
--link: var(--text);
--linkFaint: var(--textFaint);
}
.login-hint {
text-align: center;
@media all and (width >= 801px) {
display: none;
}
a {
display: inline-block;
padding: 1em 0;
width: 100%;
}
}
.btn.button-default {
min-height: 2em;
}
.new-status-notification {
position: relative;
font-size: 1.1em;
z-index: 1;
flex: 1;
}
@media all and (width <= 800px) {
.mobile-hidden {
display: none;
}
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(359deg);
}
}
@keyframes shakeError {
0% {
transform: translateX(0);
}
15% {
transform: translateX(0.375rem);
}
30% {
transform: translateX(-0.375rem);
}
45% {
transform: translateX(0.375rem);
}
60% {
transform: translateX(-0.375rem);
}
75% {
transform: translateX(0.375rem);
}
90% {
transform: translateX(-0.375rem);
}
100% {
transform: translateX(0);
}
}
// Vue transitions
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s;
}
.fade-enter-from,
.fade-leave-active {
opacity: 0;
}
/* stylelint-enable no-descending-specificity */
.visible-for-screenreader-only {
display: block;
width: 1px;
height: 1px;
margin: -1px;
overflow: hidden;
visibility: visible;
clip: rect(0 0 0 0);
padding: 0;
position: absolute;
}
*::selection {
color: var(--selectionText);
background-color: var(--selectionBackground);
}
#splash {
pointer-events: none;
transition: opacity 0.5s;
opacity: 1;
&.hidden {
opacity: 0;
}
#status {
&.css-ok {
&::before {
display: inline-block;
content: "CSS OK";
}
}
.initial-text {
display: none;
}
}
#throbber {
animation-duration: 3s;
animation-name: bounce;
animation-iteration-count: infinite;
animation-direction: normal;
transform-origin: bottom center;
&.dead {
animation-name: dead;
animation-duration: 0.5s;
animation-iteration-count: 1;
transform: rotateX(90deg) rotateY(0) rotateZ(-45deg);
}
@keyframes dead {
0% {
transform: rotateX(0) rotateY(0) rotateZ(0);
}
5% {
transform: rotateX(0) rotateY(0) rotateZ(1deg);
}
10% {
transform: rotateX(0) rotateY(0) rotateZ(-2deg);
}
15% {
transform: rotateX(0) rotateY(0) rotateZ(3deg);
}
20% {
transform: rotateX(0) rotateY(0) rotateZ(0);
}
25% {
transform: rotateX(0) rotateY(0) rotateZ(0);
}
30% {
transform: rotateX(10deg) rotateY(0) rotateZ(0);
}
35% {
transform: rotateX(-10deg) rotateY(0) rotateZ(0);
}
40% {
transform: rotateX(10deg) rotateY(0) rotateZ(0);
}
45% {
transform: rotateX(-10deg) rotateY(0) rotateZ(0);
}
50% {
transform: rotateX(10deg) rotateY(0) rotateZ(0);
}
100% {
transform: rotateX(90deg) rotateY(0) rotateZ(-45deg);
transition-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); /* easeInQuint */
}
}
@keyframes bounce {
0% {
scale: 1 1;
translate: 0 0;
animation-timing-function: ease-out;
}
10% {
scale: 1.2 0.8;
translate: 0 0;
transform: rotateZ(var(--defaultZ));
animation-timing-function: ease-out;
}
30% {
scale: 0.9 1.1;
translate: 0 -40%;
transform: rotateZ(var(--defaultZ));
animation-timing-function: ease-in;
}
40% {
scale: 1.1 0.9;
translate: 0 -50%;
transform: rotateZ(var(--defaultZ));
animation-timing-function: ease-in;
}
45% {
scale: 0.9 1.1;
translate: 0 -45%;
transform: rotateZ(var(--defaultZ));
animation-timing-function: ease-in;
}
50% {
scale: 1.05 0.95;
translate: 0 -40%;
animation-timing-function: ease-in;
}
55% {
scale: 0.985 1.025;
translate: 0 -35%;
transform: rotateZ(var(--defaultZ));
animation-timing-function: ease-in;
}
60% {
scale: 1.0125 0.9985;
translate: 0 -30%;
transform: rotateZ(var(--defaultZ));
animation-timing-function: ease-in;
}
80% {
scale: 1.0063 0.9938;
translate: 0 -10%;
transform: rotateZ(var(--defaultZ));
- animation-timing-function: ease-in-ou;
+ animation-timing-function: ease-in-out;
}
90% {
scale: 1.2 0.8;
translate: 0 0;
transform: rotateZ(var(--defaultZ));
animation-timing-function: ease-out;
}
100% {
scale: 1 1;
translate: 0 0;
transform: rotateZ(var(--defaultZ));
animation-timing-function: ease-out;
}
}
}
}
@property --shadow {
syntax: "*";
inherits: false;
}
diff --git a/src/components/block_card/block_card.vue b/src/components/block_card/block_card.vue
index 9f64a8e8cf..90b9a2b164 100644
--- a/src/components/block_card/block_card.vue
+++ b/src/components/block_card/block_card.vue
@@ -1,44 +1,47 @@
<template>
<basic-user-card :user="user">
<div class="block-card-content-container">
- <span v-if="blocked && blockExpiryAvailable" class="alert neutral">
+ <span
+ v-if="blocked && blockExpiryAvailable"
+ class="alert neutral"
+ >
{{ blockExpiry }}
</span>
{{ ' ' }}
<button
v-if="blocked"
class="btn button-default"
@click="unblockUser"
>
{{ $t('user_card.unblock') }}
</button>
<button
v-else
class="btn button-default"
@click="blockUser"
>
{{ $t('user_card.block') }}
</button>
</div>
<teleport to="#modal">
<UserTimedFilterModal
+ ref="timedBlockDialog"
:user="user"
:is-mute="false"
- ref="timedBlockDialog"
/>
</teleport>
</basic-user-card>
</template>
<script src="./block_card.js"></script>
<style lang="scss">
.block-card-content-container {
margin-top: 0.5em;
text-align: right;
button {
width: 10em;
}
}
</style>
diff --git a/src/components/emoji_picker/emoji_picker.scss b/src/components/emoji_picker/emoji_picker.scss
index 05600c790e..e318cdb404 100644
--- a/src/components/emoji_picker/emoji_picker.scss
+++ b/src/components/emoji_picker/emoji_picker.scss
@@ -1,230 +1,232 @@
.emoji-picker {
--__emoji-picker-header: 2.2em;
width: 25em;
max-width: calc(100vw - 20px); // popover gives 10px margin from window edge
display: flex;
flex-direction: column;
&-header-image {
display: inline-flex;
justify-content: center;
align-items: center;
width: var(--__emoji-picker-header);
max-width: var(--__emoji-picker-header);
height: var(--__emoji-picker-header);
max-height: var(--__emoji-picker-header);
.still-image {
width: var(--__emoji-picker-header);
max-width: var(--__emoji-picker-header);
height: var(--__emoji-picker-header);
max-height: var(--__emoji-picker-header);
object-fit: contain;
--_still_image-label-scale: 0.5;
}
}
.keep-open,
.too-many-emoji,
.hide-custom-emoji {
padding: 0.5em;
line-height: normal;
}
.hide-custom-emoji {
padding-top: 0;
}
.too-many-emoji {
display: flex;
flex-direction: column;
}
.keep-open-label {
padding: 0 0.5em;
display: flex;
}
.heading {
display: flex;
flex-direction: column;
padding: 0.7em 0.5em 0;
}
.content {
display: flex;
flex-direction: column;
flex: 1 1 auto;
min-height: 0;
}
.emoji-tabs {
flex-grow: 1;
display: flex;
flex-flow: row nowrap;
overflow: auto hidden;
}
.additional-tabs {
display: flex;
border-left: 1px solid;
border-left-color: var(--border);
padding-left: 0.5em;
flex: 0 0 auto;
}
.additional-tabs,
.emoji-tabs {
flex-basis: auto;
display: flex;
align-content: center;
scrollbar-width: thin;
&-item {
padding: 0 0.5em;
cursor: pointer;
width: var(--__emoji-picker-header);
max-width: var(--__emoji-picker-header);
height: var(--__emoji-picker-header);
max-height: var(--__emoji-picker-header);
display: flex;
align-items: center;
.svg-inline--fa {
font-size: 1.85em;
}
&.disabled {
opacity: 0.5;
pointer-events: none;
}
&.toggled {
border-bottom: 0.2em solid;
}
}
}
.sticker-picker {
flex: 1 1 auto;
}
.stickers,
.emoji {
&-content {
display: flex;
flex-direction: column;
flex: 1 1 auto;
min-height: 0;
&.hidden {
opacity: 0;
pointer-events: none;
position: absolute;
}
}
}
.emoji {
&-search {
padding-bottom: 0.5em;
flex: 0 0 auto;
input {
width: 100%;
}
}
&-groups {
height: 100%;
min-height: 200px;
flex: 1 1 1px;
position: relative;
overflow: auto;
scrollbar-gutter: stable both-edges;
user-select: none;
mask:
linear-gradient(to top, white 0, transparent 100%) bottom no-repeat,
linear-gradient(to bottom, white 0, transparent 100%) top no-repeat,
linear-gradient(to top, white, white);
transition: mask-size 150ms;
mask-size: 100% 20px, 100% 20px, auto;
// Autoprefixed seem to ignore this one, and also syntax is different
/* stylelint-disable mask-composite */
/* stylelint-disable declaration-property-value-no-unknown */
+ /* stylelint-disable scss/declaration-property-value-no-unknown */
/* TODO check if this is still needed */
mask-composite: xor;
/* stylelint-enable declaration-property-value-no-unknown */
+ /* stylelint-enable scss/declaration-property-value-no-unknown */
/* stylelint-enable mask-composite */
mask-composite: exclude;
&.scrolled {
&-top {
mask-size: 100% 20px, 100% 0, auto;
}
&-bottom {
mask-size: 100% 0, 100% 20px, auto;
}
}
}
&-item {
width: var(--emoji-size);
height: var(--emoji-size);
box-sizing: border-box;
display: flex;
line-height: var(--emoji-size);
align-items: center;
justify-content: center;
margin: 0.2em;
cursor: pointer;
.emoji-picker-emoji.-custom {
object-fit: contain;
width: var(--emoji-size);
max-width: var(--emoji-size);
height: var(--emoji-size);
max-height: var(--emoji-size);
--_still_image-label-scale: 0.5;
}
.emoji-picker-emoji.-unicode {
font-size: calc(var(--emoji-size) * 0.8);
overflow: hidden;
}
}
&-group {
display: grid;
grid-template-columns: repeat(var(--__amount), 1fr);
place-items: center center;
justify-content: center;
grid-template-rows: repeat(1, auto);
&.first-row {
grid-template-rows: repeat(2, auto);
.emoji-item {
grid-row: 2;
}
}
&-title {
font-size: 0.85em;
grid-column: span var(--__amount);
width: 100%;
margin: 0;
padding-left: 0.3em;
&.disabled {
display: none;
}
}
}
}
}
diff --git a/src/components/gallery/gallery.vue b/src/components/gallery/gallery.vue
index 6043e64e99..7209e91447 100644
--- a/src/components/gallery/gallery.vue
+++ b/src/components/gallery/gallery.vue
@@ -1,191 +1,193 @@
<template>
<div
ref="galleryContainer"
class="Gallery"
:class="{ '-long': tooManyAttachments && hidingLong }"
>
<div class="gallery-rows">
<div
v-for="(row, rowIndex) in rows"
:key="rowIndex"
class="gallery-row"
:style="rowStyle(row)"
:class="{ '-audio': row.audio, '-minimal': row.minimal, '-grid': grid }"
>
<div
class="gallery-row-inner"
:class="{ '-grid': grid }"
>
<Attachment
v-for="(attachment, attachmentIndex) in row.items"
:key="attachment.id"
class="gallery-item"
:compact="compact"
:nsfw="nsfw"
:attachment="attachment"
:size="size"
:editable="editable"
:remove="removeAttachment"
:shift-up="!(attachmentIndex === 0 && rowIndex === 0) && shiftUpAttachment"
:shift-dn="!(attachmentIndex === row.items.length - 1 && rowIndex === rows.length - 1) && shiftDnAttachment"
:edit="editAttachment"
:description="descriptions && descriptions[attachment.id]"
:hide-description="size === 'small' || tooManyAttachments && hidingLong"
:style="itemStyle(attachment.id, row.items)"
@set-media="onMedia"
@natural-size-load="onNaturalSizeLoad"
/>
</div>
</div>
</div>
<div
v-if="tooManyAttachments"
class="many-attachments"
>
<div class="many-attachments-text">
{{ $t("status.many_attachments", { number: attachments.length }) }}
</div>
<div class="many-attachments-buttons">
<span
v-if="!hidingLong"
class="many-attachments-button"
>
<button
class="button-unstyled -link"
@click="toggleHidingLong(true)"
>
{{ $t("status.collapse_attachments") }}
</button>
</span>
<span
v-if="hidingLong"
class="many-attachments-button"
>
<button
class="button-unstyled -link"
@click="toggleHidingLong(false)"
>
{{ $t("status.show_all_attachments") }}
</button>
</span>
<span
v-if="hidingLong"
class="many-attachments-button"
>
<button
class="button-unstyled -link"
@click="openGallery"
>
{{ $t("status.open_gallery") }}
</button>
</span>
</div>
</div>
</div>
</template>
<script src='./gallery.js'></script>
<style lang="scss">
.Gallery {
.gallery-rows {
display: flex;
flex-direction: column;
}
.gallery-row {
position: relative;
height: 0;
width: 100%;
flex-grow: 1;
.gallery-row-inner {
position: absolute;
inset: 0;
display: flex;
flex-flow: row wrap;
align-content: stretch;
.gallery-item {
margin: 0 0.5em 0 0;
flex-grow: 1;
height: 100%;
box-sizing: border-box;
// to make failed images a bit more noticeable on chromium
min-width: 2em;
&:last-child {
margin: 0;
}
}
&.-grid {
width: 100%;
height: auto;
position: relative;
display: grid;
grid-gap: 0.5em;
grid-template-columns: repeat(auto-fill, minmax(15em, 1fr));
.gallery-item {
margin: 0;
height: 200px;
}
}
}
&.-grid,
&.-minimal {
height: auto;
.gallery-row-inner {
position: relative;
}
}
&:not(:first-child) {
margin-top: 0.5em;
}
}
&.-long {
.gallery-rows {
max-height: 25em;
overflow: hidden;
mask:
linear-gradient(to top, white, transparent) bottom/100% 70px no-repeat,
linear-gradient(to top, white, white);
/* Autoprefixed seem to ignore this one, and also syntax is different */
/* stylelint-disable mask-composite */
/* stylelint-disable declaration-property-value-no-unknown */
+ /* stylelint-disable scss/declaration-property-value-no-unknown */
/* TODO check if this is still needed */
mask-composite: xor;
+ /* stylelint-enable scss/declaration-property-value-no-unknown */
/* stylelint-enable declaration-property-value-no-unknown */
/* stylelint-enable mask-composite */
mask-composite: exclude;
}
}
.many-attachments-text {
text-align: center;
line-height: 2;
}
.many-attachments-buttons {
display: flex;
}
.many-attachments-button {
display: flex;
flex: 1;
justify-content: center;
line-height: 2;
button {
padding: 0 2em;
}
}
}
</style>
diff --git a/src/components/mute_card/mute_card.vue b/src/components/mute_card/mute_card.vue
index 60638d3ba6..83520dc22f 100644
--- a/src/components/mute_card/mute_card.vue
+++ b/src/components/mute_card/mute_card.vue
@@ -1,44 +1,47 @@
<template>
<basic-user-card :user="user">
<div class="mute-card-content-container">
- <span v-if="muted && muteExpiryAvailable" class="alert neutral">
+ <span
+ v-if="muted && muteExpiryAvailable"
+ class="alert neutral"
+ >
{{ muteExpiry }}
</span>
{{ ' ' }}
<button
v-if="muted"
class="btn button-default"
@click="unmuteUser"
>
{{ $t('user_card.unmute') }}
</button>
<button
v-else
class="btn button-default"
@click="muteUser"
>
{{ $t('user_card.mute') }}
</button>
</div>
<teleport to="#modal">
<UserTimedFilterModal
+ ref="timedMuteDialog"
:user="user"
:is-mute="true"
- ref="timedMuteDialog"
/>
</teleport>
</basic-user-card>
</template>
<script src="./mute_card.js"></script>
<style lang="scss">
.mute-card-content-container {
margin-top: 0.5em;
text-align: right;
button {
width: 10em;
}
}
</style>
diff --git a/src/components/settings_modal/tabs/filtering_tab.vue b/src/components/settings_modal/tabs/filtering_tab.vue
index 2b05d1906c..a699653f05 100644
--- a/src/components/settings_modal/tabs/filtering_tab.vue
+++ b/src/components/settings_modal/tabs/filtering_tab.vue
@@ -1,405 +1,405 @@
<template>
<div
:label="$t('settings.filtering')"
class="filtering-tab"
>
<div class="setting-item">
<h2>{{ $t('settings.filter.clutter') }}</h2>
<ul class="setting-list">
<li>
<ChoiceSetting
v-if="user"
id="replyVisibility"
path="replyVisibility"
:options="replyVisibilityOptions"
>
{{ $t('settings.replies_in_timeline') }}
</ChoiceSetting>
</li>
<li>
<BooleanSetting
expert="1"
path="hidePostStats"
>
{{ $t('settings.hide_post_stats') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting
expert="1"
path="hideUserStats"
>
{{ $t('settings.hide_user_stats') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="hideBotIndication">
{{ $t('settings.hide_actor_type_indication') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="hideScrobbles">
{{ $t('settings.hide_scrobbles') }}
</BooleanSetting>
<ul class="setting-list suboptions">
<li>
<UnitSetting
key="hideScrobblesAfter"
path="hideScrobblesAfter"
:units="['m', 'h', 'd']"
unit-set="time"
expert="1"
>
{{ $t('settings.hide_scrobbles_after') }}
</UnitSetting>
</li>
</ul>
</li>
<h3>{{ $t('settings.attachments') }}</h3>
<li>
<IntegerSetting
path="maxThumbnails"
expert="1"
:min="0"
>
{{ $t('settings.max_thumbnails') }}
</IntegerSetting>
</li>
<li>
<BooleanSetting path="hideAttachments">
{{ $t('settings.hide_attachments_in_tl') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="hideAttachmentsInConv">
{{ $t('settings.hide_attachments_in_convo') }}
</BooleanSetting>
</li>
</ul>
</div>
<div class="setting-item">
<h2>{{ $t('settings.filter.mute_filter') }}</h2>
<ul class="setting-list">
<li>
{{ $t('user_card.default_mute_expiration') }}
<Select
id="onMuteDefaultActionLv1"
v-model="onMuteDefaultActionLv1"
>
<option
v-for="option in muteBlockLv1Options"
:key="option.key"
:value="option.value"
>
{{ option.label }}
</option>
</Select>
<ul
- class="setting-list suboptions"
v-if="onMuteDefaultActionLv1 === 'temporarily'"
+ class="setting-list suboptions"
>
<li>
<UnitSetting
path="onMuteDefaultAction"
unit-set="time"
:units="['s', 'm', 'h', 'd']"
:min="0"
>
{{ $t('user_card.default_expiration_time') }}
</UnitSetting>
</li>
</ul>
</li>
<li v-if="blockExpirationSupported">
{{ $t('user_card.default_block_expiration') }}
<Select
id="onBlockDefaultActionLv1"
v-model="onBlockDefaultActionLv1"
>
<option
v-for="option in muteBlockLv1Options"
:key="option.key"
:value="option.value"
>
{{ option.label }}
</option>
</Select>
<ul
- class="setting-list suboptions"
v-if="onBlockDefaultActionLv1 === 'temporarily'"
+ class="setting-list suboptions"
>
<li>
<UnitSetting
path="onBlockDefaultAction"
unit-set="time"
:units="['s', 'm', 'h', 'd']"
:min="0"
>
{{ $t('user_card.default_expiration_time') }}
</UnitSetting>
</li>
</ul>
</li>
<li>
<BooleanSetting path="hideFilteredStatuses">
{{ $t('settings.hide_muted_statuses') }}
</BooleanSetting>
<ul class="setting-list suboptions">
<li>
<BooleanSetting
parent-path="hideFilteredStatuses"
:parent-invert="true"
path="hideWordFilteredPosts"
expert="1"
>
{{ $t('settings.hide_filtered_statuses') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting
v-if="user"
parent-path="hideFilteredStatuses"
:parent-invert="true"
path="hideMutedThreads"
>
{{ $t('settings.hide_muted_threads') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting
v-if="user"
parent-path="hideFilteredStatuses"
:parent-invert="true"
path="hideMutedPosts"
>
{{ $t('settings.hide_muted_posts') }}
</BooleanSetting>
</li>
</ul>
</li>
<li>
<BooleanSetting path="muteBotStatuses">
{{ $t('settings.mute_bot_posts') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="muteSensitiveStatuses">
{{ $t('settings.mute_sensitive_posts') }}
</BooleanSetting>
</li>
<li>
<h3>{{ $t('settings.filter.custom_filters') }}</h3>
<p class="muteFiltersActions">
<span class="total">
{{ $t('settings.filter.total_count', { count: muteFiltersDraft.length }) }}
</span>
<button
class="add-button button-default"
type="button"
@click="createFilter()"
>
{{ $t('settings.filter.new') }}
</button>
<button
class="add-button button-default"
type="button"
@click="importFilter()"
>
{{ $t('settings.filter.import') }}
</button>
</p>
<div class="muteFilterContainer">
<div
v-for="filter in muteFiltersDraft"
:key="filter[0]"
class="mute-filter"
:style="{ order: filter[1].order }"
>
<div class="filter-name">
<label
:for="'filterName' + filter[0]"
>
{{ $t('settings.filter.name') }}
</label>
{{ ' ' }}
<input
:id="'filterName' + filter[0]"
class="input"
:value="filter[1].name"
@input="updateFilter(filter[0], 'name', $event.target.value)"
>
<span
v-if="filter[1].expires !== null && Date.now() > filter[1].expires"
class="alert neutral"
>
{{ $t('settings.filter.expired') }}
</span>
</div>
<div class="filter-enabled">
<Checkbox
:id="'filterHide' + filter[0]"
:model-value="filter[1].hide"
:name="'filterHide' + filter[0]"
@update:model-value="updateFilter(filter[0], 'hide', $event)"
>
{{ $t('settings.filter.hide') }}
</Checkbox>
{{ ' ' }}
<Checkbox
:id="'filterEnabled' + filter[0]"
:model-value="filter[1].enabled"
:name="'filterEnabled' + filter[0]"
@update:model-value="updateFilter(filter[0], 'enabled', $event)"
>
{{ $t('settings.enabled') }}
</Checkbox>
</div>
<div class="filter-type filter-field">
<label :for="'filterType' + filter[0]">
<HelpIndicator>
<p>
{{ $t('settings.filter.help.word') }}
</p>
<p>
{{ $t('settings.filter.help.user') }}
</p>
<i18n-t
keypath="settings.filter.help.regexp"
tag="p"
>
<template #link>
<a
:href="$t('settings.filter.help.regexp_url')"
target="_blank"
>
{{ $t('settings.filter.help.regexp_link') }}
</a>
</template>
</i18n-t>
</HelpIndicator>
{{ $t('settings.filter.type') }}
</label>
<Select
:id="'filterType' + filter[0]"
class="filter-field-value"
:model-value="filter[1].type"
@update:model-value="updateFilter(filter[0], 'type', $event)"
>
<option value="word">
{{ $t('settings.filter.plain') }}
</option>
<option value="regexp">
{{ $t('settings.filter.regexp') }}
</option>
<option value="user">
{{ $t('settings.filter.user') }}
</option>
<option value="user_regexp">
{{ $t('settings.filter.user_regexp') }}
</option>
</Select>
</div>
<div class="filter-value filter-field">
<label
:for="'filterValue' + filter[0]"
>
{{ $t('settings.filter.value') }}
</label>
{{ ' ' }}
<input
:id="'filterValue' + filter[0]"
class="input filter-field-value"
:value="filter[1].value"
@input="updateFilter(filter[0], 'value', $event.target.value)"
>
</div>
<div class="filter-expires filter-field">
<label
:for="'filterExpires' + filter[0]"
>
{{ $t('settings.filter.expires') }}
</label>
{{ ' ' }}
<div class="filter-field-value">
<input
:id="'filterExpires' + filter[0]"
class="input"
:class="{ disabled: filter[1].expires === null }"
type="datetime-local"
:disabled="filter[1].expires === null"
:value="filter[1].expires ? getDatetimeLocal(filter[1].expires) : null"
@input="updateFilter(filter[0], 'expires', $event.target.value)"
>
{{ ' ' }}
<Checkbox
:id="'filterExpiresNever' + filter[0]"
:model-value="filter[1].expires === null"
:name="'filterExpiresNever' + filter[0]"
class="input-inset input-boolean never"
@update:model-value="updateFilter(filter[0], 'expires-never', $event)"
>
{{ $t('settings.filter.never_expires') }}
</Checkbox>
</div>
</div>
<div
v-if="!checkRegexValid(filter[0])"
class="alert error"
>
{{ $t('settings.filter.regexp_error') }}
</div>
<div class="filter-buttons">
<button
class="delete-button button-default -danger"
type="button"
@click="deleteFilter(filter[0])"
>
{{ $t('settings.filter.delete') }}
</button>
<button
class="export-button button-default"
type="button"
@click="exportFilter(filter[0])"
>
{{ $t('settings.filter.export') }}
</button>
<button
class="copy-button button-default"
type="button"
@click="copyFilter(filter[0])"
>
{{ $t('settings.filter.copy') }}
</button>
<button
class="save-button button-default"
:class="{ disabled: !muteFiltersDraftDirty[filter[0]] }"
:disabled="!muteFiltersDraftDirty[filter[0]]"
type="button"
@click="saveFilter(filter[0])"
>
{{ $t('settings.filter.save') }}
</button>
</div>
</div>
</div>
<p class="muteFiltersActionsBottom">
<span class="total">
{{ $t('settings.filter.expired_count', { count: muteFiltersExpired.length }) }}
</span>
<button
class="add-button button-default"
type="button"
:class="{ disabled: muteFiltersExpired.length === 0 }"
:disabled="muteFiltersExpired.length === 0"
@click="purgeExpiredFilters()"
>
{{ $t('settings.filter.purge_expired') }}
</button>
</p>
</li>
</ul>
</div>
</div>
</template>
<script src="./filtering_tab.js"></script>
<style src="./filtering_tab.scss"></style>
diff --git a/src/components/settings_modal/tabs/general_tab.vue b/src/components/settings_modal/tabs/general_tab.vue
index 40d40bc3e9..de8209e268 100644
--- a/src/components/settings_modal/tabs/general_tab.vue
+++ b/src/components/settings_modal/tabs/general_tab.vue
@@ -1,533 +1,536 @@
<template>
<div :label="$t('settings.general')">
<div class="setting-item">
<h2>{{ $t('settings.interface') }}</h2>
<ul class="setting-list">
<li>
<interface-language-switcher
:prompt-text="$t('settings.interfaceLanguage')"
:language="language"
:set-language="val => language = val"
/>
</li>
<li v-if="instanceSpecificPanelPresent">
<BooleanSetting path="hideISP">
{{ $t('settings.hide_isp') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="stopGifs">
{{ $t('settings.stop_gifs') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="streaming">
{{ $t('settings.streaming') }}
</BooleanSetting>
<ul class="setting-list suboptions">
<li>
<BooleanSetting
path="pauseOnUnfocused"
parent-path="streaming"
>
{{ $t('settings.pause_on_unfocused') }}
</BooleanSetting>
</li>
</ul>
</li>
<li>
<BooleanSetting
path="useStreamingApi"
expert="1"
>
{{ $t('settings.useStreamingApi') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting
path="virtualScrolling"
expert="1"
>
{{ $t('settings.virtual_scrolling') }}
</BooleanSetting>
</li>
<li>
<ChoiceSetting
id="userPopoverAvatarAction"
path="userPopoverAvatarAction"
:options="userPopoverAvatarActionOptions"
expert="1"
>
{{ $t('settings.user_popover_avatar_action') }}
</ChoiceSetting>
</li>
<li>
<BooleanSetting
path="userPopoverOverlay"
expert="1"
>
{{ $t('settings.user_popover_avatar_overlay') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting
path="alwaysShowNewPostButton"
expert="1"
>
{{ $t('settings.always_show_post_button') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting
path="autohideFloatingPostButton"
expert="1"
>
{{ $t('settings.autohide_floating_post_button') }}
</BooleanSetting>
</li>
<li v-if="instanceShoutboxPresent">
<BooleanSetting
path="hideShoutbox"
expert="1"
>
{{ $t('settings.hide_shoutbox') }}
</BooleanSetting>
</li>
<li class="select-multiple">
<span class="label">{{ $t('settings.confirm_dialogs') }}</span>
<ul class="option-list">
<li>
<BooleanSetting path="modalOnRepeat">
{{ $t('settings.confirm_dialogs_repeat') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="modalOnUnfollow">
{{ $t('settings.confirm_dialogs_unfollow') }}
</BooleanSetting>
</li>
<li>
- <BooleanSetting v-if="!blockExpirationSupported" path="modalOnBlock">
+ <BooleanSetting
+ v-if="!blockExpirationSupported"
+ path="modalOnBlock"
+ >
{{ $t('settings.confirm_dialogs_block') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="modalOnMuteConversation">
{{ $t('settings.confirm_dialogs_mute_conversation') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="modalOnMuteDomain">
{{ $t('settings.confirm_dialogs_mute_domain') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="modalOnDelete">
{{ $t('settings.confirm_dialogs_delete') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="modalOnLogout">
{{ $t('settings.confirm_dialogs_logout') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="modalOnApproveFollow">
{{ $t('settings.confirm_dialogs_approve_follow') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="modalOnDenyFollow">
{{ $t('settings.confirm_dialogs_deny_follow') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="modalOnRemoveUserFromFollowers">
{{ $t('settings.confirm_dialogs_remove_follower') }}
</BooleanSetting>
</li>
</ul>
</li>
</ul>
</div>
<div class="setting-item">
<h2>{{ $t('settings.post_look_feel') }}</h2>
<ul class="setting-list">
<li>
<ChoiceSetting
id="conversationDisplay"
path="conversationDisplay"
:options="conversationDisplayOptions"
>
{{ $t('settings.conversation_display') }}
</ChoiceSetting>
</li>
<ul
v-if="mergedConfig.conversationDisplay !== 'linear'"
class="setting-list suboptions"
>
<li>
<BooleanSetting path="conversationTreeAdvanced">
{{ $t('settings.tree_advanced') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting
path="conversationTreeFadeAncestors"
:expert="1"
>
{{ $t('settings.tree_fade_ancestors') }}
</BooleanSetting>
</li>
<li>
<IntegerSetting
path="maxDepthInThread"
:min="3"
:expert="1"
>
{{ $t('settings.max_depth_in_thread') }}
</IntegerSetting>
</li>
<li>
<ChoiceSetting
id="conversationOtherRepliesButton"
path="conversationOtherRepliesButton"
:options="conversationOtherRepliesButtonOptions"
:expert="1"
>
{{ $t('settings.conversation_other_replies_button') }}
</ChoiceSetting>
</li>
</ul>
<li>
<BooleanSetting path="collapseMessageWithSubject">
{{ $t('settings.collapse_subject') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting
path="emojiReactionsOnTimeline"
expert="1"
>
{{ $t('settings.emoji_reactions_on_timeline') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting
v-if="user"
source="profile"
path="stripRichContent"
expert="1"
>
{{ $t('settings.no_rich_text_description') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting
path="useAbsoluteTimeFormat"
expert="1"
>
{{ $t('settings.absolute_time_format') }}
</BooleanSetting>
</li>
<ul
v-if="mergedConfig.useAbsoluteTimeFormat"
class="setting-list suboptions"
>
<li>
<UnitSetting
path="absoluteTimeFormatMinAge"
unit-set="time"
:units="['s', 'm', 'h', 'd']"
:min="0"
>
{{ $t('settings.absolute_time_format_min_age') }}
</UnitSetting>
</li>
<li>
<ChoiceSetting
id="absoluteTime12h"
path="absoluteTime12h"
:options="absoluteTime12hOptions"
:expert="1"
>
{{ $t('settings.absolute_time_format_12h') }}
</ChoiceSetting>
</li>
</ul>
<h3>{{ $t('settings.attachments') }}</h3>
<li>
<BooleanSetting
path="imageCompression"
expert="1"
>
{{ $t('settings.image_compression') }}
</BooleanSetting>
</li>
<ul class="setting-list suboptions">
<li>
<BooleanSetting
path="alwaysUseJpeg"
expert="1"
parent-path="imageCompression"
>
{{ $t('settings.always_use_jpeg') }}
</BooleanSetting>
</li>
</ul>
<li>
<BooleanSetting
path="useContainFit"
expert="1"
>
{{ $t('settings.use_contain_fit') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="hideNsfw">
{{ $t('settings.nsfw_clickthrough') }}
</BooleanSetting>
</li>
<ul class="setting-list suboptions">
<li>
<BooleanSetting
path="preloadImage"
expert="1"
parent-path="hideNsfw"
>
{{ $t('settings.preload_images') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting
path="useOneClickNsfw"
expert="1"
parent-path="hideNsfw"
>
{{ $t('settings.use_one_click_nsfw') }}
</BooleanSetting>
</li>
</ul>
<li>
<BooleanSetting
path="loopVideo"
expert="1"
>
{{ $t('settings.loop_video') }}
</BooleanSetting>
<ul class="setting-list suboptions">
<li>
<BooleanSetting
path="loopVideoSilentOnly"
expert="1"
parent-path="loopVideo"
:disabled="!loopSilentAvailable"
>
{{ $t('settings.loop_video_silent_only') }}
</BooleanSetting>
<div
v-if="!loopSilentAvailable"
class="unavailable"
>
<FAIcon icon="globe" />! {{ $t('settings.limited_availability') }}
</div>
</li>
</ul>
</li>
<li>
<BooleanSetting
path="playVideosInModal"
expert="1"
>
{{ $t('settings.play_videos_in_modal') }}
</BooleanSetting>
</li>
<h3>{{ $t('settings.mention_links') }}</h3>
<li>
<ChoiceSetting
id="mentionLinkDisplay"
path="mentionLinkDisplay"
:options="mentionLinkDisplayOptions"
>
{{ $t('settings.mention_link_display') }}
</ChoiceSetting>
</li>
<li>
<BooleanSetting
path="mentionLinkShowTooltip"
expert="1"
>
{{ $t('settings.mention_link_use_tooltip') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting path="mentionLinkShowAvatar">
{{ $t('settings.mention_link_show_avatar') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting
path="mentionLinkFadeDomain"
expert="1"
>
{{ $t('settings.mention_link_fade_domain') }}
</BooleanSetting>
</li>
<li v-if="user">
<BooleanSetting
path="mentionLinkBoldenYou"
expert="1"
>
{{ $t('settings.mention_link_bolden_you') }}
</BooleanSetting>
</li>
<h3 v-if="expertLevel > 0">
{{ $t('settings.fun') }}
</h3>
<li>
<BooleanSetting
path="greentext"
expert="1"
>
{{ $t('settings.greentext') }}
</BooleanSetting>
</li>
<li v-if="user">
<BooleanSetting
path="mentionLinkShowYous"
expert="1"
>
{{ $t('settings.show_yous') }}
</BooleanSetting>
</li>
</ul>
</div>
<div
v-if="user"
class="setting-item"
>
<h2>{{ $t('settings.composing') }}</h2>
<ul class="setting-list">
<li>
<label for="default-vis">
{{ $t('settings.default_vis') }} <ProfileSettingIndicator :is-profile="true" />
<ScopeSelector
class="scope-selector"
:show-all="true"
:user-default="$store.state.profileConfig.defaultScope"
:initial-scope="$store.state.profileConfig.defaultScope"
:on-scope-change="changeDefaultScope"
/>
</label>
</li>
<li>
<!-- <BooleanSetting source="profile" path="defaultNSFW"> -->
<BooleanSetting path="sensitiveByDefault">
{{ $t('settings.sensitive_by_default') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting
path="scopeCopy"
expert="1"
>
{{ $t('settings.scope_copy') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting
path="alwaysShowSubjectInput"
expert="1"
>
{{ $t('settings.subject_input_always_show') }}
</BooleanSetting>
</li>
<li>
<ChoiceSetting
id="subjectLineBehavior"
path="subjectLineBehavior"
:options="subjectLineOptions"
expert="1"
>
{{ $t('settings.subject_line_behavior') }}
</ChoiceSetting>
</li>
<li v-if="postFormats.length > 0">
<ChoiceSetting
id="postContentType"
path="postContentType"
:options="postContentOptions"
>
{{ $t('settings.post_status_content_type') }}
</ChoiceSetting>
</li>
<li>
<BooleanSetting
path="minimalScopesMode"
expert="1"
>
{{ $t('settings.minimal_scopes_mode') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting
path="padEmoji"
expert="1"
>
{{ $t('settings.pad_emoji') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting
path="autocompleteSelect"
expert="1"
>
{{ $t('settings.autocomplete_select_first') }}
</BooleanSetting>
</li>
<li>
<BooleanSetting
path="autoSaveDraft"
>
{{ $t('settings.auto_save_draft') }}
</BooleanSetting>
</li>
<li v-if="!mergedConfig.autoSaveDraft">
<ChoiceSetting
id="unsavedPostAction"
path="unsavedPostAction"
:options="unsavedPostActionOptions"
>
{{ $t('settings.unsaved_post_action') }}
</ChoiceSetting>
</li>
</ul>
</div>
<div
class="setting-item"
>
<h2>{{ $t('settings.cache') }}</h2>
<ul class="setting-list">
<li>
<button
class="btn button-default"
@click="clearAssetCache"
>
{{ $t('settings.clear_asset_cache') }}
</button>
</li>
<li>
<button
class="btn button-default"
@click="clearEmojiCache"
>
{{ $t('settings.clear_emoji_cache') }}
</button>
</li>
</ul>
</div>
</div>
</template>
<script src="./general_tab.js"></script>
diff --git a/src/components/status_action_buttons/action_button_container.vue b/src/components/status_action_buttons/action_button_container.vue
index 75f5010ee4..b7ff10f3c6 100644
--- a/src/components/status_action_buttons/action_button_container.vue
+++ b/src/components/status_action_buttons/action_button_container.vue
@@ -1,106 +1,106 @@
<template>
<div>
<Popover
v-if="button.dropdown?.()"
:trigger="$attrs.extra ? 'hover' : 'click'"
:offset="{ y: 5 }"
:placement="$attrs.extra ? 'right' : 'top'"
>
<template #trigger>
<ActionButton
:button="button"
:status="status"
v-bind.prop="$attrs"
/>
</template>
<template #content>
<div
v-if="button.name === 'mute'"
:id="`popup-menu-${randomSeed}`"
class="dropdown-menu"
role="menu"
>
<div class="menu-item dropdown-item extra-action -icon">
<button
class="main-button"
@click="toggleUserMute"
>
<FAIcon
icon="user"
fixed-width
/>
<template v-if="userIsMuted">
{{ $t('status.unmute_user') }}
</template>
<template v-else>
{{ $t('status.mute_user') }}
</template>
</button>
</div>
<div class="menu-item dropdown-item extra-action -icon">
<button
class="main-button"
@click="toggleConversationMute"
>
<FAIcon
icon="folder-tree"
fixed-width
/>
<template v-if="conversationIsMuted">
{{ $t('status.unmute_conversation') }}
</template>
<template v-else>
{{ $t('status.mute_conversation') }}
</template>
</button>
</div>
<div class="menu-item dropdown-item extra-action -icon">
<button
class="main-button"
@click="toggleDomainMute"
>
<FAIcon
icon="globe"
fixed-width
/>
<template v-if="domainIsMuted">
{{ $t('status.unmute_domain') }}
</template>
<template v-else>
{{ $t('status.mute_domain') }}
</template>
</button>
</div>
</div>
</template>
</Popover>
<ActionButton
v-else
:button="button"
:status="status"
v-bind="$attrs"
@interacted="e => $emit('interacted')"
/>
<teleport to="#modal">
<MuteConfirm
ref="confirmConversation"
type="conversation"
:status="status"
:user="user"
/>
<MuteConfirm
ref="confirmDomain"
type="domain"
:status="status"
:user="user"
/>
<UserTimedFilterModal
+ ref="confirmUser"
:is-mute="true"
:user="user"
- ref="confirmUser"
/>
</teleport>
</div>
</template>
<script src="./action_button_container.js" />
diff --git a/src/components/status_body/status_body.scss b/src/components/status_body/status_body.scss
index f89ea8d898..a8a7d662a5 100644
--- a/src/components/status_body/status_body.scss
+++ b/src/components/status_body/status_body.scss
@@ -1,190 +1,194 @@
.StatusBody {
display: flex;
flex-direction: column;
.emoji {
--_still_image-label-scale: 0.5;
}
.attachments {
margin-top: 0.5em;
}
& .text,
& .summary {
white-space: pre-wrap;
overflow-wrap: break-word;
text-wrap: pretty;
line-height: var(--post-line-height);
}
.summary {
display: block;
font-style: italic;
padding-bottom: 0.5em;
}
.text {
&.-single-line {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
height: 1.4em;
}
}
.summary-wrapper {
margin-bottom: 0.5em;
border-style: solid;
border-width: 0 0 1px;
border-color: var(--border);
flex-grow: 0;
&.-tall {
position: relative;
.summary {
max-height: 2em;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
}
.text-wrapper {
text-overflow: ellipsis;
overflow-wrap: break-word;
overflow: hidden;
display: flex;
flex-flow: column nowrap;
&.-tall-status {
position: relative;
height: 16em;
z-index: 1;
.media-body {
min-height: 0;
mask:
linear-gradient(to top, white, transparent) bottom/100% 8em no-repeat,
linear-gradient(to top, white, white);
/* Autoprefixed seem to ignore this one, and also syntax is different */
/* stylelint-disable mask-composite */
/* stylelint-disable declaration-property-value-no-unknown */
+ /* stylelint-disable scss/declaration-property-value-no-unknown */
/* TODO check if this is still needed */
mask-composite: xor;
+ /* stylelint-enable scss/declaration-property-value-no-unknown */
/* stylelint-enable declaration-property-value-no-unknown */
/* stylelint-enable mask-composite */
mask-composite: exclude;
}
}
&.-expanded {
overflow: visible;
}
}
& .tall-status-hider,
& .tall-subject-hider,
& .status-unhider,
& .cw-status-hider {
display: inline-block;
overflow-wrap: break-word;
text-wrap: pretty;
width: 100%;
text-align: center;
}
.status-unhider {
margin-top: auto;
position: sticky;
bottom: 0;
padding-bottom: 1em;
}
.tall-status-hider {
position: absolute;
height: 5em;
margin-top: 10em;
line-height: 8em;
z-index: 2;
}
.tall-subject-hider {
// position: absolute;
padding-bottom: 0.5em;
}
& .status-unhider,
& .cw-status-hider {
overflow-wrap: break-word;
text-wrap: pretty;
svg {
color: inherit;
}
}
.toggle-button {
padding: 0.5em;
}
&.-compact {
align-items: start;
flex-direction: row;
--emoji-size: calc(var(--emojiSize, 32px) / 2);
& .body,
& .attachments {
max-height: 3.25em;
}
.body {
overflow: hidden;
white-space: normal;
min-width: 5em;
flex: 5 1 auto;
mask-size: auto 3.5em, auto auto;
mask-position: 0 0, 0 0;
mask-repeat: repeat-x, repeat;
mask-image: linear-gradient(to bottom, white 2em, transparent 3em);
/* Autoprefixed seem to ignore this one, and also syntax is different */
/* stylelint-disable mask-composite */
/* stylelint-disable declaration-property-value-no-unknown */
+ /* stylelint-disable scss/declaration-property-value-no-unknown */
/* TODO check if this is still needed */
mask-composite: xor;
+ /* stylelint-enable scss/declaration-property-value-no-unknown */
/* stylelint-enable declaration-property-value-no-unknown */
/* stylelint-enable mask-composite */
mask-composite: exclude;
}
.attachments {
margin-top: 0;
flex: 1 1 0;
min-width: 5em;
height: 100%;
margin-left: 0.5em;
}
.summary-wrapper {
.summary::after {
content: ": ";
}
line-height: inherit;
margin: 0;
border: none;
}
.text-wrapper {
display: inline-block;
width: 100%;
}
}
}
diff --git a/src/components/user_card/user_card.scss b/src/components/user_card/user_card.scss
index 418bef08ba..83b02fbc57 100644
--- a/src/components/user_card/user_card.scss
+++ b/src/components/user_card/user_card.scss
@@ -1,328 +1,330 @@
.user-card {
position: relative;
z-index: 1;
&:hover {
--_still-image-img-visibility: visible;
--_still-image-canvas-visibility: hidden;
--_still-image-label-visibility: hidden;
}
.btn-mute, .btn-mention {
display: block;
width: 100%;
}
.panel-heading {
padding: 0.5em 0;
text-align: center;
box-shadow: none;
background: transparent;
flex-direction: column;
align-items: stretch;
// create new stacking context
position: relative;
}
.background-image {
position: absolute;
inset: 0;
mask:
linear-gradient(to top, white, transparent) bottom no-repeat,
linear-gradient(to top, white, white);
// Autoprefixer seem to ignore this one, and also syntax is different
/* stylelint-disable mask-composite */
/* stylelint-disable declaration-property-value-no-unknown */
+ /* stylelint-disable scss/declaration-property-value-no-unknown */
/* TODO check if this is still needed */
mask-composite: xor;
+ /* stylelint-enable scss/declaration-property-value-no-unknown */
/* stylelint-enable declaration-property-value-no-unknown */
/* stylelint-enable mask-composite */
mask-composite: exclude;
background-size: cover;
mask-size: 100% 60%;
border-top-left-radius: calc(var(--__roundnessTop, --panelRadius) - 1px);
border-top-right-radius: calc(var(--__roundnessTop, --panelRadius) - 1px);
border-bottom-left-radius: calc(var(--__roundnessBottom, --panelRadius) - 1px);
border-bottom-right-radius: calc(var(--__roundnessBottom, --panelRadius) - 1px);
background-color: var(--profileBg);
z-index: -2;
&.hide-bio {
mask-size: 100% 40px;
}
}
&-bio {
text-align: center;
display: block;
line-height: 1.3;
padding: 1em;
margin: 0;
img {
object-fit: contain;
vertical-align: middle;
max-width: 100%;
max-height: 400px;
}
}
&.-rounded-t {
border-top-left-radius: var(--roundness);
border-top-right-radius: var(--roundness);
--__roundnessTop: var(--roundness);
--__roundnessBottom: 0;
}
&.-rounded {
border-radius: var(--roundness);
--__roundnessTop: var(--roundness);
--__roundnessBottom: var(--roundness);
}
&.-popover {
border-radius: var(--roundness);
--__roundnessTop: var(--roundness);
--__roundnessBottom: var(--roundness);
}
&.-bordered {
border-width: 1px;
border-style: solid;
border-color: var(--border);
}
}
.user-info {
padding: 0 26px;
.container {
min-width: 0;
padding: 16px 0 6px;
display: flex;
align-items: flex-start;
max-height: 56px;
> * {
min-width: 0;
}
> a {
vertical-align: middle;
display: flex;
}
.Avatar {
--_avatarShadowBox: var(--avatarShadow);
--_avatarShadowFilter: var(--avatarShadowFilter);
--_avatarShadowInset: var(--avatarShadowInset);
width: 56px;
height: 56px;
object-fit: cover;
}
}
&-avatar {
position: relative;
cursor: pointer;
&.-overlay {
position: absolute;
inset: 0;
background-color: rgb(0 0 0 / 30%);
display: flex;
justify-content: center;
align-items: center;
border-radius: var(--roundness);
opacity: 0;
transition: opacity 0.2s ease;
svg {
color: #fff;
}
}
&:hover &.-overlay {
opacity: 1;
}
}
.external-link-button,
.edit-profile-button {
cursor: pointer;
width: 2.5em;
text-align: center;
margin: -0.5em 0;
padding: 0.5em 0;
&:not(:hover) .icon {
color: var(--lightText);
}
}
.bottom-line {
font-weight: lighter;
font-size: 1.1em;
align-items: baseline;
.lock-icon {
margin-left: 0.5em;
}
.user-screen-name {
color: var(--text);
min-width: 1px;
flex: 0 1 auto;
text-overflow: ellipsis;
overflow: hidden;
}
.dailyAvg {
min-width: 1px;
flex: 0 0 auto;
margin-left: 1em;
font-size: 0.7em;
color: var(--text);
}
.user-role {
flex: none;
}
}
.user-summary {
display: block;
margin-left: 0.6em;
text-align: left;
text-overflow: ellipsis;
white-space: nowrap;
flex: 1 1 0;
// This is so that text doesn't get overlapped by avatar's shadow if it has
// big one
z-index: 1;
line-height: 2em;
--emoji-size: 1.7em;
.RichContent {
/* stylelint-disable-next-line declaration-no-important */
--link: var(--text) !important;
}
.top-line,
.bottom-line {
display: flex;
}
}
.user-name {
text-overflow: ellipsis;
overflow: hidden;
flex: 1 1 auto;
margin-right: 1em;
font-size: 1.1em;
}
.user-meta {
margin-bottom: 0.15em;
display: flex;
align-items: baseline;
line-height: 22px;
flex-wrap: wrap;
.following {
flex: 1 0 auto;
margin: 0;
margin-bottom: 0.25em;
text-align: left;
}
.highlighter {
flex: 0 1 auto;
display: flex;
flex-wrap: wrap;
margin-right: -0.5em;
align-self: start;
.userHighlightCl {
padding: 2px 10px;
flex: 1 0 auto;
}
.userHighlightSel {
padding-top: 0;
padding-bottom: 0;
flex: 1 0 auto;
}
.userHighlightText {
width: 70px;
flex: 1 0 auto;
}
.userHighlightCl,
.userHighlightText,
.userHighlightSel {
vertical-align: top;
margin-right: 0.5em;
margin-bottom: 0.25em;
}
}
}
.user-interactions {
position: relative;
display: flex;
flex-flow: row wrap;
margin-right: -0.75em;
> * {
margin: 0 0.75em 0.6em 0;
white-space: nowrap;
min-width: 95px;
}
button {
margin: 0;
}
}
.user-note {
margin: 0 0.75em 0.6em 0;
}
}
.sidebar .edit-profile-button {
display: none;
}
.user-counts {
display: flex;
line-height: 16px;
padding: 0.5em 1.5em 0;
text-align: center;
justify-content: space-between;
flex-wrap: wrap;
}
.user-count {
flex: 1 0 auto;
padding: 0.5em 0;
margin: 0 0.5em;
h5 {
font-size: 1em;
font-weight: bolder;
margin: 0 0 0.25em;
}
/* stylelint-disable-next-line no-descending-specificity */
a {
text-decoration: none;
}
}
diff --git a/src/components/user_timed_filter_modal/user_timed_filter_modal.vue b/src/components/user_timed_filter_modal/user_timed_filter_modal.vue
index 8fcf6eed84..91c05ad773 100644
--- a/src/components/user_timed_filter_modal/user_timed_filter_modal.vue
+++ b/src/components/user_timed_filter_modal/user_timed_filter_modal.vue
@@ -1,67 +1,86 @@
<template>
<confirm-modal
v-if="showing"
class="UserTimedFilterModal"
:title="$t(isMute ? $t('user_card.mute') : $t('user_card.block'))"
:confirm-text="$t(isMute ? 'user_card.mute_confirm_accept_button' : 'user_card.block_confirm_accept_button')"
:cancel-text="$t(isMute ? 'user_card.mute_confirm_cancel_button' : 'user_card.block_confirm_cancel_button')"
@accepted="accept"
@cancelled="cancel"
>
-
<p>
{{ $t(isMute ? 'user_card.expire_mute_message' : 'user_card.expire_block_message', [user.screen_name]) }}
</p>
<p>
- {{ $t('user_card.expire_in') }}
+ {{ $t('user_card.expire_in') }}
<input
id="userFilterExpires"
+ v-model="expiration"
class="input input-expire-in"
:class="{ disabled: forever }"
- v-model="expiration"
:disabled="forever"
min="1"
type="number"
>
<Select
id="userFilterExpiresUnit"
v-model="expirationUnit"
class="input unit-input unstyled"
:disabled="forever"
>
- <option key="s" value="s"> {{ $t('time.unit.seconds_suffix') }} </option>
- <option key="m" value="m"> {{ $t('time.unit.minutes_suffix') }} </option>
- <option key="h" value="h"> {{ $t('time.unit.hours_suffix') }} </option>
- <option key="d" value="d"> {{ $t('time.unit.days_suffix') }} </option>
+ <option
+ key="s"
+ value="s"
+ >
+ {{ $t('time.unit.seconds_suffix') }}
+ </option>
+ <option
+ key="m"
+ value="m"
+ >
+ {{ $t('time.unit.minutes_suffix') }}
+ </option>
+ <option
+ key="h"
+ value="h"
+ >
+ {{ $t('time.unit.hours_suffix') }}
+ </option>
+ <option
+ key="d"
+ value="d"
+ >
+ {{ $t('time.unit.days_suffix') }}
+ </option>
</Select>
{{ $t('user_card.mute_or') }}
<Checkbox
id="forever"
v-model="forever"
name="forever"
class="input-forever"
>
{{ $t('user_card.mute_block_never') }}
</Checkbox>
</p>
<template #footerLeft>
<div class="footer-left-checkbox">
<Checkbox
id="dontAskAgain"
v-model="dontAskAgain"
name="dontAskAgain"
class="input-dont-ask-again"
>
{{ $t(isMute ? 'user_card.dont_ask_again_mute' : 'user_card.dont_ask_again_block') }}
</Checkbox>
</div>
</template>
</confirm-modal>
</template>
<script src="./user_timed_filter_modal.js"></script>
<style lang="scss" src="./user_timed_filter_modal.scss" />

File Metadata

Mime Type
text/x-diff
Expires
Fri, Oct 30, 11:37 PM (15 h, 57 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1800737
Default Alt Text
(85 KB)

Event Timeline