diff --git a/assets/css/app.css b/assets/css/app.css index 48b0e1e..fa24032 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -504,7 +504,7 @@ nav .active a { text-decoration: none; } -.item a:visited { +#item-content a:visited { color: purple; } diff --git a/assets/js/app.js b/assets/js/app.js index 49792a6..dd736cc 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -1,638 +1,32 @@ -(function() { +var Miniflux = {}; - // List of subscriptions - var feeds = []; +Miniflux.App = (function() { - // List of feeds currently updating - var queue = []; + return { + Run: function() { + Miniflux.Event.ListenKeyboardEvents(); + Miniflux.Event.ListenMouseEvents(); + }, + MozillaAuth: function(action) { + navigator.id.watch({ + onlogin: function(assertion) { - // Number of concurrent requests when updating all feeds - var queue_length = 5; + var xhr = new XMLHttpRequest(); + xhr.open("POST", "?action=" + action, true); + xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + xhr.setRequestHeader("Connection", "close"); - // Keyboard shortcuts queue - var keyqueue = []; + xhr.onload = function () { + window.location.href = this.responseText; + }; - // Download full content from the original website - function download_item() - { - var container = document.getElementById("download-item"); - if (! container) return; + xhr.send("token=" + assertion); + }, + onlogout: function() {} + }); - var item_id = container.getAttribute("data-item-id"); - var message = container.getAttribute("data-before-message"); - - var img = document.createElement("img"); - img.src = "assets/img/refresh.gif"; - - container.innerHTML = ""; - container.className = "downloading"; - container.appendChild(img); - container.appendChild(document.createTextNode(" " + message)); - - var request = new XMLHttpRequest(); - - request.onload = function() { - - var response = JSON.parse(request.responseText); - - if (response.result) { - - var content = document.getElementById("item-content"); - if (content) content.innerHTML = response.content; - - if (container) { - - var message = container.getAttribute("data-after-message"); - - container.innerHTML = ""; - container.appendChild(document.createTextNode(" " + message)); - } - } - else { - - if (container) { - - var message = container.getAttribute("data-failure-message"); - - container.innerHTML = ""; - container.appendChild(document.createTextNode(" " + message)); - } - } - }; - - request.open("POST", "?action=download-item&id=" + item_id, true); - request.send(); - } - - // Flip item status between unread and read - function switch_status(item_id, hide) - { - var request = new XMLHttpRequest(); - - request.onload = function() { - - if (is_listing()) { - - var response = JSON.parse(request.responseText); - - if (response.status == "read" || response.status == "unread") { - - find_next_item(); - - if (hide) { - remove_item(response.item_id); - } - else if (response.status == "read") { - show_item_as_read(item_id); - } - else if (response.status == "unread") { - show_item_as_unread(item_id); - } - } - } - } - - request.open("POST", "?action=change-item-status&id=" + item_id, true); - request.send(); - } - - // Set all items of the current page to the status read and redirect to the main page - function mark_items_as_read(redirect) - { - var articles = document.getElementsByTagName("article"); - var idlist = []; - - for (var i = 0, ilen = articles.length; i < ilen; i++) { - idlist.push(articles[i].getAttribute("data-item-id")); - } - - var request = new XMLHttpRequest(); - - request.onload = function() { - window.location.href = redirect; - }; - - request.open("POST", "?action=mark-items-as-read", true); - request.send(JSON.stringify(idlist)); - } - - // Mark the current item read and hide this item - function mark_as_read(item_id) - { - var request = new XMLHttpRequest(); - - request.onload = function() { - remove_item(item_id); - }; - - request.open("POST", "?action=mark-item-read&id=" + item_id, true); - request.send(); - } - - // Set the current item unread and hide this item - function mark_as_unread(item_id) - { - var request = new XMLHttpRequest(); - - request.onload = function() { - remove_item(item_id); - }; - - request.open("POST", "?action=mark-item-unread&id=" + item_id, true); - request.send(); - } - - // Bookmark the selected item - function bookmark_item() - { - var item = document.getElementById("current-item"); - - if (item) { - - var item_id = item.getAttribute("data-item-id"); - var link = document.getElementById("bookmark-" + item_id); - - if (link) link.click(); + navigator.id.request(); } } - // Show an item as read (change title color and add icon) - function show_item_as_read(item_id) - { - var link = document.getElementById("open-" + item_id); - - if (link) { - link.className = "read"; - - var icon = document.createElement("span"); - icon.id = "read-icon-" + item_id; - icon.appendChild(document.createTextNode("☑ ")); - link.parentNode.insertBefore(icon, link); - } - } - - // Show an item as unread (change title color and remove read icon) - function show_item_as_unread(item_id) - { - var link = document.getElementById("open-" + item_id); - if (link) link.className = ""; - - var icon = document.getElementById("read-icon-" + item_id); - if (icon) icon.parentNode.removeChild(icon); - } - - // Show the refresh icon when updating a feed - function show_refresh_icon(feed_id) - { - var container = document.getElementById("loading-feed-" + feed_id); - - if (container) { - - var img = document.createElement("img"); - img.src = "assets/img/refresh.gif"; - - container.appendChild(img); - } - } - - // Hide the refresh icon after update - function hide_refresh_icon(feed_id) - { - var container = document.getElementById("loading-feed-" + feed_id); - if (container) container.innerHTML = ""; - - var container = document.getElementById("last-checked-feed-" + feed_id); - if (container) container.innerHTML = container.getAttribute("data-after-update"); - } - - // Update one feed in the background and execute a callback after that - function refresh_feed(feed_id, callback) - { - if (! feed_id) return false; - - show_refresh_icon(feed_id); - - var request = new XMLHttpRequest(); - - request.onload = function() { - - hide_refresh_icon(feed_id); - - try { - if (callback) { - callback(JSON.parse(this.responseText)); - } - } - catch (e) {} - }; - - request.open("POST", "?action=refresh-feed&feed_id=" + feed_id, true); - request.send(); - - return true; - } - - // Get all subscriptions from the feeds page - function get_feeds() - { - var links = document.getElementsByTagName("a"); - - for (var i = 0, ilen = links.length; i < ilen; i++) { - var feed_id = links[i].getAttribute('data-feed-id'); - if (feed_id) feeds.push(parseInt(feed_id)); - } - } - - // Refresh all feeds (use a queue to allow 5 concurrent feed updates) - function refresh_all() - { - get_feeds(); - - var interval = setInterval(function() { - - while (feeds.length > 0 && queue.length < queue_length) { - - var feed_id = feeds.shift(); - queue.push(feed_id); - - refresh_feed(feed_id, function(response) { - - var index = queue.indexOf(response.feed_id); - if (index >= 0) queue.splice(index, 1); - - if (feeds.length == 0 && queue.length == 0) { - clearInterval(interval); - window.location.href = "?action=unread"; - } - }); - } - - }, 100); - } - - // Go the next page - function open_next_page() - { - var link = document.getElementById("next-page"); - if (link) link.click(); - } - - // Go to the previous page - function open_previous_page() - { - var link = document.getElementById("previous-page"); - if (link) link.click(); - } - - // Hide one item and update the item counter on the top - function remove_item(item_id) - { - var item = document.getElementById("item-" + item_id); - - if (! item) { - item = document.getElementById("current-item"); - if (item.getAttribute("data-item-id") != item_id) item = false; - } - - if (item && item.getAttribute("data-hide")) { - - item.parentNode.removeChild(item); - var container = document.getElementById("page-counter"); - - if (container) { - - counter = parseInt(container.textContent.trim(), 10) - 1; - - if (counter == 0) { - - window.location = "?action=feeds¬hing_to_read=1"; - } - else { - - container.textContent = counter + " "; - document.title = "miniflux (" + counter + ")"; - document.getElementById("nav-counter").textContent = "(" + counter + ")"; - } - } - } - } - - // Open the original url inside a new tab - function open_original_item() - { - var link = document.getElementById("original-item"); - - if (link) { - - if (is_listing() && link.getAttribute("data-hide")) { - mark_as_read(link.getAttribute("data-item-id")); - } - - link.removeAttribute("data-action"); - link.click(); - } - } - - // Show item content - function open_item() - { - var link = document.getElementById("open-item"); - if (link) link.click(); - } - - // Show the next item - function open_next_item() - { - var link = document.getElementById("next-item"); - - if (link) { - - link.click(); - } - else if (is_listing()) { - - find_next_item(); - } - } - - // Show the previous item - function open_previous_item() - { - var link = document.getElementById("previous-item"); - - if (link) { - - link.click(); - } - else if (is_listing()) { - - find_previous_item(); - } - } - - // Change item status and select the next item in the list - function change_item_status() - { - if (is_listing() && ! document.getElementById("current-item")) { - find_next_item(); - } - - var item = document.getElementById("current-item"); - - if (item) { - switch_status(item.getAttribute("data-item-id"), item.getAttribute("data-hide")); - } - } - - // Scroll automatically the page when using keyboard shortcuts - function scroll_page_to(item) - { - var clientHeight = pageYOffset + document.documentElement.clientHeight; - var itemPosition = item.offsetTop + item.offsetHeight; - - if (clientHeight - itemPosition < 0 || clientHeight - item.offsetTop > document.documentElement.clientHeight) { - window.scrollTo(0, item.offsetTop - 10); - } - } - - // Prepare the DOM for the selected item - function set_links_item(item_id) - { - var link = document.getElementById("current-item"); - if (link) scroll_page_to(link); - - var link = document.getElementById("original-item"); - if (link) link.id = "original-" + link.getAttribute("data-item-id"); - - var link = document.getElementById("open-item"); - if (link) link.id = "open-" + link.getAttribute("data-item-id"); - - var link = document.getElementById("original-" + item_id); - if (link) link.id = "original-item"; - - var link = document.getElementById("open-" + item_id); - if (link) link.id = "open-item"; - } - - // Find the next item in the listing page - function find_next_item() - { - var items = document.getElementsByTagName("article"); - - if (! document.getElementById("current-item")) { - - items[0].id = "current-item"; - set_links_item(items[0].getAttribute("data-item-id")); - } - else { - - for (var i = 0, ilen = items.length; i < ilen; i++) { - - if (items[i].id == "current-item") { - - items[i].id = "item-" + items[i].getAttribute("data-item-id"); - - if (i + 1 < ilen) { - - items[i + 1].id = "current-item"; - set_links_item(items[i + 1].getAttribute("data-item-id")); - } - - break; - } - } - } - } - - // Find the previous item in the listing page - function find_previous_item() - { - var items = document.getElementsByTagName("article"); - - if (! document.getElementById("current-item")) { - - items[items.length - 1].id = "current-item"; - set_links_item(items[items.length - 1].getAttribute("data-item-id")); - } - else { - - for (var i = items.length - 1; i >= 0; i--) { - - if (items[i].id == "current-item") { - - items[i].id = "item-" + items[i].getAttribute("data-item-id"); - - if (i - 1 >= 0) { - - items[i - 1].id = "current-item"; - set_links_item(items[i - 1].getAttribute("data-item-id")); - } - - break; - } - } - } - } - - // Check if we are on a listing page - function is_listing() - { - if (document.getElementById("listing")) return true; - return false; - } - - // Authentication with Mozilla Persona - function mozilla_auth(action) - { - navigator.id.watch({ - onlogin: function(assertion) { - - var xhr = new XMLHttpRequest(); - xhr.open("POST", "?action=" + action, true); - xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); - xhr.setRequestHeader("Connection", "close"); - - xhr.onload = function () { - window.location.href = this.responseText; - }; - - xhr.send("token=" + assertion); - }, - onlogout: function() {} - }); - - navigator.id.request(); - } - - // Click event handler, if there is a "data-action" attribute execute the corresponding callback - document.onclick = function(e) { - - var action = e.target.getAttribute("data-action"); - - if (action) { - - switch (action) { - case 'refresh-all': - e.preventDefault(); - refresh_all(); - break; - case 'refresh-feed': - e.preventDefault(); - var feed_id = e.target.getAttribute("data-feed-id"); - refresh_feed(feed_id); - break; - case 'mark-read': - e.preventDefault(); - var item_id = e.target.getAttribute("data-item-id"); - mark_as_read(item_id); - break; - case 'mark-unread': - e.preventDefault(); - var item_id = e.target.getAttribute("data-item-id"); - mark_as_unread(item_id); - break; - case 'mark-all-read': - e.preventDefault(); - mark_items_as_read("?action=unread"); - break; - case 'mark-feed-read': - e.preventDefault(); - mark_items_as_read("?action=feed-items&feed_id=" + e.target.getAttribute("data-feed-id")); - break; - case 'original-link': - var item_id = e.target.getAttribute("data-item-id"); - mark_as_read(item_id); - break; - case 'download-item': - e.preventDefault(); - download_item(); - break; - case 'mozilla-login': - e.preventDefault(); - mozilla_auth("mozilla-auth"); - break; - case 'mozilla-link': - e.preventDefault(); - mozilla_auth("mozilla-link"); - break; - } - } - }; - - // Keyboard handler, handle keyboard shortcuts - document.onkeypress = function(e) { - - keyqueue.push(e.keyCode || e.which); - - if (keyqueue[0] == 103) { // g - - switch (keyqueue[1]) { - case undefined: - break; - case 117: // u - window.location.href = "?action=unread"; - keyqueue = []; - break; - case 98: // b - window.location.href = "?action=bookmarks"; - keyqueue = []; - break; - case 104: // h - window.location.href = "?action=history"; - keyqueue = []; - break; - case 115: // s - window.location.href = "?action=feeds"; - keyqueue = []; - break; - case 112: // p - window.location.href = "?action=config"; - keyqueue = []; - break; - default: - keyqueue = []; - break; - } - } - else { - - keyqueue = []; - - switch (e.keyCode || e.which) { - case 100: // d - download_item(); - break; - case 112: // p - case 107: // k - open_previous_item(); - break; - case 110: // n - case 106: // j - open_next_item(); - break; - case 118: // v - open_original_item(); - break; - case 111: // o - open_item(); - break; - case 109: // m - change_item_status(); - break; - case 102: // f - bookmark_item(); - break; - case 104: // h - open_previous_page(); - break - case 108: // l - open_next_page(); - break; - case 63: // ? - open("?action=show-help", "Help", "width=320,height=450,location=no,scrollbars=no,status=no,toolbar=no"); - break; - } - } - }; - })(); diff --git a/assets/js/event.js b/assets/js/event.js new file mode 100644 index 0000000..e1bf0ad --- /dev/null +++ b/assets/js/event.js @@ -0,0 +1,141 @@ +Miniflux.Event = (function() { + + var queue = []; + + return { + ListenMouseEvents: function() { + + document.onclick = function(e) { + + var action = e.target.getAttribute("data-action"); + + if (action) { + + switch (action) { + case 'refresh-all': + e.preventDefault(); + Miniflux.Feed.UpdateAll(); + break; + case 'refresh-feed': + e.preventDefault(); + Miniflux.Feed.Update(e.target.getAttribute("data-feed-id")); + break; + case 'mark-read': + e.preventDefault(); + Miniflux.Item.MarkAsRead(e.target.getAttribute("data-item-id")); + break; + case 'mark-unread': + e.preventDefault(); + Miniflux.Item.MarkAsUnread(e.target.getAttribute("data-item-id")); + break; + case 'bookmark': + e.preventDefault(); + Miniflux.Item.SwitchBookmark(Miniflux.Item.Get(e.target.getAttribute("data-item-id"))); + break; + case 'download-item': + e.preventDefault(); + Miniflux.Item.DownloadContent(); + break; + case 'original-link': + Miniflux.Item.OpenOriginal(e.target.getAttribute("data-item-id")); + break; + case 'mark-all-read': + e.preventDefault(); + Miniflux.Item.MarkListingAsRead("?action=unread"); + break; + case 'mark-feed-read': + e.preventDefault(); + Miniflux.Item.MarkListingAsRead("?action=feed-items&feed_id=" + e.target.getAttribute("data-feed-id")); + break; + case 'mozilla-login': + e.preventDefault(); + Miniflux.App.MozillaAuth("mozilla-auth"); + break; + case 'mozilla-link': + e.preventDefault(); + Miniflux.App.MozillaAuth("mozilla-link"); + break; + } + } + }; + }, + ListenKeyboardEvents: function() { + + document.onkeypress = function(e) { + + queue.push(e.keyCode || e.which); + + if (queue[0] == 103) { // g + + switch (queue[1]) { + case undefined: + break; + case 117: // u + window.location.href = "?action=unread"; + queue = []; + break; + case 98: // b + window.location.href = "?action=bookmarks"; + queue = []; + break; + case 104: // h + window.location.href = "?action=history"; + queue = []; + break; + case 115: // s + window.location.href = "?action=feeds"; + queue = []; + break; + case 112: // p + window.location.href = "?action=config"; + queue = []; + break; + default: + queue = []; + break; + } + } + else { + + queue = []; + + switch (e.keyCode || e.which) { + case 100: // d + Miniflux.Item.DownloadContent(Miniflux.Nav.GetCurrentItemId()); + break; + case 112: // p + case 107: // k + Miniflux.Nav.SelectPreviousItem(); + break; + case 110: // n + case 106: // j + Miniflux.Nav.SelectNextItem(); + break; + case 118: // v + Miniflux.Item.OpenOriginal(Miniflux.Nav.GetCurrentItemId()); + break; + case 111: // o + Miniflux.Item.Show(Miniflux.Nav.GetCurrentItemId()); + break; + case 109: // m + Miniflux.Item.SwitchStatus(Miniflux.Nav.GetCurrentItem()); + break; + case 102: // f + Miniflux.Item.SwitchBookmark(Miniflux.Nav.GetCurrentItem()); + break; + case 104: // h + Miniflux.Nav.OpenPreviousPage(); + break + case 108: // l + Miniflux.Nav.OpenNextPage(); + break; + case 63: // ? + Miniflux.Nav.ShowHelp(); + break; + } + } + } + } + }; + +})(); \ No newline at end of file diff --git a/assets/js/feed.js b/assets/js/feed.js new file mode 100644 index 0000000..3be193e --- /dev/null +++ b/assets/js/feed.js @@ -0,0 +1,93 @@ +Miniflux.Feed = (function() { + + // List of subscriptions + var feeds = []; + + // List of feeds currently updating + var queue = []; + + // Number of concurrent requests when updating all feeds + var queue_length = 5; + + // Show the refresh icon when updating a feed + function showRefreshIcon(feed_id) + { + var container = document.getElementById("loading-feed-" + feed_id); + + if (container) { + var img = document.createElement("img"); + img.src = "assets/img/refresh.gif"; + container.appendChild(img); + } + } + + // Hide the refresh icon after update + function hideRefreshIcon(feed_id) + { + var container = document.getElementById("loading-feed-" + feed_id); + if (container) container.innerHTML = ""; + + var container = document.getElementById("last-checked-feed-" + feed_id); + if (container) container.innerHTML = container.getAttribute("data-after-update"); + } + + // Get all subscriptions from the feeds page + function loadFeeds() + { + var links = document.getElementsByTagName("a"); + + for (var i = 0, ilen = links.length; i < ilen; i++) { + var feed_id = links[i].getAttribute('data-feed-id'); + if (feed_id) feeds.push(parseInt(feed_id)); + } + } + + return { + Update: function(feed_id, callback) { + + showRefreshIcon(feed_id); + + var request = new XMLHttpRequest(); + + request.onload = function() { + + hideRefreshIcon(feed_id); + + try { + if (callback) { + callback(JSON.parse(this.responseText)); + } + } + catch (e) {} + }; + + request.open("POST", "?action=refresh-feed&feed_id=" + feed_id, true); + request.send(); + }, + UpdateAll: function() { + + loadFeeds(); + + var interval = setInterval(function() { + + while (feeds.length > 0 && queue.length < queue_length) { + + var feed_id = feeds.shift(); + queue.push(feed_id); + + Miniflux.Feed.Update(feed_id, function(response) { + + var index = queue.indexOf(response.feed_id); + if (index >= 0) queue.splice(index, 1); + + if (feeds.length == 0 && queue.length == 0) { + clearInterval(interval); + window.location.href = "?action=unread"; + } + }); + } + + }, 100); + } + }; +})(); \ No newline at end of file diff --git a/assets/js/item.js b/assets/js/item.js new file mode 100644 index 0000000..ad36982 --- /dev/null +++ b/assets/js/item.js @@ -0,0 +1,314 @@ +Miniflux.Item = (function() { + + function getItem(item_id) + { + var item = document.getElementById("item-" + item_id); + + if (! item) { + item = document.getElementById("current-item"); + if (item.getAttribute("data-item-id") != item_id) item = false; + } + + return item; + } + + function changeBookmarkLabel(item_id) + { + var link = document.getElementById("bookmark-" + item_id); + + if (link && link.getAttribute("data-reverse-label")) { + var content = link.innerHTML; + link.innerHTML = link.getAttribute("data-reverse-label"); + link.setAttribute("data-reverse-label", content); + } + } + + function showItemBookmarked(item_id, item) + { + if (! Miniflux.Nav.IsListing()) { + + var link = document.getElementById("bookmark-" + item_id); + if (link) link.innerHTML = "★"; + } + else { + + var link = document.getElementById("show-" + item_id); + + if (link) { + var icon = document.createElement("span"); + icon.id = "bookmark-icon-" + item_id; + icon.appendChild(document.createTextNode("★ ")); + link.parentNode.insertBefore(icon, link); + } + + changeBookmarkLabel(item_id); + } + } + + function hideItemBookmarked(item_id, item) + { + if (! Miniflux.Nav.IsListing()) { + + var link = document.getElementById("bookmark-" + item_id); + if (link) link.innerHTML = "☆"; + } + else { + + var icon = document.getElementById("bookmark-icon-" + item_id); + if (icon) icon.parentNode.removeChild(icon); + + changeBookmarkLabel(item_id); + } + } + + function changeStatusLabel(item_id) + { + var link = document.getElementById("status-" + item_id); + + if (link) { + var content = link.innerHTML; + link.innerHTML = link.getAttribute("data-reverse-label"); + link.setAttribute("data-reverse-label", content); + } + } + + function showItemAsRead(item_id) + { + var item = getItem(item_id); + + if (item) { + if (item.getAttribute("data-hide")) { + hideItem(item); + } + else { + + item.setAttribute("data-item-status", "read"); + changeStatusLabel(item_id); + + // Show icon + var link = document.getElementById("show-" + item_id); + + if (link) { + link.className = "read"; + + var icon = document.createElement("span"); + icon.id = "read-icon-" + item_id; + icon.appendChild(document.createTextNode("☑ ")); + link.parentNode.insertBefore(icon, link); + } + + // Change action + link = document.getElementById("status-" + item_id); + if (link) link.setAttribute("data-action", "mark-unread"); + } + } + } + + function showItemAsUnread(item_id) + { + var item = getItem(item_id); + + if (item) { + if (item.getAttribute("data-hide")) { + hideItem(item); + } + else { + + item.setAttribute("data-item-status", "unread"); + changeStatusLabel(item_id); + + // Remove icon + var link = document.getElementById("show-" + item_id); + if (link) link.className = ""; + + var icon = document.getElementById("read-icon-" + item_id); + if (icon) icon.parentNode.removeChild(icon); + + // Change action + link = document.getElementById("status-" + item_id); + if (link) link.setAttribute("data-action", "mark-read"); + } + } + } + + function hideItem(item) + { + Miniflux.Nav.SelectNextItem(); + + item.parentNode.removeChild(item); + var container = document.getElementById("page-counter"); + + if (container) { + + counter = parseInt(container.textContent.trim(), 10) - 1; + + if (counter == 0) { + window.location = "?action=feeds¬hing_to_read=1"; + } + else { + container.textContent = counter + " "; + document.title = "miniflux (" + counter + ")"; + document.getElementById("nav-counter").textContent = "(" + counter + ")"; + } + } + } + + function markAsRead(item_id) + { + var request = new XMLHttpRequest(); + request.onload = function() { + if (Miniflux.Nav.IsListing()) showItemAsRead(item_id); + }; + request.open("POST", "?action=mark-item-read&id=" + item_id, true); + request.send(); + } + + function markAsUnread(item_id) + { + var request = new XMLHttpRequest(); + request.onload = function() { + if (Miniflux.Nav.IsListing()) showItemAsUnread(item_id); + }; + request.open("POST", "?action=mark-item-unread&id=" + item_id, true); + request.send(); + } + + function bookmark(item, value) + { + var item_id = item.getAttribute("data-item-id"); + var request = new XMLHttpRequest(); + + request.onload = function() { + + item.setAttribute("data-item-bookmark", value); + + if (value) { + showItemBookmarked(item_id, item); + } + else { + hideItemBookmarked(item_id, item); + } + }; + + request.open("POST", "?action=bookmark&id=" + item_id + "&value=" + value, true); + request.send(); + } + + return { + Get: getItem, + MarkAsRead: markAsRead, + MarkAsUnread: markAsUnread, + SwitchBookmark: function(item) { + + var bookmarked = item.getAttribute("data-item-bookmark"); + + if (bookmarked == "1") { + bookmark(item, 0); + } + else { + bookmark(item, 1); + } + }, + SwitchStatus: function(item) { + + var item_id = item.getAttribute("data-item-id"); + var status = item.getAttribute("data-item-status"); + + if (status == "read") { + markAsUnread(item_id); + } + else if (status == "unread") { + markAsRead(item_id); + } + }, + ChangeStatus: function(item_id, status) { + + switch (status) { + case "read": + markAsRead(item_id); + break; + case "unread": + markAsUnread(item_id); + break; + } + }, + Show: function(item_id) { + var link = document.getElementById("show-" + item_id); + if (link) link.click(); + }, + OpenOriginal: function(item_id) { + + var link = document.getElementById("original-" + item_id); + + if (link) { + if (getItem(item_id).getAttribute("data-item-status") == "unread") markAsRead(item_id); + link.removeAttribute("data-action"); + link.click(); + } + }, + DownloadContent: function() { + + var container = document.getElementById("download-item"); + if (! container) return; + + var item_id = container.getAttribute("data-item-id"); + var message = container.getAttribute("data-before-message"); + + var img = document.createElement("img"); + img.src = "./assets/img/refresh.gif"; + + container.innerHTML = ""; + container.className = "downloading"; + container.appendChild(img); + container.appendChild(document.createTextNode(" " + message)); + + var request = new XMLHttpRequest(); + + request.onload = function() { + + var response = JSON.parse(request.responseText); + + if (response.result) { + + var content = document.getElementById("item-content"); + if (content) content.innerHTML = response.content; + + if (container) { + var message = container.getAttribute("data-after-message"); + container.innerHTML = ""; + container.appendChild(document.createTextNode(" " + message)); + } + } + else { + + if (container) { + var message = container.getAttribute("data-failure-message"); + container.innerHTML = ""; + container.appendChild(document.createTextNode(" " + message)); + } + } + }; + + request.open("POST", "?action=download-item&id=" + item_id, true); + request.send(); + }, + MarkListingAsRead: function(redirect) { + var articles = document.getElementsByTagName("article"); + var listing = []; + + for (var i = 0, ilen = articles.length; i < ilen; i++) { + listing.push(articles[i].getAttribute("data-item-id")); + } + + var request = new XMLHttpRequest(); + + request.onload = function() { + window.location.href = redirect; + }; + + request.open("POST", "?action=mark-items-as-read", true); + request.send(JSON.stringify(listing)); + } + }; + +})(); \ No newline at end of file diff --git a/assets/js/nav.js b/assets/js/nav.js new file mode 100644 index 0000000..f526868 --- /dev/null +++ b/assets/js/nav.js @@ -0,0 +1,118 @@ +Miniflux.Nav = (function() { + + function scrollPageTo(item) + { + var clientHeight = pageYOffset + document.documentElement.clientHeight; + var itemPosition = item.offsetTop + item.offsetHeight; + + if (clientHeight - itemPosition < 0 || clientHeight - item.offsetTop > document.documentElement.clientHeight) { + window.scrollTo(0, item.offsetTop - 10); + } + } + + function findNextItem() + { + var items = document.getElementsByTagName("article"); + + if (! document.getElementById("current-item")) { + + items[0].id = "current-item"; + scrollPageTo(items[0]); + } + else { + + for (var i = 0, ilen = items.length; i < ilen; i++) { + + if (items[i].id == "current-item") { + + items[i].id = "item-" + items[i].getAttribute("data-item-id"); + + if (i + 1 < ilen) { + items[i + 1].id = "current-item"; + scrollPageTo(items[i + 1]); + } + + break; + } + } + } + } + + function findPreviousItem() + { + var items = document.getElementsByTagName("article"); + + if (! document.getElementById("current-item")) { + + items[items.length - 1].id = "current-item"; + scrollPageTo(items[items.length - 1]); + } + else { + + for (var i = items.length - 1; i >= 0; i--) { + + if (items[i].id == "current-item") { + + items[i].id = "item-" + items[i].getAttribute("data-item-id"); + + if (i - 1 >= 0) { + items[i - 1].id = "current-item"; + scrollPageTo(items[i - 1]); + } + + break; + } + } + } + } + + function isListing() + { + if (document.getElementById("listing")) return true; + return false; + } + + return { + GetCurrentItem: function() { + return document.getElementById("current-item"); + }, + GetCurrentItemId: function() { + var item = Miniflux.Nav.GetCurrentItem(); + if (item) return item.getAttribute("data-item-id"); + return null; + }, + OpenNextPage: function() { + var link = document.getElementById("next-page"); + if (link) link.click(); + }, + OpenPreviousPage: function() { + var link = document.getElementById("previous-page"); + if (link) link.click(); + }, + SelectNextItem: function() { + var link = document.getElementById("next-item"); + + if (link) { + link.click(); + } + else if (isListing()) { + findNextItem(); + } + }, + SelectPreviousItem: function() { + var link = document.getElementById("previous-item"); + + if (link) { + link.click(); + } + else if (isListing()) { + findPreviousItem(); + } + }, + ShowHelp: function() { + open("?action=show-help", "Help", "width=320,height=450,location=no,scrollbars=no,status=no,toolbar=no"); + }, + IsListing: isListing + }; + +})(); \ No newline at end of file diff --git a/index.php b/index.php index 0a30bf5..6a79ccb 100644 --- a/index.php +++ b/index.php @@ -23,7 +23,7 @@ Session\open(dirname($_SERVER['PHP_SELF'])); // Called before each action Router\before(function($action) { - $ignore_actions = array('login', 'google-auth', 'google-redirect-auth', 'mozilla-auth'); + $ignore_actions = array('js', 'login', 'google-auth', 'google-redirect-auth', 'mozilla-auth'); if (! isset($_SESSION['user']) && ! in_array($action, $ignore_actions)) { Response\redirect('?action=login'); @@ -49,6 +49,20 @@ Router\before(function($action) { }); +// Javascript assets +Router\get_action('js', function() { + + $data = file_get_contents('assets/js/app.js'); + $data .= file_get_contents('assets/js/feed.js'); + $data .= file_get_contents('assets/js/item.js'); + $data .= file_get_contents('assets/js/event.js'); + $data .= file_get_contents('assets/js/nav.js'); + $data .= 'Miniflux.App.Run();'; + + Response\js($data); +}); + + // Logout and destroy session Router\get_action('logout', function() { @@ -199,15 +213,6 @@ Router\post_action('mark-item-unread', function() { }); -// Ajax call to bookmark an item -Router\post_action('bookmark-item', function() { - - $id = Request\param('id'); - Model\bookmark_item($id); - Response\json(array('Ok')); -}); - - // Ajax call change item status Router\post_action('change-item-status', function() { @@ -220,6 +225,18 @@ Router\post_action('change-item-status', function() { }); +// Ajax call to add or remove a bookmark +Router\post_action('bookmark', function() { + + $id = Request\param('id'); + $value = Request\int_param('value'); + + Model\set_bookmark_value($id, $value); + + Response\json(array('id' => $id, 'value' => $value)); +}); + + // Add new bookmark Router\get_action('bookmark', function() { @@ -253,6 +270,8 @@ Router\get_action('history', function() { 'updated', Model\get_config_value('items_sorting_direction') ), + 'order' => '', + 'direction' => '', 'nb_items' => $nb_items, 'offset' => $offset, 'items_per_page' => Model\get_config_value('items_per_page'), @@ -294,6 +313,8 @@ Router\get_action('bookmarks', function() { $nb_items = Model\count_bookmarks(); Response\html(Template\layout('bookmarks', array( + 'order' => '', + 'direction' => '', 'items' => Model\get_bookmarks($offset, Model\get_config_value('items_per_page')), 'nb_items' => $nb_items, 'offset' => $offset, diff --git a/model.php b/model.php index 19a9dd8..562f61d 100644 --- a/model.php +++ b/model.php @@ -575,6 +575,7 @@ function get_bookmarks($offset = null, $limit = null) 'items.title', 'items.updated', 'items.url', + 'items.bookmark', 'items.status', 'items.content', 'items.feed_id', diff --git a/templates/bookmark_links.php b/templates/bookmark_links.php new file mode 100644 index 0000000..6fbe3e1 --- /dev/null +++ b/templates/bookmark_links.php @@ -0,0 +1,21 @@ + + + + | + + + + | + diff --git a/templates/bookmarks.php b/templates/bookmarks.php index 3c42490..65301f4 100644 --- a/templates/bookmarks.php +++ b/templates/bookmarks.php @@ -1,7 +1,5 @@ -

-
- -
-

- - - -

-

- -

-

- | - | - - - - - | - - - - - -

-
- - - + + $item, 'menu' => $menu, 'offset' => $offset, 'hide' => false)) ?> + + $menu, 'nb_items' => $nb_items, 'items_per_page' => $items_per_page, 'offset' => $offset, 'order' => $order, 'direction' => $direction)) ?>
\ No newline at end of file diff --git a/templates/feed_items.php b/templates/feed_items.php index dc9417d..612433a 100644 --- a/templates/feed_items.php +++ b/templates/feed_items.php @@ -1,9 +1,7 @@ -

go back to unread items') ?>

-
- -
-

- ★ ' : '' ?> - ☑ ' : '' ?> - - > - - -

-

- -

-

- | - | + + $item, 'menu' => $menu, 'offset' => $offset, 'hide' => false)) ?> + - - - | - - | - - - - - - - | - - - - | - - - - | - - - - - -

-
- - -
- -
- -
- 0): ?> - « -  -  - -   - $items_per_page): ?> - » - -
+
+ +
+ $menu, 'nb_items' => $nb_items, 'items_per_page' => $items_per_page, 'offset' => $offset, 'order' => $order, 'direction' => $direction, 'feed_id' => $feed['id'])) ?>
\ No newline at end of file diff --git a/templates/history.php b/templates/history.php index 52e3172..f2c7ecb 100644 --- a/templates/history.php +++ b/templates/history.php @@ -10,68 +10,11 @@
- -
-

- - - - -

-

- -

-

- | - - | - - - - | - - - - - - | - - - | - - - - - -

-
- - - + + $item, 'menu' => $menu, 'offset' => $offset, 'hide' => true)) ?> + + $menu, 'nb_items' => $nb_items, 'items_per_page' => $items_per_page, 'offset' => $offset, 'order' => $order, 'direction' => $direction)) ?>
diff --git a/templates/item.php b/templates/item.php new file mode 100644 index 0000000..cb64d13 --- /dev/null +++ b/templates/item.php @@ -0,0 +1,48 @@ +
+ > +

+ ★ ' : '' ?> + ☑ ' : '' ?> + + > + + +

+

+ +

+

+ + | + + | + + + + | + $item, 'menu' => $menu, 'offset' => $offset, 'source' => '')) ?> + + + $item, 'redirect' => $menu, 'offset' => $offset)) ?> + + + + +

+
\ No newline at end of file diff --git a/templates/layout.php b/templates/layout.php index 7b40f4e..5fcc7a7 100644 --- a/templates/layout.php +++ b/templates/layout.php @@ -9,9 +9,9 @@ - <?= isset($title) ? Helper\escape($title) : 'miniflux' ?> + <?= isset($title) ? Helper\escape($title) : 'Miniflux' ?> - +
diff --git a/templates/login.php b/templates/login.php index 6635e16..65587c1 100644 --- a/templates/login.php +++ b/templates/login.php @@ -9,10 +9,10 @@ - miniflux + Miniflux - + diff --git a/templates/paging.php b/templates/paging.php new file mode 100644 index 0000000..d2f9c6a --- /dev/null +++ b/templates/paging.php @@ -0,0 +1,9 @@ +
+ 0): ?> + « + +  + $items_per_page): ?> + » + +
\ No newline at end of file diff --git a/templates/show_item.php b/templates/show_item.php index e219d42..d7ed5f8 100644 --- a/templates/show_item.php +++ b/templates/show_item.php @@ -3,23 +3,44 @@

-
+

- +

- + - + | | - | + | + + + | + + + + | + + + + + | + \ No newline at end of file diff --git a/templates/unread_items.php b/templates/unread_items.php index d151c50..a30d0e4 100644 --- a/templates/unread_items.php +++ b/templates/unread_items.php @@ -1,7 +1,5 @@ -

-
- -
-

- - - - -

-

- -

-

- | - | + + $item, 'menu' => $menu, 'offset' => $offset, 'hide' => true)) ?> + - - - | - - | - - - - - - | - - - -

-
- - -
- -
- -
- 0): ?> - « - -   - $items_per_page): ?> - » - -
+
+ +
+ $menu, 'nb_items' => $nb_items, 'items_per_page' => $items_per_page, 'offset' => $offset, 'order' => $order, 'direction' => $direction)) ?>
diff --git a/vendor/PicoTools/AuthProvider.php b/vendor/PicoTools/AuthProvider.php index 18fa7e5..daca6c9 100644 --- a/vendor/PicoTools/AuthProvider.php +++ b/vendor/PicoTools/AuthProvider.php @@ -77,9 +77,16 @@ function mozilla_validate($token) 'content' => http_build_query($params, '', '&') ))); - $body = file_get_contents('https://verifier.login.persona.org/verify', false, $context); + $body = @file_get_contents('https://verifier.login.persona.org/verify', false, $context); $response = json_decode($body, true); + if (! $response) { + return array( + false, + '' + ); + } + return array( $response['status'] === 'okay', $response['email']