- Implemented BlogBodySanitizer to clean HTML content before saving articles, ensuring security against XSS attacks. - Added tests for BlogBodySanitizer to verify that unsafe tags and attributes are stripped from the content. - Introduced ApiLeastPrivilegeTest to ensure that unauthorized users cannot access sensitive API routes, maintaining strict access control.
53 lines
2.5 KiB
TypeScript
53 lines
2.5 KiB
TypeScript
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
|
import { useSearchParams } from 'react-router';
|
|
|
|
/**
|
|
* وضعیت لیست (جستجو، فیلترها، صفحه، تب) در query string زندگی میکند، نه در state
|
|
* کامپوننت. دلیلش «بازگشت» است: `navigate(-1)` همان URL قبلی را برمیگرداند، پس هر
|
|
* چیزی که در URL باشد سرِ جایش برمیگردد و هر چیزی که در state باشد با remount میپرد.
|
|
* سود جانبی: رفرش و اشتراک لینک هم همان نما را میدهد.
|
|
*
|
|
* مقدارِ برابرِ پیشفرض از URL حذف میشود تا آدرس تمیز بماند.
|
|
*/
|
|
export function useUrlState<T extends Record<string, string>>(defaults: T) {
|
|
const [params, setParams] = useSearchParams();
|
|
|
|
const state = useMemo(() => {
|
|
const out = { ...defaults };
|
|
(Object.keys(defaults) as (keyof T)[]).forEach((k) => {
|
|
const raw = params.get(String(k));
|
|
if (raw !== null) out[k] = raw as T[keyof T];
|
|
});
|
|
return out;
|
|
}, [params, defaults]);
|
|
|
|
// `setSearchParams` مثل setState صف نمیشود؛ دو فراخوانی در یک handler، اگر هر دو از
|
|
// `params` فعلی شروع کنند، همدیگر را پاک میکنند. این ref آخرین مقدارِ نوشتهشده را
|
|
// نگه میدارد تا فراخوانیهای پشتسرهم روی هم انباشته شوند.
|
|
const latest = useRef(params);
|
|
useEffect(() => { latest.current = params; }, [params]);
|
|
|
|
/**
|
|
* چند کلید را با هم مینویسد. `replace` پیشفرض است تا تایپ در فیلد جستجو
|
|
* تاریخچه را پر نکند و یک بار «بازگشت» کاربر را از صفحه بیرون ببرد.
|
|
*/
|
|
const setState = useCallback((patch: Partial<T>, opts?: { push?: boolean }) => {
|
|
const next = new URLSearchParams(latest.current);
|
|
Object.entries(patch).forEach(([k, v]) => {
|
|
const value = v === undefined ? '' : String(v);
|
|
if (value === '' || value === defaults[k as keyof T]) next.delete(k);
|
|
else next.set(k, value);
|
|
});
|
|
|
|
latest.current = next;
|
|
setParams(next, { replace: !opts?.push });
|
|
}, [setParams, defaults]);
|
|
|
|
return [state, setState] as const;
|
|
}
|
|
|
|
/** عدد صفحه از URL — همیشه ≥ ۱. */
|
|
export function pageOf(value: string): number {
|
|
return Math.max(1, Number(value) || 1);
|
|
}
|