-
+
diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js
index 783d748ae..842b7af51 100644
--- a/app/javascript/mastodon/reducers/compose.js
+++ b/app/javascript/mastodon/reducers/compose.js
@@ -186,11 +186,12 @@ const ignoreSuggestion = (state, position, token, completion, path) => {
};
const sortHashtagsByUse = (state, tags) => {
- const personalHistory = state.get('tagHistory');
+ const personalHistory = state.get('tagHistory').map(tag => tag.toLowerCase());
- return tags.sort((a, b) => {
- const usedA = personalHistory.includes(a.name);
- const usedB = personalHistory.includes(b.name);
+ const tagsWithLowercase = tags.map(t => ({ ...t, lowerName: t.name.toLowerCase() }));
+ const sorted = tagsWithLowercase.sort((a, b) => {
+ const usedA = personalHistory.includes(a.lowerName);
+ const usedB = personalHistory.includes(b.lowerName);
if (usedA === usedB) {
return 0;
@@ -200,6 +201,8 @@ const sortHashtagsByUse = (state, tags) => {
return 1;
}
});
+ sorted.forEach(tag => delete tag.lowerName);
+ return sorted;
};
const insertEmoji = (state, position, emojiData, needsSpace) => {
diff --git a/app/javascript/styles/fonts/roboto-mono.scss b/app/javascript/styles/fonts/roboto-mono.scss
index 909d1a13e..66f3eed9f 100644
--- a/app/javascript/styles/fonts/roboto-mono.scss
+++ b/app/javascript/styles/fonts/roboto-mono.scss
@@ -1,11 +1,12 @@
@font-face {
font-family: mastodon-font-monospace;
- src:
- local('Roboto Mono'),
+ src: local('Roboto Mono'),
url('~fonts/roboto-mono/robotomono-regular-webfont.woff2') format('woff2'),
url('~fonts/roboto-mono/robotomono-regular-webfont.woff') format('woff'),
- url('~fonts/roboto-mono/robotomono-regular-webfont.ttf') format('truetype'),
- url('~fonts/roboto-mono/robotomono-regular-webfont.svg#roboto_monoregular') format('svg');
+ url('~fonts/roboto-mono/robotomono-regular-webfont.ttf')
+ format('truetype'),
+ url('~fonts/roboto-mono/robotomono-regular-webfont.svg#roboto_monoregular')
+ format('svg');
font-weight: 400;
font-display: swap;
font-style: normal;
diff --git a/app/javascript/styles/fonts/roboto.scss b/app/javascript/styles/fonts/roboto.scss
index 0ccc43094..07cf0cb00 100644
--- a/app/javascript/styles/fonts/roboto.scss
+++ b/app/javascript/styles/fonts/roboto.scss
@@ -1,11 +1,11 @@
@font-face {
font-family: mastodon-font-sans-serif;
- src:
- local('Roboto Italic'),
+ src: local('Roboto Italic'),
url('~fonts/roboto/roboto-italic-webfont.woff2') format('woff2'),
url('~fonts/roboto/roboto-italic-webfont.woff') format('woff'),
url('~fonts/roboto/roboto-italic-webfont.ttf') format('truetype'),
- url('~fonts/roboto/roboto-italic-webfont.svg#roboto-italic-webfont') format('svg');
+ url('~fonts/roboto/roboto-italic-webfont.svg#roboto-italic-webfont')
+ format('svg');
font-weight: normal;
font-display: swap;
font-style: italic;
@@ -13,12 +13,12 @@
@font-face {
font-family: mastodon-font-sans-serif;
- src:
- local('Roboto Bold'),
+ src: local('Roboto Bold'),
url('~fonts/roboto/roboto-bold-webfont.woff2') format('woff2'),
url('~fonts/roboto/roboto-bold-webfont.woff') format('woff'),
url('~fonts/roboto/roboto-bold-webfont.ttf') format('truetype'),
- url('~fonts/roboto/roboto-bold-webfont.svg#roboto-bold-webfont') format('svg');
+ url('~fonts/roboto/roboto-bold-webfont.svg#roboto-bold-webfont')
+ format('svg');
font-weight: bold;
font-display: swap;
font-style: normal;
@@ -26,12 +26,12 @@
@font-face {
font-family: mastodon-font-sans-serif;
- src:
- local('Roboto Medium'),
+ src: local('Roboto Medium'),
url('~fonts/roboto/roboto-medium-webfont.woff2') format('woff2'),
url('~fonts/roboto/roboto-medium-webfont.woff') format('woff'),
url('~fonts/roboto/roboto-medium-webfont.ttf') format('truetype'),
- url('~fonts/roboto/roboto-medium-webfont.svg#roboto-medium-webfont') format('svg');
+ url('~fonts/roboto/roboto-medium-webfont.svg#roboto-medium-webfont')
+ format('svg');
font-weight: 500;
font-display: swap;
font-style: normal;
@@ -39,12 +39,12 @@
@font-face {
font-family: mastodon-font-sans-serif;
- src:
- local('Roboto'),
+ src: local('Roboto'),
url('~fonts/roboto/roboto-regular-webfont.woff2') format('woff2'),
url('~fonts/roboto/roboto-regular-webfont.woff') format('woff'),
url('~fonts/roboto/roboto-regular-webfont.ttf') format('truetype'),
- url('~fonts/roboto/roboto-regular-webfont.svg#roboto-regular-webfont') format('svg');
+ url('~fonts/roboto/roboto-regular-webfont.svg#roboto-regular-webfont')
+ format('svg');
font-weight: normal;
font-display: swap;
font-style: normal;
diff --git a/app/javascript/styles/mastodon-light/diff.scss b/app/javascript/styles/mastodon-light/diff.scss
index c37100a28..01725cf96 100644
--- a/app/javascript/styles/mastodon-light/diff.scss
+++ b/app/javascript/styles/mastodon-light/diff.scss
@@ -152,7 +152,7 @@ html {
}
.compose-form__autosuggest-wrapper,
-.poll__option input[type="text"],
+.poll__option input[type='text'],
.compose-form .spoiler-input__input,
.compose-form__poll-wrapper select,
.search__input,
@@ -179,7 +179,9 @@ html {
}
.compose-form__poll-wrapper select {
- background: $simple-background-color url("data:image/svg+xml;utf8,
") no-repeat right 8px center / auto 16px;
+ background: $simple-background-color
+ url("data:image/svg+xml;utf8,
")
+ no-repeat right 8px center / auto 16px;
}
.compose-form__poll-wrapper,
@@ -205,7 +207,9 @@ html {
}
.drawer__inner__mastodon {
- background: $white url('data:image/svg+xml;utf8,
') no-repeat bottom / 100% auto;
+ background: $white
+ url('data:image/svg+xml;utf8,
')
+ no-repeat bottom / 100% auto;
}
// Change the colors used in compose-form
@@ -332,11 +336,13 @@ html {
color: $white;
}
-.language-dropdown__dropdown__results__item .language-dropdown__dropdown__results__item__common-name {
+.language-dropdown__dropdown__results__item
+ .language-dropdown__dropdown__results__item__common-name {
color: lighten($ui-base-color, 8%);
}
-.language-dropdown__dropdown__results__item.active .language-dropdown__dropdown__results__item__common-name {
+.language-dropdown__dropdown__results__item.active
+ .language-dropdown__dropdown__results__item__common-name {
color: darken($ui-base-color, 12%);
}
@@ -490,7 +496,8 @@ html {
background: darken($ui-secondary-color, 10%);
}
-.react-toggle.react-toggle--checked:hover:not(.react-toggle--disabled) .react-toggle-track {
+.react-toggle.react-toggle--checked:hover:not(.react-toggle--disabled)
+ .react-toggle-track {
background: lighten($ui-highlight-color, 10%);
}
@@ -522,10 +529,10 @@ html {
}
.simple_form {
- input[type="text"],
- input[type="number"],
- input[type="email"],
- input[type="password"],
+ input[type='text'],
+ input[type='number'],
+ input[type='email'],
+ input[type='password'],
textarea {
&:hover {
border-color: lighten($ui-base-color, 12%);
@@ -682,5 +689,7 @@ html {
.mute-modal select {
border: 1px solid lighten($ui-base-color, 8%);
- background: $simple-background-color url("data:image/svg+xml;utf8,
") no-repeat right 8px center / auto 16px;
+ background: $simple-background-color
+ url("data:image/svg+xml;utf8,
")
+ no-repeat right 8px center / auto 16px;
}
diff --git a/app/javascript/styles/mastodon/accessibility.scss b/app/javascript/styles/mastodon/accessibility.scss
index c5bcb5941..deaa0afda 100644
--- a/app/javascript/styles/mastodon/accessibility.scss
+++ b/app/javascript/styles/mastodon/accessibility.scss
@@ -1,4 +1,7 @@
-$emojis-requiring-inversion: 'back' 'copyright' 'curly_loop' 'currency_exchange' 'end' 'heavy_check_mark' 'heavy_division_sign' 'heavy_dollar_sign' 'heavy_minus_sign' 'heavy_multiplication_x' 'heavy_plus_sign' 'on' 'registered' 'soon' 'spider' 'telephone_receiver' 'tm' 'top' 'wavy_dash' !default;
+$emojis-requiring-inversion: 'back' 'copyright' 'curly_loop' 'currency_exchange'
+ 'end' 'heavy_check_mark' 'heavy_division_sign' 'heavy_dollar_sign'
+ 'heavy_minus_sign' 'heavy_multiplication_x' 'heavy_plus_sign' 'on'
+ 'registered' 'soon' 'spider' 'telephone_receiver' 'tm' 'top' 'wavy_dash' !default;
%emoji-color-inversion {
filter: invert(1);
diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss
index 798d520cd..08a79c11e 100644
--- a/app/javascript/styles/mastodon/admin.scss
+++ b/app/javascript/styles/mastodon/admin.scss
@@ -1,4 +1,4 @@
-@use "sass:math";
+@use 'sass:math';
$no-columns-breakpoint: 600px;
$sidebar-width: 240px;
@@ -1147,7 +1147,10 @@ a.name-tag,
@for $i from 0 through 10 {
&--#{10 * $i} {
- background-color: rgba($ui-highlight-color, 1 * (math.div(max(1, $i), 10)));
+ background-color: rgba(
+ $ui-highlight-color,
+ 1 * (math.div(max(1, $i), 10))
+ );
}
}
}
@@ -1236,7 +1239,12 @@ a.sparkline {
.skeleton {
background-color: lighten($ui-base-color, 8%);
- background-image: linear-gradient(90deg, lighten($ui-base-color, 8%), lighten($ui-base-color, 12%), lighten($ui-base-color, 8%));
+ background-image: linear-gradient(
+ 90deg,
+ lighten($ui-base-color, 8%),
+ lighten($ui-base-color, 12%),
+ lighten($ui-base-color, 8%)
+ );
background-size: 200px 100%;
background-repeat: no-repeat;
border-radius: 4px;
@@ -1285,7 +1293,10 @@ a.sparkline {
@for $i from 0 through 10 {
&--#{10 * $i} {
- background-color: rgba($ui-highlight-color, 1 * (math.div(max(1, $i), 10)));
+ background-color: rgba(
+ $ui-highlight-color,
+ 1 * (math.div(max(1, $i), 10))
+ );
}
}
}
@@ -1431,7 +1442,7 @@ a.sparkline {
&::after {
display: block;
- content: "";
+ content: '';
width: 50px;
height: 21px;
position: absolute;
@@ -1825,7 +1836,7 @@ a.sparkline {
&::after {
position: absolute;
- content: "";
+ content: '';
width: 1px;
background: $highlight-text-color;
bottom: 0;
diff --git a/app/javascript/styles/mastodon/basics.scss b/app/javascript/styles/mastodon/basics.scss
index 413a1cdd6..1d08b12e5 100644
--- a/app/javascript/styles/mastodon/basics.scss
+++ b/app/javascript/styles/mastodon/basics.scss
@@ -14,7 +14,7 @@ body {
font-weight: 400;
color: $primary-text-color;
text-rendering: optimizelegibility;
- font-feature-settings: "kern";
+ font-feature-settings: 'kern';
text-size-adjust: none;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0%);
-webkit-tap-highlight-color: transparent;
@@ -31,7 +31,9 @@ body {
// Droid Sans => Older Androids (<4.0)
// Helvetica Neue => Older macOS <10.11
// $font-sans-serif => web-font (Roboto) fallback and newer Androids (>=4.0)
- font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", $font-sans-serif, sans-serif;
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI',
+ Oxygen, Ubuntu, Cantarell, 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
+ $font-sans-serif, sans-serif;
}
&.app-body {
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 25cbbf000..dc1153073 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -387,7 +387,7 @@ body > [data-popper-placement] {
.ellipsis {
&::after {
- content: "…";
+ content: '…';
}
}
@@ -404,7 +404,7 @@ body > [data-popper-placement] {
color: $highlight-text-color;
}
- input[type="checkbox"] {
+ input[type='checkbox'] {
display: none;
}
@@ -423,7 +423,9 @@ body > [data-popper-placement] {
&.active {
border-color: $highlight-text-color;
- background: $highlight-text-color url("data:image/svg+xml;utf8,
") center center no-repeat;
+ background: $highlight-text-color
+ url("data:image/svg+xml;utf8,
")
+ center center no-repeat;
}
}
}
@@ -647,7 +649,12 @@ body > [data-popper-placement] {
margin: 5px;
&__actions {
- background: linear-gradient(180deg, rgba($base-shadow-color, 0.8) 0, rgba($base-shadow-color, 0.35) 80%, transparent);
+ background: linear-gradient(
+ 180deg,
+ rgba($base-shadow-color, 0.8) 0,
+ rgba($base-shadow-color, 0.35) 80%,
+ transparent
+ );
display: flex;
align-items: flex-start;
justify-content: space-between;
@@ -675,7 +682,12 @@ body > [data-popper-placement] {
left: 0;
right: 0;
box-sizing: border-box;
- background: linear-gradient(0deg, rgba($base-shadow-color, 0.8) 0, rgba($base-shadow-color, 0.35) 80%, transparent);
+ background: linear-gradient(
+ 0deg,
+ rgba($base-shadow-color, 0.8) 0,
+ rgba($base-shadow-color, 0.35) 80%,
+ transparent
+ );
}
}
@@ -1080,8 +1092,13 @@ body > [data-popper-placement] {
cursor: auto;
@keyframes fade {
- 0% { opacity: 0; }
- 100% { opacity: 1; }
+ 0% {
+ opacity: 0;
+ }
+
+ 100% {
+ opacity: 1;
+ }
}
opacity: 1;
@@ -1827,11 +1844,11 @@ a.account__display-name {
justify-content: center;
flex-direction: column;
scrollbar-width: none; /* Firefox */
- -ms-overflow-style: none; /* IE 10+ */
+ -ms-overflow-style: none; /* IE 10+ */
* {
scrollbar-width: none; /* Firefox */
- -ms-overflow-style: none; /* IE 10+ */
+ -ms-overflow-style: none; /* IE 10+ */
}
&::-webkit-scrollbar,
@@ -2863,7 +2880,9 @@ $ui-header-height: 55px;
}
.drawer__inner__mastodon {
- background: lighten($ui-base-color, 13%) url('data:image/svg+xml;utf8,
') no-repeat bottom / 100% auto;
+ background: lighten($ui-base-color, 13%)
+ url('data:image/svg+xml;utf8,
')
+ no-repeat bottom / 100% auto;
flex: 1;
min-height: 47px;
display: none;
@@ -2918,7 +2937,8 @@ $ui-header-height: 55px;
overflow-y: auto;
}
- @supports (display: grid) { // hack to fix Chrome <57
+ @supports (display: grid) {
+ // hack to fix Chrome <57
contain: strict;
}
@@ -2939,7 +2959,8 @@ $ui-header-height: 55px;
}
.scrollable.fullscreen {
- @supports (display: grid) { // hack to fix Chrome <57
+ @supports (display: grid) {
+ // hack to fix Chrome <57
contain: none;
}
}
@@ -3043,7 +3064,8 @@ $ui-header-height: 55px;
transition: background-color 0.2s ease;
}
-.react-toggle:is(:hover, :focus-within):not(.react-toggle--disabled) .react-toggle-track {
+.react-toggle:is(:hover, :focus-within):not(.react-toggle--disabled)
+ .react-toggle-track {
background-color: darken($ui-base-color, 10%);
}
@@ -3051,7 +3073,8 @@ $ui-header-height: 55px;
background-color: darken($ui-highlight-color, 2%);
}
-.react-toggle--checked:is(:hover, :focus-within):not(.react-toggle--disabled) .react-toggle-track {
+.react-toggle--checked:is(:hover, :focus-within):not(.react-toggle--disabled)
+ .react-toggle-track {
background-color: $ui-highlight-color;
}
@@ -3646,7 +3669,7 @@ a.status-card.compact:hover {
&::before {
display: block;
- content: "";
+ content: '';
position: absolute;
bottom: -13px;
left: 0;
@@ -3656,7 +3679,11 @@ a.status-card.compact:hover {
pointer-events: none;
height: 28px;
z-index: 1;
- background: radial-gradient(ellipse, rgba($ui-highlight-color, 0.23) 0%, rgba($ui-highlight-color, 0) 60%);
+ background: radial-gradient(
+ ellipse,
+ rgba($ui-highlight-color, 0.23) 0%,
+ rgba($ui-highlight-color, 0) 60%
+ );
}
}
@@ -4241,7 +4268,8 @@ a.status-card.compact:hover {
align-items: center;
justify-content: center;
- @supports (display: grid) { // hack to fix Chrome <57
+ @supports (display: grid) {
+ // hack to fix Chrome <57
contain: strict;
}
@@ -5747,7 +5775,9 @@ a.status-card.compact:hover {
width: auto;
outline: 0;
font-family: inherit;
- background: $simple-background-color url("data:image/svg+xml;utf8,
") no-repeat right 8px center / auto 16px;
+ background: $simple-background-color
+ url("data:image/svg+xml;utf8,
")
+ no-repeat right 8px center / auto 16px;
border: 1px solid darken($simple-background-color, 14%);
border-radius: 4px;
padding: 6px 10px;
@@ -6154,7 +6184,12 @@ a.status-card.compact:hover {
left: 0;
right: 0;
box-sizing: border-box;
- background: linear-gradient(0deg, rgba($base-shadow-color, 0.85) 0, rgba($base-shadow-color, 0.45) 60%, transparent);
+ background: linear-gradient(
+ 0deg,
+ rgba($base-shadow-color, 0.85) 0,
+ rgba($base-shadow-color, 0.45) 60%,
+ transparent
+ );
padding: 0 15px;
opacity: 0;
transition: opacity 0.1s ease;
@@ -6295,7 +6330,7 @@ a.status-card.compact:hover {
}
&::before {
- content: "";
+ content: '';
width: 50px;
background: rgba($white, 0.35);
border-radius: 4px;
@@ -6365,7 +6400,7 @@ a.status-card.compact:hover {
position: relative;
&::before {
- content: "";
+ content: '';
width: 100%;
background: rgba($white, 0.35);
border-radius: 4px;
@@ -6448,7 +6483,11 @@ a.status-card.compact:hover {
}
.scrollable .account-card__bio::after {
- background: linear-gradient(to left, lighten($ui-base-color, 8%), transparent);
+ background: linear-gradient(
+ to left,
+ lighten($ui-base-color, 8%),
+ transparent
+ );
}
.account-gallery__container {
@@ -6509,7 +6548,7 @@ a.status-card.compact:hover {
&::before,
&::after {
display: block;
- content: "";
+ content: '';
position: absolute;
bottom: 0;
left: 50%;
@@ -6575,8 +6614,8 @@ a.status-card.compact:hover {
text-overflow: ellipsis;
cursor: pointer;
- input[type="radio"],
- input[type="checkbox"] {
+ input[type='radio'],
+ input[type='checkbox'] {
display: none;
}
@@ -6635,9 +6674,17 @@ noscript {
}
@keyframes flicker {
- 0% { opacity: 1; }
- 30% { opacity: 0.75; }
- 100% { opacity: 1; }
+ 0% {
+ opacity: 1;
+ }
+
+ 30% {
+ opacity: 0.75;
+ }
+
+ 100% {
+ opacity: 1;
+ }
}
@media screen and (max-width: 630px) and (max-height: 400px) {
@@ -6658,7 +6705,9 @@ noscript {
.navigation-bar {
& > a:first-child {
will-change: margin-top, margin-left, margin-right, width;
- transition: margin-top $duration $delay, margin-left $duration ($duration + $delay), margin-right $duration ($duration + $delay);
+ transition: margin-top $duration $delay,
+ margin-left $duration ($duration + $delay),
+ margin-right $duration ($duration + $delay);
}
& > .navigation-bar__profile-edit {
@@ -6669,15 +6718,12 @@ noscript {
.navigation-bar__actions {
& > .icon-button.close {
will-change: opacity transform;
- transition:
- opacity $duration * 0.5 $delay,
- transform $duration $delay;
+ transition: opacity $duration * 0.5 $delay, transform $duration $delay;
}
& > .compose__action-bar .icon-button {
will-change: opacity transform;
- transition:
- opacity $duration * 0.5 $delay + $duration * 0.5,
+ transition: opacity $duration * 0.5 $delay + $duration * 0.5,
transform $duration $delay;
}
}
@@ -7677,7 +7723,11 @@ noscript {
&.active {
transition: all 100ms ease-in;
transition-property: background-color, color;
- background-color: mix(lighten($ui-base-color, 12%), $ui-highlight-color, 80%);
+ background-color: mix(
+ lighten($ui-base-color, 12%),
+ $ui-highlight-color,
+ 80%
+ );
.reactions-bar__item__count {
color: lighten($highlight-text-color, 8%);
@@ -7730,7 +7780,7 @@ noscript {
&.unread {
&::before {
- content: "";
+ content: '';
position: absolute;
top: 0;
left: 0;
@@ -8258,14 +8308,14 @@ noscript {
counter-increment: list-counter;
&::before {
- content: counter(list-counter) ".";
+ content: counter(list-counter) '.';
position: absolute;
left: 0;
}
}
ul > li::before {
- content: "";
+ content: '';
position: absolute;
background-color: $darker-text-color;
border-radius: 50%;
diff --git a/app/javascript/styles/mastodon/emoji_picker.scss b/app/javascript/styles/mastodon/emoji_picker.scss
index 1042ddda8..0d7a7df2e 100644
--- a/app/javascript/styles/mastodon/emoji_picker.scss
+++ b/app/javascript/styles/mastodon/emoji_picker.scss
@@ -174,7 +174,7 @@
&:hover::before {
z-index: -1;
- content: "";
+ content: '';
position: absolute;
top: 0;
left: 0;
diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss
index 1841dc8bf..e4539deff 100644
--- a/app/javascript/styles/mastodon/forms.scss
+++ b/app/javascript/styles/mastodon/forms.scss
@@ -299,7 +299,7 @@ code {
max-width: 100%;
height: auto;
border-radius: 4px;
- background: url("images/void.png");
+ background: url('images/void.png');
&:last-child {
margin-bottom: 0;
@@ -384,7 +384,7 @@ code {
flex: 1 1 auto;
}
- input[type="checkbox"] {
+ input[type='checkbox'] {
position: absolute;
left: 0;
top: 5px;
@@ -400,12 +400,12 @@ code {
border-radius: 4px;
}
- input[type="text"],
- input[type="number"],
- input[type="email"],
- input[type="password"],
- input[type="url"],
- input[type="datetime-local"],
+ input[type='text'],
+ input[type='number'],
+ input[type='email'],
+ input[type='password'],
+ input[type='url'],
+ input[type='datetime-local'],
textarea {
box-sizing: border-box;
font-size: 16px;
@@ -443,11 +443,11 @@ code {
}
}
- input[type="text"],
- input[type="number"],
- input[type="email"],
- input[type="password"],
- input[type="datetime-local"] {
+ input[type='text'],
+ input[type='number'],
+ input[type='email'],
+ input[type='password'],
+ input[type='datetime-local'] {
&:focus:invalid:not(:placeholder-shown),
&:required:invalid:not(:placeholder-shown) {
border-color: lighten($error-red, 12%);
@@ -459,11 +459,11 @@ code {
color: lighten($error-red, 12%);
}
- input[type="text"],
- input[type="number"],
- input[type="email"],
- input[type="password"],
- input[type="datetime-local"],
+ input[type='text'],
+ input[type='number'],
+ input[type='email'],
+ input[type='password'],
+ input[type='datetime-local'],
textarea,
select {
border-color: lighten($error-red, 12%);
@@ -567,7 +567,9 @@ code {
outline: 0;
font-family: inherit;
resize: vertical;
- background: darken($ui-base-color, 10%) url("data:image/svg+xml;utf8,
") no-repeat right 8px center / auto 16px;
+ background: darken($ui-base-color, 10%)
+ url("data:image/svg+xml;utf8,
")
+ no-repeat right 8px center / auto 16px;
border: 1px solid darken($ui-base-color, 14%);
border-radius: 4px;
padding-left: 10px;
@@ -607,7 +609,11 @@ code {
right: 0;
bottom: 1px;
width: 5px;
- background-image: linear-gradient(to right, rgba(darken($ui-base-color, 10%), 0), darken($ui-base-color, 10%));
+ background-image: linear-gradient(
+ to right,
+ rgba(darken($ui-base-color, 10%), 0),
+ darken($ui-base-color, 10%)
+ );
}
}
}
@@ -995,7 +1001,7 @@ code {
flex: 1 1 auto;
}
- input[type="text"] {
+ input[type='text'] {
background: transparent;
border: 0;
padding: 10px;
diff --git a/app/javascript/styles/mastodon/modal.scss b/app/javascript/styles/mastodon/modal.scss
index a333926dd..6170877b2 100644
--- a/app/javascript/styles/mastodon/modal.scss
+++ b/app/javascript/styles/mastodon/modal.scss
@@ -1,5 +1,7 @@
.modal-layout {
- background: $ui-base-color url('data:image/svg+xml;utf8,
') repeat-x bottom fixed;
+ background: $ui-base-color
+ url('data:image/svg+xml;utf8,
')
+ repeat-x bottom fixed;
display: flex;
flex-direction: column;
height: 100vh;
diff --git a/app/javascript/styles/mastodon/polls.scss b/app/javascript/styles/mastodon/polls.scss
index 03d2cb4b2..b30932e04 100644
--- a/app/javascript/styles/mastodon/polls.scss
+++ b/app/javascript/styles/mastodon/polls.scss
@@ -64,8 +64,8 @@
max-width: calc(100% - 45px - 25px);
}
- input[type="radio"],
- input[type="checkbox"] {
+ input[type='radio'],
+ input[type='checkbox'] {
display: none;
}
@@ -73,7 +73,7 @@
flex: 1 1 auto;
}
- input[type="text"] {
+ input[type='text'] {
display: block;
box-sizing: border-box;
width: 100%;
@@ -263,7 +263,9 @@
width: auto;
outline: 0;
font-family: inherit;
- background: $simple-background-color url("data:image/svg+xml;utf8,
") no-repeat right 8px center / auto 16px;
+ background: $simple-background-color
+ url("data:image/svg+xml;utf8,
")
+ no-repeat right 8px center / auto 16px;
border: 1px solid darken($simple-background-color, 14%);
border-radius: 4px;
padding: 6px 10px;
diff --git a/app/javascript/styles/mastodon/rtl.scss b/app/javascript/styles/mastodon/rtl.scss
index 39f4653bb..e60087dab 100644
--- a/app/javascript/styles/mastodon/rtl.scss
+++ b/app/javascript/styles/mastodon/rtl.scss
@@ -229,8 +229,8 @@ body.rtl {
padding-right: 0;
}
- .simple_form .check_boxes .checkbox input[type="checkbox"],
- .simple_form .input.boolean input[type="checkbox"] {
+ .simple_form .check_boxes .checkbox input[type='checkbox'],
+ .simple_form .input.boolean input[type='checkbox'] {
left: auto;
right: 0;
}
@@ -268,12 +268,18 @@ body.rtl {
&::after {
right: auto;
left: 0;
- background-image: linear-gradient(to left, rgba(darken($ui-base-color, 10%), 0), darken($ui-base-color, 10%));
+ background-image: linear-gradient(
+ to left,
+ rgba(darken($ui-base-color, 10%), 0),
+ darken($ui-base-color, 10%)
+ );
}
}
.simple_form select {
- background: darken($ui-base-color, 10%) url("data:image/svg+xml;utf8,
") no-repeat left 8px center / auto 16px;
+ background: darken($ui-base-color, 10%)
+ url("data:image/svg+xml;utf8,
")
+ no-repeat left 8px center / auto 16px;
}
.table th,
@@ -320,11 +326,11 @@ body.rtl {
}
.fa-chevron-left::before {
- content: "\F054";
+ content: '\F054';
}
.fa-chevron-right::before {
- content: "\F053";
+ content: '\F053';
}
.column-back-button__icon {
diff --git a/app/javascript/styles/mastodon/statuses.scss b/app/javascript/styles/mastodon/statuses.scss
index ce71d11e4..a42f1f42c 100644
--- a/app/javascript/styles/mastodon/statuses.scss
+++ b/app/javascript/styles/mastodon/statuses.scss
@@ -138,7 +138,7 @@ a.button.logo-button {
}
.embed {
- .status__content[data-spoiler="folded"] {
+ .status__content[data-spoiler='folded'] {
.e-content {
display: none;
}
diff --git a/app/javascript/styles/mastodon/variables.scss b/app/javascript/styles/mastodon/variables.scss
index 2f6c41d5f..7de25f8fd 100644
--- a/app/javascript/styles/mastodon/variables.scss
+++ b/app/javascript/styles/mastodon/variables.scss
@@ -1,18 +1,18 @@
// Commonly used web colors
-$black: #000000; // Black
-$white: #ffffff; // White
-$success-green: #79bd9a !default; // Padua
-$error-red: #df405a !default; // Cerise
-$warning-red: #ff5050 !default; // Sunset Orange
-$gold-star: #ca8f04 !default; // Dark Goldenrod
+$black: #000000; // Black
+$white: #ffffff; // White
+$success-green: #79bd9a !default; // Padua
+$error-red: #df405a !default; // Cerise
+$warning-red: #ff5050 !default; // Sunset Orange
+$gold-star: #ca8f04 !default; // Dark Goldenrod
$red-bookmark: $warning-red;
// Values from the classic Mastodon UI
-$classic-base-color: #282c37; // Midnight Express
-$classic-primary-color: #9baec8; // Echo Blue
-$classic-secondary-color: #d9e1e8; // Pattens Blue
-$classic-highlight-color: #6364ff; // Brand purple
+$classic-base-color: #282c37; // Midnight Express
+$classic-primary-color: #9baec8; // Echo Blue
+$classic-secondary-color: #d9e1e8; // Pattens Blue
+$classic-highlight-color: #6364ff; // Brand purple
// Variables for defaults in UI
$base-shadow-color: $black !default;
@@ -23,10 +23,13 @@ $valid-value-color: $success-green !default;
$error-value-color: $error-red !default;
// Tell UI to use selected colors
-$ui-base-color: $classic-base-color !default; // Darkest
-$ui-base-lighter-color: lighten($ui-base-color, 26%) !default; // Lighter darkest
-$ui-primary-color: $classic-primary-color !default; // Lighter
-$ui-secondary-color: $classic-secondary-color !default; // Lightest
+$ui-base-color: $classic-base-color !default; // Darkest
+$ui-base-lighter-color: lighten(
+ $ui-base-color,
+ 26%
+) !default; // Lighter darkest
+$ui-primary-color: $classic-primary-color !default; // Lighter
+$ui-secondary-color: $classic-secondary-color !default; // Lightest
$ui-highlight-color: $classic-highlight-color !default;
// Variables for texts
diff --git a/app/javascript/styles/mastodon/widgets.scss b/app/javascript/styles/mastodon/widgets.scss
index 7a25d121b..ef7bfc6de 100644
--- a/app/javascript/styles/mastodon/widgets.scss
+++ b/app/javascript/styles/mastodon/widgets.scss
@@ -1,4 +1,4 @@
-@use "sass:math";
+@use 'sass:math';
.hero-widget {
margin-bottom: 10px;
diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb
index bcda001f5..77527f2db 100644
--- a/app/services/post_status_service.rb
+++ b/app/services/post_status_service.rb
@@ -6,6 +6,15 @@ class PostStatusService < BaseService
MIN_SCHEDULE_OFFSET = 5.minutes.freeze
+ class UnexpectedMentionsError < StandardError
+ attr_reader :accounts
+
+ def initialize(message, accounts)
+ super(message)
+ @accounts = accounts
+ end
+ end
+
# Post a text status update, fetch and notify remote users mentioned
# @param [Account] account Account from which to post
# @param [Hash] options
@@ -21,6 +30,7 @@ class PostStatusService < BaseService
# @option [Doorkeeper::Application] :application
# @option [String] :idempotency Optional idempotency key
# @option [Boolean] :with_rate_limit
+ # @option [Enumerable] :allowed_mentions Optional array of expected mentioned account IDs, raises `UnexpectedMentionsError` if unexpected accounts end up in mentions
# @return [Status]
def call(account, options = {})
@account = account
@@ -72,14 +82,27 @@ class PostStatusService < BaseService
end
def process_status!
+ @status = @account.statuses.new(status_attributes)
+ process_mentions_service.call(@status, save_records: false)
+ safeguard_mentions!(@status)
+
# The following transaction block is needed to wrap the UPDATEs to
# the media attachments when the status is created
-
ApplicationRecord.transaction do
- @status = @account.statuses.create!(status_attributes)
+ @status.save!
end
end
+ def safeguard_mentions!(status)
+ return if @options[:allowed_mentions].nil?
+ expected_account_ids = @options[:allowed_mentions].map(&:to_i)
+
+ unexpected_accounts = status.mentions.map(&:account).to_a.reject { |mentioned_account| expected_account_ids.include?(mentioned_account.id) }
+ return if unexpected_accounts.empty?
+
+ raise UnexpectedMentionsError.new('Post would be sent to unexpected accounts', unexpected_accounts)
+ end
+
def schedule_status!
status_for_validation = @account.statuses.build(status_attributes)
@@ -102,7 +125,6 @@ class PostStatusService < BaseService
def postprocess_status!
process_hashtags_service.call(@status)
- process_mentions_service.call(@status)
Trends.tags.register(@status)
LinkCrawlWorker.perform_async(@status.id)
DistributionWorker.perform_async(@status.id)
diff --git a/app/services/process_mentions_service.rb b/app/services/process_mentions_service.rb
index b117db8c2..93a96667e 100644
--- a/app/services/process_mentions_service.rb
+++ b/app/services/process_mentions_service.rb
@@ -3,12 +3,13 @@
class ProcessMentionsService < BaseService
include Payloadable
- # Scan status for mentions and fetch remote mentioned users, create
- # local mention pointers, send Salmon notifications to mentioned
- # remote users
+ # Scan status for mentions and fetch remote mentioned users,
+ # and create local mention pointers
# @param [Status] status
- def call(status)
+ # @param [Boolean] save_records Whether to save records in database
+ def call(status, save_records: true)
@status = status
+ @save_records = save_records
return unless @status.local?
@@ -55,14 +56,15 @@ class ProcessMentionsService < BaseService
next match if mention_undeliverable?(mentioned_account) || mentioned_account&.suspended?
mention = @previous_mentions.find { |x| x.account_id == mentioned_account.id }
- mention ||= mentioned_account.mentions.new(status: @status)
+ mention ||= @current_mentions.find { |x| x.account_id == mentioned_account.id }
+ mention ||= @status.mentions.new(account: mentioned_account)
@current_mentions << mention
"@#{mentioned_account.acct}"
end
- @status.save!
+ @status.save! if @save_records
end
def assign_mentions!
@@ -73,11 +75,12 @@ class ProcessMentionsService < BaseService
mentioned_account_ids = @current_mentions.map(&:account_id)
blocked_account_ids = Set.new(@status.account.block_relationships.where(target_account_id: mentioned_account_ids).pluck(:target_account_id))
- @current_mentions.select! { |mention| !(blocked_account_ids.include?(mention.account_id) || blocked_domains.include?(mention.account.domain)) }
+ dropped_mentions, @current_mentions = @current_mentions.partition { |mention| blocked_account_ids.include?(mention.account_id) || blocked_domains.include?(mention.account.domain) }
+ dropped_mentions.each(&:destroy)
end
@current_mentions.each do |mention|
- mention.save if mention.new_record?
+ mention.save if mention.new_record? && @save_records
end
# If previous mentions are no longer contained in the text, convert them
diff --git a/app/views/admin/accounts/show.html.haml b/app/views/admin/accounts/show.html.haml
index db5c255c9..c8a9d33a7 100644
--- a/app/views/admin/accounts/show.html.haml
+++ b/app/views/admin/accounts/show.html.haml
@@ -37,7 +37,7 @@
.dashboard__counters__num= number_to_human_size @account.media_attachments.sum('file_file_size')
.dashboard__counters__label= t 'admin.accounts.media_attachments'
%div
- = link_to admin_account_relationships_path(@account.id, location: 'local', relationship: 'followed_by') do
+ = link_to admin_account_relationships_path(@account.id, location: @account.local? ? nil : 'local', relationship: 'followed_by') do
.dashboard__counters__num= number_with_delimiter @account.local_followers_count
.dashboard__counters__label= t 'admin.accounts.followers'
%div
diff --git a/config/sidekiq.yml b/config/sidekiq.yml
index b8739aab3..2278329a5 100644
--- a/config/sidekiq.yml
+++ b/config/sidekiq.yml
@@ -9,52 +9,52 @@
- [scheduler]
:scheduler:
:listened_queues_only: true
-:schedule:
- scheduled_statuses_scheduler:
- every: '5m'
- class: Scheduler::ScheduledStatusesScheduler
- queue: scheduler
- trends_refresh_scheduler:
- every: '5m'
- class: Scheduler::Trends::RefreshScheduler
- queue: scheduler
- trends_review_notifications_scheduler:
- every: '6h'
- class: Scheduler::Trends::ReviewNotificationsScheduler
- queue: scheduler
- indexing_scheduler:
- every: '5m'
- class: Scheduler::IndexingScheduler
- queue: scheduler
- vacuum_scheduler:
- cron: '<%= Random.rand(0..59) %> <%= Random.rand(3..5) %> * * *'
- class: Scheduler::VacuumScheduler
- queue: scheduler
- follow_recommendations_scheduler:
- cron: '<%= Random.rand(0..59) %> <%= Random.rand(6..9) %> * * *'
- class: Scheduler::FollowRecommendationsScheduler
- queue: scheduler
- user_cleanup_scheduler:
- cron: '<%= Random.rand(0..59) %> <%= Random.rand(4..6) %> * * *'
- class: Scheduler::UserCleanupScheduler
- queue: scheduler
- ip_cleanup_scheduler:
- cron: '<%= Random.rand(0..59) %> <%= Random.rand(3..5) %> * * *'
- class: Scheduler::IpCleanupScheduler
- queue: scheduler
- pghero_scheduler:
- cron: '0 0 * * *'
- class: Scheduler::PgheroScheduler
- queue: scheduler
- instance_refresh_scheduler:
- cron: '0 * * * *'
- class: Scheduler::InstanceRefreshScheduler
- queue: scheduler
- accounts_statuses_cleanup_scheduler:
- interval: 1 minute
- class: Scheduler::AccountsStatusesCleanupScheduler
- queue: scheduler
- suspended_user_cleanup_scheduler:
- interval: 1 minute
- class: Scheduler::SuspendedUserCleanupScheduler
- queue: scheduler
+ :schedule:
+ scheduled_statuses_scheduler:
+ every: '5m'
+ class: Scheduler::ScheduledStatusesScheduler
+ queue: scheduler
+ trends_refresh_scheduler:
+ every: '5m'
+ class: Scheduler::Trends::RefreshScheduler
+ queue: scheduler
+ trends_review_notifications_scheduler:
+ every: '6h'
+ class: Scheduler::Trends::ReviewNotificationsScheduler
+ queue: scheduler
+ indexing_scheduler:
+ every: '5m'
+ class: Scheduler::IndexingScheduler
+ queue: scheduler
+ vacuum_scheduler:
+ cron: '<%= Random.rand(0..59) %> <%= Random.rand(3..5) %> * * *'
+ class: Scheduler::VacuumScheduler
+ queue: scheduler
+ follow_recommendations_scheduler:
+ cron: '<%= Random.rand(0..59) %> <%= Random.rand(6..9) %> * * *'
+ class: Scheduler::FollowRecommendationsScheduler
+ queue: scheduler
+ user_cleanup_scheduler:
+ cron: '<%= Random.rand(0..59) %> <%= Random.rand(4..6) %> * * *'
+ class: Scheduler::UserCleanupScheduler
+ queue: scheduler
+ ip_cleanup_scheduler:
+ cron: '<%= Random.rand(0..59) %> <%= Random.rand(3..5) %> * * *'
+ class: Scheduler::IpCleanupScheduler
+ queue: scheduler
+ pghero_scheduler:
+ cron: '0 0 * * *'
+ class: Scheduler::PgheroScheduler
+ queue: scheduler
+ instance_refresh_scheduler:
+ cron: '0 * * * *'
+ class: Scheduler::InstanceRefreshScheduler
+ queue: scheduler
+ accounts_statuses_cleanup_scheduler:
+ interval: 1 minute
+ class: Scheduler::AccountsStatusesCleanupScheduler
+ queue: scheduler
+ suspended_user_cleanup_scheduler:
+ interval: 1 minute
+ class: Scheduler::SuspendedUserCleanupScheduler
+ queue: scheduler
diff --git a/config/webpack/shared.js b/config/webpack/shared.js
index bbf9f51f1..405858d0c 100644
--- a/config/webpack/shared.js
+++ b/config/webpack/shared.js
@@ -81,7 +81,7 @@ module.exports = {
},
minChunks: 2,
minSize: 0,
- test: /^(?!.*[\\\/]node_modules[\\\/]react-intl[\\\/]).+$/,
+ test: /^(?!.*[\\/]node_modules[\\/]react-intl[\\/]).+$/,
},
},
},
diff --git a/lib/assets/wordmark.light.css b/lib/assets/wordmark.light.css
index 9a601f972..b8c9993fd 100644
--- a/lib/assets/wordmark.light.css
+++ b/lib/assets/wordmark.light.css
@@ -1 +1,3 @@
-use { color: #000 !important; }
+use {
+ color: #000 !important;
+}
diff --git a/package.json b/package.json
index 193b908f6..5f3528cab 100644
--- a/package.json
+++ b/package.json
@@ -13,7 +13,7 @@
"test": "${npm_execpath} run test:lint:js && ${npm_execpath} run test:jest",
"test:lint": "${npm_execpath} run test:lint:js && ${npm_execpath} run test:lint:sass",
"test:lint:js": "eslint --ext=js . --cache --report-unused-disable-directives",
- "test:lint:sass": "stylelint \"**/*.{css,scss}\"",
+ "test:lint:sass": "stylelint \"**/*.{css,scss}\" && prettier --check \"**/*.{css,scss}\"",
"test:jest": "cross-env NODE_ENV=test jest",
"format": "prettier --write \"**/*.{json,yml}\"",
"format-check": "prettier --check \"**/*.{json,yml}\""
@@ -159,8 +159,8 @@
"raf": "^3.4.1",
"react-intl-translations-manager": "^5.0.3",
"react-test-renderer": "^16.14.0",
- "stylelint": "^14.16.1",
- "stylelint-config-standard-scss": "^6.1.0",
+ "stylelint": "^15.1.0",
+ "stylelint-config-standard-scss": "^7.0.0",
"webpack-dev-server": "^3.11.3",
"yargs": "^17.6.2"
},
diff --git a/spec/controllers/api/v1/accounts/statuses_controller_spec.rb b/spec/controllers/api/v1/accounts/statuses_controller_spec.rb
index b962b3398..01d745fc0 100644
--- a/spec/controllers/api/v1/accounts/statuses_controller_spec.rb
+++ b/spec/controllers/api/v1/accounts/statuses_controller_spec.rb
@@ -1,3 +1,4 @@
+# frozen_string_literal: true
require 'rails_helper'
describe Api::V1::Accounts::StatusesController do
@@ -15,7 +16,12 @@ describe Api::V1::Accounts::StatusesController do
it 'returns http success' do
get :index, params: { account_id: user.account.id, limit: 1 }
- expect(response).to have_http_status(200)
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'returns expected headers' do
+ get :index, params: { account_id: user.account.id, limit: 1 }
+
expect(response.headers['Link'].links.size).to eq(2)
end
@@ -23,19 +29,29 @@ describe Api::V1::Accounts::StatusesController do
it 'returns http success' do
get :index, params: { account_id: user.account.id, only_media: true }
- expect(response).to have_http_status(200)
+ expect(response).to have_http_status(:ok)
end
end
context 'with exclude replies' do
+ let!(:older_statuses) { user.account.statuses.destroy_all }
+ let!(:status) { Fabricate(:status, account: user.account) }
+ let!(:status_self_reply) { Fabricate(:status, account: user.account, thread: status) }
+
before do
- Fabricate(:status, account: user.account, thread: Fabricate(:status))
+ Fabricate(:status, account: user.account, thread: Fabricate(:status)) # Reply to another user
+ get :index, params: { account_id: user.account.id, exclude_replies: true }
end
it 'returns http success' do
- get :index, params: { account_id: user.account.id, exclude_replies: true }
+ expect(response).to have_http_status(:ok)
+ end
+
+ it 'returns posts along with self replies' do
+ json = body_as_json
+ post_ids = json.map { |item| item[:id].to_i }.sort
- expect(response).to have_http_status(200)
+ expect(post_ids).to eq [status.id, status_self_reply.id]
end
end
@@ -47,7 +63,7 @@ describe Api::V1::Accounts::StatusesController do
it 'returns http success' do
get :index, params: { account_id: user.account.id, pinned: true }
- expect(response).to have_http_status(200)
+ expect(response).to have_http_status(:ok)
end
end
@@ -55,12 +71,15 @@ describe Api::V1::Accounts::StatusesController do
let(:account) { Fabricate(:account, username: 'bob', domain: 'example.com') }
let(:status) { Fabricate(:status, account: account) }
let(:private_status) { Fabricate(:status, account: account, visibility: :private) }
- let!(:pin) { Fabricate(:status_pin, account: account, status: status) }
- let!(:private_pin) { Fabricate(:status_pin, account: account, status: private_status) }
+
+ before do
+ Fabricate(:status_pin, account: account, status: status)
+ Fabricate(:status_pin, account: account, status: private_status)
+ end
it 'returns http success' do
get :index, params: { account_id: account.id, pinned: true }
- expect(response).to have_http_status(200)
+ expect(response).to have_http_status(:ok)
end
context 'when user does not follow account' do
diff --git a/spec/controllers/api/v1/statuses_controller_spec.rb b/spec/controllers/api/v1/statuses_controller_spec.rb
index 24810a5d2..bd8b8013a 100644
--- a/spec/controllers/api/v1/statuses_controller_spec.rb
+++ b/spec/controllers/api/v1/statuses_controller_spec.rb
@@ -133,6 +133,23 @@ RSpec.describe Api::V1::StatusesController, type: :controller do
end
end
+ context 'with a safeguard' do
+ let!(:alice) { Fabricate(:account, username: 'alice') }
+ let!(:bob) { Fabricate(:account, username: 'bob') }
+
+ before do
+ post :create, params: { status: '@alice hm, @bob is really annoying lately', allowed_mentions: [alice.id] }
+ end
+
+ it 'returns http unprocessable entity' do
+ expect(response).to have_http_status(422)
+ end
+
+ it 'returns serialized extra accounts in body' do
+ expect(body_as_json[:unexpected_accounts].map { |a| a.slice(:id, :acct) }).to eq [{ id: bob.id.to_s, acct: bob.acct }]
+ end
+ end
+
context 'with missing parameters' do
before do
post :create, params: {}
diff --git a/spec/serializers/rest/account_serializer_spec.rb b/spec/serializers/rest/account_serializer_spec.rb
new file mode 100644
index 000000000..ce29df3a7
--- /dev/null
+++ b/spec/serializers/rest/account_serializer_spec.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe REST::AccountSerializer do
+ let(:role) { Fabricate(:user_role, name: 'Role', highlighted: true) }
+ let(:user) { Fabricate(:user, role: role) }
+ let(:account) { user.account}
+
+ subject { JSON.parse(ActiveModelSerializers::SerializableResource.new(account, serializer: REST::AccountSerializer).to_json) }
+
+ context 'when the account is suspended' do
+ before do
+ account.suspend!
+ end
+
+ it 'returns empty roles' do
+ expect(subject['roles']).to eq []
+ end
+ end
+
+ context 'when the account has a highlighted role' do
+ let(:role) { Fabricate(:user_role, name: 'Role', highlighted: true) }
+
+ it 'returns the expected role' do
+ expect(subject['roles'].first).to include({ 'name' => 'Role' })
+ end
+ end
+
+ context 'when the account has a non-highlighted role' do
+ let(:role) { Fabricate(:user_role, name: 'Role', highlighted: false) }
+
+ it 'returns empty roles' do
+ expect(subject['roles']).to eq []
+ end
+ end
+end
diff --git a/spec/services/post_status_service_spec.rb b/spec/services/post_status_service_spec.rb
index d21270c79..28f20e9c7 100644
--- a/spec/services/post_status_service_spec.rb
+++ b/spec/services/post_status_service_spec.rb
@@ -138,7 +138,26 @@ RSpec.describe PostStatusService, type: :service do
status = subject.call(account, text: "test status update")
expect(ProcessMentionsService).to have_received(:new)
- expect(mention_service).to have_received(:call).with(status)
+ expect(mention_service).to have_received(:call).with(status, save_records: false)
+ end
+
+ it 'safeguards mentions' do
+ account = Fabricate(:account)
+ mentioned_account = Fabricate(:account, username: 'alice')
+ unexpected_mentioned_account = Fabricate(:account, username: 'bob')
+
+ expect do
+ subject.call(account, text: '@alice hm, @bob is really annoying lately', allowed_mentions: [mentioned_account.id])
+ end.to raise_error(an_instance_of(PostStatusService::UnexpectedMentionsError).and having_attributes(accounts: [unexpected_mentioned_account]))
+ end
+
+ it 'processes duplicate mentions correctly' do
+ account = Fabricate(:account)
+ mentioned_account = Fabricate(:account, username: 'alice')
+
+ expect do
+ subject.call(account, text: '@alice @alice @alice hey @alice')
+ end.not_to raise_error
end
it 'processes hashtags' do
diff --git a/spec/services/process_mentions_service_spec.rb b/spec/services/process_mentions_service_spec.rb
index 5b9d17a4c..0dd62c807 100644
--- a/spec/services/process_mentions_service_spec.rb
+++ b/spec/services/process_mentions_service_spec.rb
@@ -47,6 +47,19 @@ RSpec.describe ProcessMentionsService, type: :service do
end
end
+ context 'mentioning a user several times when not saving records' do
+ let!(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') }
+ let(:status) { Fabricate(:status, account: account, text: "Hello @#{remote_user.acct} @#{remote_user.acct} @#{remote_user.acct}", visibility: :public) }
+
+ before do
+ subject.call(status, save_records: false)
+ end
+
+ it 'creates exactly one mention' do
+ expect(status.mentions.size).to eq 1
+ end
+ end
+
context 'with an IDN domain' do
let!(:remote_user) { Fabricate(:account, username: 'sneak', protocol: :activitypub, domain: 'xn--hresiar-mxa.ch', inbox_url: 'http://example.com/inbox') }
let!(:status) { Fabricate(:status, account: account, text: "Hello @sneak@hæresiar.ch") }
diff --git a/streaming/index.js b/streaming/index.js
index dbebb573e..8ee19ae70 100644
--- a/streaming/index.js
+++ b/streaming/index.js
@@ -864,15 +864,15 @@ const startWorker = async (workerId) => {
res.write('# TYPE connected_channels gauge\n');
res.write('# HELP connected_channels The number of Redis channels the streaming server is subscribed to\n');
res.write(`connected_channels ${Object.keys(subs).length}.0\n`);
- res.write('# TYPE pg.pool.total_connections gauge \n');
- res.write('# HELP pg.pool.total_connections The total number of clients existing within the pool\n');
- res.write(`pg.pool.total_connections ${pgPool.totalCount}.0\n`);
- res.write('# TYPE pg.pool.idle_connections gauge \n');
- res.write('# HELP pg.pool.idle_connections The number of clients which are not checked out but are currently idle in the pool\n');
- res.write(`pg.pool.idle_connections ${pgPool.idleCount}.0\n`);
- res.write('# TYPE pg.pool.waiting_queries gauge \n');
- res.write('# HELP pg.pool.waiting_queries The number of queued requests waiting on a client when all clients are checked out\n');
- res.write(`pg.pool.waiting_queries ${pgPool.waitingCount}.0\n`);
+ res.write('# TYPE pg_pool_total_connections gauge\n');
+ res.write('# HELP pg_pool_total_connections The total number of clients existing within the pool\n');
+ res.write(`pg_pool_total_connections ${pgPool.totalCount}.0\n`);
+ res.write('# TYPE pg_pool_idle_connections gauge\n');
+ res.write('# HELP pg_pool_idle_connections The number of clients which are not checked out but are currently idle in the pool\n');
+ res.write(`pg_pool_idle_connections ${pgPool.idleCount}.0\n`);
+ res.write('# TYPE pg_pool_waiting_queries gauge\n');
+ res.write('# HELP pg_pool_waiting_queries The number of queued requests waiting on a client when all clients are checked out\n');
+ res.write(`pg_pool_waiting_queries ${pgPool.waitingCount}.0\n`);
res.write('# EOF\n');
res.end();
}));
diff --git a/stylelint.config.js b/stylelint.config.js
index 0f8267a81..c8c07a05b 100644
--- a/stylelint.config.js
+++ b/stylelint.config.js
@@ -10,7 +10,6 @@ module.exports = {
'color-function-notation': null,
'color-hex-length': null,
'declaration-block-no-redundant-longhand-properties': null,
- 'max-line-length': null,
'no-descending-specificity': null,
'no-duplicate-selectors': null,
'number-max-precision': 8,
@@ -18,7 +17,6 @@ module.exports = {
'property-no-vendor-prefix': null,
'selector-class-pattern': null,
'selector-id-pattern': null,
- 'string-quotes': null,
'value-keyword-case': null,
'value-no-vendor-prefix': null,
diff --git a/yarn.lock b/yarn.lock
index e0dcd6eab..033e43ebe 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1076,10 +1076,25 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
-"@csstools/selector-specificity@^2.0.2":
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-2.0.2.tgz#1bfafe4b7ed0f3e4105837e056e0a89b108ebe36"
- integrity sha512-IkpVW/ehM1hWKln4fCA3NzJU8KwD+kIOvPZA4cqxoJHtE21CCzjyp+Kxbu0i5I4tBNOlXPL9mjwnWlL0VEG4Fg==
+"@csstools/css-parser-algorithms@^2.0.1":
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.0.1.tgz#ff02629c7c95d1f4f8ea84d5ef1173461610535e"
+ integrity sha512-B9/8PmOtU6nBiibJg0glnNktQDZ3rZnGn/7UmDfrm2vMtrdlXO3p7ErE95N0up80IRk9YEtB5jyj/TmQ1WH3dw==
+
+"@csstools/css-tokenizer@^2.0.1":
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/@csstools/css-tokenizer/-/css-tokenizer-2.0.1.tgz#cb1e11752db57e69d9aa0e84c3105a25845d4055"
+ integrity sha512-sYD3H7ReR88S/4+V5VbKiBEUJF4FqvG+8aNJkxqoPAnbhFziDG22IDZc4+h+xA63SfgM+h15lq5OnLeCxQ9nPA==
+
+"@csstools/media-query-list-parser@^2.0.1":
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/@csstools/media-query-list-parser/-/media-query-list-parser-2.0.1.tgz#d85a366811563a5d002755ed10e5212a1613c91d"
+ integrity sha512-X2/OuzEbjaxhzm97UJ+95GrMeT29d1Ib+Pu+paGLuRWZnWRK9sI9r3ikmKXPWGA1C4y4JEdBEFpp9jEqCvLeRA==
+
+"@csstools/selector-specificity@^2.1.1":
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-2.1.1.tgz#c9c61d9fe5ca5ac664e1153bb0aa0eba1c6d6308"
+ integrity sha512-jwx+WCqszn53YHOfvFMJJRd/B2GqkCBt+1MJSG6o5/s8+ytHMvDZXsJgUEWLk12UnLd7HYKac4BYU5i/Ron1Cw==
"@emotion/babel-plugin@^11.7.1":
version "11.9.2"
@@ -3567,7 +3582,7 @@ cosmiconfig@^6.0.0:
path-type "^4.0.0"
yaml "^1.7.2"
-cosmiconfig@^7.0.0, cosmiconfig@^7.1.0:
+cosmiconfig@^7.0.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6"
integrity sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==
@@ -3578,6 +3593,16 @@ cosmiconfig@^7.0.0, cosmiconfig@^7.1.0:
path-type "^4.0.0"
yaml "^1.10.0"
+cosmiconfig@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.0.0.tgz#e9feae014eab580f858f8a0288f38997a7bebe97"
+ integrity sha512-da1EafcpH6b/TD8vDRaWV7xFINlHlF6zKsGwS1TsuVJTZRkquaS5HTMq7uq6h31619QjbsYl21gVDOm32KM1vQ==
+ dependencies:
+ import-fresh "^3.2.1"
+ js-yaml "^4.1.0"
+ parse-json "^5.0.0"
+ path-type "^4.0.0"
+
create-ecdh@^4.0.0:
version "4.0.4"
resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e"
@@ -3723,6 +3748,14 @@ css-tree@1.0.0-alpha.39:
mdn-data "2.0.6"
source-map "^0.6.1"
+css-tree@^2.3.1:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.3.1.tgz#10264ce1e5442e8572fc82fbe490644ff54b5c20"
+ integrity sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==
+ dependencies:
+ mdn-data "2.0.30"
+ source-map-js "^1.0.1"
+
css-what@^3.2.1:
version "3.3.0"
resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.3.0.tgz#10fec696a9ece2e591ac772d759aacabac38cd39"
@@ -5744,7 +5777,7 @@ iferr@^0.1.5:
resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501"
integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE=
-ignore@^5.2.0, ignore@^5.2.1:
+ignore@^5.2.0, ignore@^5.2.4:
version "5.2.4"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324"
integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==
@@ -7326,6 +7359,11 @@ md5.js@^1.3.4:
inherits "^2.0.1"
safe-buffer "^5.1.2"
+mdn-data@2.0.30:
+ version "2.0.30"
+ resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.30.tgz#ce4df6f80af6cfbe218ecd5c552ba13c4dfa08cc"
+ integrity sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==
+
mdn-data@2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b"
@@ -8716,7 +8754,7 @@ postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.27, postcss@^7.0.32:
source-map "^0.6.1"
supports-color "^6.1.0"
-postcss@^8.2.15, postcss@^8.4.19, postcss@^8.4.21:
+postcss@^8.2.15, postcss@^8.4.21:
version "8.4.21"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.21.tgz#c639b719a57efc3187b13a1d765675485f4134f4"
integrity sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==
@@ -10039,7 +10077,7 @@ source-list-map@^2.0.0:
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34"
integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==
-"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.2:
+"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.1, source-map-js@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
@@ -10436,39 +10474,39 @@ stylehacks@^4.0.0:
postcss "^7.0.0"
postcss-selector-parser "^3.0.0"
-stylelint-config-recommended-scss@^8.0.0:
- version "8.0.0"
- resolved "https://registry.yarnpkg.com/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-8.0.0.tgz#1c1e93e619fe2275d4c1067928d92e0614f7d64f"
- integrity sha512-BxjxEzRaZoQb7Iinc3p92GS6zRdRAkIuEu2ZFLTxJK2e1AIcCb5B5MXY9KOXdGTnYFZ+KKx6R4Fv9zU6CtMYPQ==
+stylelint-config-recommended-scss@^9.0.0:
+ version "9.0.0"
+ resolved "https://registry.yarnpkg.com/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-9.0.0.tgz#e755cf3654f3a3a6d7bdf84fe0a814595754a386"
+ integrity sha512-5e9pn3Ztfncd8s9OqvvCW7tZpYe+vGmPi7VEXX7XEp+Kj38PnKCrvFCBL+hQ7rkD4d5QzjB3BxlFEyo/30UWUw==
dependencies:
postcss-scss "^4.0.2"
- stylelint-config-recommended "^9.0.0"
- stylelint-scss "^4.0.0"
+ stylelint-config-recommended "^10.0.1"
+ stylelint-scss "^4.4.0"
-stylelint-config-recommended@^9.0.0:
- version "9.0.0"
- resolved "https://registry.yarnpkg.com/stylelint-config-recommended/-/stylelint-config-recommended-9.0.0.tgz#1c9e07536a8cd875405f8ecef7314916d94e7e40"
- integrity sha512-9YQSrJq4NvvRuTbzDsWX3rrFOzOlYBmZP+o513BJN/yfEmGSr0AxdvrWs0P/ilSpVV/wisamAHu5XSk8Rcf4CQ==
+stylelint-config-recommended@^10.0.1:
+ version "10.0.1"
+ resolved "https://registry.yarnpkg.com/stylelint-config-recommended/-/stylelint-config-recommended-10.0.1.tgz#25a8828acf6cde87dac6db2950c8c4ed82a69ae1"
+ integrity sha512-TQ4xQ48tW4QSlODcti7pgSRqBZcUaBzuh0jPpfiMhwJKBPkqzTIAU+IrSWL/7BgXlOM90DjB7YaNgFpx8QWhuA==
-stylelint-config-standard-scss@^6.1.0:
- version "6.1.0"
- resolved "https://registry.yarnpkg.com/stylelint-config-standard-scss/-/stylelint-config-standard-scss-6.1.0.tgz#a6cddd2a9430578b92fc89726a59474d5548a444"
- integrity sha512-iZ2B5kQT2G3rUzx+437cEpdcnFOQkwnwqXuY8Z0QUwIHQVE8mnYChGAquyKFUKZRZ0pRnrciARlPaR1RBtPb0Q==
+stylelint-config-standard-scss@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/stylelint-config-standard-scss/-/stylelint-config-standard-scss-7.0.0.tgz#c7076bf5afa705d9e5ddc3ede39da7a684fa0f60"
+ integrity sha512-rHgydRJxN4Q9lDcwrLFoiFA3S8CRqsUcyBBCLwEMjIwzJViluFfsOKFPSomx6hScVQgQ4//Fx0hRKiSHyO0ihw==
dependencies:
- stylelint-config-recommended-scss "^8.0.0"
- stylelint-config-standard "^29.0.0"
+ stylelint-config-recommended-scss "^9.0.0"
+ stylelint-config-standard "^30.0.1"
-stylelint-config-standard@^29.0.0:
- version "29.0.0"
- resolved "https://registry.yarnpkg.com/stylelint-config-standard/-/stylelint-config-standard-29.0.0.tgz#4cc0e0f05512a39bb8b8e97853247d3a95d66fa2"
- integrity sha512-uy8tZLbfq6ZrXy4JKu3W+7lYLgRQBxYTUUB88vPgQ+ZzAxdrvcaSUW9hOMNLYBnwH+9Kkj19M2DHdZ4gKwI7tg==
+stylelint-config-standard@^30.0.1:
+ version "30.0.1"
+ resolved "https://registry.yarnpkg.com/stylelint-config-standard/-/stylelint-config-standard-30.0.1.tgz#a84d57c240c37f7db47023ab9d2e64c49090e1eb"
+ integrity sha512-NbeHOmpRQhjZh5XB1B/S4MLRWvz4xxAxeDBjzl0tY2xEcayNhLbaRGF0ZQzq+DQZLCcPpOHeS2Ru1ydbkhkmLg==
dependencies:
- stylelint-config-recommended "^9.0.0"
+ stylelint-config-recommended "^10.0.1"
-stylelint-scss@^4.0.0:
- version "4.2.0"
- resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-4.2.0.tgz#e25fd390ee38a7e89fcfaec2a8f9dce2ec6ddee8"
- integrity sha512-HHHMVKJJ5RM9pPIbgJ/XA67h9H0407G68Rm69H4fzFbFkyDMcTV1Byep3qdze5+fJ3c0U7mJrbj6S0Fg072uZA==
+stylelint-scss@^4.4.0:
+ version "4.4.0"
+ resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-4.4.0.tgz#87ce9d049eff1ce67cce788780fbfda63099017e"
+ integrity sha512-Qy66a+/30aylFhPmUArHhVsHOun1qrO93LGT15uzLuLjWS7hKDfpFm34mYo1ndR4MCo8W4bEZM1+AlJRJORaaw==
dependencies:
lodash "^4.17.21"
postcss-media-query-parser "^0.2.3"
@@ -10476,16 +10514,20 @@ stylelint-scss@^4.0.0:
postcss-selector-parser "^6.0.6"
postcss-value-parser "^4.1.0"
-stylelint@^14.16.1:
- version "14.16.1"
- resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-14.16.1.tgz#b911063530619a1bbe44c2b875fd8181ebdc742d"
- integrity sha512-ErlzR/T3hhbV+a925/gbfc3f3Fep9/bnspMiJPorfGEmcBbXdS+oo6LrVtoUZ/w9fqD6o6k7PtUlCOsCRdjX/A==
+stylelint@^15.1.0:
+ version "15.1.0"
+ resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-15.1.0.tgz#24d7cbe06250ceca3b276393bfdeaaaba4356195"
+ integrity sha512-Tw8OyIiYhxnIHUzgoLlCyWgCUKsPYiP3TDgs7M1VbayS+q5qZly2yxABg+YPe/hFRWiu0cOtptCtpyrn1CrnYw==
dependencies:
- "@csstools/selector-specificity" "^2.0.2"
+ "@csstools/css-parser-algorithms" "^2.0.1"
+ "@csstools/css-tokenizer" "^2.0.1"
+ "@csstools/media-query-list-parser" "^2.0.1"
+ "@csstools/selector-specificity" "^2.1.1"
balanced-match "^2.0.0"
colord "^2.9.3"
- cosmiconfig "^7.1.0"
+ cosmiconfig "^8.0.0"
css-functions-list "^3.1.0"
+ css-tree "^2.3.1"
debug "^4.3.4"
fast-glob "^3.2.12"
fastest-levenshtein "^1.0.16"
@@ -10494,7 +10536,7 @@ stylelint@^14.16.1:
globby "^11.1.0"
globjoin "^0.1.4"
html-tags "^3.2.0"
- ignore "^5.2.1"
+ ignore "^5.2.4"
import-lazy "^4.0.0"
imurmurhash "^0.1.4"
is-plain-object "^5.0.0"
@@ -10504,7 +10546,7 @@ stylelint@^14.16.1:
micromatch "^4.0.5"
normalize-path "^3.0.0"
picocolors "^1.0.0"
- postcss "^8.4.19"
+ postcss "^8.4.21"
postcss-media-query-parser "^0.2.3"
postcss-resolve-nested-selector "^0.1.1"
postcss-safe-parser "^6.0.0"
@@ -10518,7 +10560,7 @@ stylelint@^14.16.1:
svg-tags "^1.0.0"
table "^6.8.1"
v8-compile-cache "^2.3.0"
- write-file-atomic "^4.0.2"
+ write-file-atomic "^5.0.0"
stylis@4.0.13:
version "4.0.13"
@@ -11788,14 +11830,6 @@ wrappy@1:
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
-write-file-atomic@^4.0.2:
- version "4.0.2"
- resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-4.0.2.tgz#a9df01ae5b77858a027fd2e80768ee433555fcfd"
- integrity sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==
- dependencies:
- imurmurhash "^0.1.4"
- signal-exit "^3.0.7"
-
write-file-atomic@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-5.0.0.tgz#54303f117e109bf3d540261125c8ea5a7320fab0"