feat: localization (FR/ES/IT), portal date picker, font matching, quote cleanup

- Add French, Spanish, and Italian translations for full UI localization
- Add Italian to language selector; show native language names
- Fix Jump to Date calendar: use React portal with fixed positioning
  to prevent clipping by overflow:hidden ancestors
- Fix font matching: time grid tasks inherit from task font settings
  when at default values (0.75rem/500), matching someday list fonts
- CSS fallback chain: time-slot-task vars fall through to task vars
- Remove broken recite.vercel.app default quote URL
- Add date-fns locales (fr, es, it) for SimpleDatePicker

v1.21.0

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
mARTin 2026-03-08 20:54:13 +01:00
parent 4c18d37bec
commit 9ac7198b24
4 changed files with 399 additions and 50 deletions

View File

@ -1,6 +1,6 @@
{
"name": "my-weekly-todo-list",
"version": "1.20.0",
"version": "1.21.0",
"description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view",
"main": "index.js",
"scripts": {

View File

@ -2051,9 +2051,9 @@ h3 {
gap: 0.25rem;
padding: 2px 0;
background: transparent;
font-family: var(--weekly-time-task-font, var(--weekly-font));
font-size: var(--weekly-time-task-size, 0.75rem);
font-weight: var(--weekly-time-task-weight, 500);
font-family: var(--weekly-time-task-font, var(--weekly-task-font, var(--weekly-font)));
font-size: var(--weekly-time-task-size, var(--weekly-task-size, 0.9rem));
font-weight: var(--weekly-time-task-weight, var(--weekly-task-weight, 400));
cursor: pointer;
white-space: normal;
word-break: break-word;

View File

@ -1,28 +1,54 @@
import React, { useState, useEffect, useRef } from 'react';
import React, { useState, useEffect, useRef, useCallback } from 'react';
import { createPortal } from 'react-dom';
import { format, addMonths, subMonths, startOfMonth, endOfMonth, startOfWeek, endOfWeek, addDays, isSameMonth, isSameDay } from 'date-fns';
import { enUS, de } from 'date-fns/locale';
import { enUS, de, fr, es, it } from 'date-fns/locale';
interface SimpleDatePickerProps {
selected: Date;
onSelect: (date: Date) => void;
onClose: () => void;
language?: string;
anchorRef?: React.RefObject<HTMLElement | null>;
}
export default function SimpleDatePicker({ selected, onSelect, onClose, language = 'en' }: SimpleDatePickerProps) {
export default function SimpleDatePicker({ selected, onSelect, onClose, language = 'en', anchorRef }: SimpleDatePickerProps) {
const [currentMonth, setCurrentMonth] = useState(new Date(selected));
const modalRef = useRef<HTMLDivElement>(null);
const locale = language === 'de' ? de : enUS;
const localeMap: Record<string, typeof enUS> = { de, fr, es, it };
const locale = localeMap[language] || enUS;
const [position, setPosition] = useState<{ top: number; left: number } | null>(null);
const updatePosition = useCallback(() => {
if (anchorRef?.current) {
const rect = anchorRef.current.getBoundingClientRect();
setPosition({
top: rect.bottom + 8,
left: rect.left + rect.width / 2 - 144, // 144 = half of 18rem (288px)
});
}
}, [anchorRef]);
useEffect(() => {
updatePosition();
window.addEventListener('resize', updatePosition);
window.addEventListener('scroll', updatePosition, true);
return () => {
window.removeEventListener('resize', updatePosition);
window.removeEventListener('scroll', updatePosition, true);
};
}, [updatePosition]);
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (modalRef.current && !modalRef.current.contains(event.target as Node)) {
// Also check if click is on the anchor/trigger button
if (anchorRef?.current && anchorRef.current.contains(event.target as Node)) return;
onClose();
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, [onClose]);
}, [onClose, anchorRef]);
const renderHeader = () => (
<div className="datepicker-header" style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '1rem', padding: '0 0.5rem' }}>
@ -116,44 +142,40 @@ export default function SimpleDatePicker({ selected, onSelect, onClose, language
);
};
return (
// Use fixed positioning via portal if we have an anchor, otherwise fallback to absolute
const usePortal = !!anchorRef?.current && !!position;
const pickerContent = (
<div ref={modalRef} style={{
position: 'absolute',
top: '100%',
left: '50%',
transform: 'translateX(-50%)',
marginTop: '0.5rem',
position: usePortal ? 'fixed' : 'absolute',
top: usePortal ? position!.top : '100%',
left: usePortal ? position!.left : '50%',
transform: usePortal ? 'none' : 'translateX(-50%)',
marginTop: usePortal ? 0 : '0.5rem',
backgroundColor: 'var(--weekly-bg, white)',
borderRadius: '0.5rem',
boxShadow: '0 20px 25px -5px rgba(0, 0, 0, 0.15), 0 10px 10px -5px rgba(0, 0, 0, 0.08)',
padding: '1rem',
zIndex: 2000,
zIndex: 9999,
width: '18rem',
border: '1px solid var(--weekly-border, #e5e7eb)',
animation: 'fadeIn 0.15s ease-out'
animation: 'simpleDatePickerFadeIn 0.15s ease-out'
}}>
{/* Decorative triangle */}
<div style={{
position: 'absolute',
top: '-0.3rem',
left: '50%',
marginLeft: '-0.375rem',
width: '0.75rem',
height: '0.75rem',
backgroundColor: 'var(--weekly-bg, white)',
transform: 'rotate(45deg)',
borderTop: '1px solid var(--weekly-border, #e5e7eb)',
borderLeft: '1px solid var(--weekly-border, #e5e7eb)'
}}></div>
{renderHeader()}
{renderDays()}
{renderCells()}
<style jsx>{`
@keyframes fadeIn {
<style>{`
@keyframes simpleDatePickerFadeIn {
from { opacity: 0; transform: translateY(-5px); }
to { opacity: 1; transform: translateY(0); }
}
`}</style>
</div>
);
if (usePortal) {
return createPortal(pickerContent, document.body);
}
return pickerContent;
}

View File

@ -426,6 +426,315 @@ const translations: Record<string, any> = {
weekdayCaseCapitalize: "Großbuchstabe (Montag)",
weekdayCaseUppercase: "Großbuchstaben (MONTAG)",
},
fr: {
settings: "Paramètres",
general: "Général",
calendar: "Connexions",
localisation: "Localisation",
account: "Compte",
runningList: "Liste continue (reporter les tâches à aujourd'hui)",
protectEventTimes: "Protéger les horaires des événements",
showTimeGrid: "Afficher la grille horaire",
timeSlotDuration: "Durée des créneaux horaires",
viewStyle: "Style d'affichage",
simpleView: "Simple",
calendarView: "Calendrier",
listView: "Liste",
language: "Langue",
dateFormat: "Format de date",
timeFormat: "Format d'heure",
saveChanges: "Enregistrer",
connectedCalendars: "Calendriers connectés",
connectMore: "En connecter d'autres",
connectGoogle: "Connecter Google Agenda",
connectApple: "Connecter le calendrier Apple",
connectSynology: "Connecter Synology",
noCalendars: "Aucun calendrier connecté.",
dataPrivacy: "Données et confidentialité",
downloadData: "Télécharger mes données",
deleteAccount: "Supprimer le compte",
name: "Nom",
email: "E-mail",
timezone: "Fuseau horaire",
changePassword: "Changer le mot de passe",
newPassword: "Nouveau mot de passe",
confirmPassword: "Confirmer le mot de passe",
someday: "UN JOUR",
lists: "Listes",
loading: "Chargement de vos tâches…",
sycing: "Synchronisation…",
synced: "Synchronisé",
localization: "Localisation",
allDayEvents: "ÉVÉNEMENTS JOURNÉE ENTIÈRE",
syncCalendar: "Synchroniser le calendrier",
toggleDarkMode: "Basculer le mode sombre",
signOut: "Se déconnecter",
startHour: "Début de journée",
endHour: "Fin de journée",
weekAbbr: "S",
goalOfWeek: "Objectif de la semaine",
goalScope: "Portée de l'objectif",
goalScopeWeek: "Par semaine",
goalScopeDay: "Par jour",
goalFallback: "Type d'objectif par défaut",
defaultGoal: "Objectif par défaut personnalisé",
showTaskCheckboxes: "Afficher les cases à cocher",
showSomeday: "Afficher la section Un jour",
showAllDay: "Afficher la section Journée entière",
allDayPosition: "Position des événements journée entière",
allDayAbove: "Au-dessus",
allDayBelow: "En dessous",
newPasswordDesc: "Laisser vide pour conserver le mot de passe actuel.",
dateAlignment: "Alignement de la date",
dateVerticalAlign: "Alignement vertical de la date",
alignTop: "Haut",
alignMiddle: "Milieu",
alignBottom: "Bas",
dateLayout: "Disposition de la date",
alignmentLeft: "Gauche",
alignmentCenter: "Centre",
alignmentRight: "Droite",
alignmentTight: "Compact",
backupRestore: "Sauvegarde et restauration",
backupRestoreDesc: "Exportez toutes vos tâches, listes et projets au format JSON. Vous pouvez modifier le fichier et le réimporter.",
exportAllData: "Exporter toutes les données (JSON)",
importData: "Importer des données",
importMode: "Mode d'importation",
importModeMerge: "Fusionner",
importModeMergeDesc: "Ajouter les données importées aux tâches existantes",
importModeReplace: "Remplacer",
importModeReplaceDesc: "Supprimer toutes les données existantes et les remplacer par les données importées",
importReplaceWarning: "Attention : toutes vos tâches, listes et projets actuels seront définitivement supprimés !",
importSelectFile: "Sélectionner un fichier JSON…",
importButton: "Importer",
importing: "Importation…",
exporting: "Exportation…",
projects: "Projets",
projectsDesc: "Organisez vos tâches avec des projets colorés",
addProject: "Ajouter un projet",
projectName: "Nom",
projectColor: "Couleur",
noProjects: "Aucun projet",
assignProject: "Attribuer un projet",
removeProject: "Retirer le projet",
weekdayFormat: "Format des jours",
weekdayFormatFull: "Nom complet (lundi)",
weekdayFormatShort: "Abrégé (lun.)",
weekdayFormatNarrow: "Étroit (L)",
weekdayFormatCustom: "Personnalisé",
customWeekdayNamesMon: "Lu; Ma; Me; Je; Ve; Sa; Di",
customWeekdayNamesSun: "Di; Lu; Ma; Me; Je; Ve; Sa",
weekdayCase: "Casse des jours",
weekdayCaseNormal: "Normal (lundi)",
weekdayCaseCapitalize: "Majuscule (Lundi)",
weekdayCaseUppercase: "Majuscules (LUNDI)",
},
es: {
settings: "Ajustes",
general: "General",
calendar: "Conexiones",
localisation: "Localización",
account: "Cuenta",
runningList: "Lista continua (pasar tareas a hoy)",
protectEventTimes: "Proteger horarios de eventos",
showTimeGrid: "Mostrar cuadrícula horaria",
timeSlotDuration: "Duración de los intervalos",
viewStyle: "Estilo de vista",
simpleView: "Simple",
calendarView: "Calendario",
listView: "Lista",
language: "Idioma",
dateFormat: "Formato de fecha",
timeFormat: "Formato de hora",
saveChanges: "Guardar cambios",
connectedCalendars: "Calendarios conectados",
connectMore: "Conectar más",
connectGoogle: "Conectar Google Calendar",
connectApple: "Conectar calendario de Apple",
connectSynology: "Conectar Synology",
noCalendars: "No hay calendarios conectados.",
dataPrivacy: "Datos y privacidad",
downloadData: "Descargar mis datos",
deleteAccount: "Eliminar cuenta",
name: "Nombre",
email: "Correo electrónico",
timezone: "Zona horaria",
changePassword: "Cambiar contraseña",
newPassword: "Nueva contraseña",
confirmPassword: "Confirmar contraseña",
someday: "ALGÚN DÍA",
lists: "Listas",
loading: "Cargando tus tareas…",
sycing: "Sincronizando…",
synced: "Sincronizado",
localization: "Localización",
allDayEvents: "EVENTOS DE TODO EL DÍA",
syncCalendar: "Sincronizar calendario",
toggleDarkMode: "Alternar modo oscuro",
signOut: "Cerrar sesión",
startHour: "Inicio del día",
endHour: "Fin del día",
weekAbbr: "S",
goalOfWeek: "Objetivo de la semana",
goalScope: "Alcance del objetivo",
goalScopeWeek: "Por semana",
goalScopeDay: "Por día",
goalFallback: "Tipo de objetivo por defecto",
defaultGoal: "Objetivo predeterminado personalizado",
showTaskCheckboxes: "Mostrar casillas en las tareas",
showSomeday: "Mostrar sección Algún día",
showAllDay: "Mostrar sección Todo el día",
allDayPosition: "Posición de eventos de todo el día",
allDayAbove: "Arriba",
allDayBelow: "Abajo",
newPasswordDesc: "Dejar en blanco para conservar la contraseña actual.",
dateAlignment: "Alineación de la fecha",
dateVerticalAlign: "Alineación vertical de la fecha",
alignTop: "Arriba",
alignMiddle: "Centro",
alignBottom: "Abajo",
dateLayout: "Disposición de la fecha",
alignmentLeft: "Izquierda",
alignmentCenter: "Centro",
alignmentRight: "Derecha",
alignmentTight: "Compacto",
backupRestore: "Copia de seguridad y restauración",
backupRestoreDesc: "Exporta todas tus tareas, listas y proyectos como archivo JSON. Puedes editar el archivo y volver a importarlo.",
exportAllData: "Exportar todos los datos (JSON)",
importData: "Importar datos",
importMode: "Modo de importación",
importModeMerge: "Combinar",
importModeMergeDesc: "Añadir los datos importados junto a las tareas existentes",
importModeReplace: "Reemplazar",
importModeReplaceDesc: "Eliminar todos los datos existentes y reemplazarlos con los datos importados",
importReplaceWarning: "Advertencia: ¡Se eliminarán permanentemente todas tus tareas, listas y proyectos actuales!",
importSelectFile: "Seleccionar archivo JSON…",
importButton: "Importar",
importing: "Importando…",
exporting: "Exportando…",
projects: "Proyectos",
projectsDesc: "Organiza las tareas con proyectos de colores",
addProject: "Añadir proyecto",
projectName: "Nombre",
projectColor: "Color",
noProjects: "Aún no hay proyectos",
assignProject: "Asignar proyecto",
removeProject: "Quitar proyecto",
weekdayFormat: "Formato de los días",
weekdayFormatFull: "Nombre completo (lunes)",
weekdayFormatShort: "Abreviado (lun.)",
weekdayFormatNarrow: "Estrecho (L)",
weekdayFormatCustom: "Personalizado",
customWeekdayNamesMon: "Lu; Ma; Mi; Ju; Vi; Sá; Do",
customWeekdayNamesSun: "Do; Lu; Ma; Mi; Ju; Vi; Sá",
weekdayCase: "Mayúsculas de los días",
weekdayCaseNormal: "Normal (lunes)",
weekdayCaseCapitalize: "Mayúscula inicial (Lunes)",
weekdayCaseUppercase: "Mayúsculas (LUNES)",
},
it: {
settings: "Impostazioni",
general: "Generali",
calendar: "Connessioni",
localisation: "Localizzazione",
account: "Account",
runningList: "Lista continua (sposta le attività a oggi)",
protectEventTimes: "Proteggi gli orari degli eventi",
showTimeGrid: "Mostra griglia oraria",
timeSlotDuration: "Durata degli intervalli",
viewStyle: "Stile di visualizzazione",
simpleView: "Semplice",
calendarView: "Calendario",
listView: "Lista",
language: "Lingua",
dateFormat: "Formato data",
timeFormat: "Formato ora",
saveChanges: "Salva modifiche",
connectedCalendars: "Calendari collegati",
connectMore: "Collega altri",
connectGoogle: "Collega Google Calendar",
connectApple: "Collega il calendario Apple",
connectSynology: "Collega Synology",
noCalendars: "Nessun calendario collegato.",
dataPrivacy: "Dati e privacy",
downloadData: "Scarica i miei dati",
deleteAccount: "Elimina account",
name: "Nome",
email: "E-mail",
timezone: "Fuso orario",
changePassword: "Cambia password",
newPassword: "Nuova password",
confirmPassword: "Conferma password",
someday: "UN GIORNO",
lists: "Liste",
loading: "Caricamento delle attività…",
sycing: "Sincronizzazione…",
synced: "Sincronizzato",
localization: "Localizzazione",
allDayEvents: "EVENTI GIORNATA INTERA",
syncCalendar: "Sincronizza calendario",
toggleDarkMode: "Attiva/disattiva modalità scura",
signOut: "Esci",
startHour: "Inizio giornata",
endHour: "Fine giornata",
weekAbbr: "S",
goalOfWeek: "Obiettivo della settimana",
goalScope: "Ambito dell'obiettivo",
goalScopeWeek: "Per settimana",
goalScopeDay: "Per giorno",
goalFallback: "Tipo di obiettivo predefinito",
defaultGoal: "Obiettivo predefinito personalizzato",
showTaskCheckboxes: "Mostra caselle di spunta sulle attività",
showSomeday: "Mostra sezione Un giorno",
showAllDay: "Mostra sezione Giornata intera",
allDayPosition: "Posizione eventi giornata intera",
allDayAbove: "Sopra",
allDayBelow: "Sotto",
newPasswordDesc: "Lascia vuoto per mantenere la password attuale.",
dateAlignment: "Allineamento della data",
dateVerticalAlign: "Allineamento verticale della data",
alignTop: "In alto",
alignMiddle: "Al centro",
alignBottom: "In basso",
dateLayout: "Disposizione della data",
alignmentLeft: "Sinistra",
alignmentCenter: "Centro",
alignmentRight: "Destra",
alignmentTight: "Compatto",
backupRestore: "Backup e ripristino",
backupRestoreDesc: "Esporta tutte le attività, le liste e i progetti come file JSON. Puoi modificare il file e reimportarlo.",
exportAllData: "Esporta tutti i dati (JSON)",
importData: "Importa dati",
importMode: "Modalità di importazione",
importModeMerge: "Unisci",
importModeMergeDesc: "Aggiungere i dati importati alle attività esistenti",
importModeReplace: "Sostituisci",
importModeReplaceDesc: "Elimina tutti i dati esistenti e sostituiscili con i dati importati",
importReplaceWarning: "Attenzione: tutte le attività, le liste e i progetti attuali verranno eliminati definitivamente!",
importSelectFile: "Seleziona file JSON…",
importButton: "Importa",
importing: "Importazione…",
exporting: "Esportazione…",
projects: "Progetti",
projectsDesc: "Organizza le attività con progetti colorati",
addProject: "Aggiungi progetto",
projectName: "Nome",
projectColor: "Colore",
noProjects: "Nessun progetto",
assignProject: "Assegna progetto",
removeProject: "Rimuovi progetto",
weekdayFormat: "Formato dei giorni",
weekdayFormatFull: "Nome completo (lunedì)",
weekdayFormatShort: "Abbreviato (lun)",
weekdayFormatNarrow: "Stretto (L)",
weekdayFormatCustom: "Personalizzato",
customWeekdayNamesMon: "Lu; Ma; Me; Gi; Ve; Sa; Do",
customWeekdayNamesSun: "Do; Lu; Ma; Me; Gi; Ve; Sa",
weekdayCase: "Maiuscole dei giorni",
weekdayCaseNormal: "Normale (lunedì)",
weekdayCaseCapitalize: "Iniziale maiuscola (Lunedì)",
weekdayCaseUppercase: "Maiuscolo (LUNEDÌ)",
},
};
// Date utilities
@ -884,8 +1193,8 @@ export default function WeeklyView() {
yearFontFamily: "Oswald",
yearFontSize: "1.5rem",
yearFontWeight: "700",
quoteSourceUrl: "https://recite.vercel.app/api/random",
quoteSourceUrls: ["https://recite.vercel.app/api/random"],
quoteSourceUrl: "",
quoteSourceUrls: [],
});
const [motivationalQuote, setMotivationalQuote] = useState("");
const [showSummary, setShowSummary] = useState(false);
@ -946,6 +1255,7 @@ export default function WeeklyView() {
const [isSearchOpen, setIsSearchOpen] = useState(false);
const [isRecurringTasksOpen, setIsRecurringTasksOpen] = useState(false);
const [showDatePicker, setShowDatePicker] = useState(false);
const datePickerBtnRef = useRef<HTMLButtonElement>(null);
const [showQuickSettings, setShowQuickSettings] = useState(false);
const [focusTimerDuration, setFocusTimerDuration] = useState(25);
@ -3802,13 +4112,27 @@ export default function WeeklyView() {
: "var(--weekly-font-headline)",
"--weekly-date-size": scaleRem(profile.dateFontSize || "0.65rem"),
"--weekly-date-weight": profile.dateFontWeight || "400",
"--weekly-time-task-font": fontVal(profile.timeTaskFontFamily)
? `"${fontVal(profile.timeTaskFontFamily)}", sans-serif`
: fontVal(profile.taskFontFamily)
? `"${fontVal(profile.taskFontFamily)}", sans-serif`
: "var(--weekly-font)",
"--weekly-time-task-size": scaleRem(profile.timeTaskFontSize || profile.taskFontSize || "0.9rem"),
"--weekly-time-task-weight": profile.timeTaskFontWeight || profile.taskFontWeight || "400",
"--weekly-time-task-font": (() => {
// If time task font is explicitly customized (not default "Inter"), use it
// Otherwise inherit from task font
const ttf = fontVal(profile.timeTaskFontFamily);
const tf = fontVal(profile.taskFontFamily);
const isDefault = !ttf || ttf === "Inter";
if (!isDefault) return `"${ttf}", sans-serif`;
if (tf) return `"${tf}", sans-serif`;
return "var(--weekly-font)";
})(),
"--weekly-time-task-size": scaleRem(
// If time task size is the old default 0.75rem, use task size instead
profile.timeTaskFontSize && profile.timeTaskFontSize !== "0.75rem"
? profile.timeTaskFontSize
: profile.taskFontSize || "0.9rem"
),
"--weekly-time-task-weight":
// If time task weight is the old default 500, use task weight instead
profile.timeTaskFontWeight && profile.timeTaskFontWeight !== "500"
? profile.timeTaskFontWeight
: profile.taskFontWeight || "400",
"--weekly-font":
"var(--font-body)" /* Force default body font as requested */,
"--weekly-task-font": fontVal(profile.taskFontFamily)
@ -4758,8 +5082,9 @@ export default function WeeklyView() {
</div>
{/* Date Picker Toggle */}
<div className="relative" style={{ zIndex: showDatePicker ? 2001 : "auto" }}>
<div>
<button
ref={datePickerBtnRef}
className={`p-1.5 hover:bg-gray-100 rounded-md transition-colors ${showDatePicker ? "text-teal-600 bg-teal-50" : "text-gray-500 hover:text-black"}`}
onClick={() => setShowDatePicker(!showDatePicker)}
title="Jump to date"
@ -4775,6 +5100,7 @@ export default function WeeklyView() {
}}
onClose={() => setShowDatePicker(false)}
language={language}
anchorRef={datePickerBtnRef}
/>
)}
</div>
@ -8141,7 +8467,7 @@ function SettingsSidebar({
showSubHourSlots: true,
allDayPosition: "below",
goalFallbackType: "quote",
quoteSourceUrl: "https://recite.vercel.app/api/random",
quoteSourceUrl: "",
headlineFont: "Inter",
headlineFontSize: "1.25rem",
headlineFontWeight: "900",
@ -9553,9 +9879,10 @@ function SettingsSidebar({
}}
>
<option value="en">English</option>
<option value="de">German</option>
<option value="fr">French</option>
<option value="es">Spanish</option>
<option value="de">Deutsch</option>
<option value="fr">Français</option>
<option value="es">Español</option>
<option value="it">Italiano</option>
</select>
</div>
@ -11378,13 +11705,13 @@ function SettingsSidebar({
API-Datenquellen (URLs)
</label>
<div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
{(profile.quoteSourceUrls || [profile.quoteSourceUrl || "https://recite.vercel.app/api/random"]).map((url: string, idx: number) => (
{(profile.quoteSourceUrls || [profile.quoteSourceUrl || ""]).map((url: string, idx: number) => (
<div key={idx} style={{ display: "flex", gap: "8px" }}>
<input
type="text"
value={url}
onChange={(e) => {
const newUrls = [...(profile.quoteSourceUrls || [profile.quoteSourceUrl || "https://recite.vercel.app/api/random"])];
const newUrls = [...(profile.quoteSourceUrls || [profile.quoteSourceUrl || ""])];
newUrls[idx] = e.target.value;
setProfile((p) => ({ ...p, quoteSourceUrls: newUrls }));
}}
@ -11402,7 +11729,7 @@ function SettingsSidebar({
<button
type="button"
onClick={() => {
const newUrls = (profile.quoteSourceUrls || [profile.quoteSourceUrl || "https://recite.vercel.app/api/random"]).filter((_val: string, i: number) => i !== idx);
const newUrls = (profile.quoteSourceUrls || [profile.quoteSourceUrl || ""]).filter((_val: string, i: number) => i !== idx);
setProfile((p) => ({ ...p, quoteSourceUrls: newUrls }));
}}
style={{
@ -11421,7 +11748,7 @@ function SettingsSidebar({
<button
type="button"
onClick={() => {
const newUrls = [...(profile.quoteSourceUrls || [profile.quoteSourceUrl || "https://recite.vercel.app/api/random"]), ""];
const newUrls = [...(profile.quoteSourceUrls || [profile.quoteSourceUrl || ""]), ""];
setProfile((p) => ({ ...p, quoteSourceUrls: newUrls }));
}}
style={{