Merge commit '37ccffa95a30772b55e3f18d486d699ee6c5f9e8' into glitch-soc/merge-upstream

This commit is contained in:
Claire
2025-11-30 17:47:27 +01:00
34 changed files with 381 additions and 328 deletions

View File

@@ -1,9 +1,5 @@
import Rails from '@rails/ujs';
import { setupLinkListeners } from './utils/links';
export function start() {
try {
Rails.start();
} catch {
// If called twice
}
setupLinkListeners();
}

View File

@@ -53,7 +53,7 @@ const renderHashtags = (hashtags: HashtagType[]) =>
const renderStatuses = (statusIds: string[]) =>
hidePeek<string>(statusIds).map((id) => (
<StatusQuoteManager key={id} id={id} />
<StatusQuoteManager contextType='search' key={id} id={id} />
));
type SearchType = 'all' | ApiSearchType;
@@ -189,7 +189,7 @@ export const SearchResults: React.FC<{ multiColumn: boolean }> = ({
onClickMore={handleSelectStatuses}
>
{results.statuses.slice(0, INITIAL_DISPLAY).map((id) => (
<StatusQuoteManager key={id} id={id} />
<StatusQuoteManager contextType='search' key={id} id={id} />
))}
</SearchSection>
)}

View File

@@ -1,11 +1,11 @@
import punycode from 'node:punycode';
import { useCallback, useId, useState } from 'react';
import { FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import punycode from 'punycode/';
import DescriptionIcon from '@/material-icons/400-24px/description-fill.svg?react';
import OpenInNewIcon from '@/material-icons/400-24px/open_in_new.svg?react';
import PlayArrowIcon from '@/material-icons/400-24px/play_arrow-fill.svg?react';

View File

@@ -1,5 +1,3 @@
import { initialState } from '@/mastodon/initial_state';
interface FocusColumnOptions {
index?: number;
focusItem?: 'first' | 'first-visible';
@@ -14,7 +12,10 @@ export function focusColumn({
focusItem = 'first',
}: FocusColumnOptions = {}) {
// Skip the leftmost drawer in multi-column mode
const indexOffset = initialState?.meta.advanced_layout ? 1 : 0;
const isMultiColumnLayout = !!document.querySelector(
'body.layout-multiple-columns',
);
const indexOffset = isMultiColumnLayout ? 1 : 0;
const column = document.querySelector(
`.column:nth-child(${index + indexOffset})`,

View File

@@ -28,6 +28,7 @@
"account.disable_notifications": "@{name}さんの投稿時の通知を停止",
"account.domain_blocking": "ブロックしているドメイン",
"account.edit_profile": "プロフィール編集",
"account.edit_profile_short": "編集",
"account.enable_notifications": "@{name}さんの投稿時に通知",
"account.endorse": "プロフィールで紹介する",
"account.familiar_followers_many": "{name1}、{name2}、他{othersCount, plural, one {one other you know} other {# others you know}}人のユーザーにフォローされています",
@@ -172,6 +173,8 @@
"column.edit_list": "リストを編集",
"column.favourites": "お気に入り",
"column.firehose": "リアルタイムフィード",
"column.firehose_local": "このサーバーのリアルタイムフィード",
"column.firehose_singular": "リアルタイムフィード",
"column.follow_requests": "フォローリクエスト",
"column.home": "ホーム",
"column.list_members": "リストのメンバーを管理",
@@ -251,6 +254,7 @@
"confirmations.remove_from_followers.message": "{name}さんはあなたをフォローしなくなります。本当によろしいですか?",
"confirmations.remove_from_followers.title": "フォロワーを削除しますか?",
"confirmations.revoke_quote.confirm": "投稿を削除",
"confirmations.revoke_quote.message": "この操作は元に戻せません。",
"confirmations.revoke_quote.title": "投稿を削除しますか?",
"confirmations.unblock.confirm": "ブロック解除",
"confirmations.unblock.title": "@{name}さんのブロックを解除しますか?",
@@ -477,6 +481,7 @@
"keyboard_shortcuts.home": "ホームタイムラインを開く",
"keyboard_shortcuts.hotkey": "ホットキー",
"keyboard_shortcuts.legend": "この一覧を表示",
"keyboard_shortcuts.load_more": "「もっと見る」ボタンに移動",
"keyboard_shortcuts.local": "ローカルタイムラインを開く",
"keyboard_shortcuts.mention": "メンション",
"keyboard_shortcuts.muted": "ミュートしたユーザーのリストを開く",
@@ -497,6 +502,7 @@
"keyboard_shortcuts.translate": "投稿を翻訳する",
"keyboard_shortcuts.unfocus": "投稿の入力欄・検索欄から離れる",
"keyboard_shortcuts.up": "カラム内一つ上に移動",
"learn_more_link.got_it": "了解",
"learn_more_link.learn_more": "もっと見る",
"lightbox.close": "閉じる",
"lightbox.next": "次",
@@ -611,6 +617,7 @@
"notification.moderation_warning.action_suspend": "あなたのアカウントは停止されました。",
"notification.own_poll": "アンケートが終了しました",
"notification.poll": "投票したアンケートが終了しました",
"notification.quoted_update": "あなたが引用した投稿を {name} が編集しました",
"notification.reblog": "{name}さんがあなたの投稿をブーストしました",
"notification.reblog.name_and_others_with_link": "{name}さんと<a>ほか{count, plural, other {#人}}</a>がブーストしました",
"notification.relationships_severance_event": "{name} との関係が失われました",
@@ -753,6 +760,7 @@
"relative_time.minutes": "{number}分前",
"relative_time.seconds": "{number}秒前",
"relative_time.today": "今日",
"remove_quote_hint.button_label": "了解",
"reply_indicator.attachments": "{count, plural, other {#件のメディア}}",
"reply_indicator.cancel": "キャンセル",
"reply_indicator.poll": "アンケート",

View File

@@ -14,7 +14,7 @@ const getStatusInputSelectors = [
(state, { id }) => state.getIn(['accounts', state.getIn(['statuses', id, 'account'])]),
(state, { id }) => state.getIn(['accounts', state.getIn(['statuses', state.getIn(['statuses', id, 'reblog']), 'account'])]),
getFilters,
(_, { contextType }) => ['detailed', 'bookmarks', 'favourites'].includes(contextType),
(_, { contextType }) => ['detailed', 'bookmarks', 'favourites', 'search'].includes(contextType),
];
function getStatusResultFunction(

View File

@@ -0,0 +1,88 @@
import { on } from 'delegated-events';
export function setupLinkListeners() {
on('click', 'a[data-confirm]', handleConfirmLink);
// We don't want to target links with data-confirm here, as those are handled already.
on('click', 'a[data-method]:not([data-confirm])', handleMethodLink);
}
function handleConfirmLink(event: MouseEvent) {
const anchor = event.currentTarget;
if (!(anchor instanceof HTMLAnchorElement)) {
return;
}
const message = anchor.dataset.confirm;
if (!message || !window.confirm(message)) {
event.preventDefault();
return;
}
if (anchor.dataset.method) {
handleMethodLink(event);
}
}
function handleMethodLink(event: MouseEvent) {
const anchor = event.currentTarget;
if (!(anchor instanceof HTMLAnchorElement)) {
return;
}
const method = anchor.dataset.method?.toLowerCase();
if (!method) {
return;
}
event.preventDefault();
// Create and submit a form with the specified method.
const form = document.createElement('form');
form.method = 'post';
form.action = anchor.href;
// Add the hidden _method input to simulate other HTTP methods.
const methodInput = document.createElement('input');
methodInput.type = 'hidden';
methodInput.name = '_method';
methodInput.value = method;
form.appendChild(methodInput);
// Add CSRF token if available for same-origin requests.
const csrf = getCSRFToken();
if (csrf && !isCrossDomain(anchor.href)) {
const csrfInput = document.createElement('input');
csrfInput.type = 'hidden';
csrfInput.name = csrf.param;
csrfInput.value = csrf.token;
form.appendChild(csrfInput);
}
// The form needs to be in the document to be submitted.
form.style.display = 'none';
document.body.appendChild(form);
// We use requestSubmit to ensure any form submit handlers are properly invoked.
form.requestSubmit();
}
function getCSRFToken() {
const param = document.querySelector<HTMLMetaElement>(
'meta[name="csrf-param"]',
);
const token = document.querySelector<HTMLMetaElement>(
'meta[name="csrf-token"]',
);
if (param && token) {
return { param: param.content, token: token.content };
}
return null;
}
function isCrossDomain(href: string) {
const link = document.createElement('a');
link.href = href;
return (
link.protocol !== window.location.protocol ||
link.host !== window.location.host
);
}

View File

@@ -6204,12 +6204,14 @@ a.status-card {
inset-inline-start: 0;
inset-inline-end: 0;
bottom: 0;
align-items: center;
justify-content: space-around; // If set to center, the fullscreen image overlay is misaligned.
> div {
flex-shrink: 0;
overflow: auto;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
}
}
@@ -7373,6 +7375,13 @@ a.status-card {
grid-template-rows: 1fr 1fr;
gap: 2px;
&--layout-1 {
// The size of single images is determined by their
// aspect-ratio, applied via inline style attribute
width: initial;
max-height: 460px;
}
&--layout-2 {
& > .media-gallery__item:nth-child(1) {
border-end-end-radius: 0;
@@ -7698,14 +7707,11 @@ a.status-card {
overflow: hidden;
position: relative;
background: $base-shadow-color;
max-width: 100%;
max-height: max(400px, 60vh);
margin-inline: auto;
max-height: 460px;
border-radius: 8px;
box-sizing: border-box;
color: $white;
display: flex;
align-items: center;
outline: 1px solid var(--media-outline-color);
outline-offset: -1px;
z-index: 2;

View File

@@ -113,8 +113,8 @@
}
.current {
color: var(--color-bg-inverted);
background: var(--color-text-on-inverted);
color: var(--color-bg-primary);
background: var(--color-text-primary);
border-radius: 100px;
cursor: default;
margin: 0 10px;

View File

@@ -6085,12 +6085,14 @@ a.status-card {
inset-inline-start: 0;
inset-inline-end: 0;
bottom: 0;
align-items: center;
justify-content: space-around; // If set to center, the fullscreen image overlay is misaligned.
> div {
flex-shrink: 0;
overflow: auto;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
}
}
@@ -7159,6 +7161,13 @@ a.status-card {
grid-template-rows: 1fr 1fr;
gap: 2px;
&--layout-1 {
// The size of single images is determined by their
// aspect-ratio, applied via inline style attribute
width: initial;
max-height: 460px;
}
&--layout-2 {
& > .media-gallery__item:nth-child(1) {
border-end-end-radius: 0;
@@ -7491,13 +7500,10 @@ a.status-card {
position: relative;
color: var(--color-text-on-media);
background: var(--color-bg-media);
max-width: 100%;
max-height: max(400px, 60vh);
margin-inline: auto;
max-height: 460px;
border-radius: 8px;
box-sizing: border-box;
display: flex;
align-items: center;
outline: 1px solid var(--color-border-media);
outline-offset: -1px;
z-index: 2;