feat: quote sources — preset buttons, universal parser, expanded local collection

Goal API (route.ts):
- Now actually uses profile.quoteSourceUrls — tries each user-configured URL
  in order before falling back to ZenQuotes
- Universal JSON parser handles ZenQuotes [{q,a}], Quotable {content,author},
  Forismatic {quoteText,quoteAuthor}, generic {text/quote/body, author/by},
  nested {data:{...}}, and advice-slip {slip:{advice}} formats
- ZenQuotes remains the built-in EN fallback when no user URLs are configured

SettingsSidebar:
- Adds clickable preset-source pill buttons below the "Add source" button:
  ZenQuotes, Stoic Quotes, Quotable, Forismatic (EN), Advice Slip
- Pills show ✓ and are disabled when the URL is already in the list
- Note explains DE/FR/IT/ES are served from the curated local collection

quotes.ts:
- Updated PRESET_QUOTE_SOURCES to match the 5 working English APIs
- +14 French quotes (Marcus Aurelius, Lombardi, Ali, MLK, Picasso, etc.)
- +13 Spanish quotes (Collier, Eliot, Darwin, Voltaire, Ashe, etc.)
- +13 Italian quotes (Collier, Eliot, Ali, MLK, Aurelius, da Vinci, etc.)

v1.84.0
This commit is contained in:
mARTin 2026-04-05 12:50:46 +02:00
parent 2016b1cb18
commit 2ec1278b19
4 changed files with 321 additions and 26 deletions

View File

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

View File

@ -33,6 +33,7 @@ export async function GET(req: Request) {
goalDefaultSentence: true, goalDefaultSentence: true,
language: true, language: true,
quoteLanguages: true, quoteLanguages: true,
quoteSourceUrls: true,
} }
}); });
@ -92,10 +93,53 @@ export async function GET(req: Request) {
return NextResponse.json({ goal: holidayHint, isHoliday: true }); return NextResponse.json({ goal: holidayHint, isHoliday: true });
} }
// 4. Fallback to ZenQuotes motivational quote (if English is in quote languages) // 4. Try user-configured quote source URLs, then ZenQuotes
const quoteLangs: string[] = (user as any)?.quoteLanguages?.length > 0 const quoteLangs: string[] = (user as any)?.quoteLanguages?.length > 0
? (user as any).quoteLanguages ? (user as any).quoteLanguages
: [user?.language || 'en']; : [user?.language || 'en'];
// Universal JSON quote parser — handles the most common free API response shapes
const parseQuoteResponse = (data: any): string | null => {
try {
// Array responses: pick a random element
if (Array.isArray(data)) {
if (data.length === 0) return null;
data = data[Math.floor(Math.random() * data.length)];
}
// ZenQuotes: [{q, a}]
if (data.q && data.a) return `${data.q}${data.a}`;
// Quotable: {content, author}
if (data.content && data.author) return `${data.content}${data.author}`;
// Forismatic: {quoteText, quoteAuthor}
if (data.quoteText) return data.quoteAuthor ? `${data.quoteText.trim()}${data.quoteAuthor.trim()}` : data.quoteText.trim();
// Common: {text, author} or {quote, author}
const text = data.text || data.quote || data.body || data.sentence;
const author = data.author || data.by || data.from || data.speaker;
if (text) return author ? `${text}${author}` : text;
// Nested: {data: {quote, author}}
if (data.data) return parseQuoteResponse(data.data);
// Nested: {slip: {advice}}
if (data.slip?.advice) return data.slip.advice;
} catch { /* ignore */ }
return null;
};
// Try each user-configured URL first
const userUrls: string[] = (user as any)?.quoteSourceUrls?.filter(Boolean) || [];
for (const url of userUrls) {
try {
const res = await fetch(url, { signal: AbortSignal.timeout(4000) });
if (res.ok) {
const data = await res.json();
const parsed = parseQuoteResponse(data);
if (parsed) return NextResponse.json({ goal: parsed, isQuote: true });
}
} catch (e) {
console.error(`Failed to fetch from ${url}:`, e);
}
}
// ZenQuotes as built-in English fallback
if (quoteLangs.includes('en')) { if (quoteLangs.includes('en')) {
try { try {
const res = await fetch('https://zenquotes.io/api/random', { signal: AbortSignal.timeout(3000) }); const res = await fetch('https://zenquotes.io/api/random', { signal: AbortSignal.timeout(3000) });

View File

@ -3771,6 +3771,54 @@ function SettingsSidebar({
> >
<Plus size={14} /> {t.addSource} <Plus size={14} /> {t.addSource}
</button> </button>
{/* Preset sources */}
<div style={{ marginTop: "8px" }}>
<p style={{ fontSize: "0.75rem", color: "var(--weekly-text-light)", marginBottom: "6px" }}>
{profile.language === "de" ? "Bekannte Quellen (klicken zum Hinzufügen):" : "Known sources (click to add):"}
</p>
<div style={{ display: "flex", flexWrap: "wrap", gap: "6px" }}>
{[
{ label: "ZenQuotes (EN)", url: "https://zenquotes.io/api/random" },
{ label: "Stoic Quotes (EN)", url: "https://stoic.tekloon.net/stoic-quote" },
{ label: "Quotable (EN)", url: "https://api.quotable.io/quotes/random" },
{ label: "Forismatic (EN)", url: "https://api.forismatic.com/api/1.0/?method=getQuote&format=json&lang=en" },
{ label: "Advice Slip (EN)", url: "https://api.adviceslip.com/advice" },
].map(({ label, url }) => {
const current: string[] = profile.quoteSourceUrls || [];
const already = current.includes(url);
return (
<button
key={url}
type="button"
disabled={already}
onClick={() => {
if (already) return;
const newUrls = [...current, url];
saveField("quoteSourceUrls", newUrls);
}}
style={{
padding: "3px 8px",
fontSize: "0.72rem",
borderRadius: "12px",
border: "1px solid var(--weekly-border)",
background: already ? "var(--weekly-teal)" : "var(--weekly-bg)",
color: already ? "white" : "var(--weekly-text)",
cursor: already ? "default" : "pointer",
opacity: already ? 0.7 : 1,
}}
>
{already ? "✓ " : "+ "}{label}
</button>
);
})}
</div>
<p style={{ fontSize: "0.72rem", color: "var(--weekly-text-light)", marginTop: "6px", fontStyle: "italic" }}>
{profile.language === "de"
? "Für DE, FR, IT, ES werden kuratierte lokale Zitate verwendet."
: "For DE, FR, IT, ES, curated local quotes are used automatically."}
</p>
</div>
</div> </div>
<p style={{ fontSize: "0.75rem", color: "var(--weekly-text-light)", marginTop: "4px" }}> <p style={{ fontSize: "0.75rem", color: "var(--weekly-text-light)", marginTop: "4px" }}>
{t.urlFormatHelp} {t.urlFormatHelp}

View File

@ -8,30 +8,11 @@ export interface LocalQuote {
} }
export const PRESET_QUOTE_SOURCES = [ export const PRESET_QUOTE_SOURCES = [
{ { name: "ZenQuotes", url: "https://zenquotes.io/api/random", language: "en" },
name: "ZenQuotes", { name: "Stoic Quotes", url: "https://stoic.tekloon.net/stoic-quote", language: "en" },
url: "https://zenquotes.io/api/random", { name: "Quotable", url: "https://api.quotable.io/quotes/random", language: "en" },
language: "en", { name: "Forismatic (EN)", url: "https://api.forismatic.com/api/1.0/?method=getQuote&format=json&lang=en", language: "en" },
tags: ["motivation", "wisdom", "life"], { name: "Advice Slip", url: "https://api.adviceslip.com/advice", language: "en" },
},
{
name: "Quotable",
url: "https://api.quotable.io/random",
language: "en",
tags: ["motivation", "wisdom", "success", "life"],
},
{
name: "Forismatic",
url: "https://api.forismatic.com/api/1.0/?method=getQuote&format=json&lang=de",
language: "de",
tags: ["motivation", "wisdom", "life"],
},
{
name: "Type.fit",
url: "https://type.fit/api/quotes",
language: "en",
tags: ["motivation", "wisdom"],
},
] as const; ] as const;
export const LOCAL_QUOTES: LocalQuote[] = [ export const LOCAL_QUOTES: LocalQuote[] = [
@ -616,6 +597,84 @@ export const LOCAL_QUOTES: LocalQuote[] = [
language: "fr", language: "fr",
tags: ["creativity", "humor"], tags: ["creativity", "humor"],
}, },
{
text: "Vous avez du pouvoir sur votre esprit, pas sur les événements extérieurs. Réalisez cela, et vous trouverez la force.",
author: "Marc Aurèle",
language: "fr",
tags: ["wisdom", "motivation", "perseverance"],
},
{
text: "Le succès c'est tomber sept fois et se relever huit.",
author: "Proverbe japonais",
language: "fr",
tags: ["perseverance", "motivation", "success"],
},
{
text: "Votre temps est limité, ne le gâchez pas en vivant la vie de quelqu'un d'autre.",
author: "Steve Jobs",
language: "fr",
tags: ["life", "wisdom", "motivation"],
},
{
text: "La perfection n'est pas atteignable. Mais en visant la perfection, nous pouvons atteindre l'excellence.",
author: "Vince Lombardi",
language: "fr",
tags: ["success", "motivation", "work"],
},
{
text: "Commencez là où vous êtes. Utilisez ce que vous avez. Faites ce que vous pouvez.",
author: "Arthur Ashe",
language: "fr",
tags: ["motivation", "work", "perseverance"],
},
{
text: "Les grandes choses ne viennent jamais de la zone de confort.",
author: "Inconnu",
language: "fr",
tags: ["motivation", "perseverance", "success"],
},
{
text: "Ne comptez pas les jours, faites compter les jours.",
author: "Muhammad Ali",
language: "fr",
tags: ["motivation", "work", "life"],
},
{
text: "Ce n'est pas parce que c'est difficile que nous n'osons pas, c'est parce que nous n'osons pas que c'est difficile.",
author: "Sénèque",
language: "fr",
tags: ["motivation", "wisdom", "perseverance"],
},
{
text: "Tout ce que vous pouvez imaginer est réel.",
author: "Pablo Picasso",
language: "fr",
tags: ["creativity", "motivation"],
},
{
text: "La vie n'est pas d'attendre que la tempête passe, c'est d'apprendre à danser sous la pluie.",
author: "Vivian Greene",
language: "fr",
tags: ["life", "perseverance", "wisdom"],
},
{
text: "Si vous ne pouvez pas voler, courez. Si vous ne pouvez pas courir, marchez. Si vous ne pouvez pas marcher, rampez. Mais continuez d'avancer.",
author: "Martin Luther King Jr.",
language: "fr",
tags: ["perseverance", "motivation"],
},
{
text: "La seule façon de faire du bon travail est d'aimer ce que vous faites.",
author: "Steve Jobs",
language: "fr",
tags: ["work", "motivation", "success"],
},
{
text: "Chaque expert a d'abord été un débutant.",
author: "Helen Hayes",
language: "fr",
tags: ["perseverance", "motivation", "work"],
},
// --- Spanish Quotes --- // --- Spanish Quotes ---
{ {
text: "No es la especie más fuerte la que sobrevive, sino la que mejor se adapta al cambio.", text: "No es la especie más fuerte la que sobrevive, sino la que mejor se adapta al cambio.",
@ -707,6 +766,78 @@ export const LOCAL_QUOTES: LocalQuote[] = [
language: "es", language: "es",
tags: ["motivation", "perseverance", "success"], tags: ["motivation", "perseverance", "success"],
}, },
{
text: "El éxito es la suma de pequeños esfuerzos repetidos día tras día.",
author: "Robert Collier",
language: "es",
tags: ["success", "perseverance", "work"],
},
{
text: "Nunca es tarde para ser lo que podrías haber sido.",
author: "George Eliot",
language: "es",
tags: ["motivation", "life", "perseverance"],
},
{
text: "No cuentes los días, haz que los días cuenten.",
author: "Muhammad Ali",
language: "es",
tags: ["motivation", "work", "life"],
},
{
text: "Si no puedes volar, corre. Si no puedes correr, camina. Si no puedes caminar, arrástrate. Pero sigue adelante.",
author: "Martin Luther King Jr.",
language: "es",
tags: ["perseverance", "motivation"],
},
{
text: "Tienes poder sobre tu mente, no sobre los eventos externos. Date cuenta de esto y encontrarás fuerza.",
author: "Marco Aurelio",
language: "es",
tags: ["wisdom", "motivation", "perseverance"],
},
{
text: "El único modo de hacer un gran trabajo es amar lo que haces.",
author: "Steve Jobs",
language: "es",
tags: ["work", "motivation", "success"],
},
{
text: "No importa cuán lento vayas, siempre y cuando no te detengas.",
author: "Confucio",
language: "es",
tags: ["perseverance", "motivation"],
},
{
text: "Todo experto fue alguna vez un principiante.",
author: "Helen Hayes",
language: "es",
tags: ["perseverance", "motivation", "work"],
},
{
text: "El futuro pertenece a quienes creen en la belleza de sus sueños.",
author: "Eleanor Roosevelt",
language: "es",
tags: ["motivation", "life", "creativity"],
},
{
text: "No es la especie más fuerte la que sobrevive, sino la más adaptable al cambio.",
author: "Charles Darwin",
language: "es",
tags: ["wisdom", "perseverance", "life"],
},
{
text: "La perseverancia es el padre del éxito.",
author: "Voltaire",
language: "es",
tags: ["perseverance", "success", "work"],
},
{
text: "Comienza donde estás. Usa lo que tienes. Haz lo que puedes.",
author: "Arthur Ashe",
language: "es",
tags: ["motivation", "work", "perseverance"],
},
// --- Italian Quotes --- // --- Italian Quotes ---
{ {
text: "La semplicità è la raffinatezza suprema.", text: "La semplicità è la raffinatezza suprema.",
@ -798,6 +929,78 @@ export const LOCAL_QUOTES: LocalQuote[] = [
language: "it", language: "it",
tags: ["motivation", "perseverance", "success"], tags: ["motivation", "perseverance", "success"],
}, },
{
text: "Il successo è la somma di piccoli sforzi ripetuti giorno dopo giorno.",
author: "Robert Collier",
language: "it",
tags: ["success", "perseverance", "work"],
},
{
text: "Non è mai troppo tardi per essere ciò che avresti potuto essere.",
author: "George Eliot",
language: "it",
tags: ["motivation", "life", "perseverance"],
},
{
text: "Non contare i giorni, fai sì che i giorni contino.",
author: "Muhammad Ali",
language: "it",
tags: ["motivation", "work", "life"],
},
{
text: "Se non puoi volare, corri. Se non puoi correre, cammina. Se non puoi camminare, striscia. Ma continua ad andare avanti.",
author: "Martin Luther King Jr.",
language: "it",
tags: ["perseverance", "motivation"],
},
{
text: "Hai potere sulla tua mente, non sugli eventi esterni. Renditi conto di questo e troverai la forza.",
author: "Marco Aurelio",
language: "it",
tags: ["wisdom", "motivation", "perseverance"],
},
{
text: "Ogni esperto è stato prima un principiante.",
author: "Helen Hayes",
language: "it",
tags: ["perseverance", "motivation", "work"],
},
{
text: "Il futuro appartiene a coloro che credono nella bellezza dei propri sogni.",
author: "Eleanor Roosevelt",
language: "it",
tags: ["motivation", "life", "creativity"],
},
{
text: "Inizia dove sei. Usa quello che hai. Fai quello che puoi.",
author: "Arthur Ashe",
language: "it",
tags: ["motivation", "work", "perseverance"],
},
{
text: "La semplicità è la massima sofisticazione.",
author: "Leonardo da Vinci",
language: "it",
tags: ["wisdom", "creativity"],
},
{
text: "Il coraggio non è l'assenza di paura, ma il giudizio che qualcos'altro è più importante della paura.",
author: "Ambrose Redmoon",
language: "it",
tags: ["motivation", "perseverance", "wisdom"],
},
{
text: "Le grandi menti discutono di idee; le menti medie discutono di eventi; le piccole menti discutono di persone.",
author: "Eleanor Roosevelt",
language: "it",
tags: ["wisdom", "creativity"],
},
{
text: "Sii tu il cambiamento che vuoi vedere nel mondo.",
author: "Mahatma Gandhi",
language: "it",
tags: ["wisdom", "life", "motivation"],
},
]; ];
/** /**