mirror of
https://github.com/glitch-soc/mastodon.git
synced 2026-03-29 03:00:33 +02:00
Merge pull request #3288 from ClearlyClaire/glitch-soc/merge-upstream
Merge upstream changes up to 366856f3bc
This commit is contained in:
@@ -68,6 +68,7 @@ docker-compose.override.yml
|
||||
|
||||
# Ignore vendored CSS reset
|
||||
app/javascript/styles/mastodon/reset.scss
|
||||
app/javascript/styles_new/mastodon/reset.scss
|
||||
|
||||
# Ignore Javascript pending https://github.com/mastodon/mastodon/pull/23631
|
||||
*.js
|
||||
|
||||
2
Gemfile
2
Gemfile
@@ -24,7 +24,7 @@ gem 'ruby-vips', '~> 2.2', require: false
|
||||
|
||||
gem 'active_model_serializers', '~> 0.10'
|
||||
gem 'addressable', '~> 2.8'
|
||||
gem 'bootsnap', '~> 1.18.0', require: false
|
||||
gem 'bootsnap', '~> 1.19.0', require: false
|
||||
gem 'browser'
|
||||
gem 'charlock_holmes', '~> 0.7.7'
|
||||
gem 'chewy', '~> 7.3'
|
||||
|
||||
16
Gemfile.lock
16
Gemfile.lock
@@ -129,7 +129,7 @@ GEM
|
||||
binding_of_caller (1.0.1)
|
||||
debug_inspector (>= 1.2.0)
|
||||
blurhash (0.1.8)
|
||||
bootsnap (1.18.6)
|
||||
bootsnap (1.19.0)
|
||||
msgpack (~> 1.2)
|
||||
brakeman (7.1.1)
|
||||
racc
|
||||
@@ -349,7 +349,7 @@ GEM
|
||||
azure-blob (~> 0.5.2)
|
||||
hashie (~> 5.0)
|
||||
jmespath (1.6.2)
|
||||
json (2.15.2)
|
||||
json (2.16.0)
|
||||
json-canonicalization (1.0.0)
|
||||
json-jwt (1.17.0)
|
||||
activesupport (>= 4.2)
|
||||
@@ -446,7 +446,7 @@ GEM
|
||||
mime-types-data (3.2025.0924)
|
||||
mini_mime (1.1.5)
|
||||
mini_portile2 (2.8.9)
|
||||
minitest (5.26.0)
|
||||
minitest (5.26.1)
|
||||
msgpack (1.8.0)
|
||||
multi_json (1.17.0)
|
||||
mutex_m (0.3.0)
|
||||
@@ -759,7 +759,7 @@ GEM
|
||||
rubocop-ast (>= 1.47.1, < 2.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 2.4.0, < 4.0)
|
||||
rubocop-ast (1.47.1)
|
||||
rubocop-ast (1.48.0)
|
||||
parser (>= 3.3.7.2)
|
||||
prism (~> 1.4)
|
||||
rubocop-capybara (2.22.1)
|
||||
@@ -778,10 +778,10 @@ GEM
|
||||
rack (>= 1.1)
|
||||
rubocop (>= 1.75.0, < 2.0)
|
||||
rubocop-ast (>= 1.44.0, < 2.0)
|
||||
rubocop-rspec (3.7.0)
|
||||
rubocop-rspec (3.8.0)
|
||||
lint_roller (~> 1.1)
|
||||
rubocop (~> 1.72, >= 1.72.1)
|
||||
rubocop-rspec_rails (2.31.0)
|
||||
rubocop (~> 1.81)
|
||||
rubocop-rspec_rails (2.32.0)
|
||||
lint_roller (~> 1.1)
|
||||
rubocop (~> 1.72, >= 1.72.1)
|
||||
rubocop-rspec (~> 3.5)
|
||||
@@ -941,7 +941,7 @@ DEPENDENCIES
|
||||
better_errors (~> 2.9)
|
||||
binding_of_caller (~> 1.0)
|
||||
blurhash (~> 0.1)
|
||||
bootsnap (~> 1.18.0)
|
||||
bootsnap (~> 1.19.0)
|
||||
brakeman (~> 7.0)
|
||||
browser
|
||||
bundler-audit (~> 0.9)
|
||||
|
||||
@@ -9,7 +9,7 @@ module Admin
|
||||
|
||||
@site_upload.destroy!
|
||||
|
||||
redirect_back fallback_location: admin_settings_path, notice: I18n.t('admin.site_uploads.destroyed_msg')
|
||||
redirect_back_or_to admin_settings_path, notice: I18n.t('admin.site_uploads.destroyed_msg')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
@@ -6,11 +6,11 @@ module ThemeHelper
|
||||
|
||||
if theme == 'system'
|
||||
''.html_safe.tap do |tags|
|
||||
tags << vite_stylesheet_tag("skins/#{flavour}/mastodon-light", type: :virtual, media: 'not all and (prefers-color-scheme: dark)', crossorigin: 'anonymous')
|
||||
tags << vite_stylesheet_tag("skins/#{flavour}/default", type: :virtual, media: '(prefers-color-scheme: dark)', crossorigin: 'anonymous')
|
||||
tags << vite_stylesheet_tag(theme_path_for(flavour, 'mastodon-light'), type: :virtual, media: 'not all and (prefers-color-scheme: dark)', crossorigin: 'anonymous')
|
||||
tags << vite_stylesheet_tag(theme_path_for(flavour, 'default'), type: :virtual, media: '(prefers-color-scheme: dark)', crossorigin: 'anonymous')
|
||||
end
|
||||
else
|
||||
vite_stylesheet_tag "skins/#{flavour}/#{theme}", type: :virtual, media: 'all', crossorigin: 'anonymous'
|
||||
vite_stylesheet_tag theme_path_for(flavour, theme), type: :virtual, media: 'all', crossorigin: 'anonymous'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -57,4 +57,8 @@ module ThemeHelper
|
||||
def theme_color_for(theme)
|
||||
theme == 'mastodon-light' ? Themes::THEME_COLORS[:light] : Themes::THEME_COLORS[:dark]
|
||||
end
|
||||
|
||||
def theme_path_for(flavour, theme)
|
||||
"skins/#{flavour}/#{theme}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -180,25 +180,24 @@ export function useHotkeys<T extends HTMLElement>(handlers: HandlerMap) {
|
||||
|
||||
if (shouldHandleEvent) {
|
||||
const matchCandidates: {
|
||||
handler: (event: KeyboardEvent) => void;
|
||||
// A candidate will be have an undefined handler if it's matched,
|
||||
// but handled in a parent component rather than this one.
|
||||
handler: ((event: KeyboardEvent) => void) | undefined;
|
||||
priority: number;
|
||||
}[] = [];
|
||||
|
||||
(Object.keys(hotkeyMatcherMap) as HotkeyName[]).forEach(
|
||||
(handlerName) => {
|
||||
const handler = handlersRef.current[handlerName];
|
||||
const hotkeyMatcher = hotkeyMatcherMap[handlerName];
|
||||
|
||||
if (handler) {
|
||||
const hotkeyMatcher = hotkeyMatcherMap[handlerName];
|
||||
const { isMatch, priority } = hotkeyMatcher(
|
||||
event,
|
||||
bufferedKeys.current,
|
||||
);
|
||||
|
||||
const { isMatch, priority } = hotkeyMatcher(
|
||||
event,
|
||||
bufferedKeys.current,
|
||||
);
|
||||
|
||||
if (isMatch) {
|
||||
matchCandidates.push({ handler, priority });
|
||||
}
|
||||
if (isMatch) {
|
||||
matchCandidates.push({ handler, priority });
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@@ -144,7 +144,7 @@ export const HandledLink: FC<HandledLinkProps & ComponentProps<'a'>> = ({
|
||||
return (
|
||||
<Link
|
||||
className={classNames('mention hashtag', className)}
|
||||
to={`/tags/${hashtag}`}
|
||||
to={`/tags/${encodeURIComponent(hashtag)}`}
|
||||
rel='tag'
|
||||
data-menu-hashtag={hashtagAccountId}
|
||||
>
|
||||
@@ -194,7 +194,7 @@ export const HandledLink: FC<HandledLinkProps & ComponentProps<'a'>> = ({
|
||||
return (
|
||||
<a
|
||||
{...props}
|
||||
href={encodeURI(href)}
|
||||
href={href}
|
||||
title={href}
|
||||
className={classNames('unhandled-link', className)}
|
||||
target='_blank'
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
import { pasteLinkCompose } from 'flavours/glitch/actions/compose_typed';
|
||||
import { openModal } from 'flavours/glitch/actions/modal';
|
||||
import { PRIVATE_QUOTE_MODAL_ID } from 'flavours/glitch/features/ui/components/confirmation_modals/private_quote_notify';
|
||||
import { me } from 'flavours/glitch/initial_state';
|
||||
import { privacyPreference } from 'flavours/glitch/utils/privacy_preference';
|
||||
|
||||
import ComposeForm from '../components/compose_form';
|
||||
@@ -73,6 +74,7 @@ const mapStateToProps = state => ({
|
||||
quoteToPrivate:
|
||||
!!state.getIn(['compose', 'quoted_status_id'])
|
||||
&& state.getIn(['compose', 'privacy']) === 'private'
|
||||
&& state.getIn(['statuses', state.getIn(['compose', 'quoted_status_id']), 'account']) !== me
|
||||
&& !state.getIn(['settings', 'dismissed_banners', PRIVATE_QUOTE_MODAL_ID]),
|
||||
isInReply: state.getIn(['compose', 'in_reply_to']) !== null,
|
||||
lang: state.getIn(['compose', 'language']),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { initialState } from '@/flavours/glitch/initial_state';
|
||||
|
||||
import { toSupportedLocale } from './locale';
|
||||
import type { LocaleOrCustom } from './types';
|
||||
import { emojiLogger } from './utils';
|
||||
// eslint-disable-next-line import/default -- Importing via worker loader.
|
||||
import EmojiWorker from './worker?worker&inline';
|
||||
@@ -24,19 +25,17 @@ export function initializeEmoji() {
|
||||
}
|
||||
|
||||
if (worker) {
|
||||
// Assign worker to const to make TS happy inside the event listener.
|
||||
const thisWorker = worker;
|
||||
const timeoutId = setTimeout(() => {
|
||||
log('worker is not ready after timeout');
|
||||
worker = null;
|
||||
void fallbackLoad();
|
||||
}, WORKER_TIMEOUT);
|
||||
thisWorker.addEventListener('message', (event: MessageEvent<string>) => {
|
||||
worker.addEventListener('message', (event: MessageEvent<string>) => {
|
||||
const { data: message } = event;
|
||||
if (message === 'ready') {
|
||||
log('worker ready, loading data');
|
||||
clearTimeout(timeoutId);
|
||||
thisWorker.postMessage('custom');
|
||||
messageWorker('custom');
|
||||
void loadEmojiLocale(userLocale);
|
||||
// Load English locale as well, because people are still used to
|
||||
// using it from before we supported other locales.
|
||||
@@ -55,20 +54,35 @@ export function initializeEmoji() {
|
||||
async function fallbackLoad() {
|
||||
log('falling back to main thread for loading');
|
||||
const { importCustomEmojiData } = await import('./loader');
|
||||
await importCustomEmojiData();
|
||||
const emojis = await importCustomEmojiData();
|
||||
if (emojis) {
|
||||
log('loaded %d custom emojis', emojis.length);
|
||||
}
|
||||
await loadEmojiLocale(userLocale);
|
||||
if (userLocale !== 'en') {
|
||||
await loadEmojiLocale('en');
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadEmojiLocale(localeString: string) {
|
||||
async function loadEmojiLocale(localeString: string) {
|
||||
const locale = toSupportedLocale(localeString);
|
||||
const { importEmojiData, localeToPath } = await import('./loader');
|
||||
|
||||
if (worker) {
|
||||
worker.postMessage(locale);
|
||||
const path = await localeToPath(locale);
|
||||
log('asking worker to load locale %s from %s', locale, path);
|
||||
messageWorker(locale, path);
|
||||
} else {
|
||||
const { importEmojiData } = await import('./loader');
|
||||
await importEmojiData(locale);
|
||||
const emojis = await importEmojiData(locale);
|
||||
if (emojis) {
|
||||
log('loaded %d emojis to locale %s', emojis.length, locale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function messageWorker(locale: LocaleOrCustom, path?: string) {
|
||||
if (!worker) {
|
||||
return;
|
||||
}
|
||||
worker.postMessage({ locale, path });
|
||||
}
|
||||
|
||||
@@ -8,44 +8,64 @@ import {
|
||||
putLatestEtag,
|
||||
} from './database';
|
||||
import { toSupportedLocale, toSupportedLocaleOrCustom } from './locale';
|
||||
import type { CustomEmojiData, LocaleOrCustom } from './types';
|
||||
import { emojiLogger } from './utils';
|
||||
import type { CustomEmojiData } from './types';
|
||||
|
||||
const log = emojiLogger('loader');
|
||||
|
||||
export async function importEmojiData(localeString: string) {
|
||||
export async function importEmojiData(localeString: string, path?: string) {
|
||||
const locale = toSupportedLocale(localeString);
|
||||
const emojis = await fetchAndCheckEtag<CompactEmoji[]>(locale);
|
||||
|
||||
// Validate the provided path.
|
||||
if (path && !/^[/a-z]*\/packs\/assets\/compact-\w+\.json$/.test(path)) {
|
||||
throw new Error('Invalid path for emoji data');
|
||||
} else {
|
||||
// Otherwise get the path if not provided.
|
||||
path ??= await localeToPath(locale);
|
||||
}
|
||||
|
||||
const emojis = await fetchAndCheckEtag<CompactEmoji[]>(locale, path);
|
||||
if (!emojis) {
|
||||
return;
|
||||
}
|
||||
const flattenedEmojis: FlatCompactEmoji[] = flattenEmojiData(emojis);
|
||||
log('loaded %d for %s locale', flattenedEmojis.length, locale);
|
||||
await putEmojiData(flattenedEmojis, locale);
|
||||
return flattenedEmojis;
|
||||
}
|
||||
|
||||
export async function importCustomEmojiData() {
|
||||
const emojis = await fetchAndCheckEtag<CustomEmojiData[]>('custom');
|
||||
const emojis = await fetchAndCheckEtag<CustomEmojiData[]>(
|
||||
'custom',
|
||||
'/api/v1/custom_emojis',
|
||||
);
|
||||
if (!emojis) {
|
||||
return;
|
||||
}
|
||||
log('loaded %d custom emojis', emojis.length);
|
||||
await putCustomEmojiData(emojis);
|
||||
return emojis;
|
||||
}
|
||||
|
||||
async function fetchAndCheckEtag<ResultType extends object[]>(
|
||||
localeOrCustom: LocaleOrCustom,
|
||||
const modules = import.meta.glob<string>(
|
||||
'../../../../../../node_modules/emojibase-data/**/compact.json',
|
||||
{
|
||||
query: '?url',
|
||||
import: 'default',
|
||||
},
|
||||
);
|
||||
|
||||
export function localeToPath(locale: Locale) {
|
||||
const key = `../../../../../../node_modules/emojibase-data/${locale}/compact.json`;
|
||||
if (!modules[key] || typeof modules[key] !== 'function') {
|
||||
throw new Error(`Unsupported locale: ${locale}`);
|
||||
}
|
||||
return modules[key]();
|
||||
}
|
||||
|
||||
export async function fetchAndCheckEtag<ResultType extends object[]>(
|
||||
localeString: string,
|
||||
path: string,
|
||||
): Promise<ResultType | null> {
|
||||
const locale = toSupportedLocaleOrCustom(localeOrCustom);
|
||||
const locale = toSupportedLocaleOrCustom(localeString);
|
||||
|
||||
// Use location.origin as this script may be loaded from a CDN domain.
|
||||
const url = new URL(location.origin);
|
||||
if (locale === 'custom') {
|
||||
url.pathname = '/api/v1/custom_emojis';
|
||||
} else {
|
||||
const modulePath = await localeToPath(locale);
|
||||
url.pathname = modulePath;
|
||||
}
|
||||
const url = new URL(path, location.origin);
|
||||
|
||||
const oldEtag = await loadLatestEtag(locale);
|
||||
const response = await fetch(url, {
|
||||
@@ -60,38 +80,20 @@ async function fetchAndCheckEtag<ResultType extends object[]>(
|
||||
}
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Failed to fetch emoji data for ${localeOrCustom}: ${response.statusText}`,
|
||||
`Failed to fetch emoji data for ${locale}: ${response.statusText}`,
|
||||
);
|
||||
}
|
||||
|
||||
const data = (await response.json()) as ResultType;
|
||||
if (!Array.isArray(data)) {
|
||||
throw new Error(
|
||||
`Unexpected data format for ${localeOrCustom}: expected an array`,
|
||||
);
|
||||
throw new Error(`Unexpected data format for ${locale}: expected an array`);
|
||||
}
|
||||
|
||||
// Store the ETag for future requests
|
||||
const etag = response.headers.get('ETag');
|
||||
if (etag) {
|
||||
await putLatestEtag(etag, localeOrCustom);
|
||||
await putLatestEtag(etag, localeString);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
const modules = import.meta.glob<string>(
|
||||
'../../../../../../node_modules/emojibase-data/**/compact.json',
|
||||
{
|
||||
query: '?url',
|
||||
import: 'default',
|
||||
},
|
||||
);
|
||||
|
||||
function localeToPath(locale: Locale) {
|
||||
const key = `../../../../../../node_modules/emojibase-data/${locale}/compact.json`;
|
||||
if (!modules[key] || typeof modules[key] !== 'function') {
|
||||
throw new Error(`Unsupported locale: ${locale}`);
|
||||
}
|
||||
return modules[key]();
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ describe('loadEmojiDataToState', () => {
|
||||
const dbCall = vi
|
||||
.spyOn(db, 'loadEmojiByHexcode')
|
||||
.mockRejectedValue(new db.LocaleNotLoadedError('en'));
|
||||
vi.spyOn(loader, 'importEmojiData').mockResolvedValueOnce();
|
||||
vi.spyOn(loader, 'importEmojiData').mockResolvedValueOnce(undefined);
|
||||
const consoleCall = vi
|
||||
.spyOn(console, 'warn')
|
||||
.mockImplementationOnce(() => null);
|
||||
|
||||
@@ -1,18 +1,25 @@
|
||||
import { importEmojiData, importCustomEmojiData } from './loader';
|
||||
import { importCustomEmojiData, importEmojiData } from './loader';
|
||||
|
||||
addEventListener('message', handleMessage);
|
||||
self.postMessage('ready'); // After the worker is ready, notify the main thread
|
||||
|
||||
function handleMessage(event: MessageEvent<string>) {
|
||||
const { data: locale } = event;
|
||||
void loadData(locale);
|
||||
function handleMessage(event: MessageEvent<{ locale: string; path?: string }>) {
|
||||
const {
|
||||
data: { locale, path },
|
||||
} = event;
|
||||
void loadData(locale, path);
|
||||
}
|
||||
|
||||
async function loadData(locale: string) {
|
||||
if (locale !== 'custom') {
|
||||
await importEmojiData(locale);
|
||||
async function loadData(locale: string, path?: string) {
|
||||
let importCount: number | undefined;
|
||||
if (locale === 'custom') {
|
||||
importCount = (await importCustomEmojiData())?.length;
|
||||
} else if (path) {
|
||||
importCount = (await importEmojiData(locale, path))?.length;
|
||||
} else {
|
||||
await importCustomEmojiData();
|
||||
throw new Error('Path is required for loading locale emoji data');
|
||||
}
|
||||
if (importCount) {
|
||||
self.postMessage(`loaded ${importCount} emojis into ${locale}`);
|
||||
}
|
||||
self.postMessage(`loaded ${locale}`);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
@typescript-eslint/no-unsafe-assignment */
|
||||
|
||||
import type { CSSProperties } from 'react';
|
||||
import { useState, useRef, useCallback } from 'react';
|
||||
import { useState, useRef, useCallback, useEffect } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
@@ -57,6 +57,8 @@ export const DetailedStatus: React.FC<{
|
||||
pictureInPicture: any;
|
||||
onToggleHidden?: (status: any) => void;
|
||||
onToggleMediaVisibility?: () => void;
|
||||
ancestors?: number;
|
||||
multiColumn?: boolean;
|
||||
expanded: boolean;
|
||||
}> = ({
|
||||
status,
|
||||
@@ -72,6 +74,8 @@ export const DetailedStatus: React.FC<{
|
||||
pictureInPicture,
|
||||
onToggleMediaVisibility,
|
||||
onToggleHidden,
|
||||
ancestors = 0,
|
||||
multiColumn = false,
|
||||
expanded,
|
||||
}) => {
|
||||
const properStatus = status?.get('reblog') ?? status;
|
||||
@@ -136,6 +140,30 @@ export const DetailedStatus: React.FC<{
|
||||
if (onTranslate) onTranslate(status);
|
||||
}, [onTranslate, status]);
|
||||
|
||||
// The component is managed and will change if the status changes
|
||||
// Ancestors can increase when loading a thread, in which case we want to scroll,
|
||||
// or decrease if a post is deleted, in which case we don't want to mess with it
|
||||
const previousAncestors = useRef(-1);
|
||||
useEffect(() => {
|
||||
if (nodeRef.current && previousAncestors.current < ancestors) {
|
||||
nodeRef.current.scrollIntoView(true);
|
||||
|
||||
// In the single-column interface, `scrollIntoView` will put the post behind the header, so compensate for that.
|
||||
if (!multiColumn) {
|
||||
const offset = document
|
||||
.querySelector('.column-header__wrapper')
|
||||
?.getBoundingClientRect().bottom;
|
||||
|
||||
if (offset) {
|
||||
const scrollingElement = document.scrollingElement ?? document.body;
|
||||
scrollingElement.scrollBy(0, -offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
previousAncestors.current = ancestors;
|
||||
}, [ancestors, multiColumn]);
|
||||
|
||||
if (!properStatus) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -162,7 +162,6 @@ class Status extends ImmutablePureComponent {
|
||||
componentDidMount () {
|
||||
attachFullscreenListener(this.onFullScreenChange);
|
||||
this.props.dispatch(fetchStatus(this.props.params.statusId, { forceFetch: true }));
|
||||
this._scrollStatusIntoView();
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props, state) {
|
||||
@@ -512,35 +511,11 @@ class Status extends ImmutablePureComponent {
|
||||
this.statusNode = c;
|
||||
};
|
||||
|
||||
_scrollStatusIntoView () {
|
||||
const { status, multiColumn } = this.props;
|
||||
|
||||
if (status) {
|
||||
requestIdleCallback(() => {
|
||||
this.statusNode?.scrollIntoView(true);
|
||||
|
||||
// In the single-column interface, `scrollIntoView` will put the post behind the header,
|
||||
// so compensate for that.
|
||||
if (!multiColumn) {
|
||||
const offset = document.querySelector('.column-header__wrapper')?.getBoundingClientRect()?.bottom;
|
||||
if (offset) {
|
||||
const scrollingElement = document.scrollingElement || document.body;
|
||||
scrollingElement.scrollBy(0, -offset);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
const { status, ancestorsIds, descendantsIds } = this.props;
|
||||
const { status, descendantsIds } = this.props;
|
||||
|
||||
const isSameStatus = status && (prevProps.status?.get('id') === status.get('id'));
|
||||
|
||||
if (status && (ancestorsIds.length > prevProps.ancestorsIds.length || !isSameStatus)) {
|
||||
this._scrollStatusIntoView();
|
||||
}
|
||||
|
||||
// Only highlight replies after the initial load
|
||||
if (prevProps.descendantsIds.length && isSameStatus) {
|
||||
const newRepliesIds = difference(descendantsIds, prevProps.descendantsIds);
|
||||
@@ -653,6 +628,8 @@ class Status extends ImmutablePureComponent {
|
||||
showMedia={this.state.showMedia}
|
||||
onToggleMediaVisibility={this.handleToggleMediaVisibility}
|
||||
pictureInPicture={pictureInPicture}
|
||||
ancestors={this.props.ancestorsIds.length}
|
||||
multiColumn={multiColumn}
|
||||
/>
|
||||
|
||||
<ActionBar
|
||||
|
||||
@@ -9,7 +9,6 @@ import { me, reduceMotion } from 'flavours/glitch/initial_state';
|
||||
import ready from 'flavours/glitch/ready';
|
||||
import { store } from 'flavours/glitch/store';
|
||||
|
||||
import { initializeEmoji } from './features/emoji';
|
||||
import { isProduction, isDevelopment } from './utils/environment';
|
||||
|
||||
function main() {
|
||||
@@ -30,6 +29,7 @@ function main() {
|
||||
});
|
||||
}
|
||||
|
||||
const { initializeEmoji } = await import('./features/emoji/index');
|
||||
initializeEmoji();
|
||||
|
||||
const root = createRoot(mountNode);
|
||||
|
||||
@@ -418,8 +418,8 @@ export const composeReducer = (state = initialState, action) => {
|
||||
const isDirect = state.get('privacy') === 'direct';
|
||||
return state
|
||||
.set('quoted_status_id', isDirect ? null : status.get('id'))
|
||||
.set('spoiler', status.get('sensitive'))
|
||||
.set('spoiler_text', status.get('spoiler_text'))
|
||||
.update('spoiler', spoiler => (spoiler) || !!status.get('spoiler_text'))
|
||||
.update('spoiler_text', (spoiler_text) => spoiler_text || status.get('spoiler_text'))
|
||||
.update('privacy', (visibility) => {
|
||||
if (['public', 'unlisted'].includes(visibility) && status.get('visibility') === 'private') {
|
||||
return 'private';
|
||||
|
||||
@@ -327,9 +327,9 @@ $content-width: 840px;
|
||||
font-weight: 700;
|
||||
color: $primary-text-color;
|
||||
text-transform: none;
|
||||
padding-bottom: 0;
|
||||
padding-top: 0;
|
||||
margin-bottom: 0;
|
||||
border-bottom: 0;
|
||||
border-top: 0;
|
||||
|
||||
.comment {
|
||||
display: block;
|
||||
|
||||
@@ -180,25 +180,24 @@ export function useHotkeys<T extends HTMLElement>(handlers: HandlerMap) {
|
||||
|
||||
if (shouldHandleEvent) {
|
||||
const matchCandidates: {
|
||||
handler: (event: KeyboardEvent) => void;
|
||||
// A candidate will be have an undefined handler if it's matched,
|
||||
// but handled in a parent component rather than this one.
|
||||
handler: ((event: KeyboardEvent) => void) | undefined;
|
||||
priority: number;
|
||||
}[] = [];
|
||||
|
||||
(Object.keys(hotkeyMatcherMap) as HotkeyName[]).forEach(
|
||||
(handlerName) => {
|
||||
const handler = handlersRef.current[handlerName];
|
||||
const hotkeyMatcher = hotkeyMatcherMap[handlerName];
|
||||
|
||||
if (handler) {
|
||||
const hotkeyMatcher = hotkeyMatcherMap[handlerName];
|
||||
const { isMatch, priority } = hotkeyMatcher(
|
||||
event,
|
||||
bufferedKeys.current,
|
||||
);
|
||||
|
||||
const { isMatch, priority } = hotkeyMatcher(
|
||||
event,
|
||||
bufferedKeys.current,
|
||||
);
|
||||
|
||||
if (isMatch) {
|
||||
matchCandidates.push({ handler, priority });
|
||||
}
|
||||
if (isMatch) {
|
||||
matchCandidates.push({ handler, priority });
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@@ -553,7 +553,6 @@ class Status extends ImmutablePureComponent {
|
||||
}
|
||||
|
||||
const {statusContentProps, hashtagBar} = getHashtagBarForStatus(status);
|
||||
|
||||
return (
|
||||
<Hotkeys handlers={handlers} focusable={!unfocusable}>
|
||||
<div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), unread, focusable: !this.props.muted })} tabIndex={this.props.muted || unfocusable ? null : 0} data-featured={featured ? 'true' : null} aria-label={textForScreenReader({intl, status, rebloggedByText, isQuote: isQuotedPost})} ref={this.handleRef} data-nosnippet={status.getIn(['account', 'noindex'], true) || undefined}>
|
||||
|
||||
@@ -64,6 +64,7 @@ const StandaloneBoostButton: FC<ReblogButtonProps> = ({ status, counters }) => {
|
||||
title={intl.formatMessage(meta ?? title)}
|
||||
icon='retweet'
|
||||
iconComponent={iconComponent}
|
||||
className='status__action-bar__button'
|
||||
onClick={!disabled ? handleClick : undefined}
|
||||
counter={
|
||||
counters
|
||||
@@ -195,6 +196,7 @@ const BoostOrQuoteMenu: FC<ReblogButtonProps> = ({ status, counters }) => {
|
||||
isMenuDisabled ? messages.all_disabled : messages.reblog_or_quote,
|
||||
)}
|
||||
icon='retweet'
|
||||
className='status__action-bar__button'
|
||||
iconComponent={boostIcon}
|
||||
counter={
|
||||
counters
|
||||
|
||||
@@ -38,7 +38,7 @@ export const HandledLink: FC<HandledLinkProps & ComponentProps<'a'>> = ({
|
||||
return (
|
||||
<Link
|
||||
className={classNames('mention hashtag', className)}
|
||||
to={`/tags/${hashtag}`}
|
||||
to={`/tags/${encodeURIComponent(hashtag)}`}
|
||||
rel='tag'
|
||||
data-menu-hashtag={hashtagAccountId}
|
||||
>
|
||||
@@ -71,7 +71,7 @@ export const HandledLink: FC<HandledLinkProps & ComponentProps<'a'>> = ({
|
||||
return (
|
||||
<a
|
||||
{...props}
|
||||
href={encodeURI(href)}
|
||||
href={href}
|
||||
title={href}
|
||||
className={classNames('unhandled-link', className)}
|
||||
target='_blank'
|
||||
|
||||
@@ -406,15 +406,19 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
status={status}
|
||||
needsStatusRefresh={quickBoosting && status.get('quote_approval') === null}
|
||||
items={menu}
|
||||
icon='ellipsis-h'
|
||||
iconComponent={MoreHorizIcon}
|
||||
direction='right'
|
||||
title={intl.formatMessage(messages.more)}
|
||||
onOpen={() => {
|
||||
dismissQuoteHint();
|
||||
return true;
|
||||
}}
|
||||
/>
|
||||
>
|
||||
<IconButton
|
||||
className='status__action-bar__button'
|
||||
icon='ellipsis-h'
|
||||
iconComponent={MoreHorizIcon}
|
||||
title={intl.formatMessage(messages.more)}
|
||||
/>
|
||||
</Dropdown>
|
||||
)}
|
||||
</RemoveQuoteHint>
|
||||
</div>
|
||||
|
||||
@@ -104,17 +104,19 @@ export const RulesSection: FC<RulesSectionProps> = ({ isLoading = false }) => {
|
||||
defaultMessage='Language'
|
||||
/>
|
||||
</label>
|
||||
<select onChange={handleLocaleChange} id='language-select'>
|
||||
{localeOptions.map((option) => (
|
||||
<option
|
||||
key={option.value}
|
||||
value={option.value}
|
||||
selected={option.value === selectedLocale}
|
||||
>
|
||||
{option.text}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<div className='select-wrapper'>
|
||||
<select onChange={handleLocaleChange} id='language-select'>
|
||||
{localeOptions.map((option) => (
|
||||
<option
|
||||
key={option.value}
|
||||
value={option.value}
|
||||
selected={option.value === selectedLocale}
|
||||
>
|
||||
{option.text}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Section>
|
||||
|
||||
@@ -24,12 +24,12 @@ export default class FollowRequestNote extends ImmutablePureComponent {
|
||||
</div>
|
||||
|
||||
<div className='follow-request-banner__action'>
|
||||
<button type='button' className='button button-tertiary button--confirmation' onClick={onAuthorize}>
|
||||
<button type='button' className='button button-secondary button--confirmation' onClick={onAuthorize}>
|
||||
<Icon id='check' icon={CheckIcon} />
|
||||
<FormattedMessage id='follow_request.authorize' defaultMessage='Authorize' />
|
||||
</button>
|
||||
|
||||
<button type='button' className='button button-tertiary button--destructive' onClick={onReject}>
|
||||
<button type='button' className='button button-secondary button--destructive' onClick={onReject}>
|
||||
<Icon id='times' icon={CloseIcon} />
|
||||
<FormattedMessage id='follow_request.reject' defaultMessage='Reject' />
|
||||
</button>
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
const iconStyle = {
|
||||
height: null,
|
||||
lineHeight: '27px',
|
||||
minWidth: `${18 * 1.28571429}px`,
|
||||
};
|
||||
|
||||
export default class TextIconButton extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
label: PropTypes.string.isRequired,
|
||||
title: PropTypes.string,
|
||||
active: PropTypes.bool,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
ariaControls: PropTypes.string,
|
||||
};
|
||||
|
||||
render () {
|
||||
const { label, title, active, ariaControls } = this.props;
|
||||
|
||||
return (
|
||||
<button
|
||||
type='button'
|
||||
title={title}
|
||||
aria-label={title}
|
||||
className={`text-icon-button ${active ? 'active' : ''}`}
|
||||
aria-expanded={active}
|
||||
onClick={this.props.onClick}
|
||||
aria-controls={ariaControls} style={iconStyle}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
import { pasteLinkCompose } from 'mastodon/actions/compose_typed';
|
||||
import { openModal } from 'mastodon/actions/modal';
|
||||
import { PRIVATE_QUOTE_MODAL_ID } from 'mastodon/features/ui/components/confirmation_modals/private_quote_notify';
|
||||
import { me } from 'mastodon/initial_state';
|
||||
|
||||
import ComposeForm from '../components/compose_form';
|
||||
|
||||
@@ -53,6 +54,7 @@ const mapStateToProps = state => ({
|
||||
quoteToPrivate:
|
||||
!!state.getIn(['compose', 'quoted_status_id'])
|
||||
&& state.getIn(['compose', 'privacy']) === 'private'
|
||||
&& state.getIn(['statuses', state.getIn(['compose', 'quoted_status_id']), 'account']) !== me
|
||||
&& !state.getIn(['settings', 'dismissed_banners', PRIVATE_QUOTE_MODAL_ID]),
|
||||
isInReply: state.getIn(['compose', 'in_reply_to']) !== null,
|
||||
lang: state.getIn(['compose', 'language']),
|
||||
|
||||
@@ -166,7 +166,7 @@ const Compose: React.FC<{ multiColumn: boolean }> = ({ multiColumn }) => {
|
||||
<div className='drawer__inner'>
|
||||
<ComposeFormContainer />
|
||||
|
||||
<div className='drawer__inner__mastodon'>
|
||||
<div className='drawer__inner__mastodon with-zig-zag-decoration'>
|
||||
<img alt='' draggable='false' src={mascot ?? elephantUIPlane} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { initialState } from '@/mastodon/initial_state';
|
||||
|
||||
import { toSupportedLocale } from './locale';
|
||||
import type { LocaleOrCustom } from './types';
|
||||
import { emojiLogger } from './utils';
|
||||
// eslint-disable-next-line import/default -- Importing via worker loader.
|
||||
import EmojiWorker from './worker?worker&inline';
|
||||
@@ -24,19 +25,17 @@ export function initializeEmoji() {
|
||||
}
|
||||
|
||||
if (worker) {
|
||||
// Assign worker to const to make TS happy inside the event listener.
|
||||
const thisWorker = worker;
|
||||
const timeoutId = setTimeout(() => {
|
||||
log('worker is not ready after timeout');
|
||||
worker = null;
|
||||
void fallbackLoad();
|
||||
}, WORKER_TIMEOUT);
|
||||
thisWorker.addEventListener('message', (event: MessageEvent<string>) => {
|
||||
worker.addEventListener('message', (event: MessageEvent<string>) => {
|
||||
const { data: message } = event;
|
||||
if (message === 'ready') {
|
||||
log('worker ready, loading data');
|
||||
clearTimeout(timeoutId);
|
||||
thisWorker.postMessage('custom');
|
||||
messageWorker('custom');
|
||||
void loadEmojiLocale(userLocale);
|
||||
// Load English locale as well, because people are still used to
|
||||
// using it from before we supported other locales.
|
||||
@@ -55,20 +54,35 @@ export function initializeEmoji() {
|
||||
async function fallbackLoad() {
|
||||
log('falling back to main thread for loading');
|
||||
const { importCustomEmojiData } = await import('./loader');
|
||||
await importCustomEmojiData();
|
||||
const emojis = await importCustomEmojiData();
|
||||
if (emojis) {
|
||||
log('loaded %d custom emojis', emojis.length);
|
||||
}
|
||||
await loadEmojiLocale(userLocale);
|
||||
if (userLocale !== 'en') {
|
||||
await loadEmojiLocale('en');
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadEmojiLocale(localeString: string) {
|
||||
async function loadEmojiLocale(localeString: string) {
|
||||
const locale = toSupportedLocale(localeString);
|
||||
const { importEmojiData, localeToPath } = await import('./loader');
|
||||
|
||||
if (worker) {
|
||||
worker.postMessage(locale);
|
||||
const path = await localeToPath(locale);
|
||||
log('asking worker to load locale %s from %s', locale, path);
|
||||
messageWorker(locale, path);
|
||||
} else {
|
||||
const { importEmojiData } = await import('./loader');
|
||||
await importEmojiData(locale);
|
||||
const emojis = await importEmojiData(locale);
|
||||
if (emojis) {
|
||||
log('loaded %d emojis to locale %s', emojis.length, locale);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function messageWorker(locale: LocaleOrCustom, path?: string) {
|
||||
if (!worker) {
|
||||
return;
|
||||
}
|
||||
worker.postMessage({ locale, path });
|
||||
}
|
||||
|
||||
@@ -8,44 +8,64 @@ import {
|
||||
putLatestEtag,
|
||||
} from './database';
|
||||
import { toSupportedLocale, toSupportedLocaleOrCustom } from './locale';
|
||||
import type { CustomEmojiData, LocaleOrCustom } from './types';
|
||||
import { emojiLogger } from './utils';
|
||||
import type { CustomEmojiData } from './types';
|
||||
|
||||
const log = emojiLogger('loader');
|
||||
|
||||
export async function importEmojiData(localeString: string) {
|
||||
export async function importEmojiData(localeString: string, path?: string) {
|
||||
const locale = toSupportedLocale(localeString);
|
||||
const emojis = await fetchAndCheckEtag<CompactEmoji[]>(locale);
|
||||
|
||||
// Validate the provided path.
|
||||
if (path && !/^[/a-z]*\/packs\/assets\/compact-\w+\.json$/.test(path)) {
|
||||
throw new Error('Invalid path for emoji data');
|
||||
} else {
|
||||
// Otherwise get the path if not provided.
|
||||
path ??= await localeToPath(locale);
|
||||
}
|
||||
|
||||
const emojis = await fetchAndCheckEtag<CompactEmoji[]>(locale, path);
|
||||
if (!emojis) {
|
||||
return;
|
||||
}
|
||||
const flattenedEmojis: FlatCompactEmoji[] = flattenEmojiData(emojis);
|
||||
log('loaded %d for %s locale', flattenedEmojis.length, locale);
|
||||
await putEmojiData(flattenedEmojis, locale);
|
||||
return flattenedEmojis;
|
||||
}
|
||||
|
||||
export async function importCustomEmojiData() {
|
||||
const emojis = await fetchAndCheckEtag<CustomEmojiData[]>('custom');
|
||||
const emojis = await fetchAndCheckEtag<CustomEmojiData[]>(
|
||||
'custom',
|
||||
'/api/v1/custom_emojis',
|
||||
);
|
||||
if (!emojis) {
|
||||
return;
|
||||
}
|
||||
log('loaded %d custom emojis', emojis.length);
|
||||
await putCustomEmojiData(emojis);
|
||||
return emojis;
|
||||
}
|
||||
|
||||
async function fetchAndCheckEtag<ResultType extends object[]>(
|
||||
localeOrCustom: LocaleOrCustom,
|
||||
const modules = import.meta.glob<string>(
|
||||
'../../../../../node_modules/emojibase-data/**/compact.json',
|
||||
{
|
||||
query: '?url',
|
||||
import: 'default',
|
||||
},
|
||||
);
|
||||
|
||||
export function localeToPath(locale: Locale) {
|
||||
const key = `../../../../../node_modules/emojibase-data/${locale}/compact.json`;
|
||||
if (!modules[key] || typeof modules[key] !== 'function') {
|
||||
throw new Error(`Unsupported locale: ${locale}`);
|
||||
}
|
||||
return modules[key]();
|
||||
}
|
||||
|
||||
export async function fetchAndCheckEtag<ResultType extends object[]>(
|
||||
localeString: string,
|
||||
path: string,
|
||||
): Promise<ResultType | null> {
|
||||
const locale = toSupportedLocaleOrCustom(localeOrCustom);
|
||||
const locale = toSupportedLocaleOrCustom(localeString);
|
||||
|
||||
// Use location.origin as this script may be loaded from a CDN domain.
|
||||
const url = new URL(location.origin);
|
||||
if (locale === 'custom') {
|
||||
url.pathname = '/api/v1/custom_emojis';
|
||||
} else {
|
||||
const modulePath = await localeToPath(locale);
|
||||
url.pathname = modulePath;
|
||||
}
|
||||
const url = new URL(path, location.origin);
|
||||
|
||||
const oldEtag = await loadLatestEtag(locale);
|
||||
const response = await fetch(url, {
|
||||
@@ -60,38 +80,20 @@ async function fetchAndCheckEtag<ResultType extends object[]>(
|
||||
}
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`Failed to fetch emoji data for ${localeOrCustom}: ${response.statusText}`,
|
||||
`Failed to fetch emoji data for ${locale}: ${response.statusText}`,
|
||||
);
|
||||
}
|
||||
|
||||
const data = (await response.json()) as ResultType;
|
||||
if (!Array.isArray(data)) {
|
||||
throw new Error(
|
||||
`Unexpected data format for ${localeOrCustom}: expected an array`,
|
||||
);
|
||||
throw new Error(`Unexpected data format for ${locale}: expected an array`);
|
||||
}
|
||||
|
||||
// Store the ETag for future requests
|
||||
const etag = response.headers.get('ETag');
|
||||
if (etag) {
|
||||
await putLatestEtag(etag, localeOrCustom);
|
||||
await putLatestEtag(etag, localeString);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
const modules = import.meta.glob<string>(
|
||||
'../../../../../node_modules/emojibase-data/**/compact.json',
|
||||
{
|
||||
query: '?url',
|
||||
import: 'default',
|
||||
},
|
||||
);
|
||||
|
||||
function localeToPath(locale: Locale) {
|
||||
const key = `../../../../../node_modules/emojibase-data/${locale}/compact.json`;
|
||||
if (!modules[key] || typeof modules[key] !== 'function') {
|
||||
throw new Error(`Unsupported locale: ${locale}`);
|
||||
}
|
||||
return modules[key]();
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ describe('loadEmojiDataToState', () => {
|
||||
const dbCall = vi
|
||||
.spyOn(db, 'loadEmojiByHexcode')
|
||||
.mockRejectedValue(new db.LocaleNotLoadedError('en'));
|
||||
vi.spyOn(loader, 'importEmojiData').mockResolvedValueOnce();
|
||||
vi.spyOn(loader, 'importEmojiData').mockResolvedValueOnce(undefined);
|
||||
const consoleCall = vi
|
||||
.spyOn(console, 'warn')
|
||||
.mockImplementationOnce(() => null);
|
||||
|
||||
@@ -1,18 +1,25 @@
|
||||
import { importEmojiData, importCustomEmojiData } from './loader';
|
||||
import { importCustomEmojiData, importEmojiData } from './loader';
|
||||
|
||||
addEventListener('message', handleMessage);
|
||||
self.postMessage('ready'); // After the worker is ready, notify the main thread
|
||||
|
||||
function handleMessage(event: MessageEvent<string>) {
|
||||
const { data: locale } = event;
|
||||
void loadData(locale);
|
||||
function handleMessage(event: MessageEvent<{ locale: string; path?: string }>) {
|
||||
const {
|
||||
data: { locale, path },
|
||||
} = event;
|
||||
void loadData(locale, path);
|
||||
}
|
||||
|
||||
async function loadData(locale: string) {
|
||||
if (locale !== 'custom') {
|
||||
await importEmojiData(locale);
|
||||
async function loadData(locale: string, path?: string) {
|
||||
let importCount: number | undefined;
|
||||
if (locale === 'custom') {
|
||||
importCount = (await importCustomEmojiData())?.length;
|
||||
} else if (path) {
|
||||
importCount = (await importEmojiData(locale, path))?.length;
|
||||
} else {
|
||||
await importCustomEmojiData();
|
||||
throw new Error('Path is required for loading locale emoji data');
|
||||
}
|
||||
if (importCount) {
|
||||
self.postMessage(`loaded ${importCount} emojis into ${locale}`);
|
||||
}
|
||||
self.postMessage(`loaded ${locale}`);
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ export const DisabledAccountBanner: React.FC = () => {
|
||||
</a>
|
||||
<button
|
||||
type='button'
|
||||
className='button button--block button-tertiary'
|
||||
className='button button--block button-secondary'
|
||||
onClick={handleLogOutClick}
|
||||
>
|
||||
<FormattedMessage
|
||||
|
||||
@@ -46,7 +46,7 @@ export const SignInBanner: React.FC = () => {
|
||||
<a
|
||||
href={sso_redirect}
|
||||
data-method='post'
|
||||
className='button button--block button-tertiary'
|
||||
className='button button--block button-secondary'
|
||||
>
|
||||
<FormattedMessage
|
||||
id='sign_in_banner.sso_redirect'
|
||||
@@ -98,7 +98,7 @@ export const SignInBanner: React.FC = () => {
|
||||
/>
|
||||
</p>
|
||||
{signupButton}
|
||||
<a href='/auth/sign_in' className='button button--block button-tertiary'>
|
||||
<a href='/auth/sign_in' className='button button--block button-secondary'>
|
||||
<FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Login' />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
@typescript-eslint/no-unsafe-assignment */
|
||||
|
||||
import type { CSSProperties } from 'react';
|
||||
import { useState, useRef, useCallback } from 'react';
|
||||
import { useState, useRef, useCallback, useEffect } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
@@ -55,6 +55,8 @@ export const DetailedStatus: React.FC<{
|
||||
pictureInPicture: any;
|
||||
onToggleHidden?: (status: any) => void;
|
||||
onToggleMediaVisibility?: () => void;
|
||||
ancestors?: number;
|
||||
multiColumn?: boolean;
|
||||
}> = ({
|
||||
status,
|
||||
onOpenMedia,
|
||||
@@ -69,6 +71,8 @@ export const DetailedStatus: React.FC<{
|
||||
pictureInPicture,
|
||||
onToggleMediaVisibility,
|
||||
onToggleHidden,
|
||||
ancestors = 0,
|
||||
multiColumn = false,
|
||||
}) => {
|
||||
const properStatus = status?.get('reblog') ?? status;
|
||||
const [height, setHeight] = useState(0);
|
||||
@@ -123,6 +127,30 @@ export const DetailedStatus: React.FC<{
|
||||
if (onTranslate) onTranslate(status);
|
||||
}, [onTranslate, status]);
|
||||
|
||||
// The component is managed and will change if the status changes
|
||||
// Ancestors can increase when loading a thread, in which case we want to scroll,
|
||||
// or decrease if a post is deleted, in which case we don't want to mess with it
|
||||
const previousAncestors = useRef(-1);
|
||||
useEffect(() => {
|
||||
if (nodeRef.current && previousAncestors.current < ancestors) {
|
||||
nodeRef.current.scrollIntoView(true);
|
||||
|
||||
// In the single-column interface, `scrollIntoView` will put the post behind the header, so compensate for that.
|
||||
if (!multiColumn) {
|
||||
const offset = document
|
||||
.querySelector('.column-header__wrapper')
|
||||
?.getBoundingClientRect().bottom;
|
||||
|
||||
if (offset) {
|
||||
const scrollingElement = document.scrollingElement ?? document.body;
|
||||
scrollingElement.scrollBy(0, -offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
previousAncestors.current = ancestors;
|
||||
}, [ancestors, multiColumn]);
|
||||
|
||||
if (!properStatus) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -164,8 +164,6 @@ class Status extends ImmutablePureComponent {
|
||||
|
||||
componentDidMount () {
|
||||
attachFullscreenListener(this.onFullScreenChange);
|
||||
|
||||
this._scrollStatusIntoView();
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps (nextProps) {
|
||||
@@ -487,35 +485,11 @@ class Status extends ImmutablePureComponent {
|
||||
this.statusNode = c;
|
||||
};
|
||||
|
||||
_scrollStatusIntoView () {
|
||||
const { status, multiColumn } = this.props;
|
||||
|
||||
if (status) {
|
||||
requestIdleCallback(() => {
|
||||
this.statusNode?.scrollIntoView(true);
|
||||
|
||||
// In the single-column interface, `scrollIntoView` will put the post behind the header,
|
||||
// so compensate for that.
|
||||
if (!multiColumn) {
|
||||
const offset = document.querySelector('.column-header__wrapper')?.getBoundingClientRect()?.bottom;
|
||||
if (offset) {
|
||||
const scrollingElement = document.scrollingElement || document.body;
|
||||
scrollingElement.scrollBy(0, -offset);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
const { status, ancestorsIds, descendantsIds } = this.props;
|
||||
const { status, descendantsIds } = this.props;
|
||||
|
||||
const isSameStatus = status && (prevProps.status?.get('id') === status.get('id'));
|
||||
|
||||
if (status && (ancestorsIds.length > prevProps.ancestorsIds.length || !isSameStatus)) {
|
||||
this._scrollStatusIntoView();
|
||||
}
|
||||
|
||||
// Only highlight replies after the initial load
|
||||
if (prevProps.descendantsIds.length && isSameStatus) {
|
||||
const newRepliesIds = difference(descendantsIds, prevProps.descendantsIds);
|
||||
@@ -619,6 +593,8 @@ class Status extends ImmutablePureComponent {
|
||||
showMedia={this.state.showMedia}
|
||||
onToggleMediaVisibility={this.handleToggleMediaVisibility}
|
||||
pictureInPicture={pictureInPicture}
|
||||
ancestors={this.props.ancestorsIds.length}
|
||||
multiColumn={multiColumn}
|
||||
/>
|
||||
|
||||
<ActionBar
|
||||
|
||||
@@ -98,7 +98,7 @@ class BundleColumnError extends PureComponent {
|
||||
<div className='error-column__message__actions'>
|
||||
{errorType === 'network' && <Button onClick={this.handleRetry}><FormattedMessage id='bundle_column_error.retry' defaultMessage='Try again' /></Button>}
|
||||
{errorType === 'error' && <CopyButton value={stacktrace}><FormattedMessage id='bundle_column_error.copy_stacktrace' defaultMessage='Copy error report' /></CopyButton>}
|
||||
<Link to='/' className={classNames('button', { 'button-tertiary': errorType !== 'routing' })}><FormattedMessage id='bundle_column_error.return' defaultMessage='Go back home' /></Link>
|
||||
<Link to='/' className={classNames('button', { 'button-secondary': errorType !== 'routing' })}><FormattedMessage id='bundle_column_error.return' defaultMessage='Go back home' /></Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -46,7 +46,7 @@ export const ModalPlaceholder: React.FC<{
|
||||
defaultMessage='Try again'
|
||||
/>
|
||||
</Button>
|
||||
<Button onClick={handleClose} className='button button-tertiary'>
|
||||
<Button onClick={handleClose} className='button button-secondary'>
|
||||
<FormattedMessage
|
||||
id='bundle_modal_error.close'
|
||||
defaultMessage='Close'
|
||||
|
||||
@@ -104,7 +104,7 @@ const LoginOrSignUp: React.FC = () => {
|
||||
<a
|
||||
href={sso_redirect}
|
||||
data-method='post'
|
||||
className='button button--block button-tertiary'
|
||||
className='button button--block button-secondary'
|
||||
>
|
||||
<FormattedMessage
|
||||
id='sign_in_banner.sso_redirect'
|
||||
@@ -143,7 +143,7 @@ const LoginOrSignUp: React.FC = () => {
|
||||
return (
|
||||
<div className='ui__navigation-bar__sign-up'>
|
||||
{signupButton}
|
||||
<a href='/auth/sign_in' className='button button-tertiary'>
|
||||
<a href='/auth/sign_in' className='button button-secondary'>
|
||||
<FormattedMessage
|
||||
id='sign_in_banner.sign_in'
|
||||
defaultMessage='Login'
|
||||
|
||||
@@ -903,6 +903,7 @@
|
||||
"status.edited_x_times": "Upraveno {count, plural, one {{count}krát} few {{count}krát} many {{count}krát} other {{count}krát}}",
|
||||
"status.embed": "Získejte kód pro vložení",
|
||||
"status.favourite": "Oblíbit",
|
||||
"status.favourites_count": "{count, plural, one {{counter} oblíbený} few {{counter} oblíbené} many {{counter} oblíbených} other {{counter} oblíbených}}",
|
||||
"status.filter": "Filtrovat tento příspěvek",
|
||||
"status.history.created": "Uživatel {name} vytvořil {date}",
|
||||
"status.history.edited": "Uživatel {name} upravil {date}",
|
||||
@@ -937,12 +938,14 @@
|
||||
"status.quotes.empty": "Tento příspěvek zatím nikdo necitoval. Pokud tak někdo učiní, uvidíte to zde.",
|
||||
"status.quotes.local_other_disclaimer": "Citace zamítnuté autorem nebudou zobrazeny.",
|
||||
"status.quotes.remote_other_disclaimer": "Pouze citace z {domain} zde budou zaručeně ukázány. Citace zamítnuté autorem nebudou zobrazeny.",
|
||||
"status.quotes_count": "{count, plural, one {{counter} citace} few {{counter} citace} many {{counter} citací} other {{counter} citací}}",
|
||||
"status.read_more": "Číst více",
|
||||
"status.reblog": "Boostnout",
|
||||
"status.reblog_or_quote": "Boostnout nebo citovat",
|
||||
"status.reblog_private": "Sdílejte znovu se svými sledujícími",
|
||||
"status.reblogged_by": "Uživatel {name} boostnul",
|
||||
"status.reblogs.empty": "Tento příspěvek ještě nikdo neboostnul. Pokud to někdo udělá, zobrazí se zde.",
|
||||
"status.reblogs_count": "{count, plural, one {{counter} boost} few {{counter} boosty} many {{counter} boostů} other {{counter} boostů}}",
|
||||
"status.redraft": "Smazat a přepsat",
|
||||
"status.remove_bookmark": "Odstranit ze záložek",
|
||||
"status.remove_favourite": "Odebrat z oblíbených",
|
||||
|
||||
@@ -903,6 +903,7 @@
|
||||
"status.edited_x_times": "Golygwyd {count, plural, one {{count} gwaith} other {{count} gwaith}}",
|
||||
"status.embed": "Cael y cod mewnblannu",
|
||||
"status.favourite": "Ffafrio",
|
||||
"status.favourites_count": "{count, plural, one {{counter} ffefryn} other {{counter} ffefryn}}",
|
||||
"status.filter": "Hidlo'r postiad hwn",
|
||||
"status.history.created": "Crëwyd gan {name} {date}",
|
||||
"status.history.edited": "Golygwyd gan {name} {date}",
|
||||
@@ -937,12 +938,14 @@
|
||||
"status.quotes.empty": "Does neb wedi dyfynnu'r postiad hwn eto. Pan fydd rhywun yn gwneud hynny, bydd yn ymddangos yma.",
|
||||
"status.quotes.local_other_disclaimer": "Bydd dyfyniadau wedi'u gwrthod gan yr awdur ddim yn cael eu dangos.",
|
||||
"status.quotes.remote_other_disclaimer": "Dim ond dyfyniadau o {domain} sy'n siŵr o gael eu dangos yma. Bydd dyfyniadau wedi'u gwrthod gan yr awdur ddim yn cael eu dangos.",
|
||||
"status.quotes_count": "{count, plural, one {{counter} dyfyniad} other {{counter} dyfyniad}}",
|
||||
"status.read_more": "Darllen rhagor",
|
||||
"status.reblog": "Hybu",
|
||||
"status.reblog_or_quote": "Hybu neu ddyfynnu",
|
||||
"status.reblog_private": "Rhannwch eto gyda'ch dilynwyr",
|
||||
"status.reblogged_by": "Hybodd {name}",
|
||||
"status.reblogs.empty": "Does neb wedi hybio'r post yma eto. Pan y bydd rhywun yn gwneud, byddent yn ymddangos yma.",
|
||||
"status.reblogs_count": "{count, plural, one {{counter} hwb} other {{counter} hwb}}",
|
||||
"status.redraft": "Dileu ac ail lunio",
|
||||
"status.remove_bookmark": "Tynnu nod tudalen",
|
||||
"status.remove_favourite": "Tynnu o'r ffefrynnau",
|
||||
|
||||
@@ -231,7 +231,7 @@
|
||||
"confirmations.delete_list.title": "Slet liste?",
|
||||
"confirmations.discard_draft.confirm": "Kassér og fortsæt",
|
||||
"confirmations.discard_draft.edit.cancel": "Fortsæt redigering",
|
||||
"confirmations.discard_draft.edit.message": "Hvis du fortsætter, kasseres alle ændringer, du har foretaget i det indlæg, du er i gang med at redigere.",
|
||||
"confirmations.discard_draft.edit.message": "Hvis du fortsætter, vil alle ændringer, du har foretaget i det indlæg, du er er ved at redigere, blive slettet.",
|
||||
"confirmations.discard_draft.edit.title": "Kassér ændringer til dit indlæg?",
|
||||
"confirmations.discard_draft.post.cancel": "Genoptag udkast",
|
||||
"confirmations.discard_draft.post.message": "Hvis du fortsætter, kasseres det indlæg, du er i gang med at udforme.",
|
||||
@@ -507,7 +507,7 @@
|
||||
"keyboard_shortcuts.pinned": "Åbn liste over fastgjorte indlæg",
|
||||
"keyboard_shortcuts.profile": "Åbn forfatters profil",
|
||||
"keyboard_shortcuts.quote": "Citér indlæg",
|
||||
"keyboard_shortcuts.reply": "Besvar indlægget",
|
||||
"keyboard_shortcuts.reply": "Besvar indlæg",
|
||||
"keyboard_shortcuts.requests": "Åbn liste over følgeanmodninger",
|
||||
"keyboard_shortcuts.search": "Fokusér søgebjælke",
|
||||
"keyboard_shortcuts.spoilers": "Vis/skjul indholdsadvarsel-felt",
|
||||
@@ -675,7 +675,7 @@
|
||||
"notifications.column_settings.filter_bar.category": "Hurtigfiltreringsbjælke",
|
||||
"notifications.column_settings.follow": "Nye følgere:",
|
||||
"notifications.column_settings.follow_request": "Nye følgeanmodninger:",
|
||||
"notifications.column_settings.group": "Gruppere",
|
||||
"notifications.column_settings.group": "Gruppér",
|
||||
"notifications.column_settings.mention": "Omtaler:",
|
||||
"notifications.column_settings.poll": "Afstemningsresultater:",
|
||||
"notifications.column_settings.push": "Push-notifikationer",
|
||||
@@ -764,7 +764,7 @@
|
||||
"privacy_policy.last_updated": "Senest opdateret {date}",
|
||||
"privacy_policy.title": "Privatlivspolitik",
|
||||
"quote_error.edit": "Citater kan ikke tilføjes ved redigering af et indlæg.",
|
||||
"quote_error.poll": "Citering ikke tilladt i afstemninger.",
|
||||
"quote_error.poll": "Citering er ikke tilladt med afstemninger.",
|
||||
"quote_error.private_mentions": "Citering er ikke tilladt med direkte omtaler.",
|
||||
"quote_error.quote": "Kun ét citat ad gangen er tilladt.",
|
||||
"quote_error.unauthorized": "Du har ikke tilladelse til at citere dette indlæg.",
|
||||
@@ -867,7 +867,7 @@
|
||||
"search_results.title": "Søg efter \"{q}\"",
|
||||
"server_banner.about_active_users": "Personer, som brugte denne server de seneste 30 dage (månedlige aktive brugere)",
|
||||
"server_banner.active_users": "aktive brugere",
|
||||
"server_banner.administered_by": "Håndteres af:",
|
||||
"server_banner.administered_by": "Administreret af:",
|
||||
"server_banner.is_one_of_many": "{domain} er en af de mange uafhængige Mastodon-servere, du kan bruge for at deltage i fediverset.",
|
||||
"server_banner.server_stats": "Serverstatstik:",
|
||||
"sign_in_banner.create_account": "Opret konto",
|
||||
@@ -902,7 +902,7 @@
|
||||
"status.edited": "Senest redigeret {date}",
|
||||
"status.edited_x_times": "Redigeret {count, plural, one {{count} gang} other {{count} gange}}",
|
||||
"status.embed": "Hent indlejringskode",
|
||||
"status.favourite": "Favorit",
|
||||
"status.favourite": "Favoritmarkér",
|
||||
"status.favourites_count": "{count, plural, one {{counter} favorit} other {{counter} favoritter}}",
|
||||
"status.filter": "Filtrér dette indlæg",
|
||||
"status.history.created": "{name} oprettet {date}",
|
||||
@@ -991,7 +991,7 @@
|
||||
"units.short.million": "{count} mio.",
|
||||
"units.short.thousand": "{count} tusind",
|
||||
"upload_area.title": "Træk og slip for at uploade",
|
||||
"upload_button.label": "Tilføj billed-, video- eller lydfil(er)",
|
||||
"upload_button.label": "Tilføj billeder, en video- eller lydfil",
|
||||
"upload_error.limit": "Grænse for filupload nået.",
|
||||
"upload_error.poll": "Filupload ikke tilladt for afstemninger.",
|
||||
"upload_error.quote": "Fil-upload ikke tilladt i citater.",
|
||||
|
||||
@@ -242,7 +242,7 @@
|
||||
"confirmations.follow_to_list.message": "Du musst {name} folgen, um das Profil zu einer Liste hinzufügen zu können.",
|
||||
"confirmations.follow_to_list.title": "Profil folgen?",
|
||||
"confirmations.logout.confirm": "Abmelden",
|
||||
"confirmations.logout.message": "Möchtest du dich wirklich abmelden?",
|
||||
"confirmations.logout.message": "Bist du sicher, dass du dich abmelden möchtest?",
|
||||
"confirmations.logout.title": "Abmelden?",
|
||||
"confirmations.missing_alt_text.confirm": "Bildbeschreibung hinzufügen",
|
||||
"confirmations.missing_alt_text.message": "Dein Beitrag enthält Medien ohne Bildbeschreibung. Mit ALT-Texten erreichst Du auch Menschen, die blind oder sehbehindert sind.",
|
||||
@@ -304,12 +304,12 @@
|
||||
"domain_pill.activitypub_lets_connect": "Somit kannst du dich nicht nur auf Mastodon mit Leuten verbinden und mit ihnen interagieren, sondern über alle sozialen Apps hinweg.",
|
||||
"domain_pill.activitypub_like_language": "ActivityPub ist sozusagen die Sprache, die Mastodon mit anderen sozialen Netzwerken spricht.",
|
||||
"domain_pill.server": "Server",
|
||||
"domain_pill.their_handle": "Deren Adresse:",
|
||||
"domain_pill.their_server": "Deren digitale Heimat. Hier „leben“ alle Beiträge von diesem Profil.",
|
||||
"domain_pill.their_username": "Deren eindeutigen Identität auf dem betreffenden Server. Es ist möglich, Profile mit dem gleichen Profilnamen auf verschiedenen Servern zu finden.",
|
||||
"domain_pill.their_handle": "Die vollständige Adresse:",
|
||||
"domain_pill.their_server": "Die digitale Heimat, in der sich alle Beiträge dieses Profils befinden.",
|
||||
"domain_pill.their_username": "Die eindeutige Identifizierung auf einem Server. Es ist möglich, denselben Profilnamen auf verschiedenen Servern im Fediverse zu finden.",
|
||||
"domain_pill.username": "Profilname",
|
||||
"domain_pill.whats_in_a_handle": "Woraus besteht eine Adresse?",
|
||||
"domain_pill.who_they_are": "Adressen teilen mit, wer jemand ist und wo sich jemand aufhält. Daher kannst du mit Leuten im gesamten Social Web interagieren, wenn es eine durch <button>ActivityPub angetriebene Plattform</button> ist.",
|
||||
"domain_pill.who_they_are": "Adressen teilen mit, wer jemand ist und wo sich jemand im Fediverse aufhält. Daher kannst du mit Leuten im gesamten Social Web interagieren, wenn es eine durch <button>ActivityPub angetriebene Plattform</button> ist.",
|
||||
"domain_pill.who_you_are": "Deine Adresse teilt mit, wer du bist und wo du dich aufhältst. Daher können andere Leute im gesamten Social Web mit dir interagieren, wenn es eine durch <button>ActivityPub angetriebene Plattform</button> ist.",
|
||||
"domain_pill.your_handle": "Deine Adresse:",
|
||||
"domain_pill.your_server": "Deine digitale Heimat. Hier „leben“ alle Beiträge von dir. Falls es dir hier nicht gefällt, kannst du jederzeit den Server wechseln und ebenso deine Follower übertragen.",
|
||||
@@ -584,8 +584,8 @@
|
||||
"navigation_bar.follows_and_followers": "Follower und Folge ich",
|
||||
"navigation_bar.import_export": "Importieren und exportieren",
|
||||
"navigation_bar.lists": "Listen",
|
||||
"navigation_bar.live_feed_local": "Live-Feed (lokal)",
|
||||
"navigation_bar.live_feed_public": "Live-Feed (öffentlich)",
|
||||
"navigation_bar.live_feed_local": "Live-Feed (Dieser Server)",
|
||||
"navigation_bar.live_feed_public": "Live-Feed (Alle Server)",
|
||||
"navigation_bar.logout": "Abmelden",
|
||||
"navigation_bar.moderation": "Moderation",
|
||||
"navigation_bar.more": "Mehr",
|
||||
@@ -693,7 +693,7 @@
|
||||
"notifications.filter.follows": "Folgt",
|
||||
"notifications.filter.mentions": "Erwähnungen",
|
||||
"notifications.filter.polls": "Umfrageergebnisse",
|
||||
"notifications.filter.statuses": "Neue Beiträge von Personen, denen du folgst",
|
||||
"notifications.filter.statuses": "Neue Beiträge von Profilen, denen du folgst",
|
||||
"notifications.grant_permission": "Berechtigung erteilen.",
|
||||
"notifications.group": "{count} Benachrichtigungen",
|
||||
"notifications.mark_as_read": "Alle Benachrichtigungen als gelesen markieren",
|
||||
@@ -903,7 +903,7 @@
|
||||
"status.edited_x_times": "{count, plural, one {{count}-mal} other {{count}-mal}} bearbeitet",
|
||||
"status.embed": "Code zum Einbetten",
|
||||
"status.favourite": "Favorisieren",
|
||||
"status.favourites_count": "{count, plural, one {{counter} Favorit} other {{counter} Favoriten}}",
|
||||
"status.favourites_count": "{count, plural, one {{counter} Mal favorisiert} other {{counter} Mal favorisiert}}",
|
||||
"status.filter": "Beitrag filtern",
|
||||
"status.history.created": "{name} erstellte {date}",
|
||||
"status.history.edited": "{name} bearbeitete {date}",
|
||||
@@ -938,14 +938,14 @@
|
||||
"status.quotes.empty": "Diesen Beitrag hat bisher noch niemand zitiert. Sobald es jemand tut, wird das Profil hier erscheinen.",
|
||||
"status.quotes.local_other_disclaimer": "Durch Autor*in abgelehnte Zitate werden nicht angezeigt.",
|
||||
"status.quotes.remote_other_disclaimer": "Nur Zitate von {domain} werden hier garantiert angezeigt. Durch Autor*in abgelehnte Zitate werden nicht angezeigt.",
|
||||
"status.quotes_count": "{count, plural, one {{counter} zitierter Beitrag} other {{counter} zitierte Beiträge}}",
|
||||
"status.quotes_count": "{count, plural, one {{counter} Mal zitiert} other {{counter} Mal zitiert}}",
|
||||
"status.read_more": "Gesamten Beitrag anschauen",
|
||||
"status.reblog": "Teilen",
|
||||
"status.reblog_or_quote": "Teilen oder zitieren",
|
||||
"status.reblog_private": "Erneut mit deinen Followern teilen",
|
||||
"status.reblogged_by": "{name} teilte",
|
||||
"status.reblogs.empty": "Diesen Beitrag hat bisher noch niemand geteilt. Sobald es jemand tut, wird das Profil hier erscheinen.",
|
||||
"status.reblogs_count": "{count, plural, one {{counter} geteilter Beitrag} other {{counter} geteilte Beiträge}}",
|
||||
"status.reblogs_count": "{count, plural, one {{counter} Mal geteilt} other {{counter} Mal geteilt}}",
|
||||
"status.redraft": "Löschen und neu erstellen",
|
||||
"status.remove_bookmark": "Lesezeichen entfernen",
|
||||
"status.remove_favourite": "Aus Favoriten entfernen",
|
||||
|
||||
@@ -903,6 +903,7 @@
|
||||
"status.edited_x_times": "Muudetud {count, plural, one{{count} kord} other {{count} korda}}",
|
||||
"status.embed": "Hangi manustamiskood",
|
||||
"status.favourite": "Lemmik",
|
||||
"status.favourites_count": "{count, plural, one {{counter} lemmik} other {{counter} lemmikut}}",
|
||||
"status.filter": "Filtreeri seda postitust",
|
||||
"status.history.created": "{name} lõi {date}",
|
||||
"status.history.edited": "{name} muutis {date}",
|
||||
@@ -937,12 +938,14 @@
|
||||
"status.quotes.empty": "Keegi pole seda postitust veel tsiteerinud. Kui keegi seda teeb, siis on ta nähtav siin.",
|
||||
"status.quotes.local_other_disclaimer": "Autori poolt tagasilükatud tsitaate ei kuvata.",
|
||||
"status.quotes.remote_other_disclaimer": "Kui kasutaja on {domain} domeenist, siis siin on tagatud vaid tema tsitaatide näitamine. Autori poolt tagasilükatud tsitaate ei kuvata.",
|
||||
"status.quotes_count": "{count, plural, one {{counter} tsiteerimine} other {{counter} tsiteerimist}}",
|
||||
"status.read_more": "Loe veel",
|
||||
"status.reblog": "Jaga",
|
||||
"status.reblog_or_quote": "Anna hoogu või tsiteeri",
|
||||
"status.reblog_private": "Jaga uuesti oma jälgijatele",
|
||||
"status.reblogged_by": "{name} jagas",
|
||||
"status.reblogs.empty": "Keegi pole seda postitust veel jaganud. Kui keegi seda teeb, siis on ta nähtav siin.",
|
||||
"status.reblogs_count": "{count, plural, one {{counter} jagamine} other {{counter} jagamist}}",
|
||||
"status.redraft": "Kustuta & alga uuesti",
|
||||
"status.remove_bookmark": "Eemalda järjehoidja",
|
||||
"status.remove_favourite": "Eemalda lemmikute seast",
|
||||
|
||||
@@ -233,7 +233,7 @@
|
||||
"confirmations.discard_draft.edit.cancel": "Palaa muokkaamaan",
|
||||
"confirmations.discard_draft.edit.message": "Jatkaminen tuhoaa kaikki muutokset, joita olet tehnyt julkaisuun, jota olet parhaillaan muokkaamassa.",
|
||||
"confirmations.discard_draft.edit.title": "Hylätäänkö luonnosjulkaisusi muutokset?",
|
||||
"confirmations.discard_draft.post.cancel": "Palaa lunnokseen",
|
||||
"confirmations.discard_draft.post.cancel": "Palaa luonnokseen",
|
||||
"confirmations.discard_draft.post.message": "Jatkaminen tuhoaa julkaisun, jota olet parhaillaan laatimassa.",
|
||||
"confirmations.discard_draft.post.title": "Hylätäänkö luonnosjulkaisusi?",
|
||||
"confirmations.discard_edit_media.confirm": "Hylkää",
|
||||
@@ -903,7 +903,7 @@
|
||||
"status.edited_x_times": "Muokattu {count, plural, one {{count} kerran} other {{count} kertaa}}",
|
||||
"status.embed": "Hanki upotuskoodi",
|
||||
"status.favourite": "Suosikki",
|
||||
"status.favourites_count": "{count, plural, one {{counter} suosikki} other {{counter} suoksikkia}}",
|
||||
"status.favourites_count": "{count, plural, one {{counter} suosikki} other {{counter} suosikkia}}",
|
||||
"status.filter": "Suodata tämä julkaisu",
|
||||
"status.history.created": "{name} loi {date}",
|
||||
"status.history.edited": "{name} muokkasi {date}",
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"account.blocked": "Bloqué·e",
|
||||
"account.blocking": "Bloqué",
|
||||
"account.cancel_follow_request": "Retirer cette demande d'abonnement",
|
||||
"account.copy": "Copier le lien vers le profil",
|
||||
"account.copy": "Copier le lien du profil",
|
||||
"account.direct": "Mention privée @{name}",
|
||||
"account.disable_notifications": "Ne plus me notifier quand @{name} publie",
|
||||
"account.domain_blocking": "Domaine bloqué",
|
||||
@@ -45,7 +45,7 @@
|
||||
"account.follow_request": "Demande d’abonnement",
|
||||
"account.follow_request_cancel": "Annuler la demande",
|
||||
"account.follow_request_cancel_short": "Annuler",
|
||||
"account.follow_request_short": "Requête",
|
||||
"account.follow_request_short": "Demander à suivre",
|
||||
"account.followers": "abonné·e·s",
|
||||
"account.followers.empty": "Personne ne suit ce compte pour l'instant.",
|
||||
"account.followers_counter": "{count, plural, one {{counter} abonné·e} other {{counter} abonné·e·s}}",
|
||||
@@ -528,7 +528,7 @@
|
||||
"limited_account_hint.action": "Afficher le profil quand même",
|
||||
"limited_account_hint.title": "Ce profil a été masqué par la modération de {domain}.",
|
||||
"link_preview.author": "Par {name}",
|
||||
"link_preview.more_from_author": "Plus via {name}",
|
||||
"link_preview.more_from_author": "Voir plus de {name}",
|
||||
"link_preview.shares": "{count, plural, one {{counter} message} other {{counter} messages}}",
|
||||
"lists.add_member": "Ajouter",
|
||||
"lists.add_to_list": "Ajouter à la liste",
|
||||
@@ -759,7 +759,7 @@
|
||||
"privacy.quote.disabled": "{visibility}, citations désactivées",
|
||||
"privacy.quote.limited": "{visibility}, citations limitées",
|
||||
"privacy.unlisted.additional": "Se comporte exactement comme « public », sauf que le message n'apparaîtra pas dans les flux en direct, les hashtags, explorer ou la recherche Mastodon, même si vous les avez activé au niveau de votre compte.",
|
||||
"privacy.unlisted.long": "Caché des résultats de recherche de Mastodon, aux tendances et aux échéanciers publics",
|
||||
"privacy.unlisted.long": "Caché des résultats de recherche de Mastodon, des tendances et des fils publics",
|
||||
"privacy.unlisted.short": "Public discret",
|
||||
"privacy_policy.last_updated": "Dernière mise à jour {date}",
|
||||
"privacy_policy.title": "Politique de confidentialité",
|
||||
@@ -903,6 +903,7 @@
|
||||
"status.edited_x_times": "Modifiée {count, plural, one {{count} fois} other {{count} fois}}",
|
||||
"status.embed": "Obtenir le code d'intégration",
|
||||
"status.favourite": "Ajouter aux favoris",
|
||||
"status.favourites_count": "{count, plural, one {{counter} favori} other {{counter} favoris}}",
|
||||
"status.filter": "Filtrer cette publication",
|
||||
"status.history.created": "créé par {name} {date}",
|
||||
"status.history.edited": "modifié par {name} {date}",
|
||||
@@ -937,12 +938,14 @@
|
||||
"status.quotes.empty": "Personne n'a encore cité ce message. Quand quelqu'un le fera, il apparaîtra ici.",
|
||||
"status.quotes.local_other_disclaimer": "Les citations rejetées par l'auteur ne seront pas affichées.",
|
||||
"status.quotes.remote_other_disclaimer": "Seules les citations de {domain} sont garanties d'être affichées ici. Les citations rejetées par l'auteur ne seront pas affichées.",
|
||||
"status.quotes_count": "{count, plural, one {{counter} citation} other {{counter} citations}}",
|
||||
"status.read_more": "En savoir plus",
|
||||
"status.reblog": "Booster",
|
||||
"status.reblog_or_quote": "Boost ou citation",
|
||||
"status.reblog_private": "Partagez à nouveau avec vos abonnés",
|
||||
"status.reblogged_by": "{name} a boosté",
|
||||
"status.reblogs.empty": "Personne n’a encore boosté cette publication. Lorsque quelqu’un le fera, elle apparaîtra ici.",
|
||||
"status.reblogs_count": "{count, plural, one {{counter} partage} other {{counter} partages}}",
|
||||
"status.redraft": "Supprimer et réécrire",
|
||||
"status.remove_bookmark": "Retirer des signets",
|
||||
"status.remove_favourite": "Retirer des favoris",
|
||||
@@ -1023,8 +1026,8 @@
|
||||
"visibility_modal.helper.privacy_editing": "La visibilité ne peut pas être modifiée après la publication d'un message.",
|
||||
"visibility_modal.helper.privacy_private_self_quote": "Les auto-citations de messages privés ne peuvent pas être rendues publiques.",
|
||||
"visibility_modal.helper.private_quoting": "Les posts accessible uniquement par les followers sur Mastodon ne peuvent être cités par d'autres personnes.",
|
||||
"visibility_modal.helper.unlisted_quoting": "Lorsque les gens vous citent, leur message sera également caché dans les calendriers tendances.",
|
||||
"visibility_modal.instructions": "Contrôlez qui peut interagir avec ce post. Vous pouvez également appliquer ces paramètres à tous les futurs messages en allant dans <link>Préférences > Valeurs par d'éfaut de publication</link>.",
|
||||
"visibility_modal.helper.unlisted_quoting": "Lorsque les gens vous citent, leur message sera également caché des fils tendances.",
|
||||
"visibility_modal.instructions": "Contrôlez qui peut interagir avec ce post. Vous pouvez également appliquer ces paramètres à tous les futurs messages en allant dans <link>Préférences > Valeurs par défaut de publication</link>.",
|
||||
"visibility_modal.privacy_label": "Visibilité",
|
||||
"visibility_modal.quote_followers": "Abonné·e·s seulement",
|
||||
"visibility_modal.quote_label": "Autoriser les citations pour",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"about.default_locale": "Défaut",
|
||||
"about.disclaimer": "Mastodon est un logiciel libre, open-source et une marque déposée de Mastodon gGmbH.",
|
||||
"about.domain_blocks.no_reason_available": "Raison non disponible",
|
||||
"about.domain_blocks.preamble": "Mastodon vous permet généralement de visualiser le contenu et d'interagir avec les utilisateur⋅rices de n'importe quel autre serveur dans le fédivers. Voici les exceptions qui ont été faites sur ce serveur-là.",
|
||||
"about.domain_blocks.preamble": "Mastodon vous permet généralement de visualiser le contenu et d'interagir avec les utilisateurs et utilisatrices de n'importe quel autre serveur dans le fédivers. Voici les exceptions qui ont été faites sur ce serveur.",
|
||||
"about.domain_blocks.silenced.explanation": "Vous ne verrez généralement pas les profils et le contenu de ce serveur, à moins que vous ne les recherchiez explicitement ou que vous ne choisissiez de les suivre.",
|
||||
"about.domain_blocks.silenced.title": "Limité",
|
||||
"about.domain_blocks.suspended.explanation": "Aucune donnée de ce serveur ne sera traitée, enregistrée ou échangée, rendant impossible toute interaction ou communication avec les comptes de ce serveur.",
|
||||
@@ -23,7 +23,7 @@
|
||||
"account.blocked": "Bloqué·e",
|
||||
"account.blocking": "Bloqué",
|
||||
"account.cancel_follow_request": "Annuler l'abonnement",
|
||||
"account.copy": "Copier le lien vers le profil",
|
||||
"account.copy": "Copier le lien du profil",
|
||||
"account.direct": "Mention privée @{name}",
|
||||
"account.disable_notifications": "Ne plus me notifier quand @{name} publie quelque chose",
|
||||
"account.domain_blocking": "Domaine bloqué",
|
||||
@@ -45,7 +45,7 @@
|
||||
"account.follow_request": "Demande d’abonnement",
|
||||
"account.follow_request_cancel": "Annuler la demande",
|
||||
"account.follow_request_cancel_short": "Annuler",
|
||||
"account.follow_request_short": "Requête",
|
||||
"account.follow_request_short": "Demander à suivre",
|
||||
"account.followers": "Abonné·e·s",
|
||||
"account.followers.empty": "Personne ne suit cet·te utilisateur·rice pour l’instant.",
|
||||
"account.followers_counter": "{count, plural, one {{counter} abonné·e} other {{counter} abonné·e·s}}",
|
||||
@@ -528,7 +528,7 @@
|
||||
"limited_account_hint.action": "Afficher le profil quand même",
|
||||
"limited_account_hint.title": "Ce profil a été masqué par la modération de {domain}.",
|
||||
"link_preview.author": "Par {name}",
|
||||
"link_preview.more_from_author": "Plus via {name}",
|
||||
"link_preview.more_from_author": "Voir plus de {name}",
|
||||
"link_preview.shares": "{count, plural, one {{counter} message} other {{counter} messages}}",
|
||||
"lists.add_member": "Ajouter",
|
||||
"lists.add_to_list": "Ajouter à la liste",
|
||||
@@ -759,7 +759,7 @@
|
||||
"privacy.quote.disabled": "{visibility}, citations désactivées",
|
||||
"privacy.quote.limited": "{visibility}, citations limitées",
|
||||
"privacy.unlisted.additional": "Se comporte exactement comme « public », sauf que le message n'apparaîtra pas dans les flux en direct, les hashtags, explorer ou la recherche Mastodon, même si vous les avez activé au niveau de votre compte.",
|
||||
"privacy.unlisted.long": "Caché des résultats de recherche de Mastodon, aux tendances et aux échéanciers publics",
|
||||
"privacy.unlisted.long": "Caché des résultats de recherche de Mastodon, des tendances et des fils publics",
|
||||
"privacy.unlisted.short": "Public discret",
|
||||
"privacy_policy.last_updated": "Dernière mise à jour {date}",
|
||||
"privacy_policy.title": "Politique de confidentialité",
|
||||
@@ -903,6 +903,7 @@
|
||||
"status.edited_x_times": "Modifié {count, plural, one {{count} fois} other {{count} fois}}",
|
||||
"status.embed": "Obtenir le code d'intégration",
|
||||
"status.favourite": "Ajouter aux favoris",
|
||||
"status.favourites_count": "{count, plural, one {{counter} favori} other {{counter} favoris}}",
|
||||
"status.filter": "Filtrer ce message",
|
||||
"status.history.created": "créé par {name} {date}",
|
||||
"status.history.edited": "modifié par {name} {date}",
|
||||
@@ -937,12 +938,14 @@
|
||||
"status.quotes.empty": "Personne n'a encore cité ce message. Quand quelqu'un le fera, il apparaîtra ici.",
|
||||
"status.quotes.local_other_disclaimer": "Les citations rejetées par l'auteur ne seront pas affichées.",
|
||||
"status.quotes.remote_other_disclaimer": "Seules les citations de {domain} sont garanties d'être affichées ici. Les citations rejetées par l'auteur ne seront pas affichées.",
|
||||
"status.quotes_count": "{count, plural, one {{counter} citation} other {{counter} citations}}",
|
||||
"status.read_more": "Lire la suite",
|
||||
"status.reblog": "Partager",
|
||||
"status.reblog_or_quote": "Boost ou citation",
|
||||
"status.reblog_private": "Partagez à nouveau avec vos abonnés",
|
||||
"status.reblogged_by": "{name} a partagé",
|
||||
"status.reblogs.empty": "Personne n’a encore partagé ce message. Lorsque quelqu’un le fera, il apparaîtra ici.",
|
||||
"status.reblogs_count": "{count, plural, one {{counter} partage} other {{counter} partages}}",
|
||||
"status.redraft": "Supprimer et réécrire",
|
||||
"status.remove_bookmark": "Retirer des marque-pages",
|
||||
"status.remove_favourite": "Retirer des favoris",
|
||||
@@ -1023,8 +1026,8 @@
|
||||
"visibility_modal.helper.privacy_editing": "La visibilité ne peut pas être modifiée après la publication d'un message.",
|
||||
"visibility_modal.helper.privacy_private_self_quote": "Les auto-citations de messages privés ne peuvent pas être rendues publiques.",
|
||||
"visibility_modal.helper.private_quoting": "Les posts accessible uniquement par les followers sur Mastodon ne peuvent être cités par d'autres personnes.",
|
||||
"visibility_modal.helper.unlisted_quoting": "Lorsque les gens vous citent, leur message sera également caché dans les calendriers tendances.",
|
||||
"visibility_modal.instructions": "Contrôlez qui peut interagir avec ce post. Vous pouvez également appliquer ces paramètres à tous les futurs messages en allant dans <link>Préférences > Valeurs par d'éfaut de publication</link>.",
|
||||
"visibility_modal.helper.unlisted_quoting": "Lorsque les gens vous citent, leur message sera également caché des fils tendances.",
|
||||
"visibility_modal.instructions": "Contrôlez qui peut interagir avec ce post. Vous pouvez également appliquer ces paramètres à tous les futurs messages en allant dans <link>Préférences > Valeurs par défaut de publication</link>.",
|
||||
"visibility_modal.privacy_label": "Visibilité",
|
||||
"visibility_modal.quote_followers": "Abonné·e·s seulement",
|
||||
"visibility_modal.quote_label": "Autoriser les citations pour",
|
||||
|
||||
@@ -189,7 +189,7 @@
|
||||
"notification.update": "{name} nuntium correxit",
|
||||
"notification_requests.accept": "Accipe",
|
||||
"notification_requests.confirm_accept_multiple.message": "Tu es accepturus {count, plural, one {una notitia petitionem} other {# notitia petitiones}}. Certus esne procedere vis?",
|
||||
"notification_requests.confirm_dismiss_multiple.message": "Tu {count, plural, one {unam petitionem notificationis} other {# petitiones notificationum}} abrogāre prōximum es. {count, plural, one {Illa} other {Eae}} facile accessū nōn erit. Certus es tē procedere velle?",
|
||||
"notification_requests.confirm_dismiss_multiple.message": "Tu {count, plural, one {unam petitionem notificationis} other {# petitiones notificationum}} abrogāre prōximum es. {count, plural, one {it} other {Eae}} facile accessū nōn erit. Certus es tē procedere velle?",
|
||||
"notifications.filter.all": "Omnia",
|
||||
"notifications.filter.polls": "Eventus electionis",
|
||||
"notifications.group": "{count} Notificātiōnēs",
|
||||
@@ -246,19 +246,102 @@
|
||||
"status.history.created": "{name} creatum {date}",
|
||||
"status.history.edited": "{name} correxit {date}",
|
||||
"status.open": "Expand this status",
|
||||
"status.quotes.empty": "Nemo hanc commentationem adhuc citavit. Cum quis citaverit, hic apparebit.",
|
||||
"status.quotes.local_other_disclaimer": "Citationes ab auctore reiectæ non monstrabuntur.",
|
||||
"status.quotes.remote_other_disclaimer": "Tantum citae ex {domain} hic exhiberi praestantur. Citae ab auctore reiectae non exhibebuntur.",
|
||||
"status.quotes_count": "{count, plural, one {{counter} citatio} other {{counter} citationes}}",
|
||||
"status.read_more": "Plura lege",
|
||||
"status.reblog": "Promovere",
|
||||
"status.reblog_or_quote": "Promovere aut cita",
|
||||
"status.reblog_private": "Iterum cum sectatoribus tuis communica",
|
||||
"status.reblogged_by": "{name} adiuvavit",
|
||||
"status.reblogs.empty": "Nemo hanc publicationem adhuc promovit. Cum quis eam promoveat, hic apparebunt.",
|
||||
"status.reblogs_count": "{count, plural, one {{counter} incrementum} other {{counter} incrementa}}",
|
||||
"status.redraft": "Dele et redig",
|
||||
"status.remove_bookmark": "Tolle signum",
|
||||
"status.remove_favourite": "Tolle ad delectis",
|
||||
"status.remove_quote": "Tolle",
|
||||
"status.replied_in_thread": "In filo responsum",
|
||||
"status.replied_to": "{name} respondit",
|
||||
"status.reply": "Respondere",
|
||||
"status.replyAll": "Responde ad filum",
|
||||
"status.report": "Referre @{name}",
|
||||
"status.request_quote": "Pretium petere",
|
||||
"status.revoke_quote": "Tolle nuntium meum ex nuntio @{name}",
|
||||
"status.sensitive_warning": "Materia delicata",
|
||||
"status.share": "Communica",
|
||||
"status.show_less_all": "Omnibus minus monstra",
|
||||
"status.show_more_all": "Omnibus plura monstra",
|
||||
"status.show_original": "Monstra originalem",
|
||||
"status.title.with_attachments": "{user} publicavit {attachmentCount, plural, one {unum annexum} other {{attachmentCount} annexa}}",
|
||||
"status.translate": "Converte",
|
||||
"status.translated_from_with": "Translatum ex {lang} per {provider}",
|
||||
"status.uncached_media_warning": "Praevisum non praesto est",
|
||||
"status.unmute_conversation": "Conversationem reserare",
|
||||
"subscribed_languages.lead": "Tantum epistolae in linguis selectis in domo tua apparebunt et indices temporum post mutationem. Neminem eligatis qui epistolas in omnibus linguis recipiat.",
|
||||
"subscribed_languages.save": "Servare mutationes",
|
||||
"subscribed_languages.target": "Muta linguas subscriptas pro {target}",
|
||||
"tabs_bar.home": "Domi",
|
||||
"tabs_bar.menu": "Elenchus",
|
||||
"tabs_bar.notifications": "Acta Vicimediorum",
|
||||
"tabs_bar.publish": "Nova publicatio",
|
||||
"tabs_bar.search": "Quaere",
|
||||
"terms_of_service.effective_as_of": "Valet ex {date}",
|
||||
"terms_of_service.title": "Termini servitii",
|
||||
"terms_of_service.upcoming_changes_on": "Mutationes venturae die {date}",
|
||||
"time_remaining.days": "{number, plural, one {# die} other {# dies}} restant",
|
||||
"time_remaining.hours": "{number, plural, one {# hora} other {# horae}} restant",
|
||||
"time_remaining.minutes": "{number, plural, one {# minutum} other {# minuta}} restant",
|
||||
"time_remaining.moments": "Momenta reliqua",
|
||||
"time_remaining.seconds": "{number, plural, one {# secundum} other {# secunda}} restant",
|
||||
"trends.counter_by_accounts": "{count, plural, one {{counter} persōna} other {{counter} persōnae}} in {days, plural, one {diē prīdiē} other {diēbus praeteritīs {days}}}",
|
||||
"trends.counter_by_accounts": "{count, plural, one {{counter} persōna} other {{counter} persōnae}} in {days, plural, one {days} other {diēbus praeteritīs {days}}}",
|
||||
"trends.trending_now": "Nunc in usu",
|
||||
"ui.beforeunload": "Si Mastodon discesseris, tua epitome peribit.",
|
||||
"units.short.billion": "{count} millia milionum",
|
||||
"units.short.million": "{count} milionum",
|
||||
"units.short.thousand": "{count} millia",
|
||||
"upload_button.label": "Imaginēs, vīdeō aut fīle audītūs adde",
|
||||
"upload_area.title": "Trahe et depone ad imponendum",
|
||||
"upload_button.label": "Adde imagines, pelliculam, aut fasciculum sonorum.",
|
||||
"upload_error.limit": "Limes onerationis superatus est.",
|
||||
"upload_error.poll": "Nullis suffragiis licet fascicula imponere.",
|
||||
"upload_error.quote": "Nullum oneramentum fasciculi cum citationibus permittitur.",
|
||||
"upload_form.drag_and_drop.instructions": "Ad annexum mediorum tollendum, preme clavem \"Space\" aut \"Enter\". Dum traheis, utere clavibus sagittariis ad annexum mediorum in quamlibet partem movendum. Preme iterum \"Space\" aut \"Enter\" ad annexum mediorum in novo loco deponendum, aut preme \"Escape\" ad desinendum.",
|
||||
"upload_form.drag_and_drop.on_drag_cancel": "Tractatio revocata est. Adiunctum medium {item} demissum est.",
|
||||
"upload_form.drag_and_drop.on_drag_end": "Adhaesum medium {item} demissum est.",
|
||||
"upload_form.drag_and_drop.on_drag_over": "Adhaesum medium {item} motum est.",
|
||||
"upload_form.drag_and_drop.on_drag_start": "Adhaesum medium {item} sublatum est.",
|
||||
"upload_form.edit": "Recolere",
|
||||
"upload_progress.label": "Uploading…"
|
||||
"upload_progress.label": "Oneratur...",
|
||||
"upload_progress.processing": "Processus…",
|
||||
"username.taken": "Illud nomen usoris occupatum est. Aliud tenta.",
|
||||
"video.close": "Pelliculam claude",
|
||||
"video.download": "Prehendere fasciculus",
|
||||
"video.exit_fullscreen": "Exitus ex plenum monitorium",
|
||||
"video.expand": "Expande pelliculam",
|
||||
"video.fullscreen": "Plenum monitorium",
|
||||
"video.hide": "celare pellicula",
|
||||
"video.mute": "Mutus",
|
||||
"video.pause": "intermittere",
|
||||
"video.play": "gignere",
|
||||
"video.skip_backward": "Redire",
|
||||
"video.skip_forward": "Progredi",
|
||||
"video.unmute": "Sordes tollere",
|
||||
"video.volume_down": "Volumen deminui",
|
||||
"video.volume_up": "Volumen augete",
|
||||
"visibility_modal.button_title": "Visibilitatem statuere",
|
||||
"visibility_modal.direct_quote_warning.text": "Si praesentia configuramenta servaveris, sententia inserta in nexum convertetur.",
|
||||
"visibility_modal.direct_quote_warning.title": "Citatio in mentitionibus privatis inseriri non possunt.",
|
||||
"visibility_modal.header": "Visibilitas et interactio",
|
||||
"visibility_modal.helper.direct_quoting": "Mentiones privatae in Mastodon scriptae ab aliis citari non possunt.",
|
||||
"visibility_modal.helper.privacy_editing": "Visibilitas mutari non potest postquam nuntius publicatus est.",
|
||||
"visibility_modal.helper.privacy_private_self_quote": "Citationes propriae nuntiorum privatorum publicari non possunt.",
|
||||
"visibility_modal.helper.private_quoting": "Nuntii in Mastodon a sequacibus tantum scriptī ab aliis citari non possunt.",
|
||||
"visibility_modal.helper.unlisted_quoting": "Cum te citant, eorum scriptum etiam ex indicibus popularibus celabitur.",
|
||||
"visibility_modal.instructions": "Régula quis cum hoc scripto agere possit. Potes etiam ordinationes omnibus scriptis futuris adhibere, navigando ad <link>Praeferentias > Regulas scriptionis praedefinitas</link>.",
|
||||
"visibility_modal.privacy_label": "Visibilitas",
|
||||
"visibility_modal.quote_followers": "Sectatores tantum",
|
||||
"visibility_modal.quote_label": "Quis citare potest",
|
||||
"visibility_modal.quote_nobody": "Sicut me",
|
||||
"visibility_modal.quote_public": "quisquis",
|
||||
"visibility_modal.save": "Servare"
|
||||
}
|
||||
|
||||
@@ -638,12 +638,14 @@
|
||||
"privacy.direct.long": "Visi ierakstā pieminētie",
|
||||
"privacy.private.long": "Tikai Tavi sekotāji",
|
||||
"privacy.private.short": "Sekotāji",
|
||||
"privacy.public.long": "Jebkurš Mastodon un ārpus tā",
|
||||
"privacy.public.long": "Jebkurš Mastodon platformā un ārpus tās",
|
||||
"privacy.public.short": "Publisks",
|
||||
"privacy.quote.anyone": "{visibility}, jebkurš var citēt",
|
||||
"privacy.quote.disabled": "{visibility}, aizliegta citēšana",
|
||||
"privacy.quote.limited": "{visibility}, ierobežota citēšana",
|
||||
"privacy.unlisted.additional": "Šis uzvedas tieši kā publisks, izņemot to, ka ieraksts neparādīsies tiešraides barotnēs vai tēmturos, izpētē vai Mastodon meklēšanā, pat ja esi to norādījis visa konta ietvaros.",
|
||||
"privacy.unlisted.long": "Netiks rādīts Mastodon meklēšanas rezultātos, populārākajos, un publiskajās laikjoslās",
|
||||
"privacy.unlisted.short": "Klusi publisks",
|
||||
"privacy_policy.last_updated": "Pēdējo reizi atjaunināta {date}",
|
||||
"privacy_policy.title": "Privātuma politika",
|
||||
"recommended": "Ieteicams",
|
||||
@@ -837,7 +839,7 @@
|
||||
"video.volume_down": "Pagriezt klusāk",
|
||||
"video.volume_up": "Pagriezt skaļāk",
|
||||
"visibility_modal.button_title": "Iestatīt redzamību",
|
||||
"visibility_modal.header": "Redzamība un mijjiedarbība",
|
||||
"visibility_modal.header": "Redzamība un mijiedarbība",
|
||||
"visibility_modal.quote_followers": "Tikai sekotāji",
|
||||
"visibility_modal.quote_label": "Kurš var citēt",
|
||||
"visibility_modal.quote_nobody": "Tikai es",
|
||||
|
||||
@@ -903,6 +903,7 @@
|
||||
"status.edited_x_times": "有編輯 {count, plural, one {{count} kái} other {{count} kái}}",
|
||||
"status.embed": "The̍h相tàu ê (embed)程式碼",
|
||||
"status.favourite": "收藏",
|
||||
"status.favourites_count": "{count, plural, one {{counter} 篇} other {{counter} 篇}} 收藏",
|
||||
"status.filter": "過濾tsit 篇 PO文",
|
||||
"status.history.created": "{name} 佇 {date} 建立",
|
||||
"status.history.edited": "{name} 佇 {date} 編輯",
|
||||
@@ -936,12 +937,14 @@
|
||||
"status.quotes.empty": "Iáu無lâng引用tsit篇PO文。Nā是有lâng引用,ē佇tsia顯示。.",
|
||||
"status.quotes.local_other_disclaimer": "Hōo作者拒絕引用ê引文bē當顯示。",
|
||||
"status.quotes.remote_other_disclaimer": "Kan-ta tuì {domain} 來ê引文tsiah保證佇tsia顯示。Hōo作者拒絕ê引文buē顯示。",
|
||||
"status.quotes_count": "{count, plural, one {{counter} 篇} other {{counter} 篇}} 引用",
|
||||
"status.read_more": "讀詳細",
|
||||
"status.reblog": "轉送",
|
||||
"status.reblog_or_quote": "轉送á是引用",
|
||||
"status.reblog_private": "Koh再hām跟tuè ê分享",
|
||||
"status.reblogged_by": "{name} kā轉送ah",
|
||||
"status.reblogs.empty": "Iáu無lâng轉送tsit篇PO文。Nā是有lâng轉送,ē佇tsia顯示。",
|
||||
"status.reblogs_count": "{count, plural, one {{counter} 篇} other {{counter} 篇}} 轉送",
|
||||
"status.redraft": "Thâi掉了後重寫",
|
||||
"status.remove_bookmark": "Thâi掉冊籤",
|
||||
"status.remove_favourite": "Tuì收藏內suá掉",
|
||||
@@ -1014,6 +1017,8 @@
|
||||
"video.volume_down": "變khah細聲",
|
||||
"video.volume_up": "變khah大聲",
|
||||
"visibility_modal.button_title": "設定通看ê程度",
|
||||
"visibility_modal.direct_quote_warning.text": "Nā是lí儲存目前ê設定,引用êPO文ē轉做連結。",
|
||||
"visibility_modal.direct_quote_warning.title": "引用bē當tàu入去私人ê提起",
|
||||
"visibility_modal.header": "通看ê程度kap互動",
|
||||
"visibility_modal.helper.direct_quoting": "Mastodon頂發布ê私人提起bē當hōo別lâng引用。",
|
||||
"visibility_modal.helper.privacy_editing": "Po文發布了後,bē當改通看ê程度。",
|
||||
|
||||
@@ -628,7 +628,7 @@
|
||||
"notification.moderation_warning.action_disable": "Ваша учётная запись была отключена.",
|
||||
"notification.moderation_warning.action_mark_statuses_as_sensitive": "Некоторые ваши посты были отмечены как содержимое деликатного характера.",
|
||||
"notification.moderation_warning.action_none": "Модераторы вынесли вам предупреждение.",
|
||||
"notification.moderation_warning.action_sensitive": "С этого момента все ваши новые посты будут отмечены как содержимое деликатного характера.",
|
||||
"notification.moderation_warning.action_sensitive": "С этого момента все ваши посты будут отмечены как содержимое деликатного характера.",
|
||||
"notification.moderation_warning.action_silence": "Ваша учётная запись была ограничена.",
|
||||
"notification.moderation_warning.action_suspend": "Ваша учётная запись была заблокирована.",
|
||||
"notification.own_poll": "Ваш опрос завершился",
|
||||
|
||||
@@ -1027,7 +1027,7 @@
|
||||
"visibility_modal.helper.privacy_private_self_quote": "Không thể công khai trích dẫn tút của bản thân hoặc tút riêng tư.",
|
||||
"visibility_modal.helper.private_quoting": "Tút chỉ dành cho người theo dõi trên Mastodon không thể được người khác trích dẫn.",
|
||||
"visibility_modal.helper.unlisted_quoting": "Khi ai đó trích dẫn bạn, tút của họ cũng sẽ bị ẩn khỏi bảng tin công khai.",
|
||||
"visibility_modal.instructions": "Kiểm soát những ai có thể tương tác với tút này. Bạn cũng có thể áp dụng cài đặt cho tất cả các tút trong tương lai bằng cách điều hướng đến <link>Thiết lập > Đăng</link>.",
|
||||
"visibility_modal.instructions": "Kiểm soát những ai có thể tương tác với tút này. Bạn cũng có thể đặt sẵn cho tất cả tút trong tương lai bằng cách truy cập <link>Thiết lập > Mặc định cho tút</link>.",
|
||||
"visibility_modal.privacy_label": "Hiển thị",
|
||||
"visibility_modal.quote_followers": "Chỉ người theo dõi",
|
||||
"visibility_modal.quote_label": "Ai có thể trích dẫn",
|
||||
|
||||
@@ -9,7 +9,6 @@ import { me, reduceMotion } from 'mastodon/initial_state';
|
||||
import ready from 'mastodon/ready';
|
||||
import { store } from 'mastodon/store';
|
||||
|
||||
import { initializeEmoji } from './features/emoji';
|
||||
import { isProduction, isDevelopment } from './utils/environment';
|
||||
|
||||
function main() {
|
||||
@@ -30,6 +29,7 @@ function main() {
|
||||
});
|
||||
}
|
||||
|
||||
const { initializeEmoji } = await import('./features/emoji/index');
|
||||
initializeEmoji();
|
||||
|
||||
const root = createRoot(mountNode);
|
||||
|
||||
@@ -341,8 +341,8 @@ export const composeReducer = (state = initialState, action) => {
|
||||
const isDirect = state.get('privacy') === 'direct';
|
||||
return state
|
||||
.set('quoted_status_id', isDirect ? null : status.get('id'))
|
||||
.set('spoiler', status.get('sensitive'))
|
||||
.set('spoiler_text', status.get('spoiler_text'))
|
||||
.update('spoiler', spoiler => (spoiler) || !!status.get('spoiler_text'))
|
||||
.update('spoiler_text', (spoiler_text) => spoiler_text || status.get('spoiler_text'))
|
||||
.update('privacy', (visibility) => {
|
||||
if (['public', 'unlisted'].includes(visibility) && status.get('visibility') === 'private') {
|
||||
return 'private';
|
||||
|
||||
@@ -327,9 +327,9 @@ $content-width: 840px;
|
||||
font-weight: 700;
|
||||
color: $primary-text-color;
|
||||
text-transform: none;
|
||||
padding-bottom: 0;
|
||||
padding-top: 0;
|
||||
margin-bottom: 0;
|
||||
border-bottom: 0;
|
||||
border-top: 0;
|
||||
|
||||
.comment {
|
||||
display: block;
|
||||
@@ -1040,10 +1040,6 @@ a.name-tag,
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.user-role {
|
||||
color: var(--user-role-accent);
|
||||
}
|
||||
|
||||
.applications-list {
|
||||
.icon {
|
||||
vertical-align: middle;
|
||||
|
||||
7
app/javascript/styles_new/application.scss
Normal file
7
app/javascript/styles_new/application.scss
Normal file
@@ -0,0 +1,7 @@
|
||||
@use 'mastodon/css_variables';
|
||||
@use 'mastodon/variables';
|
||||
@use 'common';
|
||||
|
||||
html {
|
||||
color-scheme: dark;
|
||||
}
|
||||
24
app/javascript/styles_new/common.scss
Normal file
24
app/javascript/styles_new/common.scss
Normal file
@@ -0,0 +1,24 @@
|
||||
@use 'mastodon/mixins';
|
||||
@use 'fonts/roboto';
|
||||
@use 'fonts/roboto-mono';
|
||||
|
||||
@use 'mastodon/reset';
|
||||
@use 'mastodon/basics';
|
||||
@use 'mastodon/branding';
|
||||
@use 'mastodon/containers';
|
||||
@use 'mastodon/lists';
|
||||
@use 'mastodon/widgets';
|
||||
@use 'mastodon/forms';
|
||||
@use 'mastodon/accounts';
|
||||
@use 'mastodon/components';
|
||||
@use 'mastodon/polls';
|
||||
@use 'mastodon/modal';
|
||||
@use 'mastodon/emoji_picker';
|
||||
@use 'mastodon/annual_reports';
|
||||
@use 'mastodon/about';
|
||||
@use 'mastodon/tables';
|
||||
@use 'mastodon/admin';
|
||||
@use 'mastodon/dashboard';
|
||||
@use 'mastodon/rtl';
|
||||
@use 'mastodon/accessibility';
|
||||
@use 'mastodon/rich_text';
|
||||
8
app/javascript/styles_new/contrast.scss
Normal file
8
app/javascript/styles_new/contrast.scss
Normal file
@@ -0,0 +1,8 @@
|
||||
@use 'mastodon/css_variables';
|
||||
@use 'mastodon/variables';
|
||||
@use 'common';
|
||||
@use 'contrast/diff';
|
||||
|
||||
html {
|
||||
color-scheme: dark;
|
||||
}
|
||||
54
app/javascript/styles_new/contrast/diff.scss
Normal file
54
app/javascript/styles_new/contrast/diff.scss
Normal file
@@ -0,0 +1,54 @@
|
||||
:root {
|
||||
/* TEXT TOKENS */
|
||||
|
||||
--color-text-primary: var(--color-grey-50);
|
||||
--color-text-secondary: var(--color-grey-300);
|
||||
--color-text-tertiary: var(--color-grey-400);
|
||||
--color-text-brand: var(--color-indigo-300);
|
||||
--color-text-status-links: var(--color-text-brand);
|
||||
|
||||
/* BORDER TOKENS */
|
||||
|
||||
--border-strength-primary: 18%;
|
||||
}
|
||||
|
||||
.status__content a,
|
||||
.reply-indicator__content a,
|
||||
.edit-indicator__content a,
|
||||
.link-footer a,
|
||||
.status__content__read-more-button,
|
||||
.status__content__translate-button {
|
||||
text-decoration: underline;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&.mention {
|
||||
text-decoration: none;
|
||||
|
||||
span {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
span {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.link-button:disabled {
|
||||
cursor: not-allowed;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
text-decoration: none !important;
|
||||
}
|
||||
}
|
||||
14
app/javascript/styles_new/entrypoints/inert.scss
Normal file
14
app/javascript/styles_new/entrypoints/inert.scss
Normal file
@@ -0,0 +1,14 @@
|
||||
/* This is needed for the wicg-inert polyfill */
|
||||
|
||||
[inert] {
|
||||
pointer-events: none;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
[inert],
|
||||
[inert] * {
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
1030
app/javascript/styles_new/entrypoints/mailer.scss
Normal file
1030
app/javascript/styles_new/entrypoints/mailer.scss
Normal file
File diff suppressed because it is too large
Load Diff
8
app/javascript/styles_new/fonts/inter.scss
Normal file
8
app/javascript/styles_new/fonts/inter.scss
Normal file
@@ -0,0 +1,8 @@
|
||||
@font-face {
|
||||
font-family: Inter;
|
||||
src: url('../../fonts/inter/inter-variable-font-slnt-wght.woff2')
|
||||
format('woff2-variations');
|
||||
font-weight: 100 900;
|
||||
font-style: normal;
|
||||
mso-generic-font-family: swiss;
|
||||
}
|
||||
13
app/javascript/styles_new/fonts/roboto-mono.scss
Normal file
13
app/javascript/styles_new/fonts/roboto-mono.scss
Normal file
@@ -0,0 +1,13 @@
|
||||
@font-face {
|
||||
font-family: mastodon-font-monospace;
|
||||
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');
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
font-style: normal;
|
||||
}
|
||||
55
app/javascript/styles_new/fonts/roboto.scss
Normal file
55
app/javascript/styles_new/fonts/roboto.scss
Normal file
@@ -0,0 +1,55 @@
|
||||
@font-face {
|
||||
font-family: mastodon-font-sans-serif;
|
||||
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');
|
||||
font-weight: normal;
|
||||
font-display: swap;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: mastodon-font-sans-serif;
|
||||
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');
|
||||
font-weight: bold;
|
||||
font-display: swap;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: mastodon-font-sans-serif;
|
||||
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');
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: mastodon-font-sans-serif;
|
||||
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');
|
||||
font-weight: normal;
|
||||
font-display: swap;
|
||||
font-style: normal;
|
||||
}
|
||||
9
app/javascript/styles_new/mastodon-light.scss
Normal file
9
app/javascript/styles_new/mastodon-light.scss
Normal file
@@ -0,0 +1,9 @@
|
||||
@use 'mastodon-light/css_variables';
|
||||
@use 'mastodon/variables' with (
|
||||
$emojis-requiring-inversion: 'chains'
|
||||
);
|
||||
@use 'common';
|
||||
|
||||
html {
|
||||
color-scheme: light;
|
||||
}
|
||||
214
app/javascript/styles_new/mastodon-light/css_variables.scss
Normal file
214
app/javascript/styles_new/mastodon-light/css_variables.scss
Normal file
@@ -0,0 +1,214 @@
|
||||
@use '../mastodon/theme_utils' as utils;
|
||||
|
||||
:root {
|
||||
--color-black: #000;
|
||||
--color-grey-950: #181821;
|
||||
--color-grey-800: #292938;
|
||||
--color-grey-700: #444664;
|
||||
--color-grey-600: #545778;
|
||||
--color-grey-500: #696d91;
|
||||
--color-grey-400: #8b8dac;
|
||||
--color-grey-300: #b4b6cb;
|
||||
--color-grey-200: #d8d9e3;
|
||||
--color-grey-100: #f0f0f5;
|
||||
--color-grey-50: #f0f1ff;
|
||||
--color-white: #fff;
|
||||
--color-indigo-600: #6147e6;
|
||||
--color-indigo-400: #8886ff;
|
||||
--color-indigo-300: #a5abfd;
|
||||
--color-indigo-200: #c8cdfe;
|
||||
--color-indigo-100: #e0e3ff;
|
||||
--color-indigo-50: #f0f1ff;
|
||||
--color-red-500: #ff637e;
|
||||
--color-red-600: #ec003f;
|
||||
--color-yellow-400: #ffb900;
|
||||
--color-yellow-600: #e17100;
|
||||
--color-green-400: #05df72;
|
||||
--color-green-600: #00a63e;
|
||||
|
||||
/* TEXT TOKENS */
|
||||
|
||||
--color-text-primary: var(--color-grey-950);
|
||||
--color-text-secondary: var(--color-grey-600);
|
||||
--color-text-tertiary: var(--color-grey-500);
|
||||
--color-text-on-inverted: var(--color-white);
|
||||
--color-text-brand: var(--color-indigo-600);
|
||||
--color-text-brand-soft: color-mix(
|
||||
in oklab,
|
||||
var(--color-text-primary),
|
||||
var(--color-text-brand)
|
||||
);
|
||||
--color-text-on-brand-base: var(--color-white);
|
||||
--color-text-error: var(--color-red-600);
|
||||
--color-text-on-error-base: var(--color-white);
|
||||
--color-text-warning: var(--color-yellow-600);
|
||||
--color-text-on-warning-base: var(--color-white);
|
||||
--color-text-success: var(--color-green-600);
|
||||
--color-text-on-success-base: var(--color-white);
|
||||
--color-text-disabled: var(--color-grey-300);
|
||||
--color-text-on-disabled: var(--color-grey-200);
|
||||
--color-text-bookmark-highlight: var(--color-text-error);
|
||||
--color-text-favourite-highlight: var(--color-text-warning);
|
||||
--color-text-on-media: var(--color-white);
|
||||
--color-text-status-links: var(--color-text-brand);
|
||||
|
||||
/* BACKGROUND TOKENS */
|
||||
|
||||
// Neutrals
|
||||
--color-bg-primary: var(--color-white);
|
||||
--overlay-strength-secondary: 5%;
|
||||
--color-bg-secondary-base: var(--color-grey-600);
|
||||
--color-bg-secondary: #{color-mix(
|
||||
in oklab,
|
||||
var(--color-bg-primary),
|
||||
var(--color-bg-secondary-base) var(--overlay-strength-secondary)
|
||||
)};
|
||||
--color-bg-secondary-solid: #{color-mix(
|
||||
in srgb,
|
||||
var(--color-bg-primary),
|
||||
var(--color-bg-secondary-base) var(--overlay-strength-secondary)
|
||||
)};
|
||||
--color-bg-tertiary: #{color-mix(
|
||||
in oklab,
|
||||
var(--color-bg-primary),
|
||||
var(--color-bg-secondary-base) calc(2 * var(--overlay-strength-secondary))
|
||||
)};
|
||||
|
||||
// Utility
|
||||
--color-bg-ambient: var(--color-bg-primary);
|
||||
--color-bg-elevated: var(--color-bg-primary);
|
||||
--color-bg-inverted: var(--color-grey-950);
|
||||
--color-bg-media-base: var(--color-black);
|
||||
--color-bg-media-strength: 65%;
|
||||
--color-bg-media: #{utils.css-alpha(
|
||||
var(--color-bg-media-base),
|
||||
var(--color-bg-media-strength)
|
||||
)};
|
||||
--color-bg-overlay: var(--color-bg-primary);
|
||||
--color-bg-disabled: var(--color-grey-400);
|
||||
|
||||
// Brand
|
||||
--overlay-strength-brand: 8%;
|
||||
--color-bg-brand-base: var(--color-indigo-600);
|
||||
--color-bg-brand-base-hover: color-mix(
|
||||
in oklab,
|
||||
var(--color-bg-brand-base),
|
||||
black var(--overlay-strength-brand)
|
||||
);
|
||||
--color-bg-brand-soft: #{utils.css-alpha(
|
||||
var(--color-bg-brand-base),
|
||||
calc(var(--overlay-strength-brand) * 1.5)
|
||||
)};
|
||||
--color-bg-brand-softer: #{utils.css-alpha(
|
||||
var(--color-bg-brand-base),
|
||||
var(--overlay-strength-brand)
|
||||
)};
|
||||
|
||||
// Error
|
||||
--overlay-strength-error: 12%;
|
||||
--color-bg-error-base: var(--color-red-600);
|
||||
--color-bg-error-base-hover: color-mix(
|
||||
in oklab,
|
||||
var(--color-bg-error-base),
|
||||
black var(--overlay-strength-error)
|
||||
);
|
||||
--color-bg-error-soft: #{utils.css-alpha(
|
||||
var(--color-bg-error-base),
|
||||
calc(var(--overlay-strength-error) * 1.5)
|
||||
)};
|
||||
--color-bg-error-softer: #{utils.css-alpha(
|
||||
var(--color-bg-error-base),
|
||||
var(--overlay-strength-error)
|
||||
)};
|
||||
|
||||
// Warning
|
||||
--overlay-strength-warning: 10%;
|
||||
--color-bg-warning-base: var(--color-yellow-600);
|
||||
--color-bg-warning-base-hover: color-mix(
|
||||
in oklab,
|
||||
var(--color-bg-warning-base),
|
||||
black var(--overlay-strength-warning)
|
||||
);
|
||||
--color-bg-warning-soft: #{utils.css-alpha(
|
||||
var(--color-bg-warning-base),
|
||||
calc(var(--overlay-strength-warning) * 1.5)
|
||||
)};
|
||||
--color-bg-warning-softer: #{utils.css-alpha(
|
||||
var(--color-bg-warning-base),
|
||||
var(--overlay-strength-warning)
|
||||
)};
|
||||
|
||||
// Success
|
||||
--overlay-strength-success: 15%;
|
||||
--color-bg-success-base: var(--color-green-600);
|
||||
--color-bg-success-base-hover: color-mix(
|
||||
in oklab,
|
||||
var(--color-bg-success-base),
|
||||
black var(--overlay-strength-success)
|
||||
);
|
||||
--color-bg-success-soft: #{utils.css-alpha(
|
||||
var(--color-bg-success-base),
|
||||
calc(var(--overlay-strength-success) * 1.5)
|
||||
)};
|
||||
--color-bg-success-softer: #{utils.css-alpha(
|
||||
var(--color-bg-success-base),
|
||||
var(--overlay-strength-success)
|
||||
)};
|
||||
|
||||
/* BORDER TOKENS */
|
||||
|
||||
--border-strength-primary: 15%;
|
||||
--color-border-primary: color-mix(
|
||||
in oklab,
|
||||
var(--color-bg-primary),
|
||||
var(--color-grey-950) var(--border-strength-primary)
|
||||
);
|
||||
--color-border-media: rgb(252 248 255 / 15%);
|
||||
--color-border-on-bg-secondary: var(--color-grey-200);
|
||||
--color-border-on-bg-brand-softer: var(--color-indigo-200);
|
||||
--color-border-on-bg-error-softer: #{utils.css-alpha(
|
||||
var(--color-text-error),
|
||||
50%
|
||||
)};
|
||||
--color-border-on-bg-warning-softer: #{utils.css-alpha(
|
||||
var(--color-text-warning),
|
||||
50%
|
||||
)};
|
||||
--color-border-on-bg-success-softer: #{utils.css-alpha(
|
||||
var(--color-text-success),
|
||||
50%
|
||||
)};
|
||||
--color-border-on-bg-inverted: var(--color-border-primary);
|
||||
|
||||
/* SHADOW TOKENS */
|
||||
|
||||
--shadow-strength-primary: 30%;
|
||||
--color-shadow-primary: #{utils.css-alpha(
|
||||
var(--color-black),
|
||||
var(--shadow-strength-primary)
|
||||
)};
|
||||
--dropdown-shadow:
|
||||
0 20px 25px -5px var(--color-shadow-primary),
|
||||
0 8px 10px -6px var(--color-shadow-primary);
|
||||
--overlay-icon-shadow: drop-shadow(0 0 8px var(--color-shadow-primary));
|
||||
|
||||
/* GRAPHS/CHARTS TOKENS */
|
||||
|
||||
--color-graph-primary-stroke: var(--color-text-brand);
|
||||
--color-graph-primary-fill: var(--color-bg-brand-softer);
|
||||
--color-graph-warning-stroke: var(--color-text-warning);
|
||||
--color-graph-warning-fill: var(--color-bg-warning-softer);
|
||||
--color-graph-disabled-stroke: var(--color-text-disabled);
|
||||
--color-graph-disabled-fill: var(--color-bg-disabled);
|
||||
|
||||
/* LEGACY TOKENS */
|
||||
|
||||
--rich-text-container-color: rgb(255 216 231 / 100%);
|
||||
--rich-text-text-color: rgb(114 47 83 / 100%);
|
||||
--rich-text-decorations-color: rgb(255 175 212 / 100%);
|
||||
|
||||
/* MISCELLANEOUS */
|
||||
|
||||
--outline-focus-default: 2px solid var(--color-text-brand);
|
||||
--avatar-border-radius: 8px;
|
||||
}
|
||||
45
app/javascript/styles_new/mastodon/_mixins.scss
Normal file
45
app/javascript/styles_new/mastodon/_mixins.scss
Normal file
@@ -0,0 +1,45 @@
|
||||
@mixin search-input {
|
||||
outline: 0;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
box-shadow: none;
|
||||
font-family: inherit;
|
||||
background: var(--color-bg-secondary);
|
||||
color: var(--color-text-primary);
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--color-border-on-bg-secondary);
|
||||
font-size: 17px;
|
||||
line-height: normal;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@mixin search-popout {
|
||||
background: var(--color-bg-elevated);
|
||||
border-radius: 4px;
|
||||
padding: 10px 14px;
|
||||
padding-bottom: 14px;
|
||||
margin-top: 10px;
|
||||
color: var(--color-text-secondary);
|
||||
box-shadow: 2px 4px 15px var(--color-shadow-primary);
|
||||
|
||||
h4 {
|
||||
text-transform: uppercase;
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
li {
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
em {
|
||||
font-weight: 500;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
}
|
||||
3
app/javascript/styles_new/mastodon/_theme_utils.scss
Normal file
3
app/javascript/styles_new/mastodon/_theme_utils.scss
Normal file
@@ -0,0 +1,3 @@
|
||||
@function css-alpha($base-color, $amount) {
|
||||
@return #{rgb(from $base-color r g b / $amount)};
|
||||
}
|
||||
27
app/javascript/styles_new/mastodon/_variables.scss
Normal file
27
app/javascript/styles_new/mastodon/_variables.scss
Normal file
@@ -0,0 +1,27 @@
|
||||
// Keep this filter a SCSS variable rather than
|
||||
// a CSS Custom Property due to this Safari bug:
|
||||
// https://github.com/mdn/browser-compat-data/issues/25914#issuecomment-2676190245
|
||||
$backdrop-blur-filter: blur(10px) saturate(180%) contrast(75%) brightness(70%);
|
||||
|
||||
// Language codes that uses CJK fonts
|
||||
$cjk-langs: ja, ko, zh-CN, zh-HK, zh-TW;
|
||||
|
||||
// Variables for components
|
||||
$media-modal-media-max-width: 100%;
|
||||
|
||||
// put margins on top and bottom of image to avoid the screen covered by image.
|
||||
$media-modal-media-max-height: 80%;
|
||||
|
||||
$no-gap-breakpoint: 1175px;
|
||||
$mobile-menu-breakpoint: 760px;
|
||||
$mobile-breakpoint: 630px;
|
||||
$no-columns-breakpoint: 600px;
|
||||
|
||||
$font-sans-serif: 'mastodon-font-sans-serif' !default;
|
||||
$font-display: 'mastodon-font-display' !default;
|
||||
$font-monospace: 'mastodon-font-monospace' !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;
|
||||
130
app/javascript/styles_new/mastodon/about.scss
Normal file
130
app/javascript/styles_new/mastodon/about.scss
Normal file
@@ -0,0 +1,130 @@
|
||||
@use 'variables' as *;
|
||||
|
||||
$maximum-width: 1235px;
|
||||
$fluid-breakpoint: $maximum-width + 20px;
|
||||
|
||||
.container {
|
||||
box-sizing: border-box;
|
||||
max-width: $maximum-width;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
|
||||
@media screen and (max-width: $fluid-breakpoint) {
|
||||
width: 100%;
|
||||
padding: 0 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.brand {
|
||||
position: relative;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.rules-list {
|
||||
font-size: 15px;
|
||||
line-height: 22px;
|
||||
counter-reset: list-counter;
|
||||
|
||||
li {
|
||||
position: relative;
|
||||
border-bottom: 1px solid var(--color-border-primary);
|
||||
padding: 1em 1.75em;
|
||||
padding-inline-start: 3em;
|
||||
font-weight: 500;
|
||||
counter-increment: list-counter;
|
||||
min-height: 4ch;
|
||||
|
||||
button {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
text-align: start;
|
||||
font: inherit;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&[aria-expanded='false'] .rules-list__hint {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
@supports (-webkit-line-clamp: 2) {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: counter(list-counter);
|
||||
position: absolute;
|
||||
inset-inline-start: 0;
|
||||
top: 1em;
|
||||
background: var(--color-bg-brand-base);
|
||||
color: var(--color-text-on-brand-base);
|
||||
border-radius: 50%;
|
||||
width: 4ch;
|
||||
height: 4ch;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__text {
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
&__hint {
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
.rules-languages {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
> label {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
select {
|
||||
appearance: none;
|
||||
box-sizing: border-box;
|
||||
font-size: 14px;
|
||||
color: var(--color-text-primary);
|
||||
display: block;
|
||||
width: 100%;
|
||||
outline: 0;
|
||||
font-family: inherit;
|
||||
resize: vertical;
|
||||
background: var(--color-bg-secondary);
|
||||
border: 1px solid var(--color-border-primary);
|
||||
border-radius: 4px;
|
||||
padding-inline-start: 10px;
|
||||
padding-inline-end: 30px;
|
||||
height: 41px;
|
||||
|
||||
@media screen and (width <= 600px) {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
13
app/javascript/styles_new/mastodon/accessibility.scss
Normal file
13
app/javascript/styles_new/mastodon/accessibility.scss
Normal file
@@ -0,0 +1,13 @@
|
||||
@use 'variables' as *;
|
||||
|
||||
%emoji-color-inversion {
|
||||
filter: invert(1);
|
||||
}
|
||||
|
||||
.emojione {
|
||||
@each $emoji in $emojis-requiring-inversion {
|
||||
&[title=':#{$emoji}:'] {
|
||||
@extend %emoji-color-inversion;
|
||||
}
|
||||
}
|
||||
}
|
||||
411
app/javascript/styles_new/mastodon/accounts.scss
Normal file
411
app/javascript/styles_new/mastodon/accounts.scss
Normal file
@@ -0,0 +1,411 @@
|
||||
@use 'sass:color';
|
||||
@use 'variables' as *;
|
||||
|
||||
.card {
|
||||
& > a {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
overflow: hidden;
|
||||
border-radius: 4px;
|
||||
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
.card__bar {
|
||||
background: var(--color-bg-brand-softer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__img {
|
||||
height: 130px;
|
||||
position: relative;
|
||||
background: var(--color-bg-secondary);
|
||||
border: 1px solid var(--color-border-primary);
|
||||
border-bottom: none;
|
||||
|
||||
img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
@media screen and (width <= 600px) {
|
||||
height: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
&__bar {
|
||||
position: relative;
|
||||
padding: 15px;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
background: var(--color-bg-primary);
|
||||
border: 1px solid var(--color-border-primary);
|
||||
border-top: none;
|
||||
|
||||
.avatar {
|
||||
flex: 0 0 auto;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
padding-top: 2px;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
margin: 0;
|
||||
border-radius: 4px;
|
||||
background: var(--color-bg-secondary);
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.display-name {
|
||||
margin-inline-start: 15px;
|
||||
text-align: start;
|
||||
|
||||
svg[data-hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-size: 15px;
|
||||
color: var(--color-text-primary);
|
||||
font-weight: 500;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
span {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
color: var(--color-text-secondary);
|
||||
font-weight: 400;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.pagination {
|
||||
padding: 30px 0;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
|
||||
a,
|
||||
.current,
|
||||
.newer,
|
||||
.older,
|
||||
.page,
|
||||
.gap {
|
||||
font-size: 14px;
|
||||
color: var(--color-text-primary);
|
||||
font-weight: 500;
|
||||
display: inline-block;
|
||||
padding: 6px 10px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.current {
|
||||
color: var(--color-bg-inverted);
|
||||
background: var(--color-text-on-inverted);
|
||||
border-radius: 100px;
|
||||
cursor: default;
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.gap {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.older,
|
||||
.newer {
|
||||
text-transform: uppercase;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.older {
|
||||
float: left;
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
|
||||
.newer {
|
||||
float: right;
|
||||
padding-inline-end: 0;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
cursor: default;
|
||||
color: var(--color-text-disabled);
|
||||
}
|
||||
|
||||
@media screen and (width <= 700px) {
|
||||
padding: 30px 20px;
|
||||
|
||||
.page {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.newer,
|
||||
.older {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nothing-here {
|
||||
color: var(--color-text-secondary);
|
||||
background: var(--color-bg-primary);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: default;
|
||||
border-radius: 4px;
|
||||
padding: 20px;
|
||||
min-height: 30vh;
|
||||
border: 1px solid var(--color-border-primary);
|
||||
|
||||
@media screen and (min-width: ($no-gap-breakpoint - 1)) {
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
&--no-toolbar {
|
||||
border-top: 1px solid var(--color-border-primary);
|
||||
}
|
||||
|
||||
&--under-tabs {
|
||||
border-radius: 0 0 4px 4px;
|
||||
}
|
||||
|
||||
&--flexible {
|
||||
box-sizing: border-box;
|
||||
min-height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.information-badge,
|
||||
.simple_form .overridden,
|
||||
.simple_form .recommended,
|
||||
.simple_form .not_recommended {
|
||||
display: inline-block;
|
||||
padding: 4px 6px;
|
||||
cursor: default;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
line-height: 12px;
|
||||
font-weight: 500;
|
||||
color: var(--color-text-primary);
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.information-badge,
|
||||
.simple_form .overridden,
|
||||
.simple_form .recommended,
|
||||
.simple_form .not_recommended {
|
||||
background-color: var(--color-bg-secondary);
|
||||
border: 1px solid var(--color-border-primary);
|
||||
}
|
||||
|
||||
.information-badge {
|
||||
&.superapp {
|
||||
color: var(--color-text-success);
|
||||
background-color: var(--color-bg-success-softer);
|
||||
border-color: var(--color-border-on-bg-success-softer);
|
||||
}
|
||||
}
|
||||
|
||||
.account-role {
|
||||
display: inline-flex;
|
||||
padding: 4px;
|
||||
padding-inline-end: 8px;
|
||||
border: 1px solid var(--color-text-brand);
|
||||
color: var(--color-text-brand);
|
||||
font-weight: 500;
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.5px;
|
||||
line-height: 16px;
|
||||
gap: 4px;
|
||||
border-radius: 6px;
|
||||
align-items: center;
|
||||
|
||||
svg {
|
||||
width: auto;
|
||||
height: 15px;
|
||||
opacity: 0.85;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
&__domain {
|
||||
font-weight: 400;
|
||||
opacity: 0.75;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.simple_form .not_recommended {
|
||||
color: var(--color-text-error);
|
||||
background-color: var(--color-bg-error-softer);
|
||||
border-color: var(--color-border-on-bg-error-softer);
|
||||
}
|
||||
|
||||
.account__header__fields {
|
||||
max-width: 100vw;
|
||||
padding: 0;
|
||||
margin: 15px -15px -15px;
|
||||
border: 0 none;
|
||||
border-top: 1px solid var(--color-border-primary);
|
||||
border-bottom: 1px solid var(--color-border-primary);
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
|
||||
dl {
|
||||
display: flex;
|
||||
border-bottom: 1px solid var(--color-border-primary);
|
||||
}
|
||||
|
||||
dt,
|
||||
dd {
|
||||
box-sizing: border-box;
|
||||
padding: 14px;
|
||||
text-align: center;
|
||||
max-height: 48px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: 500;
|
||||
width: 120px;
|
||||
flex: 0 0 auto;
|
||||
color: var(--color-text-primary);
|
||||
background: var(--color-bg-secondary);
|
||||
}
|
||||
|
||||
dd {
|
||||
flex: 1 1 auto;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--color-text-brand);
|
||||
text-decoration: none;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.verified {
|
||||
border: 1px solid var(--color-border-on-bg-success-softer);
|
||||
background: var(--color-bg-success-softer);
|
||||
|
||||
a {
|
||||
color: var(--color-text-success);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&__mark {
|
||||
color: var(--color-text-success);
|
||||
}
|
||||
}
|
||||
|
||||
dl:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.directory__tag .trends__item__current {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.pending-account {
|
||||
&__header {
|
||||
color: var(--color-text-secondary);
|
||||
|
||||
a {
|
||||
color: var(--color-text-primary);
|
||||
text-decoration: none;
|
||||
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
strong {
|
||||
color: var(--color-text-primary);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.warning-hint {
|
||||
font-weight: normal !important;
|
||||
}
|
||||
}
|
||||
|
||||
&__body {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.batch-table__row--muted {
|
||||
color: var(--color-text-tertiary);
|
||||
}
|
||||
|
||||
.batch-table__row--muted .pending-account__header,
|
||||
.batch-table__row--muted .accounts-table,
|
||||
.batch-table__row--muted .name-tag {
|
||||
&,
|
||||
a,
|
||||
strong {
|
||||
color: var(--color-text-tertiary);
|
||||
}
|
||||
}
|
||||
|
||||
.batch-table__row--muted .name-tag .avatar {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.batch-table__row--muted .accounts-table {
|
||||
tbody td.accounts-table__extra,
|
||||
&__count,
|
||||
&__count small {
|
||||
color: var(--color-text-tertiary);
|
||||
}
|
||||
}
|
||||
|
||||
.batch-table__row--attention {
|
||||
color: var(--color-text-warning);
|
||||
}
|
||||
|
||||
.batch-table__row--attention .pending-account__header,
|
||||
.batch-table__row--attention .accounts-table,
|
||||
.batch-table__row--attention .name-tag {
|
||||
&,
|
||||
a,
|
||||
strong {
|
||||
color: var(--color-text-warning);
|
||||
}
|
||||
}
|
||||
|
||||
.batch-table__row--attention .accounts-table {
|
||||
tbody td.accounts-table__extra,
|
||||
&__count,
|
||||
&__count small {
|
||||
color: var(--color-text-warning);
|
||||
}
|
||||
}
|
||||
2173
app/javascript/styles_new/mastodon/admin.scss
Normal file
2173
app/javascript/styles_new/mastodon/admin.scss
Normal file
File diff suppressed because it is too large
Load Diff
342
app/javascript/styles_new/mastodon/annual_reports.scss
Normal file
342
app/javascript/styles_new/mastodon/annual_reports.scss
Normal file
@@ -0,0 +1,342 @@
|
||||
@use 'variables' as *;
|
||||
|
||||
:root {
|
||||
--indigo-1: #17063b;
|
||||
--indigo-2: #2f0c7a;
|
||||
--indigo-3: #562cfc;
|
||||
--indigo-5: #858afa;
|
||||
--indigo-6: #cccfff;
|
||||
--lime: #baff3b;
|
||||
--goldenrod-2: #ffc954;
|
||||
}
|
||||
|
||||
.annual-report {
|
||||
flex: 0 0 auto;
|
||||
background: var(--indigo-1);
|
||||
padding: 24px;
|
||||
|
||||
&__header {
|
||||
margin-bottom: 16px;
|
||||
|
||||
h1 {
|
||||
font-size: 25px;
|
||||
font-weight: 600;
|
||||
line-height: 30px;
|
||||
color: var(--lime);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
line-height: 20px;
|
||||
color: var(--indigo-6);
|
||||
}
|
||||
}
|
||||
|
||||
&__bento {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr);
|
||||
grid-template-rows: minmax(0, auto) minmax(0, 1fr) minmax(0, auto) minmax(
|
||||
0,
|
||||
auto
|
||||
);
|
||||
|
||||
&__box {
|
||||
padding: 16px;
|
||||
border-radius: 8px;
|
||||
background: var(--indigo-2);
|
||||
color: var(--indigo-5);
|
||||
}
|
||||
}
|
||||
|
||||
&__summary {
|
||||
&__most-boosted-post {
|
||||
grid-column: span 2;
|
||||
grid-row: span 2;
|
||||
padding: 0;
|
||||
|
||||
.status__content,
|
||||
.content-warning {
|
||||
color: var(--indigo-6);
|
||||
}
|
||||
|
||||
.detailed-status {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.content-warning {
|
||||
border: 0;
|
||||
background: var(--indigo-1);
|
||||
|
||||
.link-button {
|
||||
color: var(--indigo-5);
|
||||
}
|
||||
}
|
||||
|
||||
.detailed-status__meta__line {
|
||||
border-bottom-color: var(--indigo-3);
|
||||
}
|
||||
|
||||
.detailed-status__meta {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.detailed-status__meta,
|
||||
.poll__footer,
|
||||
.poll__link,
|
||||
.detailed-status .logo,
|
||||
.detailed-status__display-name {
|
||||
color: var(--indigo-5);
|
||||
}
|
||||
|
||||
.detailed-status__meta .animated-number,
|
||||
.detailed-status__display-name strong {
|
||||
color: var(--indigo-6);
|
||||
}
|
||||
|
||||
.poll__chart {
|
||||
background-color: var(--indigo-3);
|
||||
|
||||
&.leading {
|
||||
background-color: var(--goldenrod-2);
|
||||
}
|
||||
}
|
||||
|
||||
.status-card,
|
||||
.hashtag-bar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__followers {
|
||||
grid-column: span 1;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
padding-block-start: 24px;
|
||||
padding-block-end: 24px;
|
||||
|
||||
--sparkline-gradient-top: rgba(86, 44, 252, 50%);
|
||||
--sparkline-gradient-bottom: rgba(86, 44, 252, 0%);
|
||||
|
||||
&__foreground {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&__number {
|
||||
font-size: 31px;
|
||||
font-weight: 600;
|
||||
line-height: 37px;
|
||||
color: var(--lime);
|
||||
}
|
||||
|
||||
&__label {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 17px;
|
||||
color: var(--indigo-6);
|
||||
}
|
||||
|
||||
&__footnote {
|
||||
display: block;
|
||||
font-weight: 400;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
svg {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
inset-inline-end: 0;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
height: 70%;
|
||||
width: auto;
|
||||
|
||||
path:first-child {
|
||||
fill: url('#gradient') !important;
|
||||
fill-opacity: 1 !important;
|
||||
}
|
||||
|
||||
path:last-child {
|
||||
stroke: var(--color-graph-primary-stroke) !important;
|
||||
fill: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__archetype {
|
||||
grid-column: span 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
gap: 8px;
|
||||
padding: 0;
|
||||
|
||||
img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
&__label {
|
||||
padding: 16px;
|
||||
padding-bottom: 8px;
|
||||
font-size: 14px;
|
||||
line-height: 17px;
|
||||
font-weight: 600;
|
||||
color: var(--lime);
|
||||
}
|
||||
}
|
||||
|
||||
&__most-used-app {
|
||||
grid-column: span 1;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
box-sizing: border-box;
|
||||
|
||||
&__label {
|
||||
font-size: 14px;
|
||||
line-height: 17px;
|
||||
font-weight: 600;
|
||||
color: var(--indigo-6);
|
||||
}
|
||||
|
||||
&__icon {
|
||||
font-size: 14px;
|
||||
line-height: 17px;
|
||||
font-weight: 600;
|
||||
color: var(--goldenrod-2);
|
||||
}
|
||||
}
|
||||
|
||||
&__percentile {
|
||||
grid-row: span 2;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
text-align: center;
|
||||
text-wrap: balance;
|
||||
padding: 16px 8px;
|
||||
|
||||
&__label {
|
||||
font-size: 14px;
|
||||
line-height: 17px;
|
||||
}
|
||||
|
||||
&__number {
|
||||
font-size: 54px;
|
||||
font-weight: 600;
|
||||
line-height: 73px;
|
||||
color: var(--goldenrod-2);
|
||||
}
|
||||
|
||||
&__footnote {
|
||||
font-size: 11px;
|
||||
line-height: 14px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
&__new-posts {
|
||||
grid-column: span 2;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
&__label {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
line-height: 24px;
|
||||
color: var(--indigo-6);
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&__number {
|
||||
font-size: 76px;
|
||||
font-weight: 600;
|
||||
line-height: 91px;
|
||||
color: var(--goldenrod-2);
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
svg {
|
||||
position: absolute;
|
||||
inset-inline-start: -7px;
|
||||
top: -4px;
|
||||
z-index: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__most-used-hashtag {
|
||||
grid-column: span 2;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
|
||||
&__hashtag {
|
||||
font-size: 42px;
|
||||
font-weight: 600;
|
||||
line-height: 58px;
|
||||
color: var(--indigo-6);
|
||||
margin-inline-start: -100%;
|
||||
margin-inline-end: -100%;
|
||||
}
|
||||
|
||||
&__label {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 17px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.annual-report-modal {
|
||||
max-width: 600px;
|
||||
background: var(--indigo-1);
|
||||
border-radius: 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-y: auto;
|
||||
|
||||
.loading-indicator .circular-progress {
|
||||
color: var(--lime);
|
||||
}
|
||||
|
||||
@media screen and (max-width: $no-columns-breakpoint) {
|
||||
border-bottom: 0;
|
||||
border-radius: 16px 16px 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.notification-group--annual-report {
|
||||
.notification-group__icon {
|
||||
color: var(--lime);
|
||||
}
|
||||
|
||||
.notification-group__main .link-button {
|
||||
font-weight: 500;
|
||||
color: var(--lime);
|
||||
}
|
||||
}
|
||||
300
app/javascript/styles_new/mastodon/basics.scss
Normal file
300
app/javascript/styles_new/mastodon/basics.scss
Normal file
@@ -0,0 +1,300 @@
|
||||
@use 'variables' as *;
|
||||
|
||||
html.has-modal {
|
||||
&,
|
||||
body {
|
||||
touch-action: none;
|
||||
overscroll-behavior: none;
|
||||
-webkit-overflow-scrolling: auto;
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
|
||||
body {
|
||||
overflow: hidden !important;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: $font-sans-serif, sans-serif;
|
||||
background: var(--color-bg-ambient);
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
font-weight: 400;
|
||||
color: var(--color-text-primary);
|
||||
text-rendering: optimizelegibility;
|
||||
|
||||
// Disable kerning for Japanese text to preserve monospaced alignment for readability
|
||||
&:not(:lang(ja)) {
|
||||
font-feature-settings: 'kern';
|
||||
}
|
||||
|
||||
text-size-adjust: none;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0%);
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
|
||||
&.system-font {
|
||||
// system-ui => standard property (Chrome/Android WebView 56+, Opera 43+, Safari 11+)
|
||||
// -apple-system => Safari <11 specific
|
||||
// BlinkMacSystemFont => Chrome <56 on macOS specific
|
||||
// Segoe UI => Windows 7/8/10
|
||||
// Oxygen => KDE
|
||||
// Ubuntu => Unity/Ubuntu
|
||||
// Cantarell => GNOME
|
||||
// Fira Sans => Firefox OS
|
||||
// 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;
|
||||
}
|
||||
|
||||
&.app-body {
|
||||
padding: 0;
|
||||
padding-left: env(safe-area-inset-left);
|
||||
padding-right: env(safe-area-inset-right);
|
||||
box-sizing: border-box;
|
||||
|
||||
&.layout-single-column {
|
||||
height: auto;
|
||||
min-height: 100vh;
|
||||
min-height: 100dvh;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
&.layout-multiple-columns {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
}
|
||||
|
||||
&.player {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
& > div {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.video-player video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-height: 100vh;
|
||||
}
|
||||
|
||||
.media-gallery {
|
||||
margin-top: 0;
|
||||
height: 100% !important;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.media-gallery__item {
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.embed {
|
||||
margin: 0;
|
||||
padding-bottom: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&.admin {
|
||||
padding: 0;
|
||||
background: var(--color-bg-primary);
|
||||
}
|
||||
|
||||
&.error {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.dialog {
|
||||
vertical-align: middle;
|
||||
margin: 20px;
|
||||
|
||||
&__illustration {
|
||||
img {
|
||||
display: block;
|
||||
max-width: 470px;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
margin-top: -120px;
|
||||
margin-bottom: -45px;
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
&:focus {
|
||||
border-radius: 4px;
|
||||
outline: var(--outline-focus-default);
|
||||
}
|
||||
|
||||
&:focus:not(:focus-visible) {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
|
||||
&:focus:not(:focus-visible) {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.app-holder {
|
||||
&,
|
||||
& > div,
|
||||
& > noscript {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
outline: 0 !important;
|
||||
}
|
||||
|
||||
& > noscript {
|
||||
min-height: 100vh;
|
||||
min-height: 100dvh;
|
||||
}
|
||||
}
|
||||
|
||||
.layout-single-column .app-holder {
|
||||
&,
|
||||
& > div {
|
||||
min-height: 100vh;
|
||||
min-height: 100dvh;
|
||||
}
|
||||
}
|
||||
|
||||
.layout-multiple-columns .app-holder {
|
||||
&,
|
||||
& > div {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.error-boundary,
|
||||
.app-holder noscript {
|
||||
flex-direction: column;
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
line-height: 1.7;
|
||||
color: var(--color-text-error);
|
||||
text-align: center;
|
||||
|
||||
& > div {
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 0.85em;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--color-text-brand);
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__footer {
|
||||
color: var(--color-text-secondary);
|
||||
font-size: 13px;
|
||||
|
||||
a {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
display: inline;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: var(--color-text-secondary);
|
||||
font: inherit;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
line-height: inherit;
|
||||
cursor: pointer;
|
||||
outline: 0;
|
||||
transition: color 300ms linear;
|
||||
text-decoration: underline;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&.copied {
|
||||
color: var(--mas-status-success-color);
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.logo-resources {
|
||||
// Not using display: none because of https://bugs.chromium.org/p/chromium/issues/detail?id=258029
|
||||
visibility: hidden;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
width: 0;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
inset-inline-start: 0;
|
||||
z-index: -1000;
|
||||
}
|
||||
|
||||
// NoScript adds a __ns__pop2top class to the full ancestry of blocked elements,
|
||||
// to set the z-index to a high value, which messes with modals and dropdowns.
|
||||
// Blocked elements can in theory only be media and frames/embeds, so they
|
||||
// should only appear in statuses, under divs and articles.
|
||||
body,
|
||||
div,
|
||||
article {
|
||||
.__ns__pop2top {
|
||||
z-index: unset !important;
|
||||
}
|
||||
}
|
||||
5
app/javascript/styles_new/mastodon/branding.scss
Normal file
5
app/javascript/styles_new/mastodon/branding.scss
Normal file
@@ -0,0 +1,5 @@
|
||||
@use 'variables' as *;
|
||||
|
||||
.logo {
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
11433
app/javascript/styles_new/mastodon/components.scss
Normal file
11433
app/javascript/styles_new/mastodon/components.scss
Normal file
File diff suppressed because it is too large
Load Diff
166
app/javascript/styles_new/mastodon/containers.scss
Normal file
166
app/javascript/styles_new/mastodon/containers.scss
Normal file
@@ -0,0 +1,166 @@
|
||||
@use 'variables' as *;
|
||||
|
||||
.container-alt {
|
||||
width: 700px;
|
||||
margin: 0 auto;
|
||||
|
||||
@media screen and (width <= 740px) {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.logo-container {
|
||||
margin: 50px auto;
|
||||
|
||||
h1 {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.logo {
|
||||
height: 42px;
|
||||
margin-inline-end: 10px;
|
||||
}
|
||||
|
||||
a {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: var(--color-text-primary);
|
||||
text-decoration: none;
|
||||
outline: 0;
|
||||
padding: 12px 16px;
|
||||
line-height: 32px;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.compose-standalone {
|
||||
.compose-form {
|
||||
width: 400px;
|
||||
margin: 0 auto;
|
||||
padding: 10px 0;
|
||||
padding-bottom: 20px;
|
||||
box-sizing: border-box;
|
||||
|
||||
@media screen and (width <= 400px) {
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.account-header {
|
||||
width: 400px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
box-sizing: border-box;
|
||||
padding: 20px 0;
|
||||
margin-top: 40px;
|
||||
margin-bottom: 10px;
|
||||
border-bottom: 1px solid var(--color-border-primary);
|
||||
|
||||
@media screen and (width <= 440px) {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
flex: 0 0 auto;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
margin: 0;
|
||||
border-radius: var(--avatar-border-radius);
|
||||
}
|
||||
}
|
||||
|
||||
.name {
|
||||
flex: 1 1 auto;
|
||||
color: var(--color-text-primary);
|
||||
|
||||
.username {
|
||||
display: block;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.logout-link {
|
||||
display: block;
|
||||
font-size: 32px;
|
||||
line-height: 40px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
.redirect {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100vh;
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
|
||||
&__logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 30px;
|
||||
|
||||
img {
|
||||
height: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
&__message {
|
||||
text-align: center;
|
||||
|
||||
h1 {
|
||||
font-size: 17px;
|
||||
line-height: 22px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 30px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--color-text-brand);
|
||||
font-weight: 500;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__link {
|
||||
margin-top: 15px;
|
||||
}
|
||||
}
|
||||
228
app/javascript/styles_new/mastodon/css_variables.scss
Normal file
228
app/javascript/styles_new/mastodon/css_variables.scss
Normal file
@@ -0,0 +1,228 @@
|
||||
@use 'theme_utils' as utils;
|
||||
|
||||
:root {
|
||||
--color-black: #000;
|
||||
--color-grey-950: #181821;
|
||||
--color-grey-800: #292938;
|
||||
--color-grey-700: #444664;
|
||||
--color-grey-600: #545778;
|
||||
--color-grey-500: #696d91;
|
||||
--color-grey-400: #8b8dac;
|
||||
--color-grey-300: #b4b6cb;
|
||||
--color-grey-200: #d8d9e3;
|
||||
--color-grey-100: #f0f0f5;
|
||||
--color-grey-50: #f0f1ff;
|
||||
--color-white: #fff;
|
||||
--color-indigo-600: #6147e6;
|
||||
--color-indigo-400: #8886ff;
|
||||
--color-indigo-300: #a5abfd;
|
||||
--color-indigo-200: #c8cdfe;
|
||||
--color-indigo-100: #e0e3ff;
|
||||
--color-indigo-50: #f0f1ff;
|
||||
--color-red-500: #ff637e;
|
||||
--color-red-600: #ec003f;
|
||||
--color-yellow-400: #ffb900;
|
||||
--color-yellow-600: #e17100;
|
||||
--color-green-400: #05df72;
|
||||
--color-green-600: #00a63e;
|
||||
|
||||
/* TEXT TOKENS */
|
||||
|
||||
--color-text-primary: var(--color-grey-50);
|
||||
--color-text-secondary: var(--color-grey-400);
|
||||
--color-text-tertiary: var(--color-grey-500);
|
||||
--color-text-on-inverted: var(--color-grey-950);
|
||||
--color-text-brand: var(--color-indigo-400);
|
||||
--color-text-brand-soft: color-mix(
|
||||
in oklab,
|
||||
var(--color-text-primary),
|
||||
var(--color-text-brand)
|
||||
);
|
||||
--color-text-on-brand-base: var(--color-white);
|
||||
--color-text-error: var(--color-red-500);
|
||||
--color-text-on-error-base: var(--color-white);
|
||||
--color-text-warning: var(--color-yellow-400);
|
||||
--color-text-on-warning-base: var(--color-white);
|
||||
--color-text-success: var(--color-green-400);
|
||||
--color-text-on-success-base: var(--color-white);
|
||||
--color-text-disabled: var(--color-grey-600);
|
||||
--color-text-on-disabled: var(--color-grey-400);
|
||||
--color-text-bookmark-highlight: var(--color-text-error);
|
||||
--color-text-favourite-highlight: var(--color-text-warning);
|
||||
--color-text-on-media: var(--color-white);
|
||||
--color-text-status-links: color-mix(
|
||||
in oklab,
|
||||
var(--color-text-primary),
|
||||
var(--color-text-secondary)
|
||||
);
|
||||
|
||||
/* BACKGROUND TOKENS */
|
||||
|
||||
// Neutrals
|
||||
--color-bg-primary: var(--color-grey-950);
|
||||
--overlay-strength-secondary: 10%;
|
||||
--color-bg-secondary-base: var(--color-indigo-200);
|
||||
--color-bg-secondary: #{utils.css-alpha(
|
||||
var(--color-bg-secondary-base),
|
||||
var(--overlay-strength-secondary)
|
||||
)};
|
||||
--color-bg-secondary-solid: color-mix(
|
||||
in srgb,
|
||||
var(--color-bg-primary),
|
||||
var(--color-bg-secondary-base) var(--overlay-strength-secondary)
|
||||
);
|
||||
--color-bg-tertiary: color-mix(
|
||||
in oklab,
|
||||
var(--color-bg-primary),
|
||||
var(--color-bg-secondary-base) calc(2 * var(--overlay-strength-secondary))
|
||||
);
|
||||
|
||||
// Utility
|
||||
--color-bg-ambient: var(--color-bg-primary);
|
||||
--color-bg-elevated: var(--color-grey-800);
|
||||
--color-bg-inverted: var(--color-grey-50);
|
||||
--color-bg-media-base: var(--color-black);
|
||||
--color-bg-media-strength: 65%;
|
||||
--color-bg-media: #{utils.css-alpha(
|
||||
var(--color-bg-media-base),
|
||||
var(--color-bg-media-strength)
|
||||
)};
|
||||
--color-bg-overlay: var(--color-bg-primary);
|
||||
--color-bg-disabled: var(--color-grey-700);
|
||||
|
||||
// Brand
|
||||
--overlay-strength-brand: 10%;
|
||||
--color-bg-brand-base: var(--color-indigo-600);
|
||||
--color-bg-brand-base-hover: color-mix(
|
||||
in oklab,
|
||||
var(--color-bg-brand-base),
|
||||
black var(--overlay-strength-brand)
|
||||
);
|
||||
--color-bg-brand-soft: #{utils.css-alpha(
|
||||
var(--color-bg-brand-base),
|
||||
calc(var(--overlay-strength-brand) * 1.5)
|
||||
)};
|
||||
--color-bg-brand-softer: #{utils.css-alpha(
|
||||
var(--color-bg-brand-base),
|
||||
var(--overlay-strength-brand)
|
||||
)};
|
||||
|
||||
// Error
|
||||
--overlay-strength-error: 12%;
|
||||
--color-bg-error-base: var(--color-red-600);
|
||||
--color-bg-error-base-hover: color-mix(
|
||||
in oklab,
|
||||
var(--color-bg-error-base),
|
||||
black var(--overlay-strength-error)
|
||||
);
|
||||
--color-bg-error-soft: #{utils.css-alpha(
|
||||
var(--color-bg-error-base),
|
||||
calc(var(--overlay-strength-error) * 1.5)
|
||||
)};
|
||||
--color-bg-error-softer: #{utils.css-alpha(
|
||||
var(--color-bg-error-base),
|
||||
var(--overlay-strength-error)
|
||||
)};
|
||||
|
||||
// Warning
|
||||
--overlay-strength-warning: 10%;
|
||||
--color-bg-warning-base: var(--color-yellow-600);
|
||||
--color-bg-warning-base-hover: color-mix(
|
||||
in oklab,
|
||||
var(--color-bg-warning-base),
|
||||
black var(--overlay-strength-warning)
|
||||
);
|
||||
--color-bg-warning-soft: #{utils.css-alpha(
|
||||
var(--color-bg-warning-base),
|
||||
calc(var(--overlay-strength-warning) * 1.5)
|
||||
)};
|
||||
--color-bg-warning-softer: #{utils.css-alpha(
|
||||
var(--color-bg-warning-base),
|
||||
var(--overlay-strength-warning)
|
||||
)};
|
||||
|
||||
// Success
|
||||
--overlay-strength-success: 15%;
|
||||
--color-bg-success-base: var(--color-green-600);
|
||||
--color-bg-success-base-hover: color-mix(
|
||||
in oklab,
|
||||
var(--color-bg-success-base),
|
||||
black var(--overlay-strength-success)
|
||||
);
|
||||
--color-bg-success-soft: #{utils.css-alpha(
|
||||
var(--color-bg-success-base),
|
||||
calc(var(--overlay-strength-success) * 1.5)
|
||||
)};
|
||||
--color-bg-success-softer: #{utils.css-alpha(
|
||||
var(--color-bg-success-base),
|
||||
var(--overlay-strength-success)
|
||||
)};
|
||||
|
||||
/* BORDER TOKENS */
|
||||
|
||||
--border-strength-primary: 18%;
|
||||
--color-border-primary: #{utils.css-alpha(
|
||||
var(--color-indigo-200),
|
||||
var(--border-strength-primary)
|
||||
)};
|
||||
--color-border-media: rgb(252 248 255 / 15%);
|
||||
--color-border-on-bg-secondary: #{utils.css-alpha(
|
||||
var(--color-indigo-200),
|
||||
calc(var(--border-strength-primary) / 1.5)
|
||||
)};
|
||||
--color-border-on-bg-brand-softer: var(--color-border-primary);
|
||||
--color-border-on-bg-error-softer: #{utils.css-alpha(
|
||||
var(--color-text-error),
|
||||
50%
|
||||
)};
|
||||
--color-border-on-bg-warning-softer: #{utils.css-alpha(
|
||||
var(--color-text-warning),
|
||||
50%
|
||||
)};
|
||||
--color-border-on-bg-success-softer: #{utils.css-alpha(
|
||||
var(--color-text-success),
|
||||
50%
|
||||
)};
|
||||
--color-border-on-bg-inverted: var(--color-border-primary);
|
||||
|
||||
/* SHADOW TOKENS */
|
||||
|
||||
--shadow-strength-primary: 80%;
|
||||
--color-shadow-primary: #{utils.css-alpha(
|
||||
var(--color-black),
|
||||
var(--shadow-strength-primary)
|
||||
)};
|
||||
--dropdown-shadow:
|
||||
0 20px 25px -5px var(--color-shadow-primary),
|
||||
0 8px 10px -6px var(--color-shadow-primary);
|
||||
--overlay-icon-shadow: drop-shadow(0 0 8px var(--color-shadow-primary));
|
||||
|
||||
/* GRAPHS/CHARTS TOKENS */
|
||||
|
||||
--color-graph-primary-stroke: var(--color-text-brand);
|
||||
--color-graph-primary-fill: var(--color-bg-brand-softer);
|
||||
--color-graph-warning-stroke: var(--color-text-warning);
|
||||
--color-graph-warning-fill: var(--color-bg-warning-softer);
|
||||
--color-graph-disabled-stroke: var(--color-text-disabled);
|
||||
--color-graph-disabled-fill: var(--color-bg-disabled);
|
||||
|
||||
/* LEGACY TOKENS */
|
||||
|
||||
--rich-text-container-color: rgb(87 24 60 / 100%);
|
||||
--rich-text-text-color: rgb(255 175 212 / 100%);
|
||||
--rich-text-decorations-color: rgb(128 58 95 / 100%);
|
||||
|
||||
/* MISCELLANEOUS */
|
||||
|
||||
--outline-focus-default: 2px solid var(--color-text-brand);
|
||||
--avatar-border-radius: 8px;
|
||||
}
|
||||
|
||||
body {
|
||||
// Variable for easily inverting directional UI elements,
|
||||
--text-x-direction: 1;
|
||||
|
||||
&.rtl {
|
||||
--text-x-direction: -1;
|
||||
}
|
||||
}
|
||||
120
app/javascript/styles_new/mastodon/dashboard.scss
Normal file
120
app/javascript/styles_new/mastodon/dashboard.scss
Normal file
@@ -0,0 +1,120 @@
|
||||
@use 'variables' as *;
|
||||
|
||||
.dashboard__counters {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin: 0 -5px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
& > div {
|
||||
box-sizing: border-box;
|
||||
flex: 0 0 33.333%;
|
||||
padding: 0 5px;
|
||||
margin-bottom: 10px;
|
||||
|
||||
& > div,
|
||||
& > a {
|
||||
padding: 20px;
|
||||
background: var(--color-bg-primary);
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--color-border-primary);
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
& > a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
display: block;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
background: var(--color-bg-brand-softer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__num,
|
||||
&__text {
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
font-size: 24px;
|
||||
color: var(--color-text-primary);
|
||||
margin-bottom: 20px;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
&__text {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
&__label {
|
||||
font-size: 14px;
|
||||
color: var(--color-text-secondary);
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.dashboard {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr);
|
||||
gap: 10px;
|
||||
|
||||
@media screen and (width <= 1350px) {
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||
}
|
||||
|
||||
&__item {
|
||||
&--span-double-column {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
&--span-double-row {
|
||||
grid-row: span 2;
|
||||
}
|
||||
|
||||
h4 {
|
||||
padding-top: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
&__quick-access {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
border-radius: 4px;
|
||||
background: var(--color-bg-brand-base);
|
||||
color: var(--color-text-on-brand-base);
|
||||
transition: all 100ms ease-in;
|
||||
font-size: 14px;
|
||||
padding: 8px 16px;
|
||||
text-decoration: none;
|
||||
margin-bottom: 4px;
|
||||
|
||||
&:active,
|
||||
&:focus,
|
||||
&:hover {
|
||||
background-color: var(--color-bg-brand-base-hover);
|
||||
transition: all 200ms ease-out;
|
||||
}
|
||||
|
||||
&.positive {
|
||||
background: var(--color-bg-success-softer);
|
||||
color: var(--color-text-success);
|
||||
}
|
||||
|
||||
&.negative {
|
||||
background: var(--color-bg-error-softer);
|
||||
color: var(--color-text-error);
|
||||
}
|
||||
|
||||
span {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
}
|
||||
248
app/javascript/styles_new/mastodon/emoji_picker.scss
Normal file
248
app/javascript/styles_new/mastodon/emoji_picker.scss
Normal file
@@ -0,0 +1,248 @@
|
||||
@use 'variables' as *;
|
||||
|
||||
.emoji-mart {
|
||||
font-size: 13px;
|
||||
display: inline-block;
|
||||
|
||||
&,
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.emoji-mart-emoji {
|
||||
padding: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.emoji-mart-bar {
|
||||
&:first-child {
|
||||
background: var(--color-bg-tertiary);
|
||||
border-bottom: 1px solid var(--color-border-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.emoji-mart-anchors {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0 6px;
|
||||
line-height: 0;
|
||||
}
|
||||
|
||||
.emoji-mart-anchor {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding: 12px 4px;
|
||||
overflow: hidden;
|
||||
transition: color 0.1s ease-out;
|
||||
cursor: pointer;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
color: var(--color-text-secondary);
|
||||
|
||||
&:hover {
|
||||
color: color-mix(
|
||||
in oklab,
|
||||
var(--color-text-primary),
|
||||
var(--color-text-secondary)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
.emoji-mart-anchor-selected {
|
||||
color: var(--color-text-brand);
|
||||
|
||||
&:hover {
|
||||
color: var(--color-text-brand-soft);
|
||||
}
|
||||
|
||||
.emoji-mart-anchor-bar {
|
||||
bottom: -1px;
|
||||
}
|
||||
}
|
||||
|
||||
.emoji-mart-anchor-bar {
|
||||
position: absolute;
|
||||
bottom: -5px;
|
||||
inset-inline-start: 0;
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
background-color: var(--color-text-brand);
|
||||
}
|
||||
|
||||
.emoji-mart-anchors {
|
||||
i {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
max-width: 22px;
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: currentColor;
|
||||
max-height: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
.emoji-mart-scroll {
|
||||
overflow-y: scroll;
|
||||
height: 270px;
|
||||
max-height: 35vh;
|
||||
padding: 0 6px 6px;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.emoji-mart-search {
|
||||
padding: 10px;
|
||||
padding-inline-end: 45px;
|
||||
position: relative;
|
||||
|
||||
input {
|
||||
font-size: 16px;
|
||||
font-weight: 400;
|
||||
padding: 7px 9px;
|
||||
padding-inline-end: 25px;
|
||||
font-family: inherit;
|
||||
display: block;
|
||||
width: 100%;
|
||||
background: var(--color-bg-secondary);
|
||||
color: var(--color-text-secondary);
|
||||
border: 1px solid var(--color-border-primary);
|
||||
border-radius: 4px;
|
||||
|
||||
&::-moz-focus-inner {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
&:active,
|
||||
&:focus {
|
||||
outline: none !important;
|
||||
border-width: 1px !important;
|
||||
}
|
||||
|
||||
&::-webkit-search-cancel-button {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.emoji-mart-search-icon {
|
||||
position: absolute;
|
||||
top: 18px;
|
||||
inset-inline-end: 45px + 5px;
|
||||
z-index: 2;
|
||||
padding: 2px 5px 1px;
|
||||
border: 0;
|
||||
background: none;
|
||||
transition: all 100ms linear;
|
||||
transition-property: opacity;
|
||||
pointer-events: auto;
|
||||
|
||||
&:disabled {
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
svg {
|
||||
fill: currentColor;
|
||||
}
|
||||
}
|
||||
|
||||
.emoji-mart-category .emoji-mart-emoji {
|
||||
cursor: pointer;
|
||||
|
||||
span {
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
display: inline-flex !important;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&:hover::before {
|
||||
z-index: -1;
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
inset-inline-start: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: var(--color-bg-brand-softer);
|
||||
border-radius: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.emoji-mart-category-label {
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
position: -webkit-sticky;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
|
||||
span {
|
||||
display: block;
|
||||
width: 100%;
|
||||
font-weight: 500;
|
||||
padding: 5px 6px;
|
||||
}
|
||||
}
|
||||
|
||||
/* For screenreaders only, via https://stackoverflow.com/a/19758620 */
|
||||
.emoji-mart-sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip-path: inset(50%);
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.emoji-mart-category-list {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.emoji-mart-category-list li {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.emoji-mart-emoji {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
font-size: 0;
|
||||
|
||||
span {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
}
|
||||
|
||||
.emoji-mart-no-results {
|
||||
font-size: 14px;
|
||||
color: var(--color-text-tertiary);
|
||||
text-align: center;
|
||||
padding: 5px 6px;
|
||||
padding-top: 70px;
|
||||
|
||||
.emoji-mart-no-results-label {
|
||||
margin-top: 0.2em;
|
||||
}
|
||||
|
||||
.emoji-mart-emoji:hover::before {
|
||||
cursor: default;
|
||||
content: none;
|
||||
}
|
||||
}
|
||||
|
||||
.emoji-mart-preview {
|
||||
display: none;
|
||||
}
|
||||
1449
app/javascript/styles_new/mastodon/forms.scss
Normal file
1449
app/javascript/styles_new/mastodon/forms.scss
Normal file
File diff suppressed because it is too large
Load Diff
19
app/javascript/styles_new/mastodon/lists.scss
Normal file
19
app/javascript/styles_new/mastodon/lists.scss
Normal file
@@ -0,0 +1,19 @@
|
||||
.no-list {
|
||||
list-style: none;
|
||||
|
||||
li {
|
||||
display: inline-block;
|
||||
margin: 0 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.recovery-codes {
|
||||
list-style: none;
|
||||
margin: 0 auto;
|
||||
|
||||
li {
|
||||
font-size: 125%;
|
||||
line-height: 1.5;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
}
|
||||
53
app/javascript/styles_new/mastodon/modal.scss
Normal file
53
app/javascript/styles_new/mastodon/modal.scss
Normal file
@@ -0,0 +1,53 @@
|
||||
@use 'variables' as *;
|
||||
|
||||
.modal-layout {
|
||||
background: var(--color-bg-brand-softer);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.modal-layout__mastodon {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
|
||||
> div {
|
||||
flex: 1;
|
||||
max-height: 235px;
|
||||
position: relative;
|
||||
|
||||
img {
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
inset-inline-start: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (width <= 600px) {
|
||||
.account-header {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.with-zig-zag-decoration {
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: auto 0 0;
|
||||
height: 32px;
|
||||
background-color: var(--color-bg-brand-softer);
|
||||
|
||||
/* Decorative zig-zag pattern at the bottom of the page */
|
||||
mask-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 234.80078 31.757813" width="234.80078" height="31.757812"><path d="M19.599609 0c-1.05 0-2.10039.375-2.90039 1.125L0 16.925781v14.832031h234.80078V17.025391l-16.5-15.900391c-1.6-1.5-4.20078-1.5-5.80078 0l-13.80078 13.099609c-1.6 1.5-4.19883 1.5-5.79883 0L179.09961 1.125c-1.6-1.5-4.19883-1.5-5.79883 0L159.5 14.224609c-1.6 1.5-4.20078 1.5-5.80078 0L139.90039 1.125c-1.6-1.5-4.20078-1.5-5.80078 0l-13.79883 13.099609c-1.6 1.5-4.20078 1.5-5.80078 0L100.69922 1.125c-1.600001-1.5-4.198829-1.5-5.798829 0l-13.59961 13.099609c-1.6 1.5-4.200781 1.5-5.800781 0L61.699219 1.125c-1.6-1.5-4.198828-1.5-5.798828 0L42.099609 14.224609c-1.6 1.5-4.198828 1.5-5.798828 0L22.5 1.125C21.7.375 20.649609 0 19.599609 0z" fill="black"/></svg>');
|
||||
mask-position: bottom;
|
||||
mask-repeat: repeat-x;
|
||||
z-index: -1;
|
||||
}
|
||||
}
|
||||
232
app/javascript/styles_new/mastodon/polls.scss
Normal file
232
app/javascript/styles_new/mastodon/polls.scss
Normal file
@@ -0,0 +1,232 @@
|
||||
@use 'sass:color';
|
||||
@use 'variables' as *;
|
||||
|
||||
.poll {
|
||||
margin-top: 16px;
|
||||
font-size: 14px;
|
||||
|
||||
li {
|
||||
margin-bottom: 10px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&__chart {
|
||||
border-radius: 4px;
|
||||
display: block;
|
||||
background: rgb(from var(--color-text-brand) r g b / 60%);
|
||||
height: 5px;
|
||||
min-width: 1%;
|
||||
|
||||
&.leading {
|
||||
background: var(--color-text-brand);
|
||||
}
|
||||
}
|
||||
|
||||
progress {
|
||||
border: 0;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 5px;
|
||||
appearance: none;
|
||||
background: transparent;
|
||||
|
||||
&::-webkit-progress-bar {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
// Those rules need to be entirely separate or they won't work, hence the
|
||||
// duplication
|
||||
&::-moz-progress-bar {
|
||||
border-radius: 4px;
|
||||
background: rgb(from var(--color-text-brand) r g b / 60%);
|
||||
}
|
||||
|
||||
&::-webkit-progress-value {
|
||||
border-radius: 4px;
|
||||
background: rgb(from var(--color-text-brand) r g b / 60%);
|
||||
}
|
||||
}
|
||||
|
||||
&__option {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
padding: 6px 0;
|
||||
line-height: 18px;
|
||||
cursor: default;
|
||||
overflow: hidden;
|
||||
|
||||
&__text {
|
||||
display: inline-block;
|
||||
overflow-wrap: break-word;
|
||||
max-width: calc(100% - 45px - 25px);
|
||||
}
|
||||
|
||||
input[type='radio'],
|
||||
input[type='checkbox'] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.autosuggest-input {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
input[type='text'] {
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
color: var(--color-text-primary);
|
||||
outline: 0;
|
||||
font-family: inherit;
|
||||
background: var(--color-bg-primary);
|
||||
border: 1px solid var(--color-text-secondary);
|
||||
border-radius: 4px;
|
||||
padding: 8px 12px;
|
||||
|
||||
&:focus {
|
||||
border-color: var(--color-text-brand);
|
||||
}
|
||||
|
||||
@media screen and (width <= 600px) {
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
}
|
||||
|
||||
&.selectable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.editable,
|
||||
&.disabled {
|
||||
align-items: center;
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
|
||||
&__input {
|
||||
display: block;
|
||||
position: relative;
|
||||
border: 1px solid var(--color-text-secondary);
|
||||
box-sizing: border-box;
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
border-radius: 50%;
|
||||
flex: 0 0 auto;
|
||||
|
||||
&.checkbox {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
&:active,
|
||||
&:focus,
|
||||
&:hover {
|
||||
border-color: var(--color-text-success);
|
||||
border-width: 4px;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: var(--color-bg-success-base);
|
||||
border-color: var(--color-text-success);
|
||||
}
|
||||
|
||||
&::-moz-focus-inner {
|
||||
outline: 0 !important;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&:active {
|
||||
outline: 0 !important;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
border-color: var(--color-text-disabled);
|
||||
|
||||
&.active {
|
||||
background: var(--color-text-disabled);
|
||||
}
|
||||
|
||||
&:active,
|
||||
&:focus,
|
||||
&:hover {
|
||||
border-color: var(--color-text-disabled);
|
||||
border-width: 1px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__option.editable &__input,
|
||||
&__option.disabled &__input {
|
||||
&:active,
|
||||
&:focus,
|
||||
&:hover {
|
||||
border-color: var(--color-text-primary);
|
||||
border-width: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
&__number {
|
||||
display: inline-block;
|
||||
width: 45px;
|
||||
font-weight: 700;
|
||||
flex: 0 0 45px;
|
||||
}
|
||||
|
||||
&__voted {
|
||||
padding: 0 5px;
|
||||
display: inline-block;
|
||||
|
||||
&__mark {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
&__footer {
|
||||
padding-top: 6px;
|
||||
padding-bottom: 5px;
|
||||
color: var(--color-text-tertiary);
|
||||
}
|
||||
|
||||
&__link {
|
||||
display: inline;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
color: var(--color-text-tertiary);
|
||||
text-decoration: underline;
|
||||
font-size: inherit;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&:active,
|
||||
&:focus {
|
||||
background-color: var(--color-bg-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
.button {
|
||||
height: 36px;
|
||||
padding: 0 16px;
|
||||
margin-inline-end: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.muted .poll {
|
||||
color: var(--color-text-tertiary);
|
||||
|
||||
&__chart {
|
||||
background: rgb(from var(--color-text-brand) r g b / 40%);
|
||||
|
||||
&.leading {
|
||||
background: rgb(from var(--color-text-brand) r g b / 60%);
|
||||
}
|
||||
}
|
||||
}
|
||||
58
app/javascript/styles_new/mastodon/reset.scss
Normal file
58
app/javascript/styles_new/mastodon/reset.scss
Normal file
@@ -0,0 +1,58 @@
|
||||
/* http://meyerweb.com/eric/tools/css/reset/
|
||||
v2.0 | 20110126
|
||||
License: none (public domain)
|
||||
*/
|
||||
|
||||
html, body, div, span, applet, object, iframe,
|
||||
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
||||
a, abbr, acronym, address, big, cite, code,
|
||||
del, dfn, em, img, ins, kbd, q, s, samp,
|
||||
small, strike, strong, sub, sup, tt, var,
|
||||
b, u, i, center,
|
||||
dl, dt, dd, ol, ul, li,
|
||||
fieldset, form, label, legend,
|
||||
table, caption, tbody, tfoot, thead, tr, th, td,
|
||||
article, aside, canvas, details, embed,
|
||||
figure, figcaption, footer, header, hgroup,
|
||||
menu, nav, output, ruby, section, summary,
|
||||
time, mark, audio, video {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
font: inherit;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
/* HTML5 display-role reset for older browsers */
|
||||
article, aside, details, figcaption, figure,
|
||||
footer, header, hgroup, menu, nav, section {
|
||||
display: block;
|
||||
}
|
||||
|
||||
body {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
ol, ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
blockquote, q {
|
||||
quotes: none;
|
||||
}
|
||||
|
||||
blockquote:before, blockquote:after,
|
||||
q:before, q:after {
|
||||
content: '';
|
||||
content: none;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
||||
|
||||
html:has(body.custom-scrollbars) {
|
||||
scrollbar-color: var(--color-text-secondary) var(--color-bg-secondary);
|
||||
}
|
||||
116
app/javascript/styles_new/mastodon/rich_text.scss
Normal file
116
app/javascript/styles_new/mastodon/rich_text.scss
Normal file
@@ -0,0 +1,116 @@
|
||||
.status__content__text,
|
||||
.e-content,
|
||||
.edit-indicator__content,
|
||||
.reply-indicator__content {
|
||||
code {
|
||||
background: var(--rich-text-container-color);
|
||||
padding: 4px;
|
||||
border-radius: 4px;
|
||||
color: var(--rich-text-text-color);
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: var(--rich-text-container-color);
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
color: var(--rich-text-text-color);
|
||||
|
||||
code {
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
pre,
|
||||
blockquote {
|
||||
margin-bottom: 22px;
|
||||
white-space: pre-wrap;
|
||||
unicode-bidi: plaintext;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
blockquote {
|
||||
padding-inline-start: 32px;
|
||||
color: var(--rich-text-text-color);
|
||||
white-space: normal;
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
display: block;
|
||||
content: '';
|
||||
width: 24px;
|
||||
height: 20px;
|
||||
mask-image: url('@/images/quote.svg');
|
||||
background-color: var(--rich-text-decorations-color);
|
||||
position: absolute;
|
||||
inset-inline-start: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin-top: 4px;
|
||||
border-inline-start: 3px solid var(--rich-text-decorations-color);
|
||||
padding-inline-start: 16px;
|
||||
|
||||
&::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
p:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
& > ul,
|
||||
& > ol {
|
||||
margin-bottom: 22px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
em,
|
||||
i {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
ul,
|
||||
ol {
|
||||
padding-inline-start: 24px;
|
||||
|
||||
li {
|
||||
padding-inline-start: 8px;
|
||||
|
||||
&::marker {
|
||||
text-align: end;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: '•';
|
||||
|
||||
li::marker {
|
||||
text-align: start;
|
||||
}
|
||||
}
|
||||
|
||||
ol {
|
||||
list-style-type: decimal;
|
||||
}
|
||||
}
|
||||
50
app/javascript/styles_new/mastodon/rtl.scss
Normal file
50
app/javascript/styles_new/mastodon/rtl.scss
Normal file
@@ -0,0 +1,50 @@
|
||||
@use 'variables' as *;
|
||||
|
||||
body.rtl {
|
||||
direction: rtl;
|
||||
|
||||
.reactions-bar {
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
.announcements__mastodon,
|
||||
.drawer__inner__mastodon > img {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
.compose-form .autosuggest-textarea__textarea {
|
||||
padding-right: 10px;
|
||||
padding-left: 10px + 22px;
|
||||
}
|
||||
|
||||
.columns-area {
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
.account__avatar-wrapper {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.column-header__setting-arrows {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.admin-wrapper {
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
.react-swipeable-view-container > * {
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
.column-back-button__icon {
|
||||
transform: scale(-1, 1);
|
||||
}
|
||||
|
||||
.dismissable-banner,
|
||||
.warning-banner {
|
||||
&__action {
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
375
app/javascript/styles_new/mastodon/tables.scss
Normal file
375
app/javascript/styles_new/mastodon/tables.scss
Normal file
@@ -0,0 +1,375 @@
|
||||
@use 'variables' as *;
|
||||
|
||||
.table {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
border-spacing: 0;
|
||||
border-collapse: collapse;
|
||||
|
||||
th,
|
||||
td {
|
||||
padding: 8px;
|
||||
line-height: 18px;
|
||||
vertical-align: top;
|
||||
border-bottom: 1px solid var(--color-border-primary);
|
||||
text-align: start;
|
||||
background: var(--color-bg-primary);
|
||||
|
||||
&.critical {
|
||||
font-weight: 700;
|
||||
color: var(--color-text-warning);
|
||||
}
|
||||
}
|
||||
|
||||
& > thead > tr > th {
|
||||
vertical-align: bottom;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
& > tbody > tr > th {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
& > tbody > tr:nth-child(odd) > td,
|
||||
& > tbody > tr:nth-child(odd) > th {
|
||||
background: var(--color-bg-primary);
|
||||
}
|
||||
|
||||
& > tbody > tr:last-child > td,
|
||||
& > tbody > tr:last-child > th {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--color-text-secondary);
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
color: var(--color-text-brand);
|
||||
}
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: 500;
|
||||
|
||||
@each $lang in $cjk-langs {
|
||||
&:lang(#{$lang}) {
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.inline-table {
|
||||
& > tbody > tr:nth-child(odd) {
|
||||
& > td,
|
||||
& > th {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
& > tbody > tr:first-child {
|
||||
& > td,
|
||||
& > th {
|
||||
border-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.horizontal-table {
|
||||
border-collapse: collapse;
|
||||
border-style: hidden;
|
||||
|
||||
& > tbody > tr > th,
|
||||
& > tbody > tr > td {
|
||||
padding: 11px 10px;
|
||||
background: transparent;
|
||||
border: 1px solid var(--color-border-primary);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
& > tbody > tr > th {
|
||||
color: var(--color-text-secondary);
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
&.batch-table {
|
||||
& > thead > tr > th {
|
||||
background: var(--color-bg-primary);
|
||||
border-top: 1px solid var(--color-border-primary);
|
||||
border-bottom: 1px solid var(--color-border-primary);
|
||||
|
||||
&:first-child {
|
||||
border-radius: 4px 0 0;
|
||||
border-inline-start: 1px solid var(--color-border-primary);
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-radius: 0 4px 0 0;
|
||||
border-inline-end: 1px solid var(--color-border-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&--invites tbody td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
|
||||
.table-wrapper {
|
||||
overflow: auto;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
samp {
|
||||
font-family: $font-monospace, monospace;
|
||||
}
|
||||
|
||||
button.table-action-link {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
button.table-action-link,
|
||||
a.table-action-link {
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
margin-inline-end: 5px;
|
||||
padding: 0 10px;
|
||||
color: var(--color-text-secondary);
|
||||
font-weight: 500;
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover {
|
||||
color: var(--color-text-brand);
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.batch-table {
|
||||
&--no-toolbar {
|
||||
.batch-table__toolbar {
|
||||
position: static;
|
||||
height: 4px;
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__toolbar,
|
||||
&__row {
|
||||
display: flex;
|
||||
|
||||
&__select {
|
||||
box-sizing: border-box;
|
||||
padding: 8px 16px;
|
||||
cursor: pointer;
|
||||
min-height: 100%;
|
||||
|
||||
input {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
&--aligned {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
input {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__actions,
|
||||
&__content {
|
||||
padding: 8px 0;
|
||||
padding-inline-end: 16px;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
}
|
||||
|
||||
&__toolbar {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 200;
|
||||
border: 1px solid var(--color-border-primary);
|
||||
background: var(--color-bg-primary);
|
||||
border-radius: 4px 4px 0 0;
|
||||
height: 47px;
|
||||
align-items: center;
|
||||
|
||||
&__actions {
|
||||
text-align: end;
|
||||
padding-inline-end: 16px - 5px;
|
||||
|
||||
.table-action-link {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__select-all {
|
||||
background: var(--color-bg-primary);
|
||||
height: 47px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid var(--color-border-primary);
|
||||
border-top: 0;
|
||||
color: var(--color-text-primary);
|
||||
display: none;
|
||||
|
||||
&.active {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.selected,
|
||||
.not-selected {
|
||||
display: none;
|
||||
|
||||
&.active {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
span {
|
||||
padding: 8px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
button {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
font: inherit;
|
||||
color: var(--color-text-brand);
|
||||
border-radius: 4px;
|
||||
font-weight: 700;
|
||||
padding: 8px;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
background: var(--color-bg-secondary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__form {
|
||||
padding: 16px;
|
||||
border: 1px solid var(--color-border-primary);
|
||||
border-top: 0;
|
||||
background: var(--color-bg-primary);
|
||||
|
||||
.fields-row {
|
||||
padding-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__row {
|
||||
border: 1px solid var(--color-border-primary);
|
||||
border-top: 0;
|
||||
background: var(--color-bg-primary);
|
||||
|
||||
@media screen and (max-width: $no-gap-breakpoint) {
|
||||
.optional &:first-child {
|
||||
border-top: 1px solid var(--color-border-primary);
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-radius: 0 0 4px 4px;
|
||||
}
|
||||
|
||||
&__content {
|
||||
padding-top: 12px;
|
||||
padding-bottom: 16px;
|
||||
overflow: hidden;
|
||||
|
||||
&--unpadded {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&--padded {
|
||||
padding: 12px 16px 16px;
|
||||
}
|
||||
|
||||
&--with-image {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__image {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-inline-end: 10px;
|
||||
|
||||
.emojione {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
&__text {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
&__quote {
|
||||
padding: 12px;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
&__extra {
|
||||
flex: 0 0 auto;
|
||||
text-align: end;
|
||||
color: var(--color-text-secondary);
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.directory__tag {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
|
||||
a {
|
||||
background: transparent;
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.optional .batch-table__toolbar,
|
||||
&.optional .batch-table__row__select {
|
||||
@media screen and (max-width: $no-gap-breakpoint) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Reset the status card to not have borders, background or padding when
|
||||
// inline in the table of statuses
|
||||
.batch-table__row__content > .status__card {
|
||||
border: none;
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@media screen and (width <= 870px) {
|
||||
.accounts-table tbody td.optional {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.one-liner {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
183
app/javascript/styles_new/mastodon/widgets.scss
Normal file
183
app/javascript/styles_new/mastodon/widgets.scss
Normal file
@@ -0,0 +1,183 @@
|
||||
@use 'sass:color';
|
||||
@use 'variables' as *;
|
||||
|
||||
.directory {
|
||||
&__tag {
|
||||
box-sizing: border-box;
|
||||
margin-bottom: 10px;
|
||||
|
||||
& > a,
|
||||
& > div {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border: 1px solid var(--color-border-primary);
|
||||
border-radius: 4px;
|
||||
padding: 15px;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
box-shadow: 0 0 15px var(--color-shadow-primary);
|
||||
}
|
||||
|
||||
& > a {
|
||||
&:hover,
|
||||
&:active,
|
||||
&:focus {
|
||||
background: var(--color-bg-primary);
|
||||
}
|
||||
}
|
||||
|
||||
&.active > a {
|
||||
background: var(--color-bg-brand-base);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&.disabled > div {
|
||||
opacity: 0.5;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
h4 {
|
||||
flex: 1 1 auto;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: var(--color-text-primary);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
.fa {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
small {
|
||||
display: block;
|
||||
font-weight: 400;
|
||||
font-size: 15px;
|
||||
margin-top: 8px;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
&.active h4 {
|
||||
&,
|
||||
.fa,
|
||||
small,
|
||||
.trends__item__current {
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.avatar-stack {
|
||||
flex: 0 0 auto;
|
||||
width: (36px + 4px) * 3;
|
||||
}
|
||||
|
||||
&.active .avatar-stack .account__avatar {
|
||||
border-color: var(--color-text-brand);
|
||||
}
|
||||
|
||||
.trends__item__current {
|
||||
padding-inline-end: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.accounts-table {
|
||||
width: 100%;
|
||||
|
||||
.account {
|
||||
max-width: calc(56px + 30ch);
|
||||
padding: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
thead th {
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
color: var(--color-text-secondary);
|
||||
font-weight: 700;
|
||||
padding: 10px;
|
||||
|
||||
&:first-child {
|
||||
text-align: start;
|
||||
}
|
||||
}
|
||||
|
||||
tbody td {
|
||||
padding: 15px 0;
|
||||
vertical-align: middle;
|
||||
border-bottom: 1px solid var(--color-border-primary);
|
||||
}
|
||||
|
||||
tbody tr:last-child td {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
&__count {
|
||||
width: 120px;
|
||||
text-align: center;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
color: var(--color-text-primary);
|
||||
|
||||
small {
|
||||
display: block;
|
||||
color: var(--color-text-secondary);
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
tbody td.accounts-table__extra {
|
||||
width: 120px;
|
||||
text-align: end;
|
||||
color: var(--color-text-secondary);
|
||||
padding-inline-end: 16px;
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
|
||||
&:focus,
|
||||
&:hover,
|
||||
&:active {
|
||||
color: var(--color-text-brand);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__comment {
|
||||
width: 50%;
|
||||
vertical-align: initial !important;
|
||||
}
|
||||
|
||||
tbody td.accounts-table__interrelationships {
|
||||
width: 21px;
|
||||
padding-inline-end: 16px;
|
||||
}
|
||||
|
||||
.icon {
|
||||
&.active {
|
||||
color: var(--color-text-brand);
|
||||
}
|
||||
|
||||
&.passive {
|
||||
color: var(--color-text-warning);
|
||||
}
|
||||
|
||||
&.active.passive {
|
||||
color: var(--color-text-success);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: $no-gap-breakpoint) {
|
||||
tbody td.optional {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,9 +31,10 @@ class IpBlock < ApplicationRecord
|
||||
|
||||
after_commit :reset_cache
|
||||
|
||||
def to_log_human_identifier
|
||||
def to_cidr
|
||||
"#{ip}/#{ip.prefix}"
|
||||
end
|
||||
alias to_log_human_identifier to_cidr
|
||||
|
||||
class << self
|
||||
def blocked?(remote_ip)
|
||||
|
||||
@@ -9,6 +9,6 @@ class REST::Admin::IpBlockSerializer < ActiveModel::Serializer
|
||||
end
|
||||
|
||||
def ip
|
||||
"#{object.ip}/#{object.ip.prefix}"
|
||||
object.to_cidr
|
||||
end
|
||||
end
|
||||
|
||||
@@ -18,5 +18,5 @@
|
||||
domain: @domain_block.domain
|
||||
|
||||
.actions
|
||||
= link_to t('.cancel'), admin_instances_path, class: 'button button-tertiary'
|
||||
= link_to t('.cancel'), admin_instances_path, class: 'button button-secondary'
|
||||
= f.button :submit, t('.confirm'), class: 'button button--dangerous', name: :confirm
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
= f.check_box :ip_block_ids, { multiple: true, include_hidden: false }, ip_block.id
|
||||
.batch-table__row__content.pending-account
|
||||
.pending-account__header
|
||||
%samp= link_to "#{ip_block.ip}/#{ip_block.ip.prefix}", admin_accounts_path(ip: "#{ip_block.ip}/#{ip_block.ip.prefix}")
|
||||
%samp= link_to ip_block.to_cidr, admin_accounts_path(ip: ip_block.to_cidr)
|
||||
- if ip_block.comment.present?
|
||||
·
|
||||
= ip_block.comment
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
%hr.spacer/
|
||||
|
||||
.actions
|
||||
= link_to t('admin.reports.cancel'), admin_report_path(@report), class: 'button button-tertiary'
|
||||
= link_to t('admin.reports.cancel'), admin_report_path(@report), class: 'button button-secondary'
|
||||
= form.button t('admin.reports.confirm'),
|
||||
name: :confirm,
|
||||
class: 'button',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.announcements-list__item
|
||||
- if can?(:update, role)
|
||||
= link_to edit_admin_role_path(role), class: 'announcements-list__item__title' do
|
||||
%span.user-role
|
||||
%span
|
||||
= material_symbol 'group'
|
||||
|
||||
- if role.everyone?
|
||||
@@ -10,13 +10,12 @@
|
||||
= role.name
|
||||
- else
|
||||
%span.announcements-list__item__title
|
||||
%span.user-role
|
||||
= material_symbol 'group'
|
||||
= material_symbol 'group'
|
||||
|
||||
- if role.everyone?
|
||||
= t('admin.roles.everyone')
|
||||
- else
|
||||
= role.name
|
||||
- if role.everyone?
|
||||
= t('admin.roles.everyone')
|
||||
- else
|
||||
= role.name
|
||||
|
||||
.announcements-list__item__action-bar
|
||||
.announcements-list__item__meta
|
||||
|
||||
@@ -27,4 +27,4 @@
|
||||
.stacked-actions
|
||||
- accept_path = @invite_code.present? ? public_invite_url(invite_code: @invite_code, accept: @accept_token) : new_user_registration_path(accept: @accept_token)
|
||||
= link_to t('auth.rules.accept'), accept_path, class: 'button'
|
||||
= link_to t('auth.rules.back'), root_path, class: 'button button-tertiary'
|
||||
= link_to t('auth.rules.back'), root_path, class: 'button button-secondary'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
- content_for :header_tags do
|
||||
= flavoured_vite_typescript_tag 'public.tsx', crossorigin: 'anonymous'
|
||||
|
||||
- content_for :body_classes, 'modal-layout compose-standalone'
|
||||
- content_for :body_classes, 'modal-layout with-zig-zag-decoration compose-standalone'
|
||||
|
||||
- content_for :content do
|
||||
- if user_signed_in? && !@hide_header
|
||||
|
||||
@@ -11,5 +11,5 @@
|
||||
|
||||
.simple_form
|
||||
.actions
|
||||
= link_to t('generic.cancel'), settings_import_path(@bulk_import), method: :delete, class: 'button button-tertiary'
|
||||
= link_to t('generic.cancel'), settings_import_path(@bulk_import), method: :delete, class: 'button button-secondary'
|
||||
= link_to t('generic.confirm'), confirm_settings_import_path(@bulk_import), method: :post, class: 'button'
|
||||
|
||||
@@ -14,3 +14,9 @@ nan:
|
||||
username: 用者ê名
|
||||
user/invite_request:
|
||||
text: 原因
|
||||
errors:
|
||||
models:
|
||||
terms_of_service:
|
||||
attributes:
|
||||
effective_date:
|
||||
too_soon: 傷緊ah,著khah uànn佇 %{date}
|
||||
|
||||
@@ -477,7 +477,7 @@ da:
|
||||
no_file: Ingen fil valgt
|
||||
export_domain_blocks:
|
||||
import:
|
||||
description_html: En liste over domæneblokeringer er ved at blive importeret. Gennemgå listen meget nøje, især hvis man ikke selv har oprettet den.
|
||||
description_html: Du er ved at importere en liste over domæneblokeringer. Gennemgå denne liste meget omhyggeligt, især hvis du ikke selv har udarbejdet den.
|
||||
existing_relationships_warning: Eksisterende følge-relationer
|
||||
private_comment_description_html: 'For at man lettere kan holde styr på, hvorfra importerede blokeringer kommer, oprettes disse med flg. private kommentar: <q>%{comment}</q>'
|
||||
private_comment_template: Importeret fra %{source} d. %{date}
|
||||
@@ -514,7 +514,7 @@ da:
|
||||
select_capabilities: Vælg kapaciteter
|
||||
sign_in: Log ind
|
||||
status: Status
|
||||
title: Fediverse Auxiliary Service Providers
|
||||
title: Udbydere af Fediverse-hjælpetjenester
|
||||
title: FASP
|
||||
follow_recommendations:
|
||||
description_html: "<strong>Følg-anbefalinger hjælpe nye brugere til hurtigt at finde interessant indhold</strong>. Når en bruger ikke har interageret nok med andre til at generere personlige følg-anbefalinger, anbefales disse konti i stedet. De revurderes dagligt baseret på en blanding af konti med de flest nylige engagementer og fleste lokale følger-antal for et givet sprog."
|
||||
@@ -1186,7 +1186,7 @@ da:
|
||||
new_trending_statuses:
|
||||
title: Indlæg, der trender
|
||||
new_trending_tags:
|
||||
title: Hashtags, der trender
|
||||
title: Populære hashtags
|
||||
subject: Nye tendenser klar til gennemgang på %{instance}
|
||||
aliases:
|
||||
add_new: Opret alias
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user