Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F85824266
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Authored By
Unknown
Size
54 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/App.scss b/src/App.scss
index d891508fe6..bf325c386f 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -1,1113 +1,1115 @@
// 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, 1rem);
--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);
+ height: 2em;
&.-transparent {
backdrop-filter: blur(0.125em) contrast(60%);
}
&::-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;
}
}
label {
&.-disabled {
color: var(--textFaint);
}
}
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;
}
&::placeholder {
color: var(--textFaint)
}
--_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;
color: var(--textFaint);
/* stylelint-disable-next-line declaration-no-important */
background-color: transparent !important;
}
&[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;
}
background-color: var(--background);
}
+ 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: repeat(auto-fit, minmax(20em, 1fr));
grid-gap: 0.5em;
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;
min-height: 2em;
+ align-items: baseline;
- .Select select {
- line-height: 1;
+ .input {
+ height: 2em;
}
> *,
> * .button-default {
--_roundness-left: 0;
--_roundness-right: 0;
position: relative;
}
> *: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: 0.6em;
max-height: 0.6em;
min-width: 0.6em;
max-width: 0.6em;
left: calc(50% + 0.5em);
top: calc(50% - 1em);
line-height: 0;
padding: 0;
margin: 0;
}
&.-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);
}
.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;
#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-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/font_control/font_control.vue b/src/components/font_control/font_control.vue
index b863d253a2..5124fd53a5 100644
--- a/src/components/font_control/font_control.vue
+++ b/src/components/font_control/font_control.vue
@@ -1,178 +1,144 @@
<template>
<div class="font-control">
<div class="setting-item">
<Checkbox
v-if="typeof fallback !== 'undefined'"
:id="name + '-o'"
class="font-checkbox setting-control setting-label"
:model-value="present"
@change="$emit('update:modelValue', typeof modelValue === 'undefined' ? fallback : undefined)"
>
<i18n-t
scope="global"
keypath="settings.style.fonts.override"
tag="span"
>
<span>
{{ label }}
</span>
</i18n-t>
</Checkbox>
</div>
{{ ' ' }}
<div
v-if="modelValue?.family"
class="font-input setting-item"
>
<label
v-if="manualEntry"
:id="name + '-label'"
:for="manualEntry ? name : name + '-font-switcher'"
class="label"
>
<i18n-t
keypath="settings.style.themes3.font.entry"
tag="span"
scope="global"
>
<template #fontFamily>
<code>font-family</code>
</template>
</i18n-t>
</label>
<label
v-else
:id="name + '-label'"
:for="manualEntry ? 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"
:title="$t('settings.style.themes3.font.lookup_local_fonts')"
@click="toggleManualEntry"
>
<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="font-selector btn-group"
>
<button
class="btn button-default"
:title="$t('settings.style.themes3.font.enter_manually')"
@click="toggleManualEntry"
>
<FAIcon
fixed-width
icon="keyboard"
/>
</button>
<Select
:id="name + '-local-font-switcher'"
:model-value="modelValue?.family"
class="custom-font"
@update:model-value="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>
</div>
</div>
</template>
<script src="./font_control.js"></script>
<style lang="scss">
-// Copy-paste of BooleanSetting
-.font-control {
- display: grid;
- grid-template-columns: subgrid;
-
- .checkbox {
- display: grid;
- grid-template-columns: subgrid;
- }
-
- .label {
- grid-area: label;
- text-align: right;
- }
-
- .-mobile & {
- .font-input {
- display: block;
- }
-
- .font-selector {
- margin-top: 0.5em;
- }
-
- .label {
- text-align: left;
- }
- }
-
- .checkbox-indicator {
- grid-area: control;
- }
-}
-
.invalid-tooltip {
margin: 0.5em 1em;
min-width: 10em;
text-align: center;
}
</style>
diff --git a/src/components/settings_modal/admin_tabs/emoji_tab.scss b/src/components/settings_modal/admin_tabs/emoji_tab.scss
index 327d0f3764..af1cfcf9cb 100644
--- a/src/components/settings_modal/admin_tabs/emoji_tab.scss
+++ b/src/components/settings_modal/admin_tabs/emoji_tab.scss
@@ -1,105 +1,110 @@
.EmojiTab {
.setting-list {
padding-left: 0.75em;
}
.toolbar {
display: flex;
flex-wrap: wrap;
gap: 0.5em;
.header-buttons {
display: flex;
flex: 1 0 auto;
justify-content: end;
+ align-items: end;
&:not(.btn-group) {
gap: 0.5em;
}
.header-text {
flex: 1 0 auto;
}
}
.button-default {
flex: 0 0 auto;
padding: 0.5em;
font-size: 1rem;
}
+
+ .popover-wrapper {
+ display: flex;
+ }
}
.selector-buttons,
.meta-buttons {
display: flex;
flex-wrap: wrap;
gap: 0.5em;
}
h5 {
margin-top: 1em;
margin-bottom: 0.25em;
}
h3.toolbar {
align-items: end;
}
.emoji-info-input {
width: 100%;
}
.emoji-data-input {
width: 40%;
margin-left: 0.5em;
margin-right: 0.5em;
}
.emoji {
width: 2em;
height: 2em;
}
.emoji-unsaved {
box-shadow: 0 3px 5px var(--cBlue);
}
.emoji-list {
display: flex;
flex-wrap: wrap;
gap: 1em;
.placeholder {
background: var(--textFaint);
border-radius: 0.5em;
width: 2em;
height: 2em;
opacity: 0.5;
}
}
}
.emoji-tab-popover-button:not(:first-child) {
margin-left: 0.5em;
}
.emoji-tab-popover-input {
margin-bottom: 0.5em;
label {
display: block;
margin-bottom: 0.5em;
}
input {
width: 20em;
}
.emoji-tab-popover-file {
padding-top: 3px;
}
.warning {
color: var(--cOrange);
}
}
diff --git a/src/components/settings_modal/admin_tabs/emoji_tab.vue b/src/components/settings_modal/admin_tabs/emoji_tab.vue
index ba68a28a75..ecec6cd57d 100644
--- a/src/components/settings_modal/admin_tabs/emoji_tab.vue
+++ b/src/components/settings_modal/admin_tabs/emoji_tab.vue
@@ -1,422 +1,447 @@
<template>
<div
class="EmojiTab"
:label="$t('admin_dash.tabs.emoji')"
>
<div class="setting-section">
<h3 class="toolbar">
<span class="header-text">
{{ $t('admin_dash.emoji.emoji_packs') }}
</span>
<span class="header-buttons btn-group">
<button
class="button button-default"
type="button"
:title="$t('admin_dash.emoji.reload')"
@click="reloadEmoji"
>
<FAIcon icon="arrows-rotate" />
{{ $t('admin_dash.emoji.reload_short') }}
</button>
<Popover
popover-class="emoji-tab-edit-popover popover-default"
trigger="click"
placement="bottom"
>
<template #trigger>
<button
class="button button-default"
type="button"
:title="$t('admin_dash.emoji.remote_packs')"
>
<FAIcon icon="download" />
{{ $t('admin_dash.emoji.remote_packs_short') }}
</button>
</template>
<template #content>
<div class="emoji-tab-popover-input">
<h3>{{ $t('admin_dash.emoji.remote_pack_instance') }}</h3>
<input
v-model="remotePackInstance"
class="input"
:placeholder="$t('admin_dash.emoji.remote_pack_instance')"
>
<button
- class="button button-default btn emoji-tab-popover-button"
+ class="button button-default emoji-tab-popover-button"
type="button"
@click="listRemotePacks"
>
{{ $t('admin_dash.emoji.do_list') }}
</button>
</div>
</template>
</Popover>
<Popover
ref="additionalRemotePopover"
popover-class="emoji-tab-edit-popover popover-default"
trigger="click"
placement="bottom"
>
<template #trigger>
<button
class="button button-default emoji-panel-additional-actions"
:title="$t('admin_dash.emoji.import_pack')"
@click="$refs.additionalRemotePopover.showPopover"
>
<FAIcon icon="folder-open" />
{{ $t('admin_dash.emoji.import_pack_short') }}
</button>
</template>
<template #content>
<div class="emoji-tab-popover-input">
<h3>{{ $t('admin_dash.emoji.new_pack_name') }}</h3>
<input
v-model="newPackName"
:placeholder="$t('admin_dash.emoji.new_pack_name')"
class="input"
>
<h3>Import pack from URL</h3>
<input
v-model="remotePackURL"
class="input"
placeholder="Pack .zip URL"
>
<button
class="button button-default btn emoji-tab-popover-button"
type="button"
:disabled="newPackName.trim() === '' || remotePackURL.trim() === ''"
@click="downloadRemoteURLPack"
>
Import
</button>
<h3>Import pack from a file</h3>
<input
type="file"
accept="application/zip"
class="emoji-tab-popover-file input"
@change="remotePackFile = $event.target.files"
>
<button
class="button button-default btn emoji-tab-popover-button"
type="button"
:disabled="newPackName.trim() === '' || remotePackFile === null || remotePackFile.length === 0"
@click="downloadRemoteFilePack"
>
Import
</button>
</div>
</template>
</Popover>
</span>
</h3>
- <div class="setting-list">
+ <div class="setting-section">
<h4 class="toolbar">
{{ $t('admin_dash.emoji.edit_pack') }}
</h4>
- <div class="selector-buttons setting-list">
+ <div class="selector-buttons">
<button
:disabled="!pack || pack.remote !== undefined"
class="button button-default btn"
type="button"
@click="deleteModalVisible = true"
>
{{ $t('admin_dash.emoji.delete_pack') }}
<ConfirmModal
v-if="deleteModalVisible"
:title="$t('admin_dash.emoji.delete_title')"
:cancel-text="$t('status.delete_confirm_cancel_button')"
:confirm-text="$t('status.delete_confirm_accept_button')"
@cancelled="deleteModalVisible = false"
@accepted="deleteEmojiPack"
>
{{ $t('admin_dash.emoji.delete_confirm', [packName]) }}
</ConfirmModal>
</button>
<button
:disabled="!pack || pack.remote === undefined"
class="button button-default btn"
type="button"
@click="$refs.downloadPackPopover.showPopover"
>
{{ $t('admin_dash.emoji.download_pack') }}
<Popover
ref="downloadPackPopover"
trigger="click"
placement="bottom"
bound-to-selector=".emoji-tab"
popover-class="emoji-tab-edit-popover popover-default"
:bound-to="{ x: 'container' }"
:offset="{ y: 5 }"
>
<template #content>
<h3>{{ $t('admin_dash.emoji.downloading_pack', [packName]) }}</h3>
<div>
<div>
<div class="emoji-tab-popover-input">
<label>
{{ $t('admin_dash.emoji.download_as_name') }}
<input
v-model="remotePackDownloadAs"
class="emoji-data-input input"
:placeholder="$t('admin_dash.emoji.download_as_name_full')"
>
</label>
<div
v-if="downloadWillReplaceLocal"
class="warning"
>
<em>{{ $t('admin_dash.emoji.replace_warning') }}</em>
</div>
</div>
<button
class="button button-default btn"
type="button"
@click="downloadRemotePack"
>
{{ $t('admin_dash.emoji.download') }}
</button>
</div>
</div>
</template>
</Popover>
</button>
<span class="btn-group">
<Select
v-model="packName"
class="form-control"
>
<option
value=""
disabled
hidden
>
{{ $t('admin_dash.emoji.emoji_pack') }}
</option>
<option
v-for="(pack, listPackName) in knownPacks"
:key="listPackName"
:label="listPackName"
>
{{ listPackName }}
</option>
</Select>
<Popover
ref="createPackPopover"
popover-class="emoji-tab-edit-popover popover-default"
trigger="click"
placement="bottom"
>
<template #trigger>
<button
class="button button-default btn emoji-tab-popover-button"
type="button"
>
{{ $t('admin_dash.emoji.create_pack') }}
</button>
</template>
<template #content>
<div class="emoji-tab-popover-input">
<h3>{{ $t('admin_dash.emoji.new_pack_name') }}</h3>
<input
v-model="newPackName"
:placeholder="$t('admin_dash.emoji.new_pack_name')"
class="input"
>
<button
class="button button-default btn emoji-tab-popover-button"
type="button"
@click="createEmojiPack"
>
{{ $t('admin_dash.emoji.create') }}
</button>
</div>
</template>
</Popover>
</span>
</div>
<h5>
{{ $t('admin_dash.emoji.metadata') }}
<ModifiedIndicator
:changed="$refs.emojiPopovers && $refs.emojiPopovers.some(p => p.isEdited)"
message-key="admin_dash.emoji.emoji_changed"
/>
</h5>
<ul class="setting-list">
<li>
- <label :class="{ ['-disabled']: !pack || pack.remote !== undefined }">
- {{ $t('admin_dash.emoji.description') }}
- <ModifiedIndicator
- :changed="metaEdited('description')"
- message-key="admin_dash.emoji.metadata_changed"
- />
-
- <textarea
- v-model="packMeta.description"
- :disabled="!pack || pack.remote !== undefined"
- class="bio resize-height input"
- />
+ <label
+ class="setting-item"
+ :class="{ ['-disabled']: !pack || pack.remote !== undefined }"
+ >
+ <span class="setting-label">
+ <ModifiedIndicator
+ :changed="metaEdited('description')"
+ message-key="admin_dash.emoji.metadata_changed"
+ />
+ {{ $t('admin_dash.emoji.description') }}
+ </span>
+ <div>
+ <textarea
+ v-model="packMeta.description"
+ :disabled="!pack || pack.remote !== undefined"
+ class="bio resize-height input setting-control"
+ />
+ </div>
</label>
</li>
<li>
- <label :class="{ ['-disabled']: !pack || pack.remote !== undefined }">
- {{ $t('admin_dash.emoji.homepage') }}
- <ModifiedIndicator
- :changed="metaEdited('homepage')"
- message-key="admin_dash.emoji.metadata_changed"
- />
+ <label
+ class="setting-item"
+ :class="{ ['-disabled']: !pack || pack.remote !== undefined }"
+ >
+ <span class="setting-label">
+ <ModifiedIndicator
+ :changed="metaEdited('homepage')"
+ message-key="admin_dash.emoji.metadata_changed"
+ />
+ {{ $t('admin_dash.emoji.homepage') }}
+ </span>
<input
v-model="packMeta.homepage"
- class="emoji-info-input input"
+ class="emoji-info-input input setting-control"
:disabled="!pack || pack.remote !== undefined"
>
</label>
</li>
<li>
- <label :class="{ ['-disabled']: !pack || pack.remote !== undefined }">
- {{ $t('admin_dash.emoji.fallback_src') }}
- <ModifiedIndicator
- :changed="metaEdited('fallback-src')"
- message-key="admin_dash.emoji.metadata_changed"
- />
+ <label
+ class="setting-item"
+ :class="{ ['-disabled']: !pack || pack.remote !== undefined }"
+ >
+ <span class="setting-label">
+ <ModifiedIndicator
+ :changed="metaEdited('fallback-src')"
+ message-key="admin_dash.emoji.metadata_changed"
+ />
+ {{ $t('admin_dash.emoji.fallback_src') }}
+ </span>
<input
v-model="packMeta['fallback-src']"
- class="emoji-info-input input"
+ class="emoji-info-input input setting-control"
:disabled="!pack || pack.remote !== undefined"
>
</label>
</li>
<li>
- <label :class="{ ['-disabled']: !pack || pack.remote !== undefined }">
- {{ $t('admin_dash.emoji.fallback_sha256') }}
+ <label
+ class="setting-item"
+ :class="{ ['-disabled']: !pack || pack.remote !== undefined }"
+ >
+ <span class="setting-label">
+ {{ $t('admin_dash.emoji.fallback_sha256') }}
+ </span>
<input
v-model="packMeta['fallback-src-sha256']"
:disabled="!pack || pack.remote !== undefined"
- class="emoji-info-input input"
+ class="emoji-info-input input setting-control"
>
</label>
</li>
<li>
- <Checkbox
- v-model="packMeta['share-files']"
- :disabled="!pack || pack.remote !== undefined"
- >
- {{ $t('admin_dash.emoji.share') }}
- </Checkbox>
-
- <ModifiedIndicator
- :changed="metaEdited('share-files')"
- message-key="admin_dash.emoji.metadata_changed"
- />
+ <div class="setting-item">
+ <Checkbox
+ v-model="packMeta['share-files']"
+ :disabled="!pack || pack.remote !== undefined"
+ class="setting-label setting-control"
+ >
+ <ModifiedIndicator
+ :changed="metaEdited('share-files')"
+ message-key="admin_dash.emoji.metadata_changed"
+ />
+ {{ $t('admin_dash.emoji.share') }}
+ </Checkbox>
+ </div>
+ </li>
+ <li>
<div class="meta-buttons">
<button
v-if="pack && pack.remote === undefined"
class="button button-default btn"
type="button"
@click="savePackMetadata"
>
{{ $t('admin_dash.emoji.save_meta') }}
</button>
<button
v-if="pack && pack.remote === undefined"
class="button button-default btn"
type="button"
@click="savePackMetadata"
>
{{ $t('admin_dash.emoji.revert_meta') }}
</button>
</div>
</li>
</ul>
<h5>
{{ $t('admin_dash.emoji.files') }}
<ModifiedIndicator
:changed="$refs.emojiPopovers && $refs.emojiPopovers.some(p => p.isEdited)"
message-key="admin_dash.emoji.emoji_changed"
/>
</h5>
<div
class="emoji-list setting-list"
>
<EmojiEditingPopover
v-if="pack && pack.remote === undefined"
placement="bottom"
new-upload
:title="$t('admin_dash.emoji.adding_new')"
:pack-name="packName"
@update-pack-files="updatePackFiles"
@display-error="displayError"
>
<template #trigger>
<FAIcon
icon="plus"
size="2x"
:title="$t('admin_dash.emoji.add_file')"
/>
</template>
</EmojiEditingPopover>
<template v-if="!pack">
<div
v-for="(_, i) in new Array(20)"
:key="i"
class="placeholder"
/>
</template>
<EmojiEditingPopover
v-for="(file, shortcode) in (pack?.files || [])"
ref="emojiPopovers"
:key="shortcode"
placement="top"
:title="$t(`admin_dash.emoji.${pack?.remote === undefined ? 'editing' : 'copying'}`, [shortcode])"
:shortcode="shortcode"
:file="file"
:pack-name="packName"
:remote="pack?.remote"
:known-local-packs="knownLocalPacks"
@update-pack-files="updatePackFiles"
@display-error="displayError"
>
<template #trigger>
<StillImage
class="emoji"
:src="emojiAddr(file)"
:title="`:${shortcode}:`"
:alt="`:${shortcode}:`"
/>
</template>
</EmojiEditingPopover>
</div>
</div>
<h3>{{ $t('admin_dash.emoji.advanced') }}</h3>
<button
class="button button-default btn"
type="button"
@click="importFromFS"
>
<FAIcon icon="server" />
{{ $t('admin_dash.emoji.importFS') }}
</button>
</div>
</div>
</template>
<script src="./emoji_tab.js"></script>
<style lang="scss" src="./emoji_tab.scss"></style>
diff --git a/src/components/settings_modal/helpers/boolean_setting.vue b/src/components/settings_modal/helpers/boolean_setting.vue
index 7f3adf5ade..2134ad7ce6 100644
--- a/src/components/settings_modal/helpers/boolean_setting.vue
+++ b/src/components/settings_modal/helpers/boolean_setting.vue
@@ -1,72 +1,45 @@
<template>
<label
v-if="matchesExpertLevel"
class="BooleanSetting setting-item"
>
<Checkbox
class="setting-control setting-label"
:model-value="visibleState"
:disabled="shouldBeDisabled"
:indeterminate="isIndeterminate"
@update:model-value="update"
>
<span
class="label"
:class="{ 'faint': shouldBeDisabled }"
>
<ModifiedIndicator
:changed="isChanged"
:onclick="reset"
/>
<ProfileSettingIndicator :is-profile="isProfileSetting" />
{{ ' ' }}
<template v-if="backendDescriptionLabel">
{{ backendDescriptionLabel }}
</template>
<template v-else-if="source === 'admin'">
MISSING LABEL FOR {{ path }}
</template>
<slot v-else />
</span>
</Checkbox>
<p
v-if="backendDescriptionDescription || showDescription"
class="setting-description"
:class="{ 'faint': shouldBeDisabled }"
>
<slot name="description">
{{ backendDescriptionDescription + ' ' }}
</slot>
</p>
<DraftButtons />
</label>
</template>
<script src="./boolean_setting.js"></script>
-
-<style lang="scss">
-.BooleanSetting {
- display: grid;
- grid-template-columns: subgrid;
-
- .checkbox {
- display: grid;
- grid-template-columns: subgrid;
- }
-
- .label {
- grid-area: label;
- text-align: right;
- }
-
- .-mobile & {
- .label {
- text-align: left;
- }
- }
-
- .checkbox-indicator {
- grid-area: control;
- }
-}
-</style>
diff --git a/src/components/settings_modal/settings_modal.scss b/src/components/settings_modal/settings_modal.scss
index c757d29649..1ca9ec3180 100644
--- a/src/components/settings_modal/settings_modal.scss
+++ b/src/components/settings_modal/settings_modal.scss
@@ -1,235 +1,255 @@
.settings-modal {
overflow: hidden;
h2 {
font-size: 1.3rem;
font-weight: 500;
margin-top: 1em;
margin-bottom: 1em;
margin-right: 1em;
}
h3 {
font-size: 1.2rem;
font-weight: 500;
margin-top: 1em;
margin-bottom: 0.5em;
margin-right: 1em;
border-bottom: 1px solid var(--border);
padding-bottom: 0.25em;
box-sizing: border-box;
padding-left: 0.5em;
}
h4 {
font-size: 1.1rem;
margin-top: 1em;
margin-right: 1em;
margin-bottom: 0.5em;
margin-left: 1em;
}
h5 {
font-size: 1rem;
margin-right: 1em;
margin-bottom: 0.25em;
margin-top: 0.75em;
}
p {
line-height: 1.5;
}
.suboptions {
margin-left: 1em;
}
.sidenote {
margin-left: 5em;
padding: 0.25em 1em;
margin-top: 0.25em;
}
.setting-description {
margin-top: 0.2em;
margin-bottom: 0;
font-size: 80%;
}
.setting-item {
display: grid;
grid-template-areas:
"label control"
". desc"
". draft";
grid-template-columns: 4fr 5fr;
column-gap: 0.5em;
align-items: baseline;
padding: 0.5em 0;
.setting-label {
grid-area: label;
text-align: right;
}
.ModifiedIndicator,
.ProfileSettingIndicator {
grid-area: indicator;
}
.setting-control {
grid-area: control;
+
+ &.checkbox {
+ display: grid;
+ grid-template-columns: subgrid;
+
+ .label {
+ grid-area: label;
+ text-align: right;
+ }
+
+ .checkbox-indicator {
+ grid-area: control;
+ }
+
+ .-mobile & {
+ .label {
+ text-align: left;
+ }
+ }
+ }
}
.setting-control.setting-label {
grid-column: 1 / 3;
text-align: left;
}
.setting-description {
grid-area: desc;
}
.DraftButtons {
grid-area: draft;
}
}
.vertical-tab-switcher {
height: 100%;
}
.setting-list,
.option-list {
list-style-type: none;
padding-left: 0;
margin: 0;
.btn:not(.dropdown-button) {
padding: 0 2em;
}
.btn-group {
.button-default {
flex: 0 1 auto;
}
}
&.two-column {
display: grid;
grid-template-columns: 1fr 1fr;
margin-left: 2em;
border-bottom: 1px solid var(--border);
padding-bottom: 0.5em;
margin-bottom: 1em;
.setting-item {
grid-template-columns: 3fr 1fr;
}
> li {
margin: 0;
break-inside: avoid;
}
}
}
.settings-modal-panel {
overflow: hidden;
transition: transform;
transition-timing-function: ease-in-out;
transition-duration: 300ms;
width: 70em;
max-width: 90vw;
height: 90vh;
@media all and (width <= 800px) {
max-width: 100vw;
height: 100%;
}
>.panel-body {
height: 100%;
overflow-y: hidden;
.btn {
min-height: 2em;
}
}
}
.settings-footer {
display: flex;
flex-wrap: wrap;
line-height: 2;
>* {
margin-right: 0.5em;
}
.extra-content {
display: flex;
flex-grow: 1;
}
}
&.-mobile {
.tabs {
.menu-item {
font-size: 1.2em;
padding-top: 0.75em;
padding-bottom: 0.75em;
}
}
.setting-item {
grid-template-columns: 1fr min-content;
column-gap: 0.5em;
padding: 1em 1em;
align-items: center;
.setting-label {
text-align: left;
}
}
ul {
padding: 0;
li:not(:first-child) {
.setting-item {
border-top: 1px solid var(--border);
}
}
}
.setting-list:not(.suboptions),
.option-list {
&.two-column {
grid-template-columns: 1fr;
}
}
}
&.peek {
.settings-modal-panel {
/* Explanation:
* Modal is positioned vertically centered.
* 100vh - 100% = Distance between modal's top+bottom boundaries and screen
* (100vh - 100%) / 2 = Distance between bottom (or top) boundary and screen
* + 100% - we move modal completely off-screen, it's top boundary touches
* bottom of the screen
* - 50px - leaving tiny amount of space so that titlebar + tiny amount of modal is visible
*/
transform: translateY(calc(((100vh - 100%) / 2 + 100%) - 50px));
@media all and (width <= 800px) {
/* For mobile, the modal takes 100% of the available screen.
This ensures the minimized modal is always 50px above the browser bottom
bar regardless of whether or not it is visible.
*/
transform: translateY(calc(100% - 50px));
}
}
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Thu, Oct 29, 1:56 PM (1 d, 5 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1795746
Default Alt Text
(54 KB)
Attached To
Mode
rPUFE pleroma-fe-upstream
Attached
Detach File
Event Timeline