Skip to content

Commit f7fcb6a

Browse files
author
maro114510
committed
fix: Refactor
1 parent a4ecb82 commit f7fcb6a

2 files changed

Lines changed: 75 additions & 42 deletions

File tree

content.js

Lines changed: 55 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const FEEDLY_API_BASE = "https://api.feedly.com";
1313
// Feedly Read Later pages can vary by user id and legacy paths.
1414
const READ_LATER_PATTERNS = [
1515
/https:\/\/feedly\.com\/i\/board\/content\/user\/[^/]+\/tag\/global\.saved/i,
16-
/https:\/\/feedly\.com\/i\/read-later/i
16+
/https:\/\/feedly\.com\/i\/read-later/i,
1717
];
1818
const READ_LATER_PATH_HINTS = ["/i/read-later", "/i/board/content/user/"];
1919
const READ_LATER_CACHE_MS = 3000;
@@ -30,7 +30,7 @@ const TOOLBAR_BUTTON_SELECTOR = "button.EntryToolbar__button";
3030
const READ_LATER_SELECTORS = [
3131
".EntryMetadataReadLater a[role='button']",
3232
".EntryMetadataReadLater button",
33-
"a[role='button'] .InterestingMetadata__icon"
33+
"a[role='button'] .InterestingMetadata__icon",
3434
];
3535
const READ_LATER_LABELS = ["read later", "後で読む", "あとで読む"];
3636

@@ -116,8 +116,8 @@ async function feedlyApiRequest(endpoint, options = {}) {
116116
headers: {
117117
"Authorization": `Bearer ${token}`,
118118
"Content-Type": "application/json",
119-
...options.headers
120-
}
119+
...options.headers,
120+
},
121121
});
122122

123123
if (!response.ok) {
@@ -130,7 +130,9 @@ async function feedlyApiRequest(endpoint, options = {}) {
130130
}
131131

132132
// DELETE requests may return empty body
133-
if (response.status === 204 || response.headers.get("content-length") === "0") {
133+
if (
134+
response.status === 204 || response.headers.get("content-length") === "0"
135+
) {
134136
return { success: true };
135137
}
136138

@@ -160,8 +162,9 @@ function parseEntryItems(items) {
160162
}
161163
return items.map((item) => ({
162164
id: item.id,
163-
url: item.alternate?.[0]?.href || item.canonicalUrl || item.originId || null,
164-
title: item.title || "Untitled"
165+
url: item.alternate?.[0]?.href || item.canonicalUrl || item.originId ||
166+
null,
167+
title: item.title || "Untitled",
165168
})).filter((item) => item.url);
166169
}
167170

@@ -174,7 +177,7 @@ function parseEntryItems(items) {
174177
async function fetchSavedEntriesViaAPI(userId, count = 100) {
175178
const streamId = encodeURIComponent(`user/${userId}/tag/global.saved`);
176179
const response = await feedlyApiRequest(
177-
`/v3/streams/contents?streamId=${streamId}&count=${count}&ranked=newest`
180+
`/v3/streams/contents?streamId=${streamId}&count=${count}&ranked=newest`,
178181
);
179182
return parseEntryItems(response.items);
180183
}
@@ -189,10 +192,19 @@ async function fetchAllSavedEntriesViaAPI(userId) {
189192
const allEntries = [];
190193
let continuation = null;
191194
const PAGE_SIZE = 100;
195+
const MAX_PAGES = 1000; // Safety limit to prevent infinite loops
196+
let pageCount = 0;
192197

193198
do {
199+
if (pageCount++ >= MAX_PAGES) {
200+
console.warn(
201+
"[Feedly Opener] Reached maximum page limit while fetching saved entries",
202+
);
203+
break;
204+
}
194205
const streamId = encodeURIComponent(`user/${userId}/tag/global.saved`);
195-
let url = `/v3/streams/contents?streamId=${streamId}&count=${PAGE_SIZE}&ranked=newest`;
206+
let url =
207+
`/v3/streams/contents?streamId=${streamId}&count=${PAGE_SIZE}&ranked=newest`;
196208
if (continuation) {
197209
url += `&continuation=${encodeURIComponent(continuation)}`;
198210
}
@@ -225,13 +237,19 @@ async function unsaveEntriesViaAPI(userId, entryIds) {
225237
// Process DELETE requests in parallel batches to balance speed and rate limiting
226238
for (let i = 0; i < entryIds.length; i += BATCH_SIZE) {
227239
const batch = entryIds.slice(i, i + BATCH_SIZE);
228-
await Promise.all(
240+
const results = await Promise.all(
229241
batch.map((entryId) =>
230242
feedlyApiRequest(`/v3/tags/${tagId}/${encodeURIComponent(entryId)}`, {
231-
method: "DELETE"
243+
method: "DELETE",
232244
})
233-
)
245+
),
234246
);
247+
const failures = results.filter((res) => !res.success);
248+
if (failures.length > 0) {
249+
throw new Error(
250+
`Failed to unsave some entries via API: ${failures.length} failures`,
251+
);
252+
}
235253
}
236254
return true;
237255
}
@@ -244,7 +262,9 @@ async function unsaveEntriesViaAPI(userId, entryIds) {
244262
async function handleOpenViaAPI(settings) {
245263
const token = await getAccessToken();
246264
if (!token) {
247-
throw new Error("No access token available. Please ensure you are logged into Feedly.");
265+
throw new Error(
266+
"No access token available. Please ensure you are logged into Feedly.",
267+
);
248268
}
249269

250270
const userId = await getUserId();
@@ -259,7 +279,7 @@ async function handleOpenViaAPI(settings) {
259279
ok: true,
260280
urls: [],
261281
method: "api",
262-
message: "No saved entries found"
282+
message: "No saved entries found",
263283
};
264284
}
265285

@@ -274,7 +294,7 @@ async function handleOpenViaAPI(settings) {
274294
return {
275295
ok: true,
276296
urls: entriesToProcess.map((e) => e.url),
277-
method: "api"
297+
method: "api",
278298
};
279299
}
280300

@@ -327,7 +347,7 @@ function toOpenableUrl(href) {
327347
let url;
328348
try {
329349
url = new URL(href, location.href);
330-
} catch (error) {
350+
} catch (_error) {
331351
return null;
332352
}
333353

@@ -404,7 +424,8 @@ function isSavedButton(button) {
404424
}
405425

406426
function containsReadLaterText(element) {
407-
const text = (element.textContent || "").replace(/\s+/g, " ").trim().toLowerCase();
427+
const text = (element.textContent || "").replace(/\s+/g, " ").trim()
428+
.toLowerCase();
408429
return READ_LATER_LABELS.some((label) => text.includes(label));
409430
}
410431

@@ -478,8 +499,9 @@ async function getSavedEntriesWithUrls(settings) {
478499
const entries = getEntryElements();
479500
const seen = new Set();
480501
const results = [];
481-
const limit =
482-
settings.mode === "count" ? normalizeCount(settings.count) : Infinity;
502+
const limit = settings.mode === "count"
503+
? normalizeCount(settings.count)
504+
: Infinity;
483505

484506
for (const entry of entries) {
485507
if (results.length >= limit) {
@@ -528,15 +550,15 @@ function clickElement(element) {
528550
"mousedown",
529551
"pointerup",
530552
"mouseup",
531-
"click"
553+
"click",
532554
];
533555
for (const type of mouseEvents) {
534556
element.dispatchEvent(
535557
new MouseEvent(type, {
536558
bubbles: true,
537559
cancelable: true,
538-
view: window
539-
})
560+
view: window,
561+
}),
540562
);
541563
}
542564
}
@@ -556,7 +578,7 @@ async function loadAllEntries({ maxRounds, idleThreshold }) {
556578
for (let round = 0; round < maxRounds; round += 1) {
557579
scrollElement.scrollTo({
558580
top: scrollElement.scrollHeight,
559-
behavior: "auto"
581+
behavior: "auto",
560582
});
561583
await delay(800);
562584

@@ -621,7 +643,7 @@ async function handleOpenViaDOM(settings) {
621643
return {
622644
ok: true,
623645
urls: selected.map((item) => item.url),
624-
method: "dom"
646+
method: "dom",
625647
};
626648
}
627649

@@ -639,7 +661,10 @@ async function handleOpen(settings) {
639661
result = await handleOpenViaAPI(settings);
640662
} catch (e) {
641663
apiError = e;
642-
console.warn("[Feedly Opener] API operation failed, falling back to DOM:", e.message);
664+
console.warn(
665+
"[Feedly Opener] API operation failed, falling back to DOM:",
666+
e.message,
667+
);
643668
}
644669

645670
// Fallback to DOM-based approach if API failed
@@ -653,8 +678,9 @@ async function handleOpen(settings) {
653678
console.error("[Feedly Opener] DOM operation also failed:", domError);
654679
return {
655680
ok: false,
656-
error: `API error: ${apiError?.message || "unknown"}. DOM error: ${domError.message}`,
657-
method: "failed"
681+
error: `API error: ${apiError?.message || "unknown"
682+
}. DOM error: ${domError.message}`,
683+
method: "failed",
658684
};
659685
}
660686
}
@@ -703,7 +729,8 @@ function isRecentlyReadLater() {
703729
if (Date.now() - lastReadLaterSeenAt > READ_LATER_CACHE_MS) {
704730
return false;
705731
}
706-
return location.origin === "https://feedly.com" && lastReadLaterUrl.length > 0;
732+
return location.origin === "https://feedly.com" &&
733+
lastReadLaterUrl.length > 0;
707734
}
708735

709736
// =============================================================================

popup.js

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ const usesPromises = typeof browser !== "undefined";
44
const SETTINGS_KEY = "feedlyOpenerSettings";
55
const DEFAULT_SETTINGS = {
66
mode: "all",
7-
count: 10
7+
count: 10,
88
};
99

10-
const storageArea =
11-
api.storage && api.storage.sync ? api.storage.sync : api.storage.local;
10+
const storageArea = api.storage && api.storage.sync
11+
? api.storage.sync
12+
: api.storage.local;
1213

1314
const $ = (selector) => document.querySelector(selector);
1415
const statusEl = $("#status");
@@ -76,7 +77,7 @@ const LoadingManager = {
7677
}
7778
if (this.text) this.text.textContent = message;
7879
this.hideTimeout = setTimeout(() => this.hide(), 1500);
79-
}
80+
},
8081
};
8182

8283
// =============================================================================
@@ -184,13 +185,13 @@ function clampCount(value) {
184185
}
185186

186187
function readSettingsFromForm() {
187-
const mode =
188-
modeInputs.find((input) => input.checked)?.value || DEFAULT_SETTINGS.mode;
188+
const mode = modeInputs.find((input) => input.checked)?.value ||
189+
DEFAULT_SETTINGS.mode;
189190
const count = clampCount(Number.parseInt(countInput.value, 10));
190191

191192
return {
192193
mode,
193-
count
194+
count,
194195
};
195196
}
196197

@@ -243,9 +244,9 @@ async function run() {
243244
try {
244245
response = await tabsSendMessage(tab.id, {
245246
type: "FEEDLY_OPEN",
246-
settings
247+
settings,
247248
});
248-
} catch (error) {
249+
} catch (_error) {
249250
response = await ensureContentScript(tab, settings);
250251
if (!response) {
251252
setStatus("Open a Feedly Read Later tab first.");
@@ -273,7 +274,7 @@ async function run() {
273274

274275
setStatus(`Opened ${urls.length} tabs. Reloading page.`);
275276
LoadingManager.showSuccess(`${urls.length} opened`);
276-
} catch (error) {
277+
} catch (_error) {
277278
setStatus("Failed to open tabs. Please try again.");
278279
LoadingManager.showError("Failed");
279280
} finally {
@@ -299,8 +300,13 @@ async function init() {
299300
});
300301
});
301302

303+
// debounce
304+
let saveTimeout = null;
302305
countInput.addEventListener("input", () => {
303-
saveSettings(readSettingsFromForm());
306+
clearTimeout(saveTimeout);
307+
saveTimeout = setTimeout(() => {
308+
saveSettings(readSettingsFromForm());
309+
}, 300);
304310
});
305311

306312
runButton.addEventListener("click", run);
@@ -315,14 +321,14 @@ init();
315321
async function ensureContentScript(tab, settings) {
316322
try {
317323
await tabsExecuteScript(tab.id, "content.js");
318-
} catch (error) {
324+
} catch (_error) {
319325
return null;
320326
}
321327

322328
await delay(100);
323329
try {
324330
return await tabsSendMessage(tab.id, { type: "FEEDLY_OPEN", settings });
325-
} catch (error) {
331+
} catch (_error) {
326332
return null;
327333
}
328334
}
@@ -331,7 +337,7 @@ function tabsExecuteScript(tabId, file) {
331337
if (api.scripting && api.scripting.executeScript) {
332338
return api.scripting.executeScript({
333339
target: { tabId },
334-
files: [file]
340+
files: [file],
335341
});
336342
}
337343

0 commit comments

Comments
 (0)