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 @@
+
+
+ = t('remove bookmark') ?>
+ |
+
+
+ = t('bookmark') ?>
+ |
+
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 @@
-
= t('No bookmark') ?>
-
-
-
-
-
- = Helper\escape(Helper\summary(strip_tags($item['content']), 50, 300)) ?>
-
-
- = Helper\escape($item['feed_title']) ?> |
- = dt('%e %B %Y %k:%M', $item['updated']) ?> |
-
-
-
- = t('remove bookmark') ?>
- |
-
-
-
- = t('original link') ?>
-
-
-
-
-
-
- 0): ?>
- « = t('Previous page') ?>
-
-
- $items_per_page): ?>
- = t('Next page') ?> »
-
-
+
+ = \PicoTools\Template\load('item', array('item' => $item, 'menu' => $menu, 'offset' => $offset, 'hide' => false)) ?>
+
+ = \PicoTools\Template\load('paging', array('menu' => $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 @@
-
= t('This subscription is empty, go back to unread items ') ?>
-
-
-
-
- = $item['bookmark'] ? '★ ' : '' ?>
- = $item['status'] === 'read' ? '☑ ' : '' ?>
-
- >
- = Helper\escape($item['title']) ?>
-
-
-
- = Helper\escape(Helper\summary(strip_tags($item['content']), 50, 300)) ?>
-
-
- = Helper\get_host_from_url($item['url']) ?> |
- = dt('%e %B %Y %k:%M', $item['updated']) ?> |
+
+ = \PicoTools\Template\load('item', array('item' => $item, 'menu' => $menu, 'offset' => $offset, 'hide' => false)) ?>
+
-
-
- = t('remove bookmark') ?> |
-
- = t('bookmark') ?> |
-
-
-
-
-
- = t('mark as read') ?>
- |
-
-
- = t('mark as unread') ?>
- |
-
-
-
- = t('remove') ?> |
-
-
-
- = t('original link') ?>
-
-
-
-
-
-
-
-
+
+ = \PicoTools\Template\load('paging', array('menu' => $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 @@
-
-
-
-
- = Helper\escape(Helper\summary(strip_tags($item['content']), 50, 300)) ?>
-
-
- = Helper\escape($item['feed_title']) ?> |
-
- = dt('%e %B %Y %k:%M', $item['updated']) ?> |
-
-
-
- = t('bookmark') ?> |
-
-
-
-
- = t('mark as unread') ?>
- |
-
-
- = t('remove') ?> |
-
-
-
- = t('original link') ?>
-
-
-
-
-
-
- 0): ?>
- « = t('Previous page') ?>
-
-
- $items_per_page): ?>
- = t('Next page') ?> »
-
-
+
+ = \PicoTools\Template\load('item', array('item' => $item, 'menu' => $menu, 'offset' => $offset, 'hide' => true)) ?>
+
+ = \PicoTools\Template\load('paging', array('menu' => $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['bookmark'] ? '★ ' : '' ?>
+ = $item['status'] === 'read' ? '☑ ' : '' ?>
+
+ >
+ = Helper\escape($item['title']) ?>
+
+
+
+ = Helper\escape(Helper\summary(strip_tags($item['content']), 50, 300)) ?>
+
+
+
+ = Helper\get_host_from_url($item['url']) ?> |
+
+ = Helper\escape($item['feed_title']) ?> |
+
+
+
+ = dt('%e %B %Y %k:%M', $item['updated']) ?> |
+ = \PicoTools\Template\load('bookmark_links', array('item' => $item, 'menu' => $menu, 'offset' => $offset, 'source' => '')) ?>
+
+
+ = \PicoTools\Template\load('status_links', array('item' => $item, 'redirect' => $menu, 'offset' => $offset)) ?>
+
+
+ = t('original link') ?>
+
+
+
\ 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' ?>
-
+