Merge remote-tracking branch 'parent/main' into upstream-20241216

This commit is contained in:
KMY 2024-12-16 10:14:31 +09:00
commit 3784ad273c
555 changed files with 7564 additions and 3363 deletions

View file

@ -10,12 +10,11 @@ import type {
ApiAccountJSON,
ApiServerFeaturesJSON,
} from 'mastodon/api_types/accounts';
import type { ApiCustomEmojiJSON } from 'mastodon/api_types/custom_emoji';
import emojify from 'mastodon/features/emoji/emoji';
import { unescapeHTML } from 'mastodon/utils/html';
import { CustomEmojiFactory } from './custom_emoji';
import type { CustomEmoji } from './custom_emoji';
import { CustomEmojiFactory, makeEmojiMap } from './custom_emoji';
import type { CustomEmoji, EmojiMap } from './custom_emoji';
// AccountField
interface AccountFieldShape extends Required<ApiAccountFieldJSON> {
@ -136,15 +135,6 @@ export const accountDefaultValues: AccountShape = {
const AccountFactory = ImmutableRecord<AccountShape>(accountDefaultValues);
type EmojiMap = Record<string, ApiCustomEmojiJSON>;
function makeEmojiMap(emojis: ApiCustomEmojiJSON[]) {
return emojis.reduce<EmojiMap>((obj, emoji) => {
obj[`:${emoji.shortcode}:`] = emoji;
return obj;
}, {});
}
function createAccountField(
jsonField: ApiAccountFieldJSON,
emojiMap: EmojiMap,

View file

@ -1,12 +1,12 @@
import type { RecordOf } from 'immutable';
import { Record } from 'immutable';
import type { RecordOf, List as ImmutableList } from 'immutable';
import { Record as ImmutableRecord, isList } from 'immutable';
import type { ApiCustomEmojiJSON } from 'mastodon/api_types/custom_emoji';
type CustomEmojiShape = Required<ApiCustomEmojiJSON>; // no changes from server shape
export type CustomEmoji = RecordOf<CustomEmojiShape>;
export const CustomEmojiFactory = Record<CustomEmojiShape>({
export const CustomEmojiFactory = ImmutableRecord<CustomEmojiShape>({
shortcode: '',
static_url: '',
url: '',
@ -18,3 +18,20 @@ export const CustomEmojiFactory = Record<CustomEmojiShape>({
aliases: [],
license: '',
});
export type EmojiMap = Record<string, ApiCustomEmojiJSON>;
export function makeEmojiMap(
emojis: ApiCustomEmojiJSON[] | ImmutableList<CustomEmoji>,
) {
if (isList(emojis)) {
return emojis.reduce<EmojiMap>((obj, emoji) => {
obj[`:${emoji.shortcode}:`] = emoji.toJS() as ApiCustomEmojiJSON;
return obj;
}, {});
} else
return emojis.reduce<EmojiMap>((obj, emoji) => {
obj[`:${emoji.shortcode}:`] = emoji;
return obj;
}, {});
}

View file

@ -0,0 +1,109 @@
import type { RecordOf } from 'immutable';
import { Record, List } from 'immutable';
import escapeTextContentForBrowser from 'escape-html';
import type { ApiPollJSON, ApiPollOptionJSON } from 'mastodon/api_types/polls';
import emojify from 'mastodon/features/emoji/emoji';
import { CustomEmojiFactory, makeEmojiMap } from './custom_emoji';
import type { CustomEmoji, EmojiMap } from './custom_emoji';
interface PollOptionTranslationShape {
title: string;
titleHtml: string;
}
export type PollOptionTranslation = RecordOf<PollOptionTranslationShape>;
export const PollOptionTranslationFactory = Record<PollOptionTranslationShape>({
title: '',
titleHtml: '',
});
interface PollOptionShape extends Required<ApiPollOptionJSON> {
voted: boolean;
titleHtml: string;
translation: PollOptionTranslation | null;
}
export function createPollOptionTranslationFromServerJSON(
translation: { title: string },
emojiMap: EmojiMap,
) {
return PollOptionTranslationFactory({
...translation,
titleHtml: emojify(
escapeTextContentForBrowser(translation.title),
emojiMap,
),
});
}
export type PollOption = RecordOf<PollOptionShape>;
export const PollOptionFactory = Record<PollOptionShape>({
title: '',
votes_count: 0,
voted: false,
titleHtml: '',
translation: null,
});
interface PollShape
extends Omit<ApiPollJSON, 'emojis' | 'options' | 'own_votes'> {
emojis: List<CustomEmoji>;
options: List<PollOption>;
own_votes?: List<number>;
}
export type Poll = RecordOf<PollShape>;
export const PollFactory = Record<PollShape>({
id: '',
expires_at: '',
expired: false,
multiple: false,
voters_count: 0,
votes_count: 0,
voted: false,
emojis: List<CustomEmoji>(),
options: List<PollOption>(),
own_votes: List(),
});
export function createPollFromServerJSON(
serverJSON: ApiPollJSON,
previousPoll?: Poll,
) {
const emojiMap = makeEmojiMap(serverJSON.emojis);
return PollFactory({
...serverJSON,
emojis: List(serverJSON.emojis.map((emoji) => CustomEmojiFactory(emoji))),
own_votes: serverJSON.own_votes ? List(serverJSON.own_votes) : undefined,
options: List(
serverJSON.options.map((optionJSON, index) => {
const option = PollOptionFactory({
...optionJSON,
voted: serverJSON.own_votes?.includes(index) || false,
titleHtml: emojify(
escapeTextContentForBrowser(optionJSON.title),
emojiMap,
),
});
const prevOption = previousPoll?.options.get(index);
if (prevOption?.translation && prevOption.title === option.title) {
const { translation } = prevOption;
option.set(
'translation',
createPollOptionTranslationFromServerJSON(translation, emojiMap),
);
}
return option;
}),
),
});
}

View file

@ -0,0 +1,21 @@
import type { ApiSearchResultsJSON } from 'mastodon/api_types/search';
import type { ApiHashtagJSON } from 'mastodon/api_types/tags';
export type SearchType = 'account' | 'hashtag' | 'accounts' | 'statuses';
export interface RecentSearch {
q: string;
type?: SearchType;
}
export interface SearchResults {
accounts: string[];
statuses: string[];
hashtags: ApiHashtagJSON[];
}
export const createSearchResults = (serverJSON: ApiSearchResultsJSON) => ({
accounts: serverJSON.accounts.map((account) => account.id),
statuses: serverJSON.statuses.map((status) => status.id),
hashtags: serverJSON.hashtags,
});