do not auto-load the first item in the reading pane
authorxangelo <git@xangelo.ca>
Thu, 7 Apr 2022 19:36:02 +0000 (15:36 -0400)
committerxangelo <git@xangelo.ca>
Thu, 7 Apr 2022 20:07:28 +0000 (16:07 -0400)
This seems useful, but if the user is currently reading something when
the feed item list refreshes it will pull their focus away.

html/app.html [new file with mode: 0644]
html/style.css
src/server.ts

diff --git a/html/app.html b/html/app.html
new file mode 100644 (file)
index 0000000..0928724
--- /dev/null
@@ -0,0 +1,80 @@
+<!doctype html>
+<html>
+  <head>
+    <title>News River</title>
+    <meta charset="utf-8">
+    <link rel="stylesheet" href="style.css">
+    <script src="https://unpkg.com/htmx.org@1.7.0"></script>
+    <base target="_blank">
+  </head>
+  <body class="app">
+    <div id="feed-pane" valign="top" hx-get="/feeds" hx-trigger="load, newFeed from:body, every 900s" hx-target="#feed-list">
+      <h1>Feed List</h1>
+      <div id="feed-list"></div>
+      <div id="add-feed">
+        <form hx-post="/feeds">
+          <input type="url" name="link" placeholder="Link to RSS Feed">
+          <button type="submit" class="btn">Add</button>
+        </form>
+      </div>
+    </div>
+    <div id="list-pane"></div>
+    <div id="reading-pane"></div>
+    <div id="status-bar">
+      A project by <a href="https://xangelo.ca">xangelo</a> that is still in active <a href="https://git.xangelo.ca/?p=rss-reader.git;a=summary">development</a> (2022)
+    </div>
+  </body>
+  <script>
+    function $(sel, root, opts) {
+      const el = (root || document).querySelectorAll(sel);
+      if(opts && opts.array) { 
+        return Array.from(el);
+      }
+      if(el.length === 1) {
+        return el[0];
+      }
+      else {
+        return Array.from(el);
+      }
+    }
+
+function activeMarker(e) {
+  // clear sibling active class.
+  if(e.target.parentElement.tagName === 'LI') {
+    const parent = e.target.parentElement.parentElement;
+    $('a.active', parent, {array: true}).forEach(el => {
+      el.classList.remove('active');
+    });
+  }
+
+  e.target.classList.add('active');
+}
+
+    $('body').addEventListener('click', e => {
+      const actions = e.target.getAttribute('data-actions');
+      if(actions && actions.split(' ').includes('activate')) {
+        activeMarker(e);
+      }
+      if (e.target.classList.contains('unread') && e.target.getAttribute('data-feed-item-id') !== null) {
+        e.target.classList.remove('unread');
+        // ok it was unread.. so we should figure out the id that it belongs
+        // to and decr the counter for that!
+        const feedId = e.target.getAttribute('data-feed-id');
+        const el = $(`#feed-list a[data-feed-id="${feedId}"] .unread-count`);
+        if(el) {
+          if(el.innerHTML.length) {
+            let count = parseInt(el.innerHTML.split('(')[1].split(')')[0]);
+            count--;
+            if(count < 1) {
+              el.innerHTML = '';
+              el.parentElement.classList.remove('unread');
+            }
+            else {
+              el.innerHTML = `(${count})`;
+            }
+          }
+        }
+      }
+    });
+  </script>
+</html>
index 73fb32c8c3a89a11fed045e5ad90deaef6cc5f01..d7fdaa761806159e59f84c6350ae7407da58db35 100644 (file)
@@ -16,6 +16,10 @@ h1 {
   padding: 4px 8px;
 }
 
   padding: 4px 8px;
 }
 
+a {
+  color: #1e61df;
+}
+
 .app {
   display: grid;
   grid-template-columns: 25% 75% auto;
 .app {
   display: grid;
   grid-template-columns: 25% 75% auto;
@@ -30,7 +34,7 @@ h1 {
 }
 
 #feed-item-list {
 }
 
 #feed-item-list {
-  height: calc(100% - 70px);
+  height: calc(100% - 75px);
 }
 #feed-item-list span {
   float: right;
 }
 #feed-item-list span {
   float: right;
index c2a3fdaf1163b43d80da4e7dd6ce0f1525fac57a..4e1404e5385254aff7098fa56191ee991e2f3cf7 100644 (file)
@@ -60,6 +60,9 @@ function apiDelete(endpoint: string, fn: WrappedApiHandler, view?: (arr: any) =>
   apiWrapper('delete', endpoint, fn, view);
 }
 
   apiWrapper('delete', endpoint, fn, view);
 }
 
+apiPost('/login', async (req, res): Promise<any> => {
+});
+
 apiPost('/feeds', async (req, res): Promise<any> => {
   // get info about the feed
   const url = req.body.link;
 apiPost('/feeds', async (req, res): Promise<any> => {
   // get info about the feed
   const url = req.body.link;
@@ -156,8 +159,9 @@ apiGet('/feeds/:feed_id/items', async (req, res): Promise<any> => {
       <ul class="scrollable list" id="feed-item-list">
   ${feedData.items.map((item: any, index: number) => {
     const read = !!item.read_at;
       <ul class="scrollable list" id="feed-item-list">
   ${feedData.items.map((item: any, index: number) => {
     const read = !!item.read_at;
+    const first = index === 0;
     return `<li>
     return `<li>
-      <a href="#" class="${index === 0 ? 'active': ''} ${read ? '': 'unread'}" data-actions="activate" hx-get="/feeds/${item.feed_id}/items/${item.id}" hx-trigger="${index === 0 ? 'load,': ''}click" hx-target="#reading-pane" data-feed-item-id="${item.id}" data-feed-id="${item.feed_id}">${item.title}
+      <a href="#" class="${first ? 'active': ''} ${read ? '': 'unread'}" data-actions="activate" hx-get="/feeds/${item.feed_id}/items/${item.id}" hx-trigger="click" hx-target="#reading-pane" data-feed-item-id="${item.id}" data-feed-id="${item.feed_id}">${item.title}
       <span class="date">${reasonable(new Date(item.pub_date * 1000))}</span>
       </a>
     </li>
       <span class="date">${reasonable(new Date(item.pub_date * 1000))}</span>
       </a>
     </li>