Merge branch 'develop' of https://codeberg.org/mkljczk/pl-fe into develop

This commit is contained in:
2026-03-21 20:02:12 +00:00
393 changed files with 2828 additions and 2143 deletions

View File

@@ -3,7 +3,7 @@ import * as v from 'valibot';
import { instanceSchema } from '@/entities/instance';
import { filteredArray } from '@/entities/utils';
import { type Features, getFeatures } from '@/features';
import request, { getNextLink, getPrevLink, type RequestBody } from '@/request';
import request, { getLinks, type RequestBody } from '@/request';
import { PaginatedResponse } from '@/responses';
import type { Instance } from '@/entities/instance';
@@ -69,15 +69,18 @@ class PlApiBaseClient {
) => {
const targetSchema = isArray ? filteredArray(schema) : schema;
const processResponse = (response: PlApiResponse<any>) =>
new PaginatedResponse<T, IsArray>(
const processResponse = (response: PlApiResponse<any>) => {
const { prev: prevLink, next: nextLink } = getLinks(response);
return new PaginatedResponse<T, IsArray>(
v.parse(targetSchema, response.json) as IsArray extends true ? Array<T> : T,
{
previous: getMore(getPrevLink(response)),
next: getMore(getNextLink(response)),
previous: getMore(prevLink),
next: getMore(nextLink),
partial: response.status === 206,
},
);
};
const getMore = (input: string | null) =>
input ? () => this.request(input).then(processResponse) : null;

View File

@@ -327,7 +327,7 @@ const accounts = (client: PlApiBaseClient) => ({
*/
getAccountEndorsements: (accountId: string, params?: GetAccountEndorsementsParams) =>
client.paginatedGet(
`/api/v1/${[PLEROMA].includes(client.features.version.software as string) ? 'pleroma/' : ''}accounts/${accountId}/endorsements`,
`/api/v1/${client.features.version.software === PLEROMA ? 'pleroma/' : ''}accounts/${accountId}/endorsements`,
{ params },
accountSchema,
),

View File

@@ -1525,7 +1525,7 @@ const admin = (client: PlApiBaseClient) => {
const response = await client.request('/api/v1/admin/custom_emojis', {
method: 'POST',
body: params,
contentType: '',
formData: true,
});
return v.parse(adminCustomEmojiSchema, response.json);
@@ -1535,7 +1535,7 @@ const admin = (client: PlApiBaseClient) => {
const response = await client.request(`/api/v1/admin/custom_emojis/${emojiId}`, {
method: 'PATCH',
body: params,
contentType: '',
formData: true,
});
return v.parse(adminCustomEmojiSchema, response.json);

View File

@@ -81,7 +81,7 @@ const drive = (client: PlApiBaseClient) => ({
method: 'POST',
body: { file },
params: { folderId },
contentType: '',
formData: true,
});
return v.parse(driveFileSchema, response.json);

View File

@@ -131,7 +131,7 @@ const events = (client: PlApiBaseClient) => ({
*/
getEventIcs: async (statusId: string) => {
const response = await client.request(`/api/v1/pleroma/events/${statusId}/ics`, {
contentType: '',
formData: true,
});
return response.data;

View File

@@ -16,7 +16,7 @@ const media = (client: PlApiBaseClient) => ({
uploadMedia: async (params: UploadMediaParams, meta?: RequestMeta) => {
const response = await client.request(
client.features.mediaV2 ? '/api/v2/media' : '/api/v1/media',
{ ...meta, method: 'POST', body: params, contentType: '' },
{ ...meta, method: 'POST', body: params, formData: true },
);
return v.parse(mediaAttachmentSchema, response.json);

View File

@@ -11,7 +11,7 @@ import {
} from '@/entities';
import { filteredArray } from '@/entities/utils';
import { GOTOSOCIAL, ICESHRIMP_NET, MITRA, PIXELFED, PLEROMA } from '@/features';
import { getNextLink, getPrevLink } from '@/request';
import { getLinks } from '@/request';
import { PaginatedResponse } from '@/responses';
import type { accounts } from './accounts';
@@ -40,8 +40,7 @@ const paginatedIceshrimpAccountsList = async <T>(
const items = await client.accounts.getAccounts(ids);
const prevLink = getPrevLink(response);
const nextLink = getNextLink(response);
const { prev: prevLink, next: nextLink } = getLinks(response);
return new PaginatedResponse(items, {
previous: prevLink ? () => paginatedIceshrimpAccountsList(client, prevLink, fn) : null,

View File

@@ -19,7 +19,7 @@ const oauth = (client: PlApiBaseClient) => ({
* @see {@link https://docs.joinmastodon.org/methods/oauth/#authorize}
*/
authorize: async (params: OauthAuthorizeParams) => {
const response = await client.request('/oauth/authorize', { params, contentType: '' });
const response = await client.request('/oauth/authorize', { params, formData: true });
return v.parse(v.string(), response.json);
},
@@ -76,7 +76,7 @@ const oauth = (client: PlApiBaseClient) => ({
const response = await client.request('/oauth/token', {
method: 'POST',
body: params,
contentType: '',
formData: true,
});
return v.parse(tokenSchema, { scope: params.scope || '', ...response.json });
@@ -91,7 +91,7 @@ const oauth = (client: PlApiBaseClient) => ({
const response = await client.request<EmptyObject>('/oauth/revoke', {
method: 'POST',
body: params,
contentType: '',
formData: true,
});
client.socket?.close();

View File

@@ -559,7 +559,7 @@ const settings = (client: PlApiBaseClient) => ({
response = await client.request('/api/v1/import', {
method: 'POST',
body: { data: list, type: 'following', mode },
contentType: '',
formData: true,
});
break;
case MITRA:
@@ -572,7 +572,7 @@ const settings = (client: PlApiBaseClient) => ({
response = await client.request('/api/pleroma/follow_import', {
method: 'POST',
body: { list },
contentType: '',
formData: true,
});
}
@@ -611,14 +611,14 @@ const settings = (client: PlApiBaseClient) => ({
response = await client.request('/api/v1/import', {
method: 'POST',
body: { data: list, type: 'blocks', mode },
contentType: '',
formData: true,
});
break;
default:
response = await client.request('/api/pleroma/blocks_import', {
method: 'POST',
body: { list },
contentType: '',
formData: true,
});
}
@@ -640,14 +640,14 @@ const settings = (client: PlApiBaseClient) => ({
response = await client.request('/api/v1/import', {
method: 'POST',
body: { data: list, type: 'blocks', mode },
contentType: '',
formData: true,
});
break;
default:
response = await client.request('/api/pleroma/mutes_import', {
method: 'POST',
body: { list },
contentType: '',
formData: true,
});
}

View File

@@ -110,7 +110,7 @@ const stories = (client: PlApiBaseClient) => ({
const response = await client.request('/api/web/stories/v1/add', {
method: 'POST',
body: { file },
contentType: '',
formData: true,
});
return v.parse(storyMediaSchema, response.json);

View File

@@ -50,12 +50,8 @@ const baseStatusSchema = v.object({
account: v.pipe(
v.unknown(),
v.transform((account) => {
if (
typeof window !== 'undefined' &&
(window as any).__PL_API_FALLBACK_ACCOUNT &&
JSON.stringify(account) === '{}'
)
return (window as any).__PL_API_FALLBACK_ACCOUNT;
const fallbackAccount = (window as any)?.__PL_API_FALLBACK_ACCOUNT;
if (fallbackAccount && Object.keys(account as object).length === 0) return fallbackAccount;
return account;
}),
accountSchema,

View File

@@ -40,7 +40,9 @@ const coerceObject = <T extends v.ObjectEntries>(shape: T) =>
v.optional(
v.pipe(
v.any(),
v.transform((input) => (typeof input === 'object' && input !== null ? input : {})),
v.transform((input) =>
typeof input === 'object' && !Array.isArray(input) && input !== null ? input : {},
),
v.object(shape),
),
{},

View File

@@ -22,6 +22,7 @@ export * from './scheduled-statuses';
export * from './search';
export * from './settings';
export * from './statuses';
export * from './stories';
export * from './streaming';
export * from './timelines';
export * from './trends';

View File

@@ -22,14 +22,17 @@ type Response<T = any> = {
@param {object} response - Fetch API response object
@returns {object} Link object
*/
const getLinks = (response: Pick<Response, 'headers'>): LinkHeader =>
new LinkHeader(response.headers?.get('link') || undefined);
const getLinks = (
response: Pick<Response, 'headers'>,
): { next: string | null; prev: string | null } => {
const headers = response.headers?.get('link');
const linkHeader = (headers && new LinkHeader(headers)) || null;
const getNextLink = (response: Pick<Response, 'headers'>): string | null =>
getLinks(response).refs.find((link) => link.rel.toLocaleLowerCase() === 'next')?.uri || null;
const getPrevLink = (response: Pick<Response, 'headers'>): string | null =>
getLinks(response).refs.find((link) => link.rel.toLocaleLowerCase() === 'prev')?.uri || null;
return {
next: linkHeader?.refs.find((link) => link.rel.toLocaleLowerCase() === 'next')?.uri || null,
prev: linkHeader?.refs.find((link) => link.rel.toLocaleLowerCase() === 'prev')?.uri || null,
};
};
interface AsyncRefreshHeader {
id: string;
@@ -78,6 +81,7 @@ interface RequestBody<Params = Record<string, any>> {
onUploadProgress?: (e: ProgressEvent) => void;
signal?: AbortSignal;
contentType?: string;
formData?: boolean;
idempotencyKey?: string;
}
@@ -96,6 +100,7 @@ function request<T = any>(
onUploadProgress,
signal,
contentType = 'application/json',
formData,
idempotencyKey,
}: RequestBody = {},
) {
@@ -108,10 +113,10 @@ function request<T = any>(
else if (this.accessToken) headers.set('Authorization', `Bearer ${this.accessToken}`);
else if (this.customAuthorizationToken)
headers.set('Authorization', this.customAuthorizationToken);
if (contentType !== '' && body) headers.set('Content-Type', contentType);
if (!formData) headers.set('Content-Type', contentType);
if (idempotencyKey) headers.set('Idempotency-Key', idempotencyKey);
body = body && contentType === '' ? serialize(body, { indices: true }) : JSON.stringify(body);
body = body && formData ? serialize(body, { indices: true }) : JSON.stringify(body);
// Fetch API doesn't report upload progress, use XHR
if (onUploadProgress) {
@@ -196,8 +201,6 @@ export {
type RequestMeta,
type AsyncRefreshHeader,
getLinks,
getNextLink,
getPrevLink,
getAsyncRefreshHeader,
request as default,
};

View File

@@ -56,7 +56,7 @@
"oxlint": "^1.55.0",
"typedoc": "^0.28.17",
"typedoc-material-theme": "^1.4.1",
"typedoc-plugin-valibot": "^1.0.0",
"typedoc-plugin-valibot": "^1.0.2",
"typescript": "^5.9.3",
"vite": "^8.0.0",
"vite-plugin-dts": "^4.5.4",

View File

@@ -7,6 +7,25 @@ const config = {
navigation: {
includeCategories: true,
},
categorizeByGroup: true,
sort: ['kind', 'alphabetical'],
kindSortOrder: ['Function', 'Class', 'Interface', 'TypeAlias', 'Variable', 'Enum'],
intentionallyNotExported: [
'CreateStatusOptionalParams',
'CreateStatusWithContent',
'CreateStatusWithMedia',
'EditStatusOptionalParams',
'GetTrends',
'LanguageParam',
'OnlyEventsParam',
'OnlyMediaParam',
'Params',
'PlApiClientConstructorOpts',
'WithMutedParam',
'WithRelationshipsParam',
],
groupReferencesByType: true,
suppressCommentWarningsInDeclarationFiles: true,
};
export default config;