(feat): improved mobile notifications

(feat): add lint rules
(feat): improve markdown inline code box
This commit is contained in:
Alois 2026-08-05 21:41:55 +02:00
commit 4a841de073
Signed by: alois
SSH key fingerprint: SHA256:GBzT2DXvAuGV9XIV5W3WrzVpjU54FThmxHXdbz95J24
35 changed files with 777 additions and 287 deletions

View file

@ -34,35 +34,25 @@ function getColumnCount(width: number, itemCount: number) {
type KlipyKind = "gif" | "meme";
type KlipyMediaFile = {
url?: string;
width?: number;
height?: number;
};
type KlipyMediaFormats = Record<string, KlipyMediaFile | undefined>;
type KlipyItem = {
id: number | string;
title?: string;
file?: Record<string, KlipyMediaFormats | undefined>;
file?: Record<
string,
| Record<
string,
| {
url?: string;
width?: number;
height?: number;
}
| undefined
>
| undefined
>;
blur_preview?: string;
};
type KlipyPage = {
items: KlipyItem[];
currentPage: number;
hasNext: boolean;
};
type KlipyResponse = {
data?: {
data?: KlipyItem[];
current_page?: number;
has_next?: boolean;
};
};
type PickerMedia = {
key: React.Key;
url: string;
@ -105,7 +95,11 @@ async function fetchKlipyPage({
kind: KlipyKind;
page: number;
search: string;
}): Promise<KlipyPage> {
}): Promise<{
items: KlipyItem[];
currentPage: number;
hasNext: boolean;
}> {
const params = new URLSearchParams({
page: String(page),
per_page: String(pageSize),
@ -128,7 +122,13 @@ async function fetchKlipyPage({
throw new Error(`Klipy request failed with status ${response.status}`);
}
const body = (await response.json()) as KlipyResponse;
const body = (await response.json()) as {
data?: {
data?: KlipyItem[];
current_page?: number;
has_next?: boolean;
};
};
const data = body.data;
return {

View file

@ -66,6 +66,13 @@ export default function InputComponent({
return () => cancelAnimationFrame(frame);
}, [userId]);
useEffect(() => {
if (replyTo === undefined) return;
const frame = requestAnimationFrame(() => composerRef.current?.focus());
return () => cancelAnimationFrame(frame);
}, [replyTo]);
useEffect(() => {
const focusComposerOnType = (event: KeyboardEvent) => {
const composer = composerRef.current;

View file

@ -145,8 +145,6 @@ type SendMessageGet = (
data: { SendTime: number },
) => Promise<{ data: RawMessage }>;
type GetChatSecret = (userId: number) => Promise<Uint8Array | null>;
type StoredDraftState = ChatDraft & {
accountId: number;
userId: number;
@ -204,7 +202,7 @@ export async function fetchReplyMessage({
ownId: number;
chatUserId: number;
send: SendMessageGet;
getChatSecret: GetChatSecret;
getChatSecret: (userId: number) => Promise<Uint8Array | null>;
}) {
const message = await getMessage({
sendTime: replyTo,

View file

@ -21,12 +21,6 @@ import {
} from "./values";
import Wrapper from "@tensamin/user/wrapper";
type MessageChunk = {
key: string;
messages: Array<RawMessage | LiveMessage>;
startIndex: number;
};
function shouldFetchPreviousPage({
entry,
hasNextPage,
@ -108,7 +102,11 @@ function buildMessageChunks(
keyPrefix: string,
startOffset = 0,
) {
const chunks: MessageChunk[] = [];
const chunks: {
key: string;
messages: Array<RawMessage | LiveMessage>;
startIndex: number;
}[] = [];
for (let end = messages.length; end > 0; end -= MESSAGES_PER_VIRTUAL_ROW) {
const start = Math.max(0, end - MESSAGES_PER_VIRTUAL_ROW);
@ -282,7 +280,7 @@ export default function Screen() {
getItemKey,
estimateSize,
overscan: 2,
paddingStart: composerHeight + 8,
paddingStart: composerHeight + 20,
});
const totalSize = virtualizer.getTotalSize();
const contentHeight = Math.max(totalSize, viewportHeight);