-
Notifications
You must be signed in to change notification settings - Fork 113
Expand file tree
/
Copy pathquotes.js
More file actions
341 lines (280 loc) · 11.7 KB
/
quotes.js
File metadata and controls
341 lines (280 loc) · 11.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
/*
* Material You NewTab
* Copyright (c) 2023-2025 XengShi
* Licensed under the GNU General Public License v3.0 (GPL-3.0)
* You should have received a copy of the GNU General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
*/
// Multilingual quotes API
const metadataUrl = "https://prem-k-r.github.io/multilingual-quotes-api/minified/metadata.json";
const baseQuoteUrl = "https://prem-k-r.github.io/multilingual-quotes-api/minified/";
const quotesContainer = document.querySelector(".quotesContainer");
const authorName = document.querySelector(".authorName span");
const authorContainer = document.querySelector(".authorName");
const MAX_QUOTE_LENGTH = 140;
const MIN_QUOTES_FOR_LANG = 100;
const ONE_DAY = 24 * 60 * 60 * 1000;
// Fallback quote for when everything fails
const FALLBACK_QUOTE = {
quote: "Don't watch the clock; do what it does. Keep going.",
author: "Sam Levenson"
};
let lastKnownLanguage = null;
// Clear all quotes-related data from localStorage
function clearQuotesStorage() {
const keys = Object.keys(localStorage);
keys.forEach(key => {
if (key.startsWith("quotes_")) {
localStorage.removeItem(key);
}
});
quotesContainer.textContent = "";
authorName.textContent = "";
}
// Clear quotes for all languages except the specified one
function clearOtherLanguageQuotes(exceptLang) {
const keys = Object.keys(localStorage);
keys.forEach(key => {
if (
key.startsWith("quotes_") &&
!key.includes(`quotes_${exceptLang}`) &&
key !== "quotes_metadata_timestamp"
) {
localStorage.removeItem(key);
}
});
}
// Check if we need to fetch data for a language
function needsDataFetch(lang) {
// Always fetch if offline
if (!navigator.onLine) return false;
// Check if language changed
if (lastKnownLanguage !== null && lastKnownLanguage !== currentLanguage) {
return true;
}
// Check if any required data is missing
const requiredKeys = [
`quotes_${lang}`,
`quotes_${lang}_timestamp`,
`quotes_${lang}_count`
];
if (requiredKeys.some(key => !localStorage.getItem(key))) {
return true;
}
// Check if data is stale based on quote count
const storedCount = parseInt(localStorage.getItem(`quotes_${lang}_count`)) || 0;
const storedTimestamp = localStorage.getItem(`quotes_${lang}_timestamp`);
const timeDiff = Date.now() - new Date(storedTimestamp).getTime();
// If count is 0, it means no data available for this language
// Only refresh after 1 day to check if quotes were added
if (storedCount === 0) {
return timeDiff > ONE_DAY;
}
// Time-based validation for languages with actual quotes
const maxAge = storedCount < MIN_QUOTES_FOR_LANG ? ONE_DAY : 7 * ONE_DAY;
return timeDiff > maxAge;
}
// Determine target language based on availability
function getTargetLanguage(currentLang, metadata) {
// If current language is English, use it
if (currentLang === "en") {
return "en";
}
// Check if current language has enough quotes
const langFile = metadata?.files?.[`${currentLang}.json`];
if (langFile && langFile.count >= MIN_QUOTES_FOR_LANG) {
return currentLang;
}
// Fallback to English
return "en";
}
// Fetch metadata from the API
async function fetchMetadata() {
try {
const response = await fetch(metadataUrl);
return await response.json();
} catch (error) {
console.error("Error fetching metadata:", error);
throw error;
}
}
// Fetch quotes for a specific language
async function fetchQuotes(lang) {
try {
const url = `${baseQuoteUrl}${lang}.json`;
const response = await fetch(url);
return await response.json();
} catch (error) {
console.error(`Error fetching quotes for ${lang}:`, error);
throw error;
}
}
// Store quotes and metadata in localStorage
function storeQuotesData(lang, quotes, metadata) {
const timestamp = new Date().toISOString();
localStorage.setItem(`quotes_${lang}`, JSON.stringify(quotes));
localStorage.setItem(`quotes_${lang}_timestamp`, timestamp);
if (metadata) {
localStorage.setItem("quotes_metadata_timestamp", metadata.lastUpdated);
const quoteCount = metadata.files?.[`${lang}.json`]?.count || quotes.length;
localStorage.setItem(`quotes_${lang}_count`, quoteCount.toString());
}
}
// Store "no data available" information for languages without quotes
function storeNoDataInfo(lang, metadata) {
const timestamp = new Date().toISOString();
localStorage.setItem(`quotes_${lang}`, JSON.stringify([])); // Empty array
localStorage.setItem(`quotes_${lang}_timestamp`, timestamp);
localStorage.setItem(`quotes_${lang}_count`, "0"); // 0 indicates no data available
if (metadata) {
localStorage.setItem("quotes_metadata_timestamp", metadata.lastUpdated);
}
}
// Get stored quotes for a language
function getStoredQuotes(lang) {
const storedQuotes = localStorage.getItem(`quotes_${lang}`);
return storedQuotes ? JSON.parse(storedQuotes) : null;
}
// Display fallback quote
function displayFallbackQuote() {
quotesContainer.textContent = FALLBACK_QUOTE.quote;
authorName.textContent = FALLBACK_QUOTE.author;
}
// Get quotes for the current language
async function getQuotesForLanguage(forceRefresh = false) {
try {
// Check if language has changed
const languageChanged = lastKnownLanguage !== null && lastKnownLanguage !== currentLanguage;
lastKnownLanguage = currentLanguage;
// Check if we need to fetch new data
const shouldFetch = forceRefresh || needsDataFetch(currentLanguage);
if (shouldFetch) {
// Fetch metadata first to determine availability
const metadata = await fetchMetadata();
const targetLang = getTargetLanguage(currentLanguage, metadata);
// Store info about current language availability
const currentLangFile = metadata.files?.[`${currentLanguage}.json`];
const currentLangCount = currentLangFile?.count || 0;
// If current language has no quotes, store that info
if (currentLangCount === 0 && currentLanguage !== "en") {
storeNoDataInfo(currentLanguage, metadata);
}
// Fetch quotes for target language
const quotes = await fetchQuotes(targetLang);
storeQuotesData(targetLang, quotes, metadata);
clearOtherLanguageQuotes(currentLanguage || targetLang);
return quotes;
} else {
// Use stored data
const storedCount = parseInt(localStorage.getItem(`quotes_${currentLanguage}_count`)) || 0;
// If current language has no quotes (count is 0), use English fallback
if (storedCount === 0 && currentLanguage !== "en") {
let englishQuotes = getStoredQuotes("en");
// If no English quotes stored, we need to fetch them
if (!englishQuotes || englishQuotes.length === 0) {
const metadata = await fetchMetadata();
englishQuotes = await fetchQuotes("en");
storeQuotesData("en", englishQuotes, metadata);
}
return englishQuotes;
}
// Return stored quotes for current language
return getStoredQuotes(currentLanguage);
}
} catch (error) {
console.error("Error getting quotes:", error);
// Try to use any stored data as fallback
let quotes = getStoredQuotes(currentLanguage) || getStoredQuotes("en");
if (!quotes || quotes.length === 0) {
// Return hardcoded fallback quote if everything fails
return [FALLBACK_QUOTE];
}
return quotes;
}
}
// Display a random quote that meets the length requirements
function displayRandomQuote(quotes) {
if (!quotes || quotes.length === 0) {
displayFallbackQuote();
return;
}
let selectedQuote;
const maxAttempts = 15; // Prevent infinite loop
// Try to find a quote that fits within the character limit
for (let attempts = 0; attempts < maxAttempts; attempts++) {
const randomIndex = Math.floor(Math.random() * quotes.length);
selectedQuote = quotes[randomIndex];
const totalLength = selectedQuote.quote.length + selectedQuote.author.length;
if (totalLength <= MAX_QUOTE_LENGTH) {
break;
}
}
// Display the selected quote
quotesContainer.textContent = selectedQuote.quote;
authorName.textContent = selectedQuote.author;
// Animate .authorName width to fit content
requestAnimationFrame(() => {
const fullWidth = authorName.scrollWidth;
const padding = 16;
authorContainer.style.width = (fullWidth + padding * 2) + "px";
});
}
// Main function to load and display a quote
async function loadAndDisplayQuote(forceRefresh = false) {
try {
const quotes = await getQuotesForLanguage(forceRefresh);
displayRandomQuote(quotes);
} catch (error) {
console.error("Error loading quote:", error);
displayFallbackQuote();
}
}
document.addEventListener("DOMContentLoaded", () => {
const hideSearchWith = document.getElementById("shortcut_switchcheckbox");
const quotesToggle = document.getElementById("quotesToggle");
const motivationalQuotesCont = document.getElementById("motivationalQuotesCont");
const motivationalQuotesCheckbox = document.getElementById("motivationalQuotesCheckbox");
const searchWithContainer = document.getElementById("search-with-container");
const hideSearchCheckbox = document.getElementById("hideSearchCheckbox")
// Load states from localStorage
hideSearchWith.checked = localStorage.getItem("showShortcutSwitch") === "true";
motivationalQuotesCheckbox.checked = localStorage.getItem("motivationalQuotesVisible") !== "false";
// Initialize language tracking
lastKnownLanguage = currentLanguage;
// Function to update quotes visibility and handle state changes
const updateMotivationalQuotesState = () => {
const isHideSearchWithEnabled = hideSearchWith.checked;
const isMotivationalQuotesEnabled = motivationalQuotesCheckbox.checked;
const isHideSearchChecked = hideSearchCheckbox.checked;
// Save state to localStorage
localStorage.setItem("motivationalQuotesVisible", isMotivationalQuotesEnabled);
// Handle visibility based on settings
if (!isHideSearchWithEnabled && !isHideSearchChecked) {
quotesToggle.classList.add("inactive");
motivationalQuotesCont.style.display = "none";
clearQuotesStorage();
return;
}
// Update UI visibility
quotesToggle.classList.remove("inactive");
searchWithContainer.style.display = (isMotivationalQuotesEnabled || isHideSearchChecked) ? "none" : "flex";
motivationalQuotesCont.style.display = isMotivationalQuotesEnabled ? "flex" : "none";
// Load quotes if motivational quotes are enabled
if (isMotivationalQuotesEnabled) {
loadAndDisplayQuote(false);
} else {
clearQuotesStorage();
}
};
// Apply initial state
updateMotivationalQuotesState();
// Event Listeners
hideSearchWith.addEventListener("change", () => {
searchWithContainer.style.display = "flex";
updateMotivationalQuotesState();
});
hideSearchCheckbox.addEventListener("change", () => {
updateMotivationalQuotesState();
});
motivationalQuotesCheckbox.addEventListener("change", updateMotivationalQuotesState);
});