Просмотр исходного кода

refactor(js): reorder functions and add comments

Frédéric Guillot 8 месяцев назад
Родитель
Сommit
50197c2be3
2 измененных файлов с 285 добавлено и 211 удалено
  1. 274 200
      internal/ui/static/js/app.js
  2. 11 11
      internal/ui/static/js/bootstrap.js

+ 274 - 200
internal/ui/static/js/app.js

@@ -1,3 +1,15 @@
+// Sentinel values for specific list navigation
+const TOP = 9999;
+const BOTTOM = -9999;
+
+/**
+ * Get the CSRF token from the HTML document.
+ *
+ * @returns {string} The CSRF token.
+ */
+function getCsrfToken() {
+    return document.body.dataset.csrfToken || "";
+}
 
 /**
  * Open a new tab with the given URL.
@@ -11,26 +23,6 @@ function openNewTab(url) {
     win.focus();
 }
 
-/**
- * Filter visible elements based on the selector.
- *
- * @param {string} selector
- * @returns {Array<Element>}
- */
-function getVisibleElements(selector) {
-    const elements = document.querySelectorAll(selector);
-    return [...elements].filter((element) => element.offsetParent !== null);
-}
-
-/**
- * Get all visible entries on the current page.
- *
- * @return {Array<Element>}
- */
-function getVisibleEntries() {
-    return getVisibleElements(".items .item");
-}
-
 /**
  * Scroll the page to the given element.
  *
@@ -48,7 +40,13 @@ function scrollPageTo(element, evenIfOnScreen) {
     }
 }
 
-// OnClick attaches a listener to the elements that match the selector.
+/**
+ * Attach a click event listener to elements matching the selector.
+ *
+ * @param {string} selector
+ * @param {function} callback
+ * @param {boolean} noPreventDefault
+ */
 function onClick(selector, callback, noPreventDefault) {
     document.querySelectorAll(selector).forEach((element) => {
         element.onclick = (event) => {
@@ -60,6 +58,13 @@ function onClick(selector, callback, noPreventDefault) {
     });
 }
 
+/**
+ * Attach an auxiliary click event listener to elements matching the selector.
+ *
+ * @param {string} selector
+ * @param {function} callback
+ * @param {boolean} noPreventDefault
+ */
 function onAuxClick(selector, callback, noPreventDefault) {
     document.querySelectorAll(selector).forEach((element) => {
         element.onauxclick = (event) => {
@@ -71,6 +76,254 @@ function onAuxClick(selector, callback, noPreventDefault) {
     });
 }
 
+/**
+ * Filter visible elements based on the selector.
+ *
+ * @param {string} selector
+ * @returns {Array<Element>}
+ */
+function getVisibleElements(selector) {
+    const elements = document.querySelectorAll(selector);
+    return [...elements].filter((element) => element.offsetParent !== null);
+}
+
+/**
+ * Get all visible entries on the current page.
+ *
+ * @return {Array<Element>}
+ */
+function getVisibleEntries() {
+    return getVisibleElements(".items .item");
+}
+
+/**
+ * Check if the current view is a list view.
+ *
+ * @returns {boolean}
+ */
+function isListView() {
+    return document.querySelector(".items") !== null;
+}
+
+/**
+ * Check if the current view is an entry view.
+ *
+ * @return {boolean}
+ */
+function isEntryView() {
+    return document.querySelector("section.entry") !== null;
+}
+
+/**
+ * Find the entry element for the given element.
+ *
+ * @returns {Element|null}
+ */
+function findEntry(element) {
+    if (isListView()) {
+        if (element) {
+            return element.closest(".item");
+        }
+        return document.querySelector(".current-item");
+    }
+    return document.querySelector(".entry");
+}
+
+/**
+ * Navigate to a specific page.
+ *
+ * @param {string} page - The page to navigate to.
+ * @param {boolean} reloadOnFail - If true, reload the current page if the target page is not found.
+ */
+function goToPage(page, reloadOnFail = false) {
+    const element = document.querySelector(":is(a, button)[data-page=" + page + "]");
+
+    if (element) {
+        document.location.href = element.href;
+    } else if (reloadOnFail) {
+        window.location.reload();
+    }
+}
+
+/**
+ * Navigate to the previous page.
+ *
+ * If the offset is a KeyboardEvent, it will navigate to the previous item in the list.
+ * If the offset is a number, it will jump that many items in the list.
+ * If the offset is TOP, it will jump to the first item in the list.
+ * If the offset is BOTTOM, it will jump to the last item in the list.
+ * If the current view is an entry view, it will redirect to the previous page.
+ *
+ * @param {number|KeyboardEvent} offset - How many items to jump for focus.
+ */
+function goToPreviousPage(offset) {
+    if (offset instanceof KeyboardEvent) offset = -1;
+    if (isListView()) {
+        goToListItem(offset);
+    } else {
+        goToPage("previous");
+    }
+}
+
+/**
+ * Navigate to the next page.
+ *
+ * If the offset is a KeyboardEvent, it will navigate to the next item in the list.
+ * If the offset is a number, it will jump that many items in the list.
+ * If the offset is TOP, it will jump to the first item in the list.
+ * If the offset is BOTTOM, it will jump to the last item in the list.
+ * If the current view is an entry view, it will redirect to the next page.
+ *
+ * @param {number|KeyboardEvent} offset - How many items to jump for focus.
+ */
+function goToNextPage(offset) {
+    if (offset instanceof KeyboardEvent) offset = 1;
+    if (isListView()) {
+        goToListItem(offset);
+    } else {
+        goToPage("next");
+    }
+}
+
+/**
+ * Navigate to the individual feed or feeds page.
+ *
+ * If the current view is an entry view, it will redirect to the feed link of the entry.
+ * If the current view is a list view, it will redirect to the feeds page.
+ */
+function goToFeedOrFeedsPage() {
+    if (isEntryView()) {
+        goToFeedPage();
+    } else {
+        goToPage("feeds");
+    }
+}
+
+/**
+ * Navigate to the feed page of the current entry.
+ *
+ * If the current view is an entry view, it will redirect to the feed link of the entry.
+ * If the current view is a list view, it will redirect to the feed link of the currently selected item.
+ * If no feed link is available, it will do nothing.
+ */
+function goToFeedPage() {
+    if (isEntryView()) {
+        const feedAnchor = document.querySelector("span.entry-website a");
+        if (feedAnchor !== null) {
+            window.location.href = feedAnchor.href;
+        }
+    } else {
+        const currentItemFeed = document.querySelector(".current-item :is(a, button)[data-feed-link]");
+        if (currentItemFeed !== null) {
+            window.location.href = currentItemFeed.getAttribute("href");
+        }
+    }
+}
+
+/**
+ * Navigate to the add subscription page.
+ *
+ * @returns {void}
+ */
+function goToAddSubscriptionPage() {
+    window.location.href = document.body.dataset.addSubscriptionUrl;
+}
+
+/**
+ * Navigate to the next or previous item in the list.
+ *
+ * If the offset is TOP, it will jump to the first item in the list.
+ * If the offset is BOTTOM, it will jump to the last item in the list.
+ * If the offset is a number, it will jump that many items in the list.
+ * If the current view is an entry view, it will redirect to the next or previous page.
+ *
+ * @param {number} offset - How many items to jump for focus.
+ * @return {void}
+ */
+function goToListItem(offset) {
+    const items = getVisibleEntries();
+    if (items.length === 0) {
+        return;
+    }
+
+    if (document.querySelector(".current-item") === null) {
+        items[0].classList.add("current-item");
+        items[0].focus();
+        return;
+    }
+
+    for (let i = 0; i < items.length; i++) {
+        if (items[i].classList.contains("current-item")) {
+            items[i].classList.remove("current-item");
+
+            // By default adjust selection by offset
+            let itemOffset = (i + offset + items.length) % items.length;
+            // Allow jumping to top or bottom
+            if (offset === TOP) {
+                itemOffset = 0;
+            } else if (offset === BOTTOM) {
+                itemOffset = items.length - 1;
+            }
+            const item = items[itemOffset];
+
+            item.classList.add("current-item");
+            scrollPageTo(item);
+            item.focus();
+
+            break;
+        }
+    }
+}
+
+/**
+ * Handle the share action for the entry.
+ *
+ * If the share status is "shared", it will trigger the Web Share API.
+ * If the share status is "share", it will send an Ajax request to fetch the share URL and then trigger the Web Share API.
+ * If the Web Share API is not supported, it will redirect to the entry URL.
+ */
+async function handleShare() {
+    const link = document.querySelector(':is(a, button)[data-share-status]');
+    const title = document.querySelector(".entry-header > h1 > a");
+    if (link.dataset.shareStatus === "shared") {
+        await triggerWebShare(title, link.href);
+    }
+    else if (link.dataset.shareStatus === "share") {
+        const request = new RequestBuilder(link.href);
+        request.withCallback((r) => {
+            // Ensure title is not null before passing to triggerWebShare
+            triggerWebShare(title, r.url);
+        });
+        request.withHttpMethod("GET");
+        request.execute();
+    }
+}
+
+/**
+ * Trigger the Web Share API to share the entry.
+ *
+ * If the Web Share API is not supported, it will redirect to the entry URL.
+ *
+ * @param {Element} title - The title element of the entry.
+ * @param {string} url - The URL of the entry to share.
+ */
+async function triggerWebShare(title, url) {
+    if (!navigator.canShare) {
+        console.error("Your browser doesn't support the Web Share API.");
+        window.location = url;
+        return;
+    }
+    try {
+        await navigator.share({
+            title: title ? title.textContent : url,
+            url: url
+        });
+    } catch (err) {
+        console.error(err);
+    }
+    window.location.reload();
+}
+
 // make logo element as button on mobile layout
 function checkMenuToggleModeByLayout() {
     const logoElement = document.querySelector(".logo");
@@ -490,114 +743,6 @@ function unsubscribeFromFeed() {
     }
 }
 
-/**
- * @param {string} page Page to redirect to.
- * @param {boolean} fallbackSelf Refresh actual page if the page is not found.
- */
-function goToPage(page, fallbackSelf = false) {
-    const element = document.querySelector(":is(a, button)[data-page=" + page + "]");
-
-    if (element) {
-        document.location.href = element.href;
-    } else if (fallbackSelf) {
-        window.location.reload();
-    }
-}
-
-/**
- *
- * @param {(number|event)} offset - many items to jump for focus.
- */
-function goToPrevious(offset) {
-    if (offset instanceof KeyboardEvent) {
-        offset = -1;
-    }
-    if (isListView()) {
-        goToListItem(offset);
-    } else {
-        goToPage("previous");
-    }
-}
-
-/**
- *
- * @param {(number|event)} offset - How many items to jump for focus.
- */
-function goToNext(offset) {
-    if (offset instanceof KeyboardEvent) {
-        offset = 1;
-    }
-    if (isListView()) {
-        goToListItem(offset);
-    } else {
-        goToPage("next");
-    }
-}
-
-function goToFeedOrFeeds() {
-    if (isEntry()) {
-        goToFeed();
-    } else {
-        goToPage('feeds');
-    }
-}
-
-function goToFeed() {
-    if (isEntry()) {
-        const feedAnchor = document.querySelector("span.entry-website a");
-        if (feedAnchor !== null) {
-            window.location.href = feedAnchor.href;
-        }
-    } else {
-        const currentItemFeed = document.querySelector(".current-item :is(a, button)[data-feed-link]");
-        if (currentItemFeed !== null) {
-            window.location.href = currentItemFeed.getAttribute("href");
-        }
-    }
-}
-
-// Sentinel values for specific list navigation
-const TOP = 9999;
-const BOTTOM = -9999;
-
-/**
- * @param {number} offset How many items to jump for focus.
- */
-function goToListItem(offset) {
-    const items = getVisibleEntries();
-    if (items.length === 0) {
-        return;
-    }
-
-    if (document.querySelector(".current-item") === null) {
-        items[0].classList.add("current-item");
-        items[0].focus();
-        return;
-    }
-
-    for (let i = 0; i < items.length; i++) {
-        if (items[i].classList.contains("current-item")) {
-            items[i].classList.remove("current-item");
-
-            // By default adjust selection by offset
-            let itemOffset = (i + offset + items.length) % items.length;
-            // Allow jumping to top or bottom
-            if (offset === TOP) {
-                itemOffset = 0;
-            } else if (offset === BOTTOM) {
-                itemOffset = items.length - 1;
-            }
-            const item = items[itemOffset];
-
-            item.classList.add("current-item");
-            scrollPageTo(item);
-            item.focus();
-
-            break;
-        }
-    }
-}
-
 function scrollToCurrentItem() {
     const currentItem = document.querySelector(".current-item");
     if (currentItem !== null) {
@@ -636,24 +781,6 @@ function updateUnreadCounterValue(callback) {
     }
 }
 
-function isEntry() {
-    return document.querySelector("section.entry") !== null;
-}
-
-function isListView() {
-    return document.querySelector(".items") !== null;
-}
-
-function findEntry(element) {
-    if (isListView()) {
-        if (element) {
-            return element.closest(".item");
-        }
-        return document.querySelector(".current-item");
-    }
-    return document.querySelector(".entry");
-}
-
 function handleConfirmationMessage(linkElement, callback) {
     if (linkElement.tagName !== 'A' && linkElement.tagName !== "BUTTON") {
         linkElement = linkElement.parentNode;
@@ -724,11 +851,6 @@ function showToast(label, iconElement) {
     }, 100);
 }
 
-/** Navigate to the new subscription page. */
-function goToAddSubscription() {
-    window.location.href = document.body.dataset.addSubscriptionUrl;
-}
-
 /**
  * save player position to allow to resume playback later
  * @param {Element} playerElement
@@ -773,54 +895,6 @@ function isPlayerPlaying(element) {
         element.readyState > 2; //https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/readyState
 }
 
-/**
- * handle new share entires and already shared entries
- */
-async function handleShare() {
-    const link = document.querySelector(':is(a, button)[data-share-status]');
-    const title = document.querySelector(".entry-header > h1 > a");
-    if (link.dataset.shareStatus === "shared") {
-        await checkShareAPI(title, link.href);
-    }
-    if (link.dataset.shareStatus === "share") {
-        const request = new RequestBuilder(link.href);
-        request.withCallback((r) => {
-            checkShareAPI(title, r.url);
-        });
-        request.withHttpMethod("GET");
-        request.execute();
-    }
-}
-
-/**
-* wrapper for Web Share API
-*/
-async function checkShareAPI(title, url) {
-    if (!navigator.canShare) {
-        console.error("Your browser doesn't support the Web Share API.");
-        window.location = url;
-        return;
-    }
-    try {
-        await navigator.share({
-            title: title ? title.textContent : url,
-            url: url
-        });
-    } catch (err) {
-        console.error(err);
-    }
-    window.location.reload();
-}
-
-/**
- * Get the CSRF token from the HTML document.
- *
- * @returns {string} The CSRF token.
- */
-function getCsrfToken() {
-    return document.body.dataset.csrfToken || "";
-}
-
 /**
  * Handle all clicks on media player controls button on enclosures.
  * Will change the current speed and position of the player accordingly.

+ 11 - 11
internal/ui/static/js/bootstrap.js

@@ -6,17 +6,17 @@ document.addEventListener("DOMContentLoaded", () => {
         keyboardHandler.on("g u", () => goToPage("unread"));
         keyboardHandler.on("g b", () => goToPage("starred"));
         keyboardHandler.on("g h", () => goToPage("history"));
-        keyboardHandler.on("g f", goToFeedOrFeeds);
+        keyboardHandler.on("g f", goToFeedOrFeedsPage);
         keyboardHandler.on("g c", () => goToPage("categories"));
         keyboardHandler.on("g s", () => goToPage("settings"));
-        keyboardHandler.on("g g", () => goToPrevious(TOP));
-        keyboardHandler.on("G", () => goToNext(BOTTOM));
-        keyboardHandler.on("ArrowLeft", goToPrevious);
-        keyboardHandler.on("ArrowRight", goToNext);
-        keyboardHandler.on("k", goToPrevious);
-        keyboardHandler.on("p", goToPrevious);
-        keyboardHandler.on("j", goToNext);
-        keyboardHandler.on("n", goToNext);
+        keyboardHandler.on("g g", () => goToPreviousPage(TOP));
+        keyboardHandler.on("G", () => goToNextPage(BOTTOM));
+        keyboardHandler.on("ArrowLeft", goToPreviousPage);
+        keyboardHandler.on("ArrowRight", goToNextPage);
+        keyboardHandler.on("k", goToPreviousPage);
+        keyboardHandler.on("p", goToPreviousPage);
+        keyboardHandler.on("j", goToNextPage);
+        keyboardHandler.on("n", goToNextPage);
         keyboardHandler.on("h", () => goToPage("previous"));
         keyboardHandler.on("l", () => goToPage("next"));
         keyboardHandler.on("z t", scrollToCurrentItem);
@@ -32,10 +32,10 @@ document.addEventListener("DOMContentLoaded", () => {
         keyboardHandler.on("s", () => handleSaveEntry());
         keyboardHandler.on("d", handleFetchOriginalContent);
         keyboardHandler.on("f", () => handleBookmark());
-        keyboardHandler.on("F", goToFeed);
+        keyboardHandler.on("F", goToFeedPage);
         keyboardHandler.on("R", handleRefreshAllFeeds);
         keyboardHandler.on("?", showKeyboardShortcuts);
-        keyboardHandler.on("+", goToAddSubscription);
+        keyboardHandler.on("+", goToAddSubscriptionPage);
         keyboardHandler.on("#", unsubscribeFromFeed);
         keyboardHandler.on("/", () => goToPage("search"));
         keyboardHandler.on("a", () => {