Harmonise la liste des notes et l’éditeur (titres, dates, retour), rend la saisie rapide et les raccourcis de l’accueil utilisables sur téléphone, et conserve le panneau latéral repliable.
114 lines
3.0 KiB
TypeScript
114 lines
3.0 KiB
TypeScript
import { format, type Locale } from 'date-fns'
|
||
import { toJalaali } from 'jalaali-js'
|
||
|
||
const JALALI_MONTHS_FA = [
|
||
'فروردین',
|
||
'اردیبهشت',
|
||
'خرداد',
|
||
'تیر',
|
||
'مرداد',
|
||
'شهریور',
|
||
'مهر',
|
||
'آبان',
|
||
'آذر',
|
||
'دی',
|
||
'بهمن',
|
||
'اسفند',
|
||
] as const
|
||
|
||
const PERSIAN_DIGITS = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'] as const
|
||
|
||
/** Western digits → Persian (Extended Arabic-Indic) numerals, e.g. 1405 → ۱۴۰۵ */
|
||
export function toPersianDigits(input: string): string {
|
||
return input.replace(/\d/g, (ch) => PERSIAN_DIGITS[Number(ch)] ?? ch)
|
||
}
|
||
|
||
function pad2(n: number): string {
|
||
return n < 10 ? `0${n}` : `${n}`
|
||
}
|
||
|
||
function formatJalaliAbsolute(date: Date, pattern: string): string {
|
||
const { jy, jm, jd } = toJalaali(date)
|
||
const monthName = JALALI_MONTHS_FA[jm - 1]
|
||
const h = pad2(date.getHours())
|
||
const min = pad2(date.getMinutes())
|
||
|
||
let s: string
|
||
switch (pattern) {
|
||
case 'd MMM yyyy':
|
||
s = `${jd} ${monthName} ${jy}`
|
||
break
|
||
case 'd MMM yyyy · HH:mm':
|
||
s = `${jd} ${monthName} ${jy} · ${h}:${min}`
|
||
break
|
||
case 'd MMM yyyy HH:mm':
|
||
s = `${jd} ${monthName} ${jy} ${h}:${min}`
|
||
break
|
||
case 'd MMM · HH:mm':
|
||
s = `${jd} ${monthName} · ${h}:${min}`
|
||
break
|
||
case 'MMM d, yyyy':
|
||
s = `${monthName} ${jd}، ${jy}`
|
||
break
|
||
default:
|
||
s = `${jd} ${monthName} ${jy}`
|
||
}
|
||
return toPersianDigits(s)
|
||
}
|
||
|
||
/**
|
||
* Absolute calendar dates for Persian (`fa`) use the Solar Hijri (Jalali / هجری شمسی) calendar.
|
||
* Times remain in the user's local timezone. For relative phrases, keep using `formatDistanceToNow` with `faIR`.
|
||
*/
|
||
export function formatAbsoluteDateLocalized(
|
||
date: Date | string,
|
||
language: string,
|
||
pattern: string,
|
||
locale: Locale
|
||
): string {
|
||
const d = typeof date === 'string' ? new Date(date) : date
|
||
if (language === 'fa') {
|
||
return formatJalaliAbsolute(d, pattern)
|
||
}
|
||
return format(d, pattern, { locale })
|
||
}
|
||
|
||
const INTL_LOCALE: Record<string, string> = {
|
||
zh: 'zh-CN',
|
||
fa: 'fa-IR',
|
||
}
|
||
|
||
/**
|
||
* Calendar day for note lists and the editor, in the user's locale.
|
||
* Uses the local timezone (same as `formatAbsoluteDateLocalized`) so a late-evening
|
||
* note does not jump to the previous UTC day. Persian uses Jalali + Persian digits.
|
||
*/
|
||
export function formatNoteCalendarDate(
|
||
date: Date | string,
|
||
language: string,
|
||
): string {
|
||
const d = typeof date === 'string' ? new Date(date) : date
|
||
if (Number.isNaN(d.getTime())) return ''
|
||
if (language === 'fa') {
|
||
return formatJalaliAbsolute(d, 'd MMM yyyy')
|
||
}
|
||
const locale = INTL_LOCALE[language] || language
|
||
try {
|
||
return new Intl.DateTimeFormat(locale, {
|
||
day: 'numeric',
|
||
month: 'long',
|
||
year: 'numeric',
|
||
}).format(d)
|
||
} catch {
|
||
return new Intl.DateTimeFormat('en', {
|
||
day: 'numeric',
|
||
month: 'long',
|
||
year: 'numeric',
|
||
}).format(d)
|
||
}
|
||
}
|
||
|
||
export function dateTextDirection(language: string): 'rtl' | 'ltr' {
|
||
return language === 'fa' || language === 'ar' ? 'rtl' : 'ltr'
|
||
}
|