Compose side fixes (#40000)

This commit is contained in:
Echo
2026-07-30 14:26:42 +02:00
committed by GitHub
parent c47bf15bd9
commit 9830f6d361
8 changed files with 86 additions and 21 deletions

View File

@@ -24,6 +24,7 @@ export interface ApiImageAttachmentJSON extends BaseApiMediaAttachmentJSON {
meta: {
original: ApiImageAttachmentMetaJSON;
small: ApiImageAttachmentMetaJSON;
focus?: ApiFocusAttachmentMetaJSON;
};
}
@@ -42,10 +43,7 @@ export interface ApiVideoAttachmentJSON extends BaseApiMediaAttachmentJSON {
colors: ApiColorsAttachmentMetaJSON;
original: ApiVideoAttachmentMetaJSON;
small: ApiImageAttachmentMetaJSON;
focus: {
x: number;
y: number;
};
focus?: ApiFocusAttachmentMetaJSON;
};
}
@@ -54,6 +52,7 @@ export interface ApiGifvAttachmentJSON extends BaseApiMediaAttachmentJSON {
meta: {
original: ApiVideoAttachmentMetaJSON;
small: ApiImageAttachmentMetaJSON;
focus?: ApiFocusAttachmentMetaJSON;
};
}
@@ -89,3 +88,8 @@ export interface ApiColorsAttachmentMetaJSON {
foreground: string;
accent: string;
}
export interface ApiFocusAttachmentMetaJSON {
x: number;
y: number;
}

View File

@@ -23,12 +23,12 @@ export const textAtCursorMatchesToken = (
word = word.trim();
if (word.length < 3 || (word[0] && !searchTokens.includes(word[0]))) {
return [null, null];
return [null, null] as const;
}
if (word.length > 0) {
return [left + 1, word];
return [left + 1, word] as const;
} else {
return [null, null];
return [null, null] as const;
}
};

View File

@@ -153,7 +153,7 @@ const AutosuggestTextarea = forwardRef(({
}
}, [lang]);
const renderSuggestion = (suggestion, i) => {
const renderSuggestion = useCallback((suggestion, i) => {
let inner, key;
if (suggestion.type === 'emoji') {
@@ -172,7 +172,7 @@ const AutosuggestTextarea = forwardRef(({
{inner}
</div>
);
};
}, [selectedSuggestion, handleSuggestionClick]);
const handleRef = useCallback((element) => {
textareaRef.current = element;

View File

@@ -95,13 +95,18 @@ export interface PopoverProps {
* enable it to be sized and positioned. The `ref` prop
* is not passed when the `popoverElement` prop is provided.
*/
props: Record<string, unknown> & {
ref?: React.RefCallback<HTMLElement>;
style: React.CSSProperties;
};
props: PopoverChildProps;
}) => React.ReactNode;
}
export interface PopoverChildProps {
ref?: React.RefCallback<HTMLElement>;
style: React.CSSProperties;
'data-popover-placement': Placement;
'data-popover-reference-hidden'?: boolean;
'data-popover-escaped'?: boolean;
}
export const Popover: React.FC<PopoverProps> = ({
isOpen,
onClose,

View File

@@ -56,13 +56,15 @@ const getFrequentlyUsedLanguages = createSelector(
const isTextLongEnoughForGuess = (text: string) => text.length > 20;
const LanguageDropdownMenu: React.FC<{
const emptyArray: Language[] = [];
export const LanguageDropdownMenu: React.FC<{
value: string;
guess?: string;
guess: string;
onClose: () => void;
onChange: (arg0: string) => void;
}> = ({ value, guess, onClose, onChange }) => {
const languages = preloadedLanguages as Language[];
const languages = preloadedLanguages ?? emptyArray;
const intl = useIntl();
const [searchValue, setSearchValue] = useState('');
const nodeRef = useRef<HTMLDivElement>(null);
@@ -317,7 +319,7 @@ export const LanguageDropdown: React.FC = () => {
const text = useAppSelector((state) => state.compose.get('text') as string);
const current =
(preloadedLanguages as Language[]).find((lang) => lang[0] === value) ?? [];
(preloadedLanguages ?? []).find((lang) => lang[0] === value) ?? [];
const handleMouseDown = useCallback(() => {
if (!open && document.activeElement instanceof HTMLElement) {

View File

@@ -1,9 +1,9 @@
import { Map as ImmutableMap } from 'immutable';
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
import { STORE_HYDRATE } from '../actions/store';
const initialState = ImmutableMap({
accept_content_types: [],
accept_content_types: ImmutableList(),
});
export default function meta(state = initialState, action) {

View File

@@ -30,7 +30,7 @@ export const selectPlainStatus = createAppSelector(
export const selectAccountStatus = createAppSelector(
[
selectPlainStatus,
(state, statusId: string) => {
(state, statusId?: string | null) => {
const accountId = state.statuses.getIn([statusId, 'account']);
if (typeof accountId !== 'string') {
return null;

View File

@@ -2,9 +2,16 @@ import type { CompactEmoji } from 'emojibase';
import { http, HttpResponse } from 'msw';
import { action } from 'storybook/actions';
import type { MediaAttachmentType } from '@/mastodon/api_types/media_attachments';
import { toSupportedLocale } from '@/mastodon/features/emoji/locale';
import { customEmojiFactory, relationshipsFactoryAPI } from './factories';
import {
customEmojiFactory,
mediaAttachmentFactoryAPI,
relationshipsFactoryAPI,
} from './factories';
const mediaStorageMap = new Map<string, File>();
export const mockHandlers = {
mute: http.post<{ id: string }>('/api/v1/accounts/:id/mute', ({ params }) => {
@@ -43,6 +50,53 @@ export const mockHandlers = {
);
},
),
mediaUpload: http.post('/api/v2/media', async ({ request }) => {
action('uploaded media')();
const formData = await request.formData();
const file = formData.get('file');
if (!file) {
return new HttpResponse('Missing media', { status: 400 });
}
if (!(file instanceof File)) {
return new HttpResponse('Media is not file', { status: 400 });
}
const { type: mimeType } = file;
const id = mediaStorageMap.size.toString();
mediaStorageMap.set(id, file);
let type: MediaAttachmentType = 'unknown';
if (mimeType === 'image/gif') {
type = 'gifv';
} else if (mimeType.startsWith('image/')) {
type = 'image';
} else if (mimeType.startsWith('video/')) {
type = 'video';
} else if (mimeType.startsWith('audio/')) {
type = 'audio';
}
return HttpResponse.json(
mediaAttachmentFactoryAPI({
id,
type,
url: `/mock_media/${id}`,
preview_url: `/mock_media/${id}`,
}),
);
}),
mediaGet: http.get<{ id: string }>('/mock_media/:id', async ({ params }) => {
const { id } = params;
action(`getting media id ${id}`)();
const media = mediaStorageMap.get(id);
if (!media) {
return new HttpResponse('Not found', { status: 404 });
}
return HttpResponse.arrayBuffer(await media.arrayBuffer());
}),
emojiCustomData: http.get('/api/v1/custom_emojis', () => {
action('fetching custom emoji data')();
return HttpResponse.json([customEmojiFactory()]);