From c0b388cae442493d8ecdc4b7f18936872b2663b3 Mon Sep 17 00:00:00 2001 From: Brent Hunter Date: Fri, 5 Jul 2013 16:57:15 -0700 Subject: [PATCH 01/34] Initial Commit of "The Old Feeder" --- LICENSE | 0 Rakefile | 0 app/assistants/add-assistant.js | 0 app/assistants/add-detail-assistant.js | 0 app/assistants/app-assistant.js | 0 app/assistants/article-assistant.js | 0 app/assistants/articles-assistant.js | 0 app/assistants/base-assistant.js | 0 app/assistants/configure-sharing-assistant.js | 0 app/assistants/credentials-assistant.js | 0 app/assistants/dashboard-assistant.js | 0 app/assistants/debug-assistant.js | 0 app/assistants/expired-assistant.js | 0 app/assistants/folder-assistant.js | 0 app/assistants/help-assistant.js | 0 app/assistants/home-assistant.js | 0 .../instapaper-credentials-assistant.js | 0 app/assistants/login-assistant.js | 0 .../notification-feeds-assistant.js | 0 app/assistants/preferences-assistant.js | 0 app/assistants/search-assistant.js | 0 app/lib/ajax.js | 0 app/lib/instapaper.js | 0 app/lib/log.js | 0 app/lib/sharing.js | 23 ++++---- app/models/all-articles.js | 0 app/models/all-sources.js | 7 ++- app/models/all-subscriptions.js | 0 app/models/api.js | 55 ++++++++++++------ app/models/article-container.js | 0 app/models/article.js | 8 ++- app/models/countable.js | 0 app/models/credentials.js | 0 app/models/feeder.js | 0 app/models/folder-subscriptions.js | 0 app/models/folder.js | 0 app/models/folders.js | 0 app/models/load-more.js | 0 app/models/preferences.js | 0 app/models/search.js | 0 app/models/shared.js | 0 app/models/sort-order.js | 0 app/models/starred.js | 0 app/models/subscription-container.js | 0 app/models/subscription.js | 0 app/views/add-detail/add-detail-scene.html | 0 app/views/add/add-scene.html | 0 app/views/add/subscription.html | 0 app/views/article/article-scene.html | 0 app/views/articles/article.html | 0 app/views/articles/articles-scene.html | 0 app/views/articles/divider.html | 0 app/views/articles/spacer.html | 0 .../configure-sharing-scene.html | 0 app/views/configure-sharing/item.html | 0 app/views/credentials/credentials-scene.html | 2 +- app/views/dashboard/dashboard-scene.html | 0 app/views/debug/debug-scene.html | 0 app/views/debug/message.html | 0 app/views/debug/messages.html | 0 app/views/expired/expired-scene.html | 0 app/views/folder/folder-scene.html | 0 app/views/folder/folder.html | 0 app/views/help/help-scene.html | 13 ++++- app/views/home/divider.html | 0 app/views/home/home-scene.html | 2 +- app/views/home/source.html | 0 .../instapaper-credentials-scene.html | 0 app/views/login/login-scene.html | 0 app/views/notification-feeds/feed.html | 0 .../notification-feeds-scene.html | 0 app/views/preferences/preferences-scene.html | 0 appinfo.json | 8 +-- config/excludes.txt | 0 feeder-webos-master/.project | 12 ++++ framework/prototype-1.6.0.3.js | 0 framework_config.json | 0 images/folder-grey.png | Bin images/go-back-footer.png | Bin images/go-back.png | Bin images/gradient.png | Bin images/list-separator.png | Bin images/list.png | Bin images/mark-all-read.png | Bin images/next-article.png | Bin images/play.png | Bin images/previous-article.png | Bin images/read-footer-on.png | Bin images/read-footer.png | Bin images/refresh.png | Bin images/rightarrow.png | Bin images/rss-grey.png | Bin images/search.png | Bin images/sendto-footer.png | Bin images/shared-footer-on.png | Bin images/shared-footer.png | Bin images/shared-grey.png | Bin images/smiley.png | Bin images/starred-footer-on.png | Bin images/starred-footer.png | Bin images/starred-grey.png | Bin images/video.png | Bin index.html | 0 resources/de_de/strings.json | 3 + resources/en_us/strings.json | 3 + screenshots/screenshot-010-1.jpg | Bin screenshots/screenshot-010-2.jpg | Bin screenshots/screenshot-010-3.jpg | Bin screenshots/screenshot-010-4.jpg | Bin sources.json | 0 spec/assistants/first-assistant-spec.js | 0 spec/helpers/mojo-stub.js | 0 spec/helpers/scene-controller-stub.js | 0 spec/helpers/stage-controller-stub.js | 0 spec/support/jasmine_config.rb | 0 spec/support/jasmine_runner.rb | 0 stylesheets/feeder.css | 0 117 files changed, 93 insertions(+), 43 deletions(-) mode change 100644 => 100755 LICENSE mode change 100644 => 100755 Rakefile mode change 100644 => 100755 app/assistants/add-assistant.js mode change 100644 => 100755 app/assistants/add-detail-assistant.js mode change 100644 => 100755 app/assistants/app-assistant.js mode change 100644 => 100755 app/assistants/article-assistant.js mode change 100644 => 100755 app/assistants/articles-assistant.js mode change 100644 => 100755 app/assistants/base-assistant.js mode change 100644 => 100755 app/assistants/configure-sharing-assistant.js mode change 100644 => 100755 app/assistants/credentials-assistant.js mode change 100644 => 100755 app/assistants/dashboard-assistant.js mode change 100644 => 100755 app/assistants/debug-assistant.js mode change 100644 => 100755 app/assistants/expired-assistant.js mode change 100644 => 100755 app/assistants/folder-assistant.js mode change 100644 => 100755 app/assistants/help-assistant.js mode change 100644 => 100755 app/assistants/home-assistant.js mode change 100644 => 100755 app/assistants/instapaper-credentials-assistant.js mode change 100644 => 100755 app/assistants/login-assistant.js mode change 100644 => 100755 app/assistants/notification-feeds-assistant.js mode change 100644 => 100755 app/assistants/preferences-assistant.js mode change 100644 => 100755 app/assistants/search-assistant.js mode change 100644 => 100755 app/lib/ajax.js mode change 100644 => 100755 app/lib/instapaper.js mode change 100644 => 100755 app/lib/log.js mode change 100644 => 100755 app/lib/sharing.js mode change 100644 => 100755 app/models/all-articles.js mode change 100644 => 100755 app/models/all-sources.js mode change 100644 => 100755 app/models/all-subscriptions.js mode change 100644 => 100755 app/models/api.js mode change 100644 => 100755 app/models/article-container.js mode change 100644 => 100755 app/models/article.js mode change 100644 => 100755 app/models/countable.js mode change 100644 => 100755 app/models/credentials.js mode change 100644 => 100755 app/models/feeder.js mode change 100644 => 100755 app/models/folder-subscriptions.js mode change 100644 => 100755 app/models/folder.js mode change 100644 => 100755 app/models/folders.js mode change 100644 => 100755 app/models/load-more.js mode change 100644 => 100755 app/models/preferences.js mode change 100644 => 100755 app/models/search.js mode change 100644 => 100755 app/models/shared.js mode change 100644 => 100755 app/models/sort-order.js mode change 100644 => 100755 app/models/starred.js mode change 100644 => 100755 app/models/subscription-container.js mode change 100644 => 100755 app/models/subscription.js mode change 100644 => 100755 app/views/add-detail/add-detail-scene.html mode change 100644 => 100755 app/views/add/add-scene.html mode change 100644 => 100755 app/views/add/subscription.html mode change 100644 => 100755 app/views/article/article-scene.html mode change 100644 => 100755 app/views/articles/article.html mode change 100644 => 100755 app/views/articles/articles-scene.html mode change 100644 => 100755 app/views/articles/divider.html mode change 100644 => 100755 app/views/articles/spacer.html mode change 100644 => 100755 app/views/configure-sharing/configure-sharing-scene.html mode change 100644 => 100755 app/views/configure-sharing/item.html mode change 100644 => 100755 app/views/credentials/credentials-scene.html mode change 100644 => 100755 app/views/dashboard/dashboard-scene.html mode change 100644 => 100755 app/views/debug/debug-scene.html mode change 100644 => 100755 app/views/debug/message.html mode change 100644 => 100755 app/views/debug/messages.html mode change 100644 => 100755 app/views/expired/expired-scene.html mode change 100644 => 100755 app/views/folder/folder-scene.html mode change 100644 => 100755 app/views/folder/folder.html mode change 100644 => 100755 app/views/help/help-scene.html mode change 100644 => 100755 app/views/home/divider.html mode change 100644 => 100755 app/views/home/home-scene.html mode change 100644 => 100755 app/views/home/source.html mode change 100644 => 100755 app/views/instapaper-credentials/instapaper-credentials-scene.html mode change 100644 => 100755 app/views/login/login-scene.html mode change 100644 => 100755 app/views/notification-feeds/feed.html mode change 100644 => 100755 app/views/notification-feeds/notification-feeds-scene.html mode change 100644 => 100755 app/views/preferences/preferences-scene.html mode change 100644 => 100755 appinfo.json mode change 100644 => 100755 config/excludes.txt create mode 100644 feeder-webos-master/.project mode change 100644 => 100755 framework/prototype-1.6.0.3.js mode change 100644 => 100755 framework_config.json mode change 100644 => 100755 images/folder-grey.png mode change 100644 => 100755 images/go-back-footer.png mode change 100644 => 100755 images/go-back.png mode change 100644 => 100755 images/gradient.png mode change 100644 => 100755 images/list-separator.png mode change 100644 => 100755 images/list.png mode change 100644 => 100755 images/mark-all-read.png mode change 100644 => 100755 images/next-article.png mode change 100644 => 100755 images/play.png mode change 100644 => 100755 images/previous-article.png mode change 100644 => 100755 images/read-footer-on.png mode change 100644 => 100755 images/read-footer.png mode change 100644 => 100755 images/refresh.png mode change 100644 => 100755 images/rightarrow.png mode change 100644 => 100755 images/rss-grey.png mode change 100644 => 100755 images/search.png mode change 100644 => 100755 images/sendto-footer.png mode change 100644 => 100755 images/shared-footer-on.png mode change 100644 => 100755 images/shared-footer.png mode change 100644 => 100755 images/shared-grey.png mode change 100644 => 100755 images/smiley.png mode change 100644 => 100755 images/starred-footer-on.png mode change 100644 => 100755 images/starred-footer.png mode change 100644 => 100755 images/starred-grey.png mode change 100644 => 100755 images/video.png mode change 100644 => 100755 index.html mode change 100644 => 100755 resources/de_de/strings.json mode change 100644 => 100755 resources/en_us/strings.json mode change 100644 => 100755 screenshots/screenshot-010-1.jpg mode change 100644 => 100755 screenshots/screenshot-010-2.jpg mode change 100644 => 100755 screenshots/screenshot-010-3.jpg mode change 100644 => 100755 screenshots/screenshot-010-4.jpg mode change 100644 => 100755 sources.json mode change 100644 => 100755 spec/assistants/first-assistant-spec.js mode change 100644 => 100755 spec/helpers/mojo-stub.js mode change 100644 => 100755 spec/helpers/scene-controller-stub.js mode change 100644 => 100755 spec/helpers/stage-controller-stub.js mode change 100644 => 100755 spec/support/jasmine_config.rb mode change 100644 => 100755 spec/support/jasmine_runner.rb mode change 100644 => 100755 stylesheets/feeder.css diff --git a/LICENSE b/LICENSE old mode 100644 new mode 100755 diff --git a/Rakefile b/Rakefile old mode 100644 new mode 100755 diff --git a/app/assistants/add-assistant.js b/app/assistants/add-assistant.js old mode 100644 new mode 100755 diff --git a/app/assistants/add-detail-assistant.js b/app/assistants/add-detail-assistant.js old mode 100644 new mode 100755 diff --git a/app/assistants/app-assistant.js b/app/assistants/app-assistant.js old mode 100644 new mode 100755 diff --git a/app/assistants/article-assistant.js b/app/assistants/article-assistant.js old mode 100644 new mode 100755 diff --git a/app/assistants/articles-assistant.js b/app/assistants/articles-assistant.js old mode 100644 new mode 100755 diff --git a/app/assistants/base-assistant.js b/app/assistants/base-assistant.js old mode 100644 new mode 100755 diff --git a/app/assistants/configure-sharing-assistant.js b/app/assistants/configure-sharing-assistant.js old mode 100644 new mode 100755 diff --git a/app/assistants/credentials-assistant.js b/app/assistants/credentials-assistant.js old mode 100644 new mode 100755 diff --git a/app/assistants/dashboard-assistant.js b/app/assistants/dashboard-assistant.js old mode 100644 new mode 100755 diff --git a/app/assistants/debug-assistant.js b/app/assistants/debug-assistant.js old mode 100644 new mode 100755 diff --git a/app/assistants/expired-assistant.js b/app/assistants/expired-assistant.js old mode 100644 new mode 100755 diff --git a/app/assistants/folder-assistant.js b/app/assistants/folder-assistant.js old mode 100644 new mode 100755 diff --git a/app/assistants/help-assistant.js b/app/assistants/help-assistant.js old mode 100644 new mode 100755 diff --git a/app/assistants/home-assistant.js b/app/assistants/home-assistant.js old mode 100644 new mode 100755 diff --git a/app/assistants/instapaper-credentials-assistant.js b/app/assistants/instapaper-credentials-assistant.js old mode 100644 new mode 100755 diff --git a/app/assistants/login-assistant.js b/app/assistants/login-assistant.js old mode 100644 new mode 100755 diff --git a/app/assistants/notification-feeds-assistant.js b/app/assistants/notification-feeds-assistant.js old mode 100644 new mode 100755 diff --git a/app/assistants/preferences-assistant.js b/app/assistants/preferences-assistant.js old mode 100644 new mode 100755 diff --git a/app/assistants/search-assistant.js b/app/assistants/search-assistant.js old mode 100644 new mode 100755 diff --git a/app/lib/ajax.js b/app/lib/ajax.js old mode 100644 new mode 100755 diff --git a/app/lib/instapaper.js b/app/lib/instapaper.js old mode 100644 new mode 100755 diff --git a/app/lib/log.js b/app/lib/log.js old mode 100644 new mode 100755 diff --git a/app/lib/sharing.js b/app/lib/sharing.js old mode 100644 new mode 100755 index d5387e4..02e8b77 --- a/app/lib/sharing.js +++ b/app/lib/sharing.js @@ -1,13 +1,14 @@ var Sharing = { + items: [ - {id: "sharing-aa", label: $L("Google"), defaultEnabled: true}, - {id: "sharing-ab", label: $L("Share"), command: "share-with-google", defaultEnabled: true}, - {id: "sharing-ac", label: $L("Twitter"), defaultEnabled: true}, + {id: "sharing-aa", label: $L("The Old Reader"), defaultEnabled: true}, + {id: "sharing-ab", label: $L("Share"), command: "share-with-google", defaultEnabled: false}, + /*{id: "sharing-ac", label: $L("Twitter"), defaultEnabled: true}, {id: "sharing-ad", label: $L("Bad Kitty"), command: "send-to-bad-kitty", defaultEnabled: true}, {id: "sharing-ae", label: $L("Spaz"), command: "send-to-spaz", defaultEnabled: true}, {id: "sharing-an", label: $L("Twee"), command: "send-to-twee", defaultEnabled: false}, {id: "sharing-ao", label: $L("Tweed"), command: "send-to-tweed", defaultEnabled: false}, - {id: "sharing-aq", label: $L("Carbon"), command: "send-to-carbon", defaultEnabled: true}, + {id: "sharing-aq", label: $L("Carbon"), command: "send-to-carbon", defaultEnabled: true},*/ {id: "sharing-af", label: $L("Share"), defaultEnabled: true}, {id: "sharing-ag", label: $L("Facebook"), command: "send-to-facebook", defaultEnabled: true}, {id: "sharing-ah", label: $L("Email"), command: "send-to-email", defaultEnabled: true}, @@ -109,15 +110,17 @@ var Sharing = { }, shareWithGoogle: function(article, controller) { - article.turnShareOn(function() { - Feeder.notify($L("Article shared")) - }) + //article.turnShareOn(function() { + // Feeder.notify($L("Article shared")) + //}) + Feeder.notify($L("Sharing Not Available")) }, unshareWithGoogle: function(article, controller) { - article.turnShareOff(function() { - Feeder.notify($L("Article unshared")) - }) + //article.turnShareOff(function() { + // Feeder.notify($L("Article unshared")) + //}) + Feeder.notify($L("Sharing Not Available")) }, sendToFacebook: function(article, controller) { diff --git a/app/models/all-articles.js b/app/models/all-articles.js old mode 100644 new mode 100755 diff --git a/app/models/all-sources.js b/app/models/all-sources.js old mode 100644 new mode 100755 index 43ff9cc..363f1de --- a/app/models/all-sources.js +++ b/app/models/all-sources.js @@ -1,9 +1,10 @@ var AllSources = Class.create({ initialize: function(api) { this.all = new AllArticles(api) - this.starred = new Starred(api) - this.shared = new Shared(api) - this.stickySources = {items: [this.all, this.starred, this.shared]} + //this.starred = new Starred(api) + //this.shared = new Shared(api) + //this.stickySources = {items: [this.all, this.starred, this.shared]} + this.stickySources = {items: [this.all]} this.subscriptions = new AllSubscriptions(api) this.subscriptionSources = {items: []} diff --git a/app/models/all-subscriptions.js b/app/models/all-subscriptions.js old mode 100644 new mode 100755 diff --git a/app/models/api.js b/app/models/api.js old mode 100644 new mode 100755 index 3e2fbfb..bfaa53c --- a/app/models/api.js +++ b/app/models/api.js @@ -1,19 +1,20 @@ var Api = Class.create({ login: function(credentials, success, failure) { + var authSuccess = function(response) { var authMatch = response.responseText.match(/Auth\=(.*)/) this.auth = authMatch ? authMatch[1] : '' success(this.auth) }.bind(this) - new Ajax.Request("https://www.google.com/accounts/ClientLogin", { - method: "get", - parameters: {service: "reader", Email: credentials.email, Passwd: credentials.password}, + new Ajax.Request("https://theoldreader.com/reader/api/0/accounts/ClientLogin", { + method: "post", + parameters: {client: "The Old Feeder", accountType: "HOSTED_OR_GOOGLE", service: "reader", Email: credentials.email, Passwd: credentials.password}, onSuccess: authSuccess, onFailure: failure }) }, - + getTags: function(success, failure) { new Ajax.Request(Api.BASE_URL + "tag/list", { method: "get", @@ -186,8 +187,9 @@ var Api = Class.create({ onFailure: failure, onSuccess: function(response) { var json = response.responseText.evalJSON() - + if(json.denied) { + failure() } else { @@ -239,7 +241,7 @@ var Api = Class.create({ _getArticles: function(id, exclude, continuation, success, failure) { var parameters = {output: "json", n: 40} - + if(id != "user/-/state/com.google/starred" && id != "user/-/state/com.google/broadcast" && Preferences.isOldestFirst()) { @@ -253,17 +255,33 @@ var Api = Class.create({ if(exclude) { parameters.xt = exclude } - - new Ajax.Request(Api.BASE_URL + "stream/contents/" + escape(id), { - method: "get", - parameters: parameters, - requestHeaders: this._requestHeaders(), - onFailure: failure, - onSuccess: function(response) { - var articles = response.responseText.evalJSON() - success(articles.items, articles.id, articles.continuation) - } - }) + + if(id == "user/-/state/com.google/reading-list" || id == "user/-/state/com.google/broadcast" || id == "user/-/state/com.google/starred") + { + new Ajax.Request(Api.BASE_URL2 + escape(id), { + method: "get", + parameters: parameters, + requestHeaders: this._requestHeaders(), + onFailure: failure, + onSuccess: function(response) { + var articles = response.responseText.evalJSON() + success(articles.items, articles.id, articles.continuation) + } + }) + } + else + { + new Ajax.Request(Api.BASE_URL + "stream/contents/" + escape(id), { + method: "get", + parameters: parameters, + requestHeaders: this._requestHeaders(), + onFailure: failure, + onSuccess: function(response) { + var articles = response.responseText.evalJSON() + success(articles.items, articles.id, articles.continuation) + } + }) + } }, markAllRead: function(id, success, failure) { @@ -459,4 +477,5 @@ var Api = Class.create({ } }) -Api.BASE_URL = "http://www.google.com/reader/api/0/" +Api.BASE_URL = "https://theoldreader.com/reader/api/0/" +Api.BASE_URL2 = "https://theoldreader.com/reader/atom/" diff --git a/app/models/article-container.js b/app/models/article-container.js old mode 100644 new mode 100755 diff --git a/app/models/article.js b/app/models/article.js old mode 100644 new mode 100755 index dd2f8f1..3d95434 --- a/app/models/article.js +++ b/app/models/article.js @@ -123,18 +123,20 @@ var Article = Class.create({ }, turnStarOn: function(success, failure) { - this._setState("Starred", "isStarred", true, success, failure) + //this._setState("Starred", "isStarred", true, success, failure) + Feeder.notify($L("Starring Not Available")) }, turnStarOff: function(success, failure) { - this._setState("NotStarred", "isStarred", false, success, failure) + //this._setState("NotStarred", "isStarred", false, success, failure) + Feeder.notify($L("Starring Not Available")) }, _setState: function(apiState, localProperty, localValue, success, failure, sticky) { Log.debug("setting article state - " + apiState) if(apiState.match(/Read/) && this.readLocked) { - Feeder.notify("Read state has been locked by Google") + Feeder.notify("Read state has been locked by The Old Feeder") success(false) } else { diff --git a/app/models/countable.js b/app/models/countable.js old mode 100644 new mode 100755 diff --git a/app/models/credentials.js b/app/models/credentials.js old mode 100644 new mode 100755 diff --git a/app/models/feeder.js b/app/models/feeder.js old mode 100644 new mode 100755 diff --git a/app/models/folder-subscriptions.js b/app/models/folder-subscriptions.js old mode 100644 new mode 100755 diff --git a/app/models/folder.js b/app/models/folder.js old mode 100644 new mode 100755 diff --git a/app/models/folders.js b/app/models/folders.js old mode 100644 new mode 100755 diff --git a/app/models/load-more.js b/app/models/load-more.js old mode 100644 new mode 100755 diff --git a/app/models/preferences.js b/app/models/preferences.js old mode 100644 new mode 100755 diff --git a/app/models/search.js b/app/models/search.js old mode 100644 new mode 100755 diff --git a/app/models/shared.js b/app/models/shared.js old mode 100644 new mode 100755 diff --git a/app/models/sort-order.js b/app/models/sort-order.js old mode 100644 new mode 100755 diff --git a/app/models/starred.js b/app/models/starred.js old mode 100644 new mode 100755 diff --git a/app/models/subscription-container.js b/app/models/subscription-container.js old mode 100644 new mode 100755 diff --git a/app/models/subscription.js b/app/models/subscription.js old mode 100644 new mode 100755 diff --git a/app/views/add-detail/add-detail-scene.html b/app/views/add-detail/add-detail-scene.html old mode 100644 new mode 100755 diff --git a/app/views/add/add-scene.html b/app/views/add/add-scene.html old mode 100644 new mode 100755 diff --git a/app/views/add/subscription.html b/app/views/add/subscription.html old mode 100644 new mode 100755 diff --git a/app/views/article/article-scene.html b/app/views/article/article-scene.html old mode 100644 new mode 100755 diff --git a/app/views/articles/article.html b/app/views/articles/article.html old mode 100644 new mode 100755 diff --git a/app/views/articles/articles-scene.html b/app/views/articles/articles-scene.html old mode 100644 new mode 100755 diff --git a/app/views/articles/divider.html b/app/views/articles/divider.html old mode 100644 new mode 100755 diff --git a/app/views/articles/spacer.html b/app/views/articles/spacer.html old mode 100644 new mode 100755 diff --git a/app/views/configure-sharing/configure-sharing-scene.html b/app/views/configure-sharing/configure-sharing-scene.html old mode 100644 new mode 100755 diff --git a/app/views/configure-sharing/item.html b/app/views/configure-sharing/item.html old mode 100644 new mode 100755 diff --git a/app/views/credentials/credentials-scene.html b/app/views/credentials/credentials-scene.html old mode 100644 new mode 100755 index bd1b14d..5bb134f --- a/app/views/credentials/credentials-scene.html +++ b/app/views/credentials/credentials-scene.html @@ -1,6 +1,6 @@
- +
diff --git a/app/views/dashboard/dashboard-scene.html b/app/views/dashboard/dashboard-scene.html old mode 100644 new mode 100755 diff --git a/app/views/debug/debug-scene.html b/app/views/debug/debug-scene.html old mode 100644 new mode 100755 diff --git a/app/views/debug/message.html b/app/views/debug/message.html old mode 100644 new mode 100755 diff --git a/app/views/debug/messages.html b/app/views/debug/messages.html old mode 100644 new mode 100755 diff --git a/app/views/expired/expired-scene.html b/app/views/expired/expired-scene.html old mode 100644 new mode 100755 diff --git a/app/views/folder/folder-scene.html b/app/views/folder/folder-scene.html old mode 100644 new mode 100755 diff --git a/app/views/folder/folder.html b/app/views/folder/folder.html old mode 100644 new mode 100755 diff --git a/app/views/help/help-scene.html b/app/views/help/help-scene.html old mode 100644 new mode 100755 index 9a35aeb..cb6d1b1 --- a/app/views/help/help-scene.html +++ b/app/views/help/help-scene.html @@ -12,11 +12,11 @@
-
Thank you for your purchase. If you have questions or suggestions you can reach us at:
+
The Old Feeder is a fork of Semicolon Apps' excellent Google Reader app Feeder, adapted to work with The Old Reader.
@@ -24,6 +24,13 @@
History
+
+
0.8.0
+ +
+
0.7.5
+
+
0.8.3
+ +
+
0.8.2
-
FeedSpider is a fork of Semicolon Apps' excellent Google Reader app Feeder, adapted to work with The Old Reader.
+
FeedSpider is a fork of Semicolon Apps' excellent Google Reader app Feeder, adapted to work with Google Reader compatible applications. +

It currently supports: + +
- +
Reach us through: + +
History
+
+
0.9.0
+
    +
  • added support for InoReader
  • +
+
+
0.8.5
    -
  • new icon
  • +
  • new icon (Thanks RumoredNow!)
  • added support for Glimpse
  • added support for Quick Post
diff --git a/appinfo.json b/appinfo.json index 7085737..f40f4cc 100755 --- a/appinfo.json +++ b/appinfo.json @@ -1,6 +1,6 @@ { "id": "com.othelloventures.feedspider", - "version": "0.8.5", + "version": "0.9.0", "vendor": "Othello Ventures", "type": "web", "main": "index.html", diff --git a/resources/de_de/strings.json b/resources/de_de/strings.json index 13980b0..2a32e07 100755 --- a/resources/de_de/strings.json +++ b/resources/de_de/strings.json @@ -29,6 +29,7 @@ "Help": "Hilfe", "Hide read articles": "gelesene Artikel verbergen", "Hide read feeds": "gelesene Feeds verbergen", + "InoReader": "InoReader", "Large Font": "große Schrift", "Light Theme": "helles Design", "Loading More Articles...": "weitere Artikel werden geladen...", @@ -60,7 +61,7 @@ "Starred": "Markiert", "Starring Not Available": "Nicht markiert verfügbar", "Subscriptions": "Abonnements", - + "The Old Reader": "The Old Reader", "Twitter": "Twitter", "Unable to add subscription": "Abonnement kann nicht hinzugefügt werden", "Unshare": "Empfehlung entfernen", diff --git a/resources/en_us/strings.json b/resources/en_us/strings.json index d2a197c..95037cb 100755 --- a/resources/en_us/strings.json +++ b/resources/en_us/strings.json @@ -29,6 +29,7 @@ "Help": "Help", "Hide read articles": "Hide read articles", "Hide read feeds": "Hide read feeds", + "InoReader": "InoReader", "Large Font": "Large Font", "Light Theme": "Light Theme", "Loading More Articles...": "Loading More Articles...", @@ -60,6 +61,7 @@ "Starred": "Starred", "Starring Not Available": "Starring not available", "Subscriptions": "Subscriptions", + "The Old Reader": "The Old Reader", "Twitter": "Twitter", "Unable to add subscription": "Unable to add subscription", "Unshare": "Unshare", diff --git a/sources.json b/sources.json index 356f7c2..64c672a 100755 --- a/sources.json +++ b/sources.json @@ -20,6 +20,8 @@ {"source": "app/models/folder.js"}, {"source": "app/models/preferences.js"}, {"source": "app/models/search.js"}, + {"source": "app/models/tor-api.js"}, + {"source": "app/models/ino-api.js"}, {"source": "app/lib/ajax.js"}, {"source": "app/lib/instapaper.js"}, From 5b271f898a92d599c6dc698c2804673c166e2029 Mon Sep 17 00:00:00 2001 From: Brent Hunter Date: Mon, 12 Aug 2013 11:36:51 -0700 Subject: [PATCH 08/34] Build 0.9.1 - Bugfixes. Handle staring/sharing better. --- app/models/article.js | 36 ++++++++++++++++++++++++++++++---- app/models/tor-api.js | 4 ---- app/views/help/help-scene.html | 7 +++++++ appinfo.json | 2 +- 4 files changed, 40 insertions(+), 9 deletions(-) diff --git a/app/models/article.js b/app/models/article.js index 3f11a35..22fc145 100755 --- a/app/models/article.js +++ b/app/models/article.js @@ -115,19 +115,47 @@ var Article = Class.create({ }, turnShareOn: function(success, failure) { - this._setState("Shared", "isShared", true, success, failure) + if(this.api.supportsShared()) + { + this._setState("Shared", "isShared", true, success, failure) + } + else + { + Feeder.notify($L("Sharing Not Available")) + } }, turnShareOff: function(success, failure) { - this._setState("NotShared", "isShared", false, success, failure) + if(this.api.supportsShared()) + { + this._setState("NotShared", "isShared", false, success, failure) + } + else + { + Feeder.notify($L("Sharing Not Available")) + } }, turnStarOn: function(success, failure) { - this._setState("Starred", "isStarred", true, success, failure) + if(this.api.supportsStarred()) + { + this._setState("Starred", "isStarred", true, success, failure) + } + else + { + Feeder.notify($L("Starring Not Available")) + } }, turnStarOff: function(success, failure) { - this._setState("NotStarred", "isStarred", false, success, failure) + if(this.api.supportsStarred()) + { + this._setState("NotStarred", "isStarred", false, success, failure) + } + else + { + Feeder.notify($L("Starring Not Available")) + } }, _setState: function(apiState, localProperty, localValue, success, failure, sticky) { diff --git a/app/models/tor-api.js b/app/models/tor-api.js index 0be2b8f..86da40d 100755 --- a/app/models/tor-api.js +++ b/app/models/tor-api.js @@ -383,7 +383,6 @@ var TorApi = Class.create({ }, setArticleShared: function(articleId, subscriptionId, success, failure) { - Feeder.notify($L("Sharing Not Available")) /*this._editTag( articleId, subscriptionId, @@ -395,7 +394,6 @@ var TorApi = Class.create({ }, setArticleNotShared: function(articleId, subscriptionId, success, failure) { - Feeder.notify($L("Sharing Not Available")) /*this._editTag( articleId, subscriptionId, @@ -407,7 +405,6 @@ var TorApi = Class.create({ }, setArticleStarred: function(articleId, subscriptionId, success, failure) { - Feeder.notify($L("Starring Not Available")) /*this._editTag( articleId, subscriptionId, @@ -419,7 +416,6 @@ var TorApi = Class.create({ }, setArticleNotStarred: function(articleId, subscriptionId, success, failure) { - Feeder.notify($L("Starring Not Available")) /*this._editTag( articleId, subscriptionId, diff --git a/app/views/help/help-scene.html b/app/views/help/help-scene.html index a21444b..60c49a3 100755 --- a/app/views/help/help-scene.html +++ b/app/views/help/help-scene.html @@ -33,6 +33,13 @@
History
+
+
0.9.1
+
    +
  • bugfixes
  • +
+
+
0.9.0
    diff --git a/appinfo.json b/appinfo.json index f40f4cc..5df5877 100755 --- a/appinfo.json +++ b/appinfo.json @@ -1,6 +1,6 @@ { "id": "com.othelloventures.feedspider", - "version": "0.9.0", + "version": "0.9.1", "vendor": "Othello Ventures", "type": "web", "main": "index.html", From ecb3f615f9f841103cd9527f0d3e79e6e2abd4f2 Mon Sep 17 00:00:00 2001 From: Brent Hunter Date: Mon, 19 Aug 2013 15:50:10 -0700 Subject: [PATCH 09/34] Version 0.9.2 - Added support for copy to clipboard - Fixed bug causing titles to not show up correctly - Added support for simplified chinese --- app/lib/sharing.js | 8 +++ app/models/api.js | 2 +- app/views/help/help-scene.html | 9 +++ appinfo.json | 2 +- resources/de_de/strings.json | 4 ++ resources/en_us/strings.json | 4 ++ resources/zh_cn/strings.json | 107 +++++++++++++++++++++++++++++++++ 7 files changed, 134 insertions(+), 2 deletions(-) create mode 100644 resources/zh_cn/strings.json diff --git a/app/lib/sharing.js b/app/lib/sharing.js index a8f977d..9c94a3a 100755 --- a/app/lib/sharing.js +++ b/app/lib/sharing.js @@ -3,6 +3,8 @@ var Sharing = { items: [ {id: "sharing-aa", label: $L("Reader"), defaultEnabled: true}, {id: "sharing-ab", label: $L("Share"), command: "share-with-google", defaultEnabled: false}, + {id: "sharing-at", label: $L("Clipboard"), defaultEnabled: true}, + {id: "sharing-au", label: $L("Copy URL"), command: "send-to-clipboard", defaultEnabled: true}, {id: "sharing-ac", label: $L("Twitter"), defaultEnabled: true}, {id: "sharing-ad", label: $L("Project Macaw"), command: "send-to-project-macaw", defaultEnabled: true}, {id: "sharing-ae", label: $L("Glimpse"), command: "send-to-glimpse", defaultEnabled: true}, @@ -104,6 +106,7 @@ var Sharing = { case "send-to-sms": Sharing.sendToSms(article, controller); break; case "send-to-neato": Sharing.sendToNeato(article, controller); break; case "send-to-facebook": Sharing.sendToFacebook(article, controller); break; + case "send-to-clipboard": Sharing.sendToClipboard(article, controller); break; case "configure": controller.stageController.pushScene("configure-sharing", Sharing.items) } }, @@ -183,6 +186,11 @@ var Sharing = { sendToReadontouchPro: function(article, controller) { Sharing.sendToApp(controller, $L("ReadOnTouch PRO"), "com.sven-ziegler.readontouch", {action: 'addLink', url: article.url, title: article.title}) }, + + sendToClipboard: function(article, controller) { + controller.stageController.setClipboard(article.url) + Feeder.notify($L("URL Copied")) + }, sendToApp: function(controller, appName, id, params) { controller.serviceRequest("palm://com.palm.applicationManager", { diff --git a/app/models/api.js b/app/models/api.js index e02bbb7..c283b48 100755 --- a/app/models/api.js +++ b/app/models/api.js @@ -58,7 +58,7 @@ var Api = Class.create({ }, titleFor: function(id) { - this.appApi.titleFor(id) + return this.appApi.titleFor(id) }, getUnreadCounts: function(success, failure) { diff --git a/app/views/help/help-scene.html b/app/views/help/help-scene.html index 60c49a3..0581a4d 100755 --- a/app/views/help/help-scene.html +++ b/app/views/help/help-scene.html @@ -33,6 +33,15 @@
    History
+
+
0.9.2
+
    +
  • fixed issue with feed names not displaying correctly
  • +
  • added experimental support for Simplified Chinese. (Thanks to konglang_616 for the translation!)
  • +
  • added support for copying article urls to clipboard
  • +
+
+
0.9.1
    diff --git a/appinfo.json b/appinfo.json index 5df5877..87f2d10 100755 --- a/appinfo.json +++ b/appinfo.json @@ -1,6 +1,6 @@ { "id": "com.othelloventures.feedspider", - "version": "0.9.1", + "version": "0.9.2", "vendor": "Othello Ventures", "type": "web", "main": "index.html", diff --git a/resources/de_de/strings.json b/resources/de_de/strings.json index 2a32e07..dd07d61 100755 --- a/resources/de_de/strings.json +++ b/resources/de_de/strings.json @@ -12,7 +12,9 @@ "Articles": "Artikel", "by #{author}": "von #{author}", "by #{vendor}": "von #{vendor}", + "Clipboard": "Clipboard", "Combine articles": "Artikel zusammenführen", + "Copy URL": "URL kopieren", "Dark Theme": "noir Design", "Debug": "Fehlersuche", "Debug Log": "Protokoll", @@ -47,6 +49,7 @@ "Read Later": "Später lesen", "Reader": "Reader", "Relego": "Relego", + "Service" : "Dienstleistung", "Share": "Empfehlen", "Shared": "Empfohlen", "Sharing Not Available": "Nicht empfehlen verfügbar", @@ -66,6 +69,7 @@ "Unable to add subscription": "Abonnement kann nicht hinzugefügt werden", "Unshare": "Empfehlung entfernen", "URL": "URL", + "URL Copied": "URL Kopierte", "Yes": "Ja", "5 Minutes": "5 Minuten", diff --git a/resources/en_us/strings.json b/resources/en_us/strings.json index 95037cb..2b42d8e 100755 --- a/resources/en_us/strings.json +++ b/resources/en_us/strings.json @@ -12,7 +12,9 @@ "Articles": "Articles", "by #{author}": "by #{author}", "by #{vendor}": "by #{vendor}", + "Clipboard": "Clipboard", "Combine articles": "Combine articles", + "Copy URL": "Copy URL", "Dark Theme": "Dark Theme", "Debug": "Debug", "Debug Log": "Debug Log", @@ -47,6 +49,7 @@ "Read Later": "Read Later", "Reader": "Reader", "Relego": "Relego", + "Service" : "Service", "Share": "Share", "Shared": "Shared", "Sharing Not Available": "Sharing Not Available", @@ -66,6 +69,7 @@ "Unable to add subscription": "Unable to add subscription", "Unshare": "Unshare", "URL": "URL", + "URL Copied": "URL Copied", "Yes": "Yes", "5 Minutes": "5 Minutes", diff --git a/resources/zh_cn/strings.json b/resources/zh_cn/strings.json new file mode 100644 index 0000000..7a7b4d9 --- /dev/null +++ b/resources/zh_cn/strings.json @@ -0,0 +1,107 @@ +{ + "#{app} is not installed": "#{app} 未安装", + "#{app} is not installed. Would you like to install it?": "#{app} 未安装,您需要安装吗?", + "Add Subscription": "添加订阅", + "Add": "添加", + "All": "全部", + "All Accounts": "所有账户", + "All Items": "所有条目", + "Allow landscape": "允许横屏", + "Article shared": "共享条文", + "Article unshared": "未共享条文", + "Articles": "条文设置", + "by #{author}": "by #{author}", + "by #{vendor}": "by #{vendor}", + "Clipboard": "剪贴板", + "Combine articles": "合并条文", + "Copy URL": "复制网址", + "Dark Theme": "黑色主题", + "Debug": "调试", + "Debug Log": "调试日志", + "Default Accounts": "默认账户", + "Email": "邮件", + "Facebook": "Facebook", + "Feeds": "条目设置", + "Folders": "目录设置", + "General": "常规设置", + "Glimpse": "Glimpse", + "Go back after mark all read": "全部标记为已读后返回", + "Google": "Google", + "Grey Theme": "灰色主题", + "Help": "帮助", + "Hide read articles": "隐藏已读条文", + "Hide read feeds": "隐藏已读条目", + "InoReader": "InoReader", + "Large Font": "大字体", + "Light Theme": "明亮主题", + "Loading More Articles...": "加载更多条目...", + "logging in...": "登录中...", + "Login failed. Try again.": "登录失败,请重试.", + "Login": "登录", + "Logout": "注销", + "Mark read as you scroll": "滚动后标记为已读", + "Medium Font": "中字体", + "No": "否", + "Password": "密码", + "Preferences": "设置", + "Project Macaw": "Project Macaw", + "Quick Post": "Quick Post", + "Read Later": "稍后阅读", + "Reader": "阅读器", + "Relego": "Relego", + "Service" : "服务", + "Share": "共享", + "Shared": "共享条目", + "Sharing Not Available": "分享不可用", + "Small Font": "小字体", + "SMS": "短信", + "Sort alphabetically": "按字母顺序排序", + "Sort manually": "手动排序", + "Sort newest first": "按最近日期排序", + "Sort oldest first": "按最早日期排序", + "Spare Time": "Spare Time", + "Spaz": "Spaz", + "Starred": "加星标条目", + "Starring Not Available": "加星不可用", + "Subscriptions": "订阅条目", + "The Old Reader": "The Old Reader", + "Twitter": "Twitter", + "Unable to add subscription": "无法添加订阅", + "Unshare": "不共享", + "URL": "链接", + "URL Copied": "复制网址", + "Yes": "是", + + "5 Minutes": "5 分钟", + "15 Minutes": "15 分钟", + "30 Minutes": "30 分钟", + "1 Hour": "1 小时", + "4 Hours": "4 小时", + "8 Hours": "8 小时", + "Any feed": "任何条目", + "Cancel": "取消", + "New Articles": "新条文", + "No articles were found": "没有发现条文", + "Notifications": "通知设置", + "Off": "关", + "Search for \"#{query}\"": "搜索 \"#{query}\"", + "Search": "搜索", + "Search...": "搜索...", + "Select Feeds": "选择条目", + "Select feeds to watch": "选择条目阅读", + "Selected feeds": "选择过的条目", + "Show read articles": "显示已读条文", + "Show read feeds": "显示已读条目", + "You have new articles to read": "你有未读新条文", + "Landscape gesture scrolling": "横屏时使用手势区滚动", + + "URL or query": "链接或查询", + "Subscription added": "添加订阅", + "No subscriptions found": "没有找到订阅", + "Configure Sharing": "共享设置", + "Save": "保存", + "Username": "用户名", + "Configure...": "设置...", + "Article saved to Instapaper": "将文章保存到 Instapaper", + "Unable to save article": "文章无法保存", +} \ No newline at end of file From 215f2793a0f27840bd200f84a6a1d74588c41bdc Mon Sep 17 00:00:00 2001 From: Brent Hunter Date: Tue, 29 Oct 2013 14:08:45 -0700 Subject: [PATCH 10/34] Build 0.9.3 - improved handling of images in feeds - added support for tweeting via the webOS Browser - fixed a bug that would allow a user to bring up the search box when the API does not support it --- app/assistants/articles-assistant.js | 9 +++++- app/assistants/base-assistant.js | 14 +++++--- app/assistants/folder-assistant.js | 9 +++++- app/assistants/home-assistant.js | 9 +++++- app/lib/sharing.js | 9 +++++- app/views/help/help-scene.html | 9 ++++++ appinfo.json | 2 +- resources/de_de/strings.json | 2 ++ resources/en_us/strings.json | 2 ++ resources/zh_cn/strings.json | 2 ++ stylesheets/feedspider.css | 48 ++++++++++++++++++++++++++-- 11 files changed, 103 insertions(+), 12 deletions(-) diff --git a/app/assistants/articles-assistant.js b/app/assistants/articles-assistant.js index 35940dd..ade5359 100755 --- a/app/assistants/articles-assistant.js +++ b/app/assistants/articles-assistant.js @@ -316,7 +316,14 @@ var ArticlesAssistant = Class.create(BaseAssistant, { }, doSearch: function(query) { - this.controller.stageController.pushScene("articles", this.api, new Search(this.subscription.api, query, this.subscription.id)) + if(this.api.supportsSearch()) + { + this.controller.stageController.pushScene("articles", this.api, new Search(this.subscription.api, query, this.subscription.id)) + } + else + { + Feeder.notify($L("Search Not Available")) + } }, handleCommand: function($super, event) { diff --git a/app/assistants/base-assistant.js b/app/assistants/base-assistant.js index 82d2412..0250bcd 100755 --- a/app/assistants/base-assistant.js +++ b/app/assistants/base-assistant.js @@ -269,14 +269,18 @@ var BaseAssistant = Class.create({ }, listenForSearch: function() { - // The search feature is not currently available through The Old Reader API - $(this.controller.document).observe("keypress", this.startSearch) - this.controller.get("search-text").mojo.setConsumesEnterKey(false) + if(this.api.supportsSearch()) + { + $(this.controller.document).observe("keypress", this.startSearch) + this.controller.get("search-text").mojo.setConsumesEnterKey(false) + } }, stopListeningForSearch: function() { - // The search feature is not currently available through The Old Reader API - $(this.controller.document).stopObserving("keypress", this.startSearch) + if(this.api.supportsSearch()) + { + $(this.controller.document).stopObserving("keypress", this.startSearch) + } }, scrollToTop: function() { diff --git a/app/assistants/folder-assistant.js b/app/assistants/folder-assistant.js index 29128aa..789354d 100755 --- a/app/assistants/folder-assistant.js +++ b/app/assistants/folder-assistant.js @@ -109,7 +109,14 @@ var FolderAssistant = Class.create(BaseAssistant, { }, doSearch: function(query) { - this.controller.stageController.pushScene("articles", this.api, new Search(this.folder.api, query, this.folder.id)) + if(this.api.supportsSearch()) + { + this.controller.stageController.pushScene("articles", this.api, new Search(this.folder.api, query, this.folder.id)) + } + else + { + Feeder.notify($L("Search Not Available")) + } }, handleCommand: function($super, event) { diff --git a/app/assistants/home-assistant.js b/app/assistants/home-assistant.js index 5aacfd2..0680959 100755 --- a/app/assistants/home-assistant.js +++ b/app/assistants/home-assistant.js @@ -247,6 +247,13 @@ var HomeAssistant = Class.create(BaseAssistant, { }, doSearch: function(query) { - this.controller.stageController.pushScene("articles", this.api, new Search(this.api, query)) + if(this.api.supportsSearch()) + { + this.controller.stageController.pushScene("articles", this.api, new Search(this.api, query)) + } + else + { + Feeder.notify($L("Search Not Available")) + } } }) diff --git a/app/lib/sharing.js b/app/lib/sharing.js index 9c94a3a..79de490 100755 --- a/app/lib/sharing.js +++ b/app/lib/sharing.js @@ -1,5 +1,6 @@ var Sharing = { + //variables in use up to av items: [ {id: "sharing-aa", label: $L("Reader"), defaultEnabled: true}, {id: "sharing-ab", label: $L("Share"), command: "share-with-google", defaultEnabled: false}, @@ -8,6 +9,7 @@ var Sharing = { {id: "sharing-ac", label: $L("Twitter"), defaultEnabled: true}, {id: "sharing-ad", label: $L("Project Macaw"), command: "send-to-project-macaw", defaultEnabled: true}, {id: "sharing-ae", label: $L("Glimpse"), command: "send-to-glimpse", defaultEnabled: true}, + {id: "sharing-av", label: $L("Browser"), command: "send-to-browser", defaultEnabled: true}, {id: "sharing-aq", label: $L("Quick Post"), defaultEnabled: true}, {id: "sharing-ar", label: $L("Default Accounts"), command: "send-to-qp-default", defaultEnabled: true}, {id: "sharing-as", label: $L("All Accounts"), command: "send-to-qp-all", defaultEnabled: true}, @@ -106,7 +108,8 @@ var Sharing = { case "send-to-sms": Sharing.sendToSms(article, controller); break; case "send-to-neato": Sharing.sendToNeato(article, controller); break; case "send-to-facebook": Sharing.sendToFacebook(article, controller); break; - case "send-to-clipboard": Sharing.sendToClipboard(article, controller); break; + case "send-to-clipboard": Sharing.sendToClipboard(article, controller); break; + case "send-to-browser": Sharing.sendToBrowser(article, controller); break; case "configure": controller.stageController.pushScene("configure-sharing", Sharing.items) } }, @@ -191,6 +194,10 @@ var Sharing = { controller.stageController.setClipboard(article.url) Feeder.notify($L("URL Copied")) }, + + sendToBrowser: function(article, controller) { + Sharing.sendToApp(controller, $L("Browser"), "com.palm.app.browser", {target: "https://twitter.com/intent/tweet?text=" + encodeURIComponent(article.title) + "&url=" + encodeURI(article.url)}) + }, sendToApp: function(controller, appName, id, params) { controller.serviceRequest("palm://com.palm.applicationManager", { diff --git a/app/views/help/help-scene.html b/app/views/help/help-scene.html index 0581a4d..db44f9f 100755 --- a/app/views/help/help-scene.html +++ b/app/views/help/help-scene.html @@ -33,6 +33,15 @@
    History
+
+
0.9.3
+
    +
  • improved handling of images in feeds
  • +
  • added support for tweeting via the webOS Browser
  • +
  • fixed a bug that would allow a user to bring up the search box when the API does not support it
  • +
+
+
0.9.2
    diff --git a/appinfo.json b/appinfo.json index 87f2d10..6a5ddb1 100755 --- a/appinfo.json +++ b/appinfo.json @@ -1,6 +1,6 @@ { "id": "com.othelloventures.feedspider", - "version": "0.9.2", + "version": "0.9.3", "vendor": "Othello Ventures", "type": "web", "main": "index.html", diff --git a/resources/de_de/strings.json b/resources/de_de/strings.json index dd07d61..e80c511 100755 --- a/resources/de_de/strings.json +++ b/resources/de_de/strings.json @@ -10,6 +10,7 @@ "Article shared": "Artikel empfohlen", "Article unshared": "Empfehlung entfernt", "Articles": "Artikel", + "Browser": "Browser", "by #{author}": "von #{author}", "by #{vendor}": "von #{vendor}", "Clipboard": "Clipboard", @@ -49,6 +50,7 @@ "Read Later": "Später lesen", "Reader": "Reader", "Relego": "Relego", + "Search Not Available": "Nicht suche verfügbar", "Service" : "Dienstleistung", "Share": "Empfehlen", "Shared": "Empfohlen", diff --git a/resources/en_us/strings.json b/resources/en_us/strings.json index 2b42d8e..8082038 100755 --- a/resources/en_us/strings.json +++ b/resources/en_us/strings.json @@ -10,6 +10,7 @@ "Article shared": "Article shared", "Article unshared": "Article unshared", "Articles": "Articles", + "Browser": "Browser", "by #{author}": "by #{author}", "by #{vendor}": "by #{vendor}", "Clipboard": "Clipboard", @@ -49,6 +50,7 @@ "Read Later": "Read Later", "Reader": "Reader", "Relego": "Relego", + "Search Not Available": "Search Not Available", "Service" : "Service", "Share": "Share", "Shared": "Shared", diff --git a/resources/zh_cn/strings.json b/resources/zh_cn/strings.json index 7a7b4d9..3886e6c 100644 --- a/resources/zh_cn/strings.json +++ b/resources/zh_cn/strings.json @@ -10,6 +10,7 @@ "Article shared": "共享条文", "Article unshared": "未共享条文", "Articles": "条文设置", + "Browser": "浏览器", "by #{author}": "by #{author}", "by #{vendor}": "by #{vendor}", "Clipboard": "剪贴板", @@ -50,6 +51,7 @@ "Reader": "阅读器", "Relego": "Relego", "Service" : "服务", + "Search Not Available": "搜索不可用", "Share": "共享", "Shared": "共享条目", "Sharing Not Available": "分享不可用", diff --git a/stylesheets/feedspider.css b/stylesheets/feedspider.css index a8e3ba1..45d2d1c 100755 --- a/stylesheets/feedspider.css +++ b/stylesheets/feedspider.css @@ -467,9 +467,53 @@ body.lefty #small-spinner { background: url(../images/error.png) no-repeat center; } +/* Handle image sizing for Touchpad */ +@media (orientation: portrait) and (max-width: 1024px) { + #summary img { + max-width: 738px !important; + } +} + +@media (orientation: landscape) and (max-width: 1024px) { + #summary img { + max-width: 994px !important; + } +} + +/* Handle image sizing for Pre 3 */ +@media (orientation: portrait) and (max-width: 480px) { + #summary img { + max-width: 290px !important; + } +} + +@media (orientation: landscape) and (max-width: 800px) { + #summary img { + max-width: 505px !important; + } +} + +/* Handle image sizing for Pre, Pre 2, Pixi, and Veer */ +@media (orientation: portrait) and (max-width: 320px) { + #summary img { + max-width: 290px !important; + } +} + +@media (orientation: landscape) and (max-width: 480px){ + #summary img { + max-width: 450px !important; + } +} + +@media (orientation: landscape) and (max-width: 400px){ + #summary img { + max-width: 370px !important; + } +} + #summary img { - max-width: 290px !important; - height: auto !important; + height: auto !important; } #summary { From 1f56ff49de70366f8281103fef8cd0d5431b7e0a Mon Sep 17 00:00:00 2001 From: Brent Hunter Date: Wed, 18 Dec 2013 15:51:51 -0800 Subject: [PATCH 11/34] Version 0.9.5 - added support for starring articles in The Old Reader - added support for Feedly (sandbox) - fixed a bug when searching for feeds fails --- app/assistants/add-assistant.js | 12 +- app/assistants/credentials-assistant.js | 25 +- app/assistants/home-assistant.js | 9 +- app/assistants/login-assistant.js | 4 +- app/assistants/oauth-assistant.js | 304 ++++++++++ app/models/all-subscriptions.js | 13 +- app/models/api.js | 11 +- app/models/article.js | 72 ++- app/models/credentials.js | 72 ++- app/models/feedly-api.js | 606 +++++++++++++++++++ app/models/ino-api.js | 6 + app/models/subscription-container.js | 50 +- app/models/tor-api.js | 15 +- app/views/credentials/credentials-scene.html | 26 +- app/views/help/help-scene.html | 9 + app/views/oauth/oauth-scene.html | 2 + app/views/oauth/oauth.css | 86 +++ app/views/oauth/progress-indicator.png | Bin 0 -> 9613 bytes appinfo.json | 2 +- resources/de_de/strings.json | 4 + resources/en_us/strings.json | 4 + resources/zh_cn/strings.json | 4 + sources.json | 4 +- 23 files changed, 1264 insertions(+), 76 deletions(-) create mode 100755 app/assistants/oauth-assistant.js create mode 100644 app/models/feedly-api.js create mode 100755 app/views/oauth/oauth-scene.html create mode 100755 app/views/oauth/oauth.css create mode 100755 app/views/oauth/progress-indicator.png diff --git a/app/assistants/add-assistant.js b/app/assistants/add-assistant.js index fe9d576..67b5ced 100755 --- a/app/assistants/add-assistant.js +++ b/app/assistants/add-assistant.js @@ -93,8 +93,8 @@ var AddAssistant = Class.create(BaseAssistant, { }, subscriptionSearchFailure: function() { - this.controller.get("search").mojo.disabled = false - this.controller.get("search").mojo.deactivate() + this.controller.get("search-submit").mojo.disabled = false + this.controller.get("search-submit").mojo.deactivate() this.controller.modelChanged(this.searchButton) this.menuPanelOff() this.controller.get("error-message").update($L("Unable to add subscription")) @@ -117,10 +117,18 @@ var AddAssistant = Class.create(BaseAssistant, { if(json.content && json.content.content) { subscription.content = json.content.content } + else if (json.website && json.subscribers) + { + subscription.content = $L("Website") + ": " + json.website + ", " + $L("Subscribers") + ": " + json.subscribers + } if(json.feed && json.feed.length && json.feed[0].href) { subscription.url = json.feed[0].href } + else if (json.feedId) + { + subscription.url = json.feedId.substr(5) + } return subscription } diff --git a/app/assistants/credentials-assistant.js b/app/assistants/credentials-assistant.js index 347ec12..0d00363 100755 --- a/app/assistants/credentials-assistant.js +++ b/app/assistants/credentials-assistant.js @@ -6,10 +6,17 @@ var CredentialsAssistant = Class.create(BaseAssistant, { this.showMessage = showMessage this.button = {buttonLabel: $L("Login")} this.hideLogout = true + this.showFields = true + + if (credentials.service == "feedly") + { + this.showFields = false + } this.serviceChoices = [ {label: $L("The Old Reader"), value: "tor"}, - {label: $L("InoReader"), value: "ino"} + {label: $L("InoReader"), value: "ino"}, + {label: $L("Feedly"), value: "feedly"} ] }, @@ -28,6 +35,8 @@ var CredentialsAssistant = Class.create(BaseAssistant, { $super(changes) this.controller.get("password").mojo.setConsumesEnterKey(false) this.controller.get("login-failure")[this.showMessage ? "show" : "hide"]() + this.controller.get("email-group")[this.showFields ? "show" : "hide"]() + this.controller.get("password-group")[this.showFields ? "show" : "hide"]() }, cleanup: function($super) { @@ -36,9 +45,9 @@ var CredentialsAssistant = Class.create(BaseAssistant, { }, setupWidgets: function() { + this.controller.setupWidget("service", {modelProperty: "service", choices: this.serviceChoices}, this.credentials) this.controller.setupWidget("email", {modelProperty: "email", changeOnKeyPress: true, autoFocus: true, textCase: Mojo.Widget.steModeLowerCase}, this.credentials) this.controller.setupWidget("password", {modelProperty: "password", changeOnKeyPress: true}, this.credentials) - this.controller.setupWidget("service", {modelProperty: "service", choices: this.serviceChoices}, this.credentials) this.controller.setupWidget("login", {type: Mojo.Widget.activityButton}, this.button) }, @@ -67,6 +76,16 @@ var CredentialsAssistant = Class.create(BaseAssistant, { this.controller.stageController.swapScene("login", this.credentials) }, - setService: function() { + setService: function(propertyChangeEvent) { + if (propertyChangeEvent.value == "feedly") + { + this.controller.get("email-group")["hide"]() + this.controller.get("password-group")["hide"]() + } + else + { + this.controller.get("email-group")["show"]() + this.controller.get("password-group")["show"]() + } }, }) diff --git a/app/assistants/home-assistant.js b/app/assistants/home-assistant.js index 0680959..676ac70 100755 --- a/app/assistants/home-assistant.js +++ b/app/assistants/home-assistant.js @@ -121,8 +121,13 @@ var HomeAssistant = Class.create(BaseAssistant, { if("logout" == command) { var creds = new Credentials() - creds.password = false - creds.save() + creds.password = null + creds.id = null + creds.refreshToken = null + creds.accessToken = null + creds.tokenType = null + creds.plan = null + creds.clear() this.controller.stageController.swapScene("credentials", creds) } else if(command && command.feedAdded) { diff --git a/app/assistants/login-assistant.js b/app/assistants/login-assistant.js index baf3891..1f48942 100755 --- a/app/assistants/login-assistant.js +++ b/app/assistants/login-assistant.js @@ -20,7 +20,7 @@ var LoginAssistant = Class.create(BaseAssistant, { parameters: {}, onSuccess: function(response) { - if(this.credentials.email && this.credentials.password) { + if((this.credentials.email && this.credentials.password) || this.credentials.service == "feedly") { if(this.triedLogin) { Log.debug("ALREADY TRIED LOGGING IN, WHAT MAKES YOU THINK ITS GOING TO WORK NOW") } @@ -28,7 +28,7 @@ var LoginAssistant = Class.create(BaseAssistant, { this.triedLogin = true Log.debug("logging in as " + this.credentials.email) this.spinnerOn($L("logging in...")) - this.api.login(this.credentials, this.loginSuccess.bind(this), this.loginFailure.bind(this)) + this.api.login(this.credentials, this.loginSuccess.bind(this), this.loginFailure.bind(this), this.controller) } } else { diff --git a/app/assistants/oauth-assistant.js b/app/assistants/oauth-assistant.js new file mode 100755 index 0000000..97cb62e --- /dev/null +++ b/app/assistants/oauth-assistant.js @@ -0,0 +1,304 @@ +function OauthAssistant(oauthConfig) { + this.TAG = 'OauthAssistant'; + this.method = null; + this.callbackScene = oauthConfig.callbackScene; + this.requestTokenUrl = oauthConfig.requestTokenUrl; + this.authorizeUrl = oauthConfig.authorizeUrl; + this.accessTokenUrl = oauthConfig.accessTokenUrl; + this.client_id = oauthConfig.client_id; + this.client_secret = oauthConfig.client_secret; + if (oauthConfig.redirect_uri != undefined) this.redirect_uri = oauthConfig.redirect_uri; + else this.redirect_uri = 'oob'; + this.response_type = oauthConfig.response_type; + if (oauthConfig.accessTokenMethod != undefined) this.accessTokenMethod = oauthConfig.accessTokenMethod; + else this.accessTokenMethod = 'GET'; + this.scope = oauthConfig.scope; + this.url = ''; + this.requested_token = ''; + this.exchangingToken = false; +} + +OauthAssistant.prototype.setup = function() { + Log.debug(this.TAG + ': setup'); + this.controller.setupWidget('browser', {}, + this.storyViewModel = {}); + this.backModel = { + label: $L('Back'), + icon: 'back', + command: 'back' + }; + this.reloadModel = { + label: $L('Reload'), + icon: 'refresh', + command: 'refresh' + }; + this.stopModel = { + label: $L('Stop'), + icon: 'load-progress', + command: 'stop' + }; + if (!Mojo.Environment.DeviceInfo.keyboardAvailable) + { + this.cmdMenuModel = { + visible: true, + items: [{}, + this.backModel, + this.reloadModel] + }; + } + else + { + this.cmdMenuModel = { + visible: true, + items: [{}, + this.reloadModel] + }; + } + this.controller.setupWidget(Mojo.Menu.commandMenu, { + menuClass: 'no-fade' + }, + this.cmdMenuModel); + Mojo.Event.listen(this.controller.get('browser'), Mojo.Event.webViewLoadProgress, this.loadProgress.bind(this)); + Mojo.Event.listen(this.controller.get('browser'), Mojo.Event.webViewLoadStarted, this.loadStarted.bind(this)); + Mojo.Event.listen(this.controller.get('browser'), Mojo.Event.webViewLoadStopped, this.loadStopped.bind(this)); + Mojo.Event.listen(this.controller.get('browser'), Mojo.Event.webViewLoadFailed, this.loadStopped.bind(this)); + Mojo.Event.listen(this.controller.get('browser'), Mojo.Event.webViewTitleUrlChanged, this.urlChanged.bind(this)); + Mojo.Event.listen(this.controller.get('browser'), Mojo.Event.webViewMimeNotSupported, this.mimeFailure.bind(this)); +} + +OauthAssistant.prototype.urlChanged = function(event) { + if (!Mojo.Environment.DeviceInfo.keyboardAvailable && (event.url.indexOf("accounts.google.com") !== -1 || event.url.indexOf("public-api.wordpress.com") !== -1 || event.url.indexOf("http://api.twitter.com") !== -1)) + { + this.controller.window.PalmSystem.setManualKeyboardEnabled(true); + this.controller.window.PalmSystem.allowResizeOnPositiveSpaceChange(true); + this.controller.window.PalmSystem.keyboardShow(0); + } + else if (!Mojo.Environment.DeviceInfo.keyboardAvailable) + { + this.controller.window.PalmSystem.setManualKeyboardEnabled(false); + } +} + +OauthAssistant.prototype.mimeFailure = function(event) { + var callbackUrl = event.url; + var responseVars = callbackUrl.split("?"); + if (!this.exchangingToken && (responseVars[0] == this.redirect_uri + '/' || responseVars[0] == this.redirect_uri)) { + Log.debug(this.TAG + ': code got'); + this.controller.get('browser').hide(); + var response_param = responseVars[1]; + var result = response_param.match(/code=*/g); + if (result != null) { + var params = response_param.split("&"); + var code = params[0].replace("code=", ""); + Log.debug(this.TAG + ': code is ' + code); + this.codeToken(code); + } + } +} + +OauthAssistant.prototype.requestGrant = function() { + Log.debug(this.TAG + ': requestGrant'); + + var scope = ''; + for(var now in this.scope) { + if(typeof(this.scope[now]) == 'function')continue; + if(scope != '') scope = scope + '+'; + scope = scope + this.scope[now]; + } + + var url = this.authorizeUrl + '?client_id=' + this.client_id + '&redirect_uri=' + this.redirect_uri + '&response_type=' + this.response_type; + if(scope != '') url = url + '&scope=' + scope; + + this.controller.get('browser').mojo.clearCookies(); + this.controller.get('browser').mojo.openURL(url); +}; + +OauthAssistant.prototype.codeToken = function(code) { + this.exchangingToken = true; + this.url = this.accessTokenUrl; + this.code = code.replace(/[^\da-zA-Z]/g, ''); + this.method = this.accessTokenMethod; + var postParams = { + client_id: this.client_id, + client_secret: this.client_secret, + code: this.code, + grant_type: 'authorization_code', + redirect_uri: this.redirect_uri + }; + var postBody = ''; + for (var name in postParams) { + if (postBody == '') { + postBody = name + '=' + postParams[name]; + } + else { + postBody = postBody + '&' + name + '=' + postParams[name]; + } + } + Log.debug(this.TAG + ': posting ' + postBody); + new Ajax.Request(this.url, { + method: this.method, + encoding: 'UTF-8', + postBody: postBody, + onComplete: function(response) { + var response_text = response.responseText; + Log.debug(this.TAG + ': accesstoken: ' + response.status + response_text); + if (!Mojo.Environment.DeviceInfo.keyboardAvailable) + { + this.controller.window.PalmSystem.setManualKeyboardEnabled(false); + } + if (response.status == 200) + { + var responseJSON = response.responseText.evalJSON(); + this.credentials = new Credentials(); + this.credentials.email = false; + this.credentials.password = false; + this.credentials.service = "feedly"; + this.credentials.id = responseJSON.id; + this.credentials.refreshToken = responseJSON.refresh_token; + this.credentials.accessToken = responseJSON.access_token; + this.credentials.tokenType = responseJSON.token_type; + this.credentials.plan = responseJSON.plan; + + var expiryDate = new Date(); + expiryDate.setSeconds(expiryDate.getSeconds() + responseJSON.expires_in); + this.credentials.tokenExpiry = expiryDate; + + this.controller.stageController.swapScene(this.callbackScene, this.credentials); + } + else + { + this.controller.stageController.swapScene("credentials", new Credentials(), true); + } + }.bind(this) + }); +}; + +OauthAssistant.prototype.instanceBrowser = function(oauthBrowserParams) { + this.storyURL = oauthBrowserParams.authUrl; + this.callbackURL = oauthBrowserParams.callbackUrl; + this.controller.get('browser').mojo.openURL(oauthBrowserParams.authUrl); +}; + +OauthAssistant.prototype.handleCommand = function(event) { + if (event.type == Mojo.Event.command) { + switch (event.command) { + case 'refresh': + this.controller.get('browser').mojo.reloadPage(); + break; + case 'stop': + this.controller.get('browser').mojo.stopLoad(); + break; + case 'back': + this.controller.get('browser').mojo.getHistoryState(this.backForward.bind(this)); + break; + } + } + + if (event.type == Mojo.Event.back) { + this.controller.get('browser').mojo.getHistoryState(this.backForward.bind(this)); + event.stop(); + } +}; + +OauthAssistant.prototype.backForward = function(back, forward) { + if (!back) { + if (!Mojo.Environment.DeviceInfo.keyboardAvailable) + { + this.controller.window.PalmSystem.setManualKeyboardEnabled(false); + } + this.controller.stageController.swapScene("credentials", new Credentials()); + } + else { + this.controller.get('browser').mojo.goBack(); + } +}; + +// loadStarted - switch command button to stop icon & command +// +OauthAssistant.prototype.loadStarted = function(event) { + this.cmdMenuModel.items.pop(this.reloadModel); + this.cmdMenuModel.items.push(this.stopModel); + this.controller.modelChanged(this.cmdMenuModel); + this.currLoadProgressImage = 0; +}; +// loadStopped - switch command button to reload icon & command +OauthAssistant.prototype.loadStopped = function(event) { + this.cmdMenuModel.items.pop(this.stopModel); + this.cmdMenuModel.items.push(this.reloadModel); + this.controller.modelChanged(this.cmdMenuModel); +}; +// loadProgress - check for completion, then update progress +OauthAssistant.prototype.loadProgress = function(event) { + var percent = event.progress; + try { + if (percent > 100) { + percent = 100; + } + else if (percent < 0) { + percent = 0; + } + // Update the percentage complete + this.currLoadProgressPercentage = percent; + // Convert the percentage complete to an image number + // Image must be from 0 to 23 (24 images available) + var image = Math.round(percent / 4.1); + if (image > 23) { + image = 23; + } + // Ignore this update if the percentage is lower than where we're showing + if (image < this.currLoadProgressImage) { + return; + } + // Has the progress changed? + if (this.currLoadProgressImage != image) { + // Cancel the existing animator if there is one + if (this.loadProgressAnimator) { + this.loadProgressAnimator.cancel(); + delete this.loadProgressAnimator; + } + // Animate from the current value to the new value + var icon = this.controller.select('div.load-progress')[0]; + if (icon) { + this.loadProgressAnimator = Mojo.Animation.animateValue(Mojo.Animation.queueForElement(icon), "linear", this._updateLoadProgress.bind(this), { + from: this.currLoadProgressImage, + to: image, + duration: 0.5 + }); + } + } + } + catch(e) { + Mojo.Log.logException(e, e.description); + } +}; +OauthAssistant.prototype._updateLoadProgress = function(image) { + // Find the progress image + image = Math.round(image); + // Don't do anything if the progress is already displayed + if (this.currLoadProgressImage == image) { + return; + } + var icon = this.controller.select('div.load-progress'); + if (icon && icon[0]) { + icon[0].setStyle({ + 'background-position': "0px -" + (image * 48) + "px" + }); + } + this.currLoadProgressImage = image; +}; +OauthAssistant.prototype.activate = function(event) { + this.requestGrant(); +} + +OauthAssistant.prototype.deactivate = function(event) { + +} +OauthAssistant.prototype.cleanup = function(event) { + Mojo.Event.stopListening(this.controller.get('browser'), Mojo.Event.webViewLoadProgress, this.loadProgress); + Mojo.Event.stopListening(this.controller.get('browser'), Mojo.Event.webViewLoadStarted, this.loadStarted); + Mojo.Event.stopListening(this.controller.get('browser'), Mojo.Event.webViewLoadStopped, this.loadStopped); + Mojo.Event.stopListening(this.controller.get('browser'), Mojo.Event.webViewLoadFailed, this.loadStopped); + Mojo.Event.stopListening(this.controller.get('browser'), Mojo.Event.webViewTitleUrlChanged, this.urlChanged); + Mojo.Event.stopListening(this.controller.get('browser'), Mojo.Event.webViewMimeNotSupported, this.mimeFailure); +} + diff --git a/app/models/all-subscriptions.js b/app/models/all-subscriptions.js index 4317de2..65c5f12 100755 --- a/app/models/all-subscriptions.js +++ b/app/models/all-subscriptions.js @@ -71,10 +71,21 @@ var AllSubscriptions = Class.create(SubscriptionContainer, { }, sort: function(success, failure) { + var self = this if(Preferences.isManualFeedSort()) { - this.sortManually(success, failure) + if (self.api.supportsManualSort()) + { + this.sortManually(success, failure) + } + else + { + Feeder.notify($L("Manual Sort Not Available")) + Preferences.setManualFeedSort(false) + this.sortAlphabetically(success, failure) + } } else { + this.sortAlphabetically(success, failure) } }, diff --git a/app/models/api.js b/app/models/api.js index c283b48..b7edd3c 100755 --- a/app/models/api.js +++ b/app/models/api.js @@ -3,7 +3,7 @@ var Api = Class.create({ this.appApi = undefined }, - login: function(credentials, success, failure) { + login: function(credentials, success, failure, controller) { if (credentials.service == "tor") { this.appApi = new TorApi() @@ -14,6 +14,11 @@ var Api = Class.create({ this.appApi = new InoApi() this.appApi.login(credentials, success, failure) } + else if (credentials.service == "feedly") + { + this.appApi = new FeedlyApi() + this.appApi.login(credentials, success, failure, controller) + } else { // No supported service to log into @@ -135,5 +140,9 @@ var Api = Class.create({ supportsSearch: function() { return this.appApi.supportsSearch() + }, + + supportsManualSort: function() { + return this.appApi.supportsManualSort() } }) \ No newline at end of file diff --git a/app/models/article.js b/app/models/article.js index 22fc145..e56bdf3 100755 --- a/app/models/article.js +++ b/app/models/article.js @@ -10,8 +10,29 @@ var Article = Class.create({ var content = data.content || data.summary || {content: ""} this.summary = this.cleanUp(content.content) this.readLocked = data.isReadStateLocked - this.setStates(data.categories) - this.setDates(parseInt(data.crawlTimeMsec, 10)) + this.isRead = false + this.isShared = false + this.isStarred = false + + if(data.tags) + { + this.setStates(data.tags) + + if (data.unread !== undefined) + { + this.isRead = !data.unread + } + } + else if (data.unread !== undefined) + { + this.isRead = !data.unread + } + else + { + this.setStates(data.categories) + } + var pubDate = data.crawlTimeMsec || data.crawled + this.setDates(parseInt(pubDate, 10)) this.setArticleLink(data.alternate) }, @@ -40,26 +61,35 @@ var Article = Class.create({ }, setStates: function(categories) { - this.isRead = false - this.isShared = false - this.isStarred = false - categories.each(function(category) { - if(category.endsWith("/state/com.google/read")) { - this.isRead = true - } - - if(category.endsWith("/state/com.google/kept-unread")) { - this.keepUnread = true - } - - if(category.endsWith("/state/com.google/starred")) { - this.isStarred = true - } - - if(category.endsWith("/state/com.google/broadcast")) { - this.isShared = true - } + if (category.id !== undefined) + { + if(category.id.endsWith("/tag/global.read")) { + this.isRead = true + } + + if(category.id.endsWith("/tag/global.saved")) { + this.isStarred = true + } + } + else + { + if(category.endsWith("/state/com.google/read")) { + this.isRead = true + } + + if(category.endsWith("/state/com.google/kept-unread")) { + this.keepUnread = true + } + + if(category.endsWith("/state/com.google/starred")) { + this.isStarred = true + } + + if(category.endsWith("/state/com.google/broadcast")) { + this.isShared = true + } + } }.bind(this)) }, diff --git a/app/models/credentials.js b/app/models/credentials.js index d59be95..3ffe1e6 100755 --- a/app/models/credentials.js +++ b/app/models/credentials.js @@ -10,12 +10,54 @@ var Credentials = Class.create({ { this.service = "tor" } + this.id = this.idCookie().get() + this.accessToken = this.accessTokenCookie().get() + this.refreshToken = this.refreshTokenCookie().get() + this.tokenExpiry = this.tokenExpiryCookie().get() + this.tokenType = this.tokenTypeCookie().get() + this.plan = this.planCookie().get() }, save: function() { - this.emailCookie().put(this.email) - this.passwordCookie().put(this.password) - this.serviceCookie().put(this.service) + if (this.email !== undefined){ + this.emailCookie().put(this.email) + } + if (this.password !== undefined){ + this.passwordCookie().put(this.password) + } + if (this.service !== undefined){ + this.serviceCookie().put(this.service) + } + if (this.id !== undefined){ + this.idCookie().put(this.id) + } + if (this.accessToken !== undefined){ + this.accessTokenCookie().put(this.accessToken) + } + if (this.refreshToken !== undefined){ + this.refreshTokenCookie().put(this.refreshToken) + } + if (this.tokenExpiry !== undefined){ + this.tokenExpiryCookie().put(this.tokenExpiry) + } + if (this.tokenType !== undefined){ + this.tokenTypeCookie().put(this.tokenType) + } + if (this.plan !== undefined){ + this.planCookie().put(this.plan) + } + }, + + clear: function() { + this.emailCookie().remove() + this.passwordCookie().remove() + this.serviceCookie().remove() + this.idCookie().remove() + this.accessTokenCookie().remove() + this.refreshTokenCookie().remove() + this.tokenExpiryCookie().remove() + this.tokenTypeCookie().remove() + this.planCookie().remove() }, emailCookie: function() { @@ -29,6 +71,30 @@ var Credentials = Class.create({ serviceCookie: function() { return this.getCookie("service") }, + + idCookie: function() { + return this.getCookie("id") + }, + + accessTokenCookie: function() { + return this.getCookie("accessToken") + }, + + refreshTokenCookie: function() { + return this.getCookie("refreshToken") + }, + + tokenExpiryCookie: function() { + return this.getCookie("tokenExpiry") + }, + + tokenTypeCookie: function() { + return this.getCookie("tokenType") + }, + + planCookie: function() { + return this.getCookie("plan") + }, getCookie: function(name) { return new Mojo.Model.Cookie(name) diff --git a/app/models/feedly-api.js b/app/models/feedly-api.js new file mode 100644 index 0000000..a0bd209 --- /dev/null +++ b/app/models/feedly-api.js @@ -0,0 +1,606 @@ +var FeedlyApi = Class.create({ + //UPDATED 0.9.5 + login: function(credentials, success, failure, controller) { + if (credentials.id != null && credentials.accessToken != null && credentials.refreshToken != null && credentials.tokenExpiry != null) + { + this.credentials = credentials + if (this._checkTokenExpiry()) + { + success(this.credentials.accessToken) + } + else + { + failure() + return + } + } + else + { + var oauthConfig={ + callbackScene:'login', //Name of the assistant to be called on the OAuth Success + authorizeUrl: FeedlyApi.BASE_URL + 'auth/auth', + accessTokenUrl: FeedlyApi.BASE_URL + 'auth/token', + accessTokenMethod:'POST', // Optional - 'GET' by default if not specified + client_id: FeedlyApi.CLIENT_ID, + client_secret: FeedlyApi.CLIENT_SECRET, + redirect_uri:'urn:ietf:wg:oauth:2.0:oob', // Optional - 'oob' by default if not specified + response_type:'code', // now only support code + scope: ['https://cloud.feedly.com/subscriptions'] + }; + controller.stageController.pushScene('oauth',oauthConfig); + } + }, + + //UPDATED 0.9.5 + getTags: function(success, failure) { + new Ajax.Request(FeedlyApi.BASE_URL + "tags", { + method: "get", + parameters: {output: "json"}, + requestHeaders: this._requestHeaders(), + onFailure: failure, + onSuccess: function(response) {success(response.responseText.evalJSON())} + }) + }, + + //NOT CURRENTLY SUPPORTED BY API + getSortOrder: function(success, failure) { + new Ajax.Request(FeedlyApi.BASE_URL + "preference/stream/list", { + method: "get", + parameters: {output: "json"}, + requestHeaders: this._requestHeaders(), + onFailure: failure, + onSuccess: function(response) { + var prefs = response.responseText.evalJSON() + var sortOrder = {} + + if(prefs && prefs.streamprefs) { + $H(prefs.streamprefs).each(function(pair) { + pair.key = pair.key.gsub(/user\/\d+\//, "user/-/") + + $A(pair.value).each(function(pref) { + if("subscription-ordering" == pref.id) { + sortOrder[pair.key] = new SortOrder(pref.value) + } + }) + }) + } + + success(sortOrder) + } + }) + }, + + //NOT CURRENTLY SUPPORTED BY API + setSortOrder: function(sortOrder, stream) { + this._getEditToken(function(token) { + var parameters = { + T: token, + s: stream || "user/-/state/com.google/root", + k: "subscription-ordering", + v: sortOrder + } + + new Ajax.Request(FeedlyApi.BASE_URL + "preference/stream/set", { + method: "post", + parameters: parameters, + requestHeaders: this._requestHeaders() + }) + }.bind(this)) + }, + + //UPDATED 0.9.5 + unsubscribe: function(feed) { + if(feed.constructor == Folder) { + this.removeLabel(feed) + } + else { + this._ajaxDelete(FeedlyApi.BASE_URL + "subscriptions/" + encodeURIComponent(feed.id), function() {Mojo.Event.send(document, "SubscriptionDeleted", {id: feed.id, count: feed.unreadCount})}, function() {}) + } + }, + + //UPDATED 0.9.5 + removeLabel: function(folder) { + this._ajaxDelete(FeedlyApi.BASE_URL + "categories/" + encodeURIComponent(folder.id), function() {Mojo.Event.send(document, "FolderDeleted", {id: folder.id})}, function() {}) + }, + + //UPDATED 0.9.5 + searchSubscriptions: function(query, success, failure) { + var self = this + + new Ajax.Request(FeedlyApi.BASE_URL + "search/feeds", { + method: "get", + parameters: {q: query, output: "json"}, + requestHeaders: this._requestHeaders(), + onFailure: failure, + onSuccess: function(response) { + var subscriptions = response.responseText.evalJSON().results + success(subscriptions) + } + }) + }, + + //UPDATED 0.9.5 + addSubscription: function(url, success, failure) { + //Get Feed Information + new Ajax.Request(FeedlyApi.BASE_URL + "feeds/" + encodeURIComponent("feed/" + url), { + method: "get", + parameters: {output: "json"}, + requestHeaders: this._requestHeaders(), + onFailure: failure, + onSuccess: this._processSubscription.bind(this, success, failure) + }) + }, + + //UPDATED 0.9.5 + _processSubscription: function(success, failure, response) { + // Then add feed + var feed = response.responseText.evalJSON() + if (feed !== undefined && feed !== null && feed.id && feed.title) + { + var parameters = { + id: feed.id, + title: feed.title + } + + new Ajax.Request(FeedlyApi.BASE_URL + "subscriptions", { + method: "post", + postBody: JSON.stringify(parameters), + requestHeaders: this._requestHeaders(), + contentType: "application/json", + onSuccess: success, + onFailure: failure + }) + } + else + { + failure() + } + }, + + //UPDATED 0.9.5 + getAllSubscriptions: function(success, failure) { + var self = this + + new Ajax.Request(FeedlyApi.BASE_URL + "subscriptions", { + method: "get", + parameters: {output: "json"}, + requestHeaders: this._requestHeaders(), + onFailure: failure, + onSuccess: function(response) { + var subscriptions = response.responseText.evalJSON() + self.cacheTitles(subscriptions) + success(subscriptions) + } + }) + }, + + //UPDATED 0.9.5 + cacheTitles: function(subscriptions) { + var self = this + self.titles = {} + + subscriptions.each(function(subscription) { + self.titles[subscription.id] = subscription.title + }) + }, + + //UPDATED 0.9.5 + titleFor: function(id) { + return this.titles[id] + }, + + //UPDATED 0.9.5 + getUnreadCounts: function(success, failure) { + new Ajax.Request(FeedlyApi.BASE_URL + "markers/counts", { + method: "get", + parameters: {output: "json"}, + requestHeaders: this._requestHeaders(), + onFailure: failure, + onSuccess: function(response) { + var json = response.responseText.evalJSON() + + if(json.denied) { + failure() + } + else { + success(json.unreadcounts) + } + } + }) + }, + + //UPDATED 0.9.5 + getAllArticles: function(continuation, success, failure) { + this._getArticles( + "user/" + this.credentials.id + "/category/global.all", + Preferences.hideReadArticles() ? true : false, + continuation, + success, + failure + ) + }, + + //UPDATED 0.9.5 + getAllStarred: function(continuation, success, failure) { + this._getArticles( + "user/" + this.credentials.id + "/tag/global.saved", + false, + continuation, + success, + failure + ) + }, + + //NOT CURRENTLY SUPPORTED BY API + getAllShared: function(continuation, success, failure) { + this._getArticles( + "user/-/state/com.google/broadcast", + false, + continuation, + success, + failure + ) + }, + + //UPDATED 0.9.5 + getAllArticlesFor: function(id, continuation, success, failure) { + this._getArticles( + id, + Preferences.hideReadArticles() ? true : false, + continuation, + success, + failure + ) + }, + + //UPDATED 0.9.5 + _getArticles: function(id, exclude, continuation, success, failure) { + var parameters = {output: "json", count: 40} + + if(!id.endsWith("/tag/global.saved") && Preferences.isOldestFirst()) { + parameters.ranked = "oldest" + } + + if(continuation) { + parameters.continuation = continuation + } + + if(exclude) { + parameters.unreadOnly = exclude + } + + new Ajax.Request(FeedlyApi.BASE_URL + "streams/" + encodeURIComponent(id) + "/contents", { + method: "get", + parameters: parameters, + requestHeaders: this._requestHeaders(), + onFailure: failure, + onSuccess: function(response) { + var articles = response.responseText.evalJSON() + success(articles.items, articles.id, articles.continuation) + } + }) + }, + + //UPDATED 0.9.5 + markAllRead: function(id, success, failure) { + if (id === "user/-/state/com.google/reading-list") + { + id = "user/" + this.credentials.id + "/category/global.all" + } + + if (id.indexOf("category") !== -1) + { + var parameters = { + action: "markAsRead", + type: "categories", + categoryIds: [id], + asOf: new Date().getTime() + } + } + else + { + var parameters = { + action: "markAsRead", + type: "feeds", + feedIds: [id], + asOf: new Date().getTime() + } + } + + this._editMarker(parameters, success, failure) + }, + + //TODO NEXT VERSION + search: function(query, id, success, failure) { + var parameters = { + q: query, + num: 50, + output: "json" + } + + if(id) { + parameters.s = id + } + + new Ajax.Request(FeedlyApi.BASE_URL + "search/items/ids", { + method: "get", + parameters: parameters, + requestHeaders: this._requestHeaders(), + onSuccess: this.searchItemsFound.bind(this, success, failure), + onFailure: failure + }) + }, + + //TODO NEXT VERSION + searchItemsFound: function(success, failure, response) { + var self = this + var ids = response.responseText.evalJSON().results + + if(ids.length) { + self._getEditToken( + function(token) { + var parameters = { + T: token, + i: ids.map(function(n) {return n.id}) + } + + new Ajax.Request(FeedlyApi.BASE_URL + "stream/items/contents", { + method: "post", + parameters: parameters, + requestHeaders: self._requestHeaders(), + onFailure: failure, + onSuccess: function(response) { + var articles = response.responseText.evalJSON() + success(articles.items, articles.id, articles.continuation) + } + }) + } + ) + } + else { + success([], "", false) + } + }, + + //TODO NEXT VERSION + mapSearchResults: function(response) { + console.log(response.responseText) + }, + + //UPDATED 0.9.5 + setArticleRead: function(articleId, subscriptionId, success, failure) { + var parameters = { + action: "markAsRead", + type: "entries", + entryIds: [articleId] + } + + this._editMarker(parameters, success, failure) + }, + + //UPDATED 0.9.5 + setArticleNotRead: function(articleId, subscriptionId, success, failure, sticky) { + var parameters = { + action: "keepUnread", + type: "entries", + entryIds: [articleId] + } + + this._editMarker(parameters, success, failure) + }, + + //NOT CURRENTLY SUPPORTED BY API + setArticleShared: function(articleId, subscriptionId, success, failure) { + this._editTag( + articleId, + subscriptionId, + "user/-/state/com.google/broadcast", + null, + success, + failure + ) + }, + + //NOT CURRENTLY SUPPORTED BY API + setArticleNotShared: function(articleId, subscriptionId, success, failure) { + this._editTag( + articleId, + subscriptionId, + null, + "user/-/state/com.google/broadcast", + success, + failure + ) + }, + + //UPDATED 0.9.5 + setArticleStarred: function(articleId, subscriptionId, success, failure) { + var parameters = { + entryIds: [articleId] + } + this._addTag(parameters, "user/" + this.credentials.id +"/tag/global.saved", success, failure) + }, + + //UPDATED 0.9.5 + setArticleNotStarred: function(articleId, subscriptionId, success, failure) { + this._removeTag(articleId, "user/" + this.credentials.id +"/tag/global.saved", success, failure) + }, + + //UPDATED 0.9.5 + _editMarker: function(parameters, success, failure) { + new Ajax.Request(FeedlyApi.BASE_URL + "markers", { + method: "post", + postBody: JSON.stringify(parameters), + requestHeaders: this._requestHeaders(), + contentType: "application/json", + onSuccess: success, + onFailure: failure + }) + }, + + //UPDATED 0.9.5 + _addTag: function(parameters, tag, success, failure) { + Log.debug("adding tag "+ tag) + this._ajaxPut(parameters, FeedlyApi.BASE_URL + "tags/" + encodeURIComponent(tag), success, failure) + }, + + //UPDATED 0.9.5 + _removeTag: function(articleId, tag, success, failure) { + Log.debug("removing tag "+ tag) + this._ajaxDelete(FeedlyApi.BASE_URL + "tags/" + encodeURIComponent(tag) + "/" + encodeURIComponent(articleId), success, failure) + }, + + //UPDATED 0.9.5 + _requestHeaders: function() { + if (this._checkTokenExpiry()) + { + return {Authorization:"OAuth " + this.credentials.accessToken} + } + else + { + return {Authorization:"OAuth Error"} + } + }, + + //UPDATED 0.9.5 + _ajaxPut: function(params, url, success, failure) { + try { + var req = new XMLHttpRequest() + req.open("PUT", url, true) + req.setRequestHeader('Content-Type', 'application/json') + req.setRequestHeader('Authorization', "OAuth " + this.credentials.accessToken) + req.send(JSON.stringify(params)) + req.onreadystatechange = function() { + if(req.readyState === 4 && req.status === 200){ + success + } + else if(req.readyState === 4 && req.status !== 200){ + failure + } + else + { + return + } + } + } + catch (e) { + Log.debug('_ajaxPut failed! Error:' + e) + failure + } + }, + + //UPDATED 0.9.5 + _ajaxDelete: function(url, success, failure) { + try { + var req = new XMLHttpRequest() + req.open("DELETE", url, true) + req.setRequestHeader('Authorization', "OAuth " + this.credentials.accessToken) + req.send() + req.onreadystatechange = function() { + if(req.readyState === 4 && req.status === 200){ + success + } + else if(req.readyState === 4 && req.status !== 200){ + failure + } + else + { + return + } + } + } + catch (e) { + Log.debug('_ajaxDelete failed! Error:' + e) + failure + } + }, + + //UPDATED 0.9.5 + _checkTokenExpiry: function() { + if (new Date(expiryDate) > new Date()) + { + return true + } + else + { + var parameters = { + refresh_token: this.credentials.refreshToken, + client_id: FeedlyApi.CLIENT_ID, + client_secret: FeedlyApi.CLIENT_SECRET, + grant_type: "refresh_token" + } + + try { + var req = new XMLHttpRequest() + req.open("POST", FeedlyApi.BASE_URL + "auth/token", false) + req.setRequestHeader('Content-Type', 'application/json') + req.send(JSON.stringify(parameters)) + if(req.readyState === 4 && req.status === 200){ + response = req.responseText.evalJSON() + if (response) { + if (response.id) { + this.credentials.id = response.id + } + if (response.access_token) { + this.credentials.accessToken = response.access_token + } + if (response.expires_in) { + var expiryDate = new Date(); + expiryDate.setSeconds(expiryDate.getSeconds() + response.expires_in); + this.credentials.tokenExpiry = expiryDate + } + if (response.token_type) { + this.credentials.tokenType = response.token_type + } + if (response.plan) { + this.credentials.plan = response.plan + } + this.credentials.save() + return true + } + else + { + return false + } + } + else + { + throw req.responseText + } + } + catch (e) { + Log.debug('_checkTokenExpiry failed! Error:' + e) + return false + } + } + }, + + //UPDATED 0.9.5 + supportsAllArticles: function() { + return true + }, + + //UPDATED 0.9.5 + supportsStarred: function() { + return true + }, + + //UPDATED 0.9.5 + supportsShared: function() { + return false + }, + + //UPDATED 0.9.5 + supportsSearch: function() { + return false + }, + + //UPDATED 0.9.5 + supportsManualSort: function() { + return false + } +}) + +FeedlyApi.BASE_URL = "https://sandbox.feedly.com/v3/"; +FeedlyApi.CLIENT_ID = "sandbox264"; +FeedlyApi.CLIENT_SECRET = "HQ88RW5P2O5HZHPLXM1QY2Z7"; \ No newline at end of file diff --git a/app/models/ino-api.js b/app/models/ino-api.js index a6a0d2f..9ee61af 100644 --- a/app/models/ino-api.js +++ b/app/models/ino-api.js @@ -472,7 +472,13 @@ var InoApi = Class.create({ supportsSearch: function() { return false + }, + + //UPDATED 0.9.5 + supportsManualSort: function() { + return true } + }) InoApi.BASE_URL = "https://www.inoreader.com/reader/api/0/" \ No newline at end of file diff --git a/app/models/subscription-container.js b/app/models/subscription-container.js index 1d1b1bf..037c602 100755 --- a/app/models/subscription-container.js +++ b/app/models/subscription-container.js @@ -18,30 +18,38 @@ var SubscriptionContainer = Class.create(Countable, { }, move: function(subscription, beforeSubscription) { - var self = this + if (this.api.supportsManualSort()) + { + var self = this - self.items.each(function(item, index) { - if(item.id == subscription.id) { - Log.debug("removing " + subscription.id + " at index " + index) - self.items.splice(index, 1) - throw $break - } - }) + self.items.each(function(item, index) { + if(item.id == subscription.id) { + Log.debug("removing " + subscription.id + " at index " + index) + self.items.splice(index, 1) + throw $break + } + }) - if(beforeSubscription) { - self.items.each(function(item, index) { - if(item.id == beforeSubscription.id) { - Log.debug("inserting " + subscription.id + " at index " + index) - self.items.splice(index, 0, subscription) - throw $break - } - }) + if(beforeSubscription) { + self.items.each(function(item, index) { + if(item.id == beforeSubscription.id) { + Log.debug("inserting " + subscription.id + " at index " + index) + self.items.splice(index, 0, subscription) + throw $break + } + }) + } + else { + self.items.push(subscription) + } + + var sortOrder = self.items.map(function(subscription) {return subscription.sortId}).join("") + this.api.setSortOrder(sortOrder, this.subscriptionOrderingStream) } - else { - self.items.push(subscription) + else + { + Feeder.notify($L("Manual Sort Not Available")) + Preferences.setManualFeedSort(false) } - - var sortOrder = self.items.map(function(subscription) {return subscription.sortId}).join("") - this.api.setSortOrder(sortOrder, this.subscriptionOrderingStream) } }) \ No newline at end of file diff --git a/app/models/tor-api.js b/app/models/tor-api.js index 86da40d..0ce1e10 100755 --- a/app/models/tor-api.js +++ b/app/models/tor-api.js @@ -405,25 +405,25 @@ var TorApi = Class.create({ }, setArticleStarred: function(articleId, subscriptionId, success, failure) { - /*this._editTag( + this._editTag( articleId, subscriptionId, "user/-/state/com.google/starred", null, success, failure - )*/ + ) }, setArticleNotStarred: function(articleId, subscriptionId, success, failure) { - /*this._editTag( + this._editTag( articleId, subscriptionId, null, "user/-/state/com.google/starred", success, failure - )*/ + ) }, _editTag: function(articleId, subscriptionId, addTag, removeTag, success, failure) { @@ -482,7 +482,7 @@ var TorApi = Class.create({ }, supportsStarred: function() { - return false + return true }, supportsShared: function() { @@ -491,6 +491,11 @@ var TorApi = Class.create({ supportsSearch: function() { return false + }, + + //UPDATED 0.9.5 + supportsManualSort: function() { + return true } }) diff --git a/app/views/credentials/credentials-scene.html b/app/views/credentials/credentials-scene.html index e87c3bf..8e126f1 100755 --- a/app/views/credentials/credentials-scene.html +++ b/app/views/credentials/credentials-scene.html @@ -5,38 +5,38 @@
-
-
+
+
-
-
-
-
+
+
-
-
+
+
-
+
-
-
+
+
-
-
+
+
+
+
diff --git a/app/views/help/help-scene.html b/app/views/help/help-scene.html index db44f9f..6ad3c6d 100755 --- a/app/views/help/help-scene.html +++ b/app/views/help/help-scene.html @@ -33,6 +33,15 @@
History
+
+
0.9.5
+
    +
  • added support for starring articles in The Old Reader
  • +
  • added support for Feedly (sandbox)
  • +
  • fixed a bug when searching for feeds fails
  • +
+
+
0.9.3
    diff --git a/app/views/oauth/oauth-scene.html b/app/views/oauth/oauth-scene.html new file mode 100755 index 0000000..b8a5783 --- /dev/null +++ b/app/views/oauth/oauth-scene.html @@ -0,0 +1,2 @@ + +
    \ No newline at end of file diff --git a/app/views/oauth/oauth.css b/app/views/oauth/oauth.css new file mode 100755 index 0000000..ba6025f --- /dev/null +++ b/app/views/oauth/oauth.css @@ -0,0 +1,86 @@ +.palm-menu-icon.load-progress { + background-image: url(progress-indicator.png); + width: 48px; + height: 48px; + left: -24px; + margin-top: -8px; +} + +.palm-menu-icon.load-progress.progress-0 { + background-position: 0px 0px; +} +.palm-menu-icon.load-progress.progress-1 { + background-position: 0px -48px; +} +.palm-menu-icon.load-progress.progress-2 { + background-position: 0px -96px; +} +.palm-menu-icon.load-progress.progress-3 { + background-position: 0px -144px; +} +.palm-menu-icon.load-progress.progress-4 { + background-position: 0px -192px; +} +.palm-menu-icon.load-progress.progress-5 { + background-position: 0px -240px; +} +.palm-menu-icon.load-progress.progress-6 { + background-position: 0px -288px; +} +.palm-menu-icon.load-progress.progress-7 { + background-position: 0px -336px; +} +.palm-menu-icon.load-progress.progress-8 { + background-position: 0px -384px; +} +.palm-menu-icon.load-progress.progress-9 { + background-position: 0px -432px; +} +.palm-menu-icon.load-progress.progress-10 { + background-position: 0px -480px; +} +.palm-menu-icon.load-progress.progress-11 { + background-position: 0px -528px; +} +.palm-menu-icon.load-progress.progress-12 { + background-position: 0px -576px; +} +.palm-menu-icon.load-progress.progress-13 { + background-position: 0px -624px; +} +.palm-menu-icon.load-progress.progress-14 { + background-position: 0px -672px; +} +.palm-menu-icon.load-progress.progress-15 { + background-position: 0px -720px; +} +.palm-menu-icon.load-progress.progress-16 { + background-position: 0px -768px; +} +.palm-menu-icon.load-progress.progress-17 { + background-position: 0px -816px; +} +.palm-menu-icon.load-progress.progress-18 { + background-position: 0px -864px; +} +.palm-menu-icon.load-progress.progress-19 { + background-position: 0px -912px; +} +.palm-menu-icon.load-progress.progress-20 { + background-position: 0px -960px; +} +.palm-menu-icon.load-progress.progress-21 { + background-position: 0px -1008px; +} +.palm-menu-icon.load-progress.progress-22 { + background-position: 0px -1056px; +} +.palm-menu-icon.load-progress.progress-23 { + background-position: 0px -1104px; +} +.palm-menu-icon.load-progress.progress-24 { + background-position: 0px -1152px; +} +.palm-menu-icon.load-progress.progress-25 { + background-position: 0px -1200px; +} \ No newline at end of file diff --git a/app/views/oauth/progress-indicator.png b/app/views/oauth/progress-indicator.png new file mode 100755 index 0000000000000000000000000000000000000000..5264adec3b13cd25cd983302a5e33e7972fd9423 GIT binary patch literal 9613 zcmb7qc|4T;`>!ox5|JjdB#9z7BTC6K^&pC|M-0`7P-MxBwJ;RQ$d)x@%T6KNFxHGM zJCS|gm$79T&OLda=X}rkz0T|RJO9)@UeoP!UDtcNKCj*)G*wwycvxs?Xjt#4-PQsB zmZYI!7&*!aK7DblDx;wh*1L0CSr6U67{}yf_}6Cr_>wW=1Q+wjId6?nr0;Fz>!)?@ z(CSz*TQK83(%=n`By;;BlAnw6WT>;VpM1e}=C8ynC-e|!vzF%ON-ZPv%MNxwEuRMz z;Ae?A-{Bauidbop;H{y75|{p%sNv~>dEs-&3nBoI621D*S2Njt5}$wBm9@+-l`WB7 z(~JwrtY@yQtUc{>s1AMBk8+y(xtcJnt7XCDH2;#DxwqKg63w)Hj*Sdg(QXt!L64a6 z*czJG&wXw>T<_sw>9jboZEtU$;EVNBzl|CQ1NyEEUDtxG9$Zt{a8B(Gmm->I!tBPw_#dl5Z3dWpo^y6;ox zo!_~k{ajWg$Y%kR(kT@Dbe)t3#syX;@C$6F@7mJ3M^eK3M!Y}#N+n1sR3jM3K^ASfISp}}^ zarm=Ee&*yje^E;NL%gT)2S?7F7i_I7$A|vnpTHg?dEX_eaEn4LBHqxl>*cGwn_K+# z?H=3Wkm*%LYc_1s6HY(Y&PHJo;RezKid*K9Z*mZ?k$Lsseej>+p^L)3gCR~eij&@) z<4&-taCKhZTuv5nGInGvhASWRuIVH{FzUMej z{MK0rOz@0Ff_T{5uXrDiyT!JZaV|r%-Cr%5elH0MmKumpwg+oFZrku41ZyuBdXVpE zO9u>i9Z($Rvy0{q$N2baKkOt-@E45EBGGn+#xwv$6Me6x^&Z_GCBF@5FNh$|)I`u3 zjB1C3bR@rctS$Y9;mDDOu#38=zONSKMzP!(-}!Xi&+7K}d3re}M#Mn*P4!9{)B}}- zyJ4(+34z_=CNQP$kA`J07j;%wt481lw>clk*xBuC#$CVDouMy`XgM1Q1tRn_4H~?U zKv3U9wW=x4r+@0%ae@ia{Tr{C23+ zEpwdkv@U;PcdE0^;LWja6QcbnJWBRibaL8s!^fvAYE-;EPvjwRi5Yak7P13~;H3Z5 zo51NL*7b@!+2mGe^Xny}g5<}f_SlaX5xNGE%CFltv8Gz0 zw>N^`v{8T|gI$BQvCRHrZK0Coid|IkTqZ&SUe1R75qCpGasPJ`f9=DE4>?j81vzr+ z-<=T|_SlLuA)vM64PZDXOnW##dW7~e=hy5UR3V*H+hgT&wm}vdrF+htk~?3JU$#8D zjzx3Jq@>C`9U_#h8@8MYa1Q$PiLgBtn7Pv@he=Yz>U zb2Yo0i=myLwVFWGOvKn#ge)xBMqPW67^Jr?VN!f{*mF1A)6Yd=XGSOv)EA+%XO;fE zK38(a+*xSGAdOa;mX_(uLfsWVxxTr`-+>B7^K4$Y;l0fw*M46PHEX%7`pWhs)xr-V znz6Wi%T{Y4#JJ?qt(Ap|hER^oDfGk_@~xiiqQO*)q+@PTZM3CIBGk89KaF{|d|>d* z9P!Yg*ldF`JoqxxV1!x!v06n%MPfv?>#HXg77eT8^^=`iis7vayLUxQ#)rN35stY}RLHBO$BBhHDc0mB*V%+8 z;S(mxX{=ogJLgzR)|Sd9BZl`>wjQ{Z-gBnEo4s7V70=&Q=CT}d(`|EReSdd(ULn|p zJni$hw2&EL#}NONI~*N1Omlv{PT5*kNqX3*f?!P+@f85W|K;m2A4y`<#Z#8{Ei<&vqG3=HuBtwqW^llS>sE&&6C+g0n3ABhDgtC3(Q8hp|G^HE-+!$AD+e?QtR5 z_r#g2&%0Xf%TWy+RLszSJqsdT0tNK))V5_K=&!$=krX_*Vl7ZR7b@YBzHdBR#Wcy^ zJ>fF&!e8_L3;2ukqJ1oWQwlae^YF4X7lnjwO|^ddv~>(dmDgViZ`QPusIPH1{h-*drA{b+_6v0~t}!a=uQ=y0wlJQRaYPX70J=_gjXPn)>_h8w~59U zi9oX!c9IQZPG-GZ(iYPYbq7a0mjdg#rdIs@rhLtf;+*7D_9O^5?g-U_A0*<({8NTjn(jRUhCZA8GG4vU+mzlf2pXM3RWUP4$I>Rt%(yxg;e) z?$FY9Mik@j0FU#WTIS|rtzk;)zw+Jd@}be^8;AlGiL#tTbwb{yUB2o z$gSGEg10-LE8LAXFQ-`e7FyON_|V05GcB~cZcHe2_hx3&_TP7Y+moT?V$fu9-ejCV zev}WCZIVq3y2tn7@~-Ha>q^iWz2wJSG8*VYyIzTkU}KB^s~e)V*2wZzgF=6aSB|lo zd^gk~g+J1b@>BV{dcLsy$qSnImr5>?;eOrsF83K6!T<40cL|0-uY=v;;W4{;^`0*Q z`0AoGt$X7vN)_H*yc=DTth zv%}PiBA`805f;7_D|U6f#t4FCTA^o|o;vR{h|2b4VX@`AYT;OzwycwA&!_}Ln!*Fq z?nm(_dfF59^Yn|I@7wStJvNmly`Yf-Bkmi|5FzO6%^7!46R2e$S9d;$61; z|Ai<}@xYVmt@l#PsS@L*fmukw}{`%C-#lah8#ftVyj40&`C^M~Xj_nY( z<$-jT^o7O6Zt|CUcltewWty_+6pGA49z8DKrWF^@^|&szVnr!aMw+drC;g#x;F1-!N3=ol~{U5{SmC zvPtsAB?EeOSUU|Ek*$>4Su2-|uMc{b?MvvT>l{(U&8HuZ1YPesyvSHw4Ji+y3^&EJ z?n%3CR|PVCYLNdRCF(rxt69!T;ZOf{HW*qu%XktCHD1}@`Z|*&fa)7HLv2__S`1E~ z<2DNjGF|J@Cm_<#*b5QbvWkGYD~@)WuAA9`u*bt@6SI9{EhE^}a*XolbTSB_d0fO77{o1 zBsr!AalI@g*hM6%6NB$0nlwP_ z&i}rtlN-`UXp40L`1wD6^YF7X4~inR5!k#U>WmJ;jRf{$`HIk5)}gYdF+4Spd+y_e zpkLeBL0;pDcRi+=FiSpW$*mtT;Gw++91J*+H$;SB#E{QLaiPBJY{D6KS#kt~i^WNf zpt-XBuw&7Zt>Q=$j8Ww_fbJAWrp7NuUk)t5`o2~) z)Otu}zyxX4Qy*#3{C>?y`66Vzyj(1%9wJQi)c(Fi(f&@^{=^~UWoaFkssL8$0ju~x z8xyAep!H7m`{nbKi)QDa%(~5Pc-&C1WdM9nmQEF{o4BYX>O)xFHxiHi+DLLb{?NYv;8uMr>?1S-@%sN~+9zZZXs^6t#6ij6@0h^yeuuE$C#Qa1fj_09CHy4NWz+tt z)8^`iOel1&Y|^{-*)FoQwc&2INl8HdSP0j5h#)pwPDm<7LajjEG<5n#ClR4czzx1x zaW=d2DLeA~qmA~g)WyA+>+w+U6EEp;t`+Nrw=cVET>pJUE(S_A^E3%&b`$dj4sP$f ziyC)F9#ynY5H@Ep7F^f*oP0pYezFP=zhoFLqM9AHQYS!q^Fp_I0?Q-&O!+@1_urrz zC2U2*nm{{&RqrJt1{tT;;65G5Porl}-$TCH|C%Ptcqq)pZAQ#K?v(MTh(44snNx<6^O)-B&@F3{)TzS^}-K!jFa zV>ZIT6QSioHJ>ZY&jNaxvi~!>f`j|R(iv+cDQOQfxdIPTRiTcN%JWO11O)jjtQOMJ zuLAZ8C$^7enZ)v3aR!gqTeP-;5$wLMrE=U+7U&Wb0M_UImx3ym6{QBj6Dc#E#ann! z6BlifGow2#&Q~vyXp%=#nBrO>qk$eb>Aekj9TfmQb7*xXGNnhe>FfVMSN>sFNjH&w zJOn(RFfvVDpDIe~@1bNtswnMa<-ZdVN(J1jBoaifhlpb)Aa>yxyo}~F_E7^wE*5&- zS`-NTh4nUww+DYej@B5{4U^y=(L~N5H!LKqcv_?@!zCs(k!d?gEjh->`KnCgzq}?Q z_#LhR6XRm-oxOKz$wKPe!1|C98?sA5!2wv14uq=n+|b|o(SH{IbXRz$PMkTmpa(2` zRd(_JUDltz(>I<6!Z7FSiHN)2B6UJ*Cy{9yrzzCdOChZq%0Q}LZzF=rdjrr5J>+Ek z4@mY|u++453S?23`hX_1-Jv!?9uyefwu69s@3jf+-<8UwW{DC4Jzl~$yFTz-_U`X( zXdoa4p9D`|6xb&Bc35^^Ej^O8* zcqBciMP-ED`R#Yah7rS7je-o1Ys@v8tQS#}QqybjTb$i~R-MoeyBb4|iVK!g4ZkLu zF@wjDR<$JU4!3Q8W6VPML!}vVmiTaGTB^z_{KVQzYkEm6W3o656f(J+x2m6%M;K@K z-r5)8-U#<@{yc)Qa8wAg_5l7;4_gqq5s^a5fWzf2C$ghJTs!k#p#@ zge`PC!mOHlcaneT4DlkZ#>BY^qibSe^>_G7HGdB9rFUm2F1RB_pD(E}BLufG#XNhmb>F4(lTZUW*+FPML3W8!AbNT9vX zEw@Z!CC%wCWQS|J?LXlMPF{CPY~CAzMtQ9a!zhEF&RZCMGXl|{7eZe6HQZ2qRbnn* zJ2vEa|K5fS8Xxy#9k_C7{EdNv_Qhelnn;c>7c48mWFq`ySoFmiVh4p!_q=s^(^!@n z24CP3+X7(~*vj%1Z|VR$byD?R8YQnVLq>;n6bqj!4{`Y!STQU{xoxoWGX*qv?NDUc z2v>b~;$qVR|C-BZg%iA;UE)Y9#MtP{+a}8b)?knW$TwLGhuvxe3z4rQ;w)F;{9v=3 z4&d=skjt1zD(1cmfvxzw4P|+Af!euo48D3iBqfGF*cQMD?m#b%|E&-?O^|JA{-_%m z{QDr)DXRnc@k#6@^`z(EW#$NWfIs-I$3vU6@Kn1QrCI4^t~E5iyZ%7SfR~ahzCSTc z-t#}eS#D9f#gS^C?k-oH4Rz2;Q6=tY8xh#U_#t&J?qW&brbVBh0;5lCtsTX9Ox7bM zl_bp(;dx1GH4}}Wc1e+4e>yLYJlbgNSlV5$J^pX8hXpTjbXEX#Co@Gu4o1JF;b4JHT<3+-tZKQjVRzIWLkZJhEG$=KmWr322m$ z&Q(_);N-1C1c7D{+YHh7XyYazSgDEvicXNKL8j+p>IXgfLkdbA;^}YKdrl(aZjFbu zgPMzvsf47I_(SJT^gW2++cE0UQkO#G`Ig49JM|(BVWyTF=>;K#wv~&mO~t*fjlWH& z1Ajo|&hhsVoHHU?8%T@8C!?SazFv3*$8US3eo`UOfRE+(#4LrA(DVBULLLZU6;-)F?tdJID)*XlNpp9~M{nqi$jF0aUr|1KFRZJjQt)k*{p-8?**o z@z|0xLeC77$37o~=!3)FcY-&MV*T#LX3L$8z3u~{6F_}|tE|9ldOgMOs9#)B8u6}V zniX>5MB^WhTdMt>s=Hck$Q(AHi|PkC>5uqfviE<1n)P`wi*w5V>9=juVs@!*pv2pbN0L zkcUq1{+a|@S6kRc*o&g%}o-J#v` zX3vA^57XETMH4as;Xpn*yH>5~asNziw1U)DLeFWWsI_q^gLq3v=Y!xvGb`aX)}RsW zBNYV^+NT1uAL+wf0}3Wg zsSc4dg5_UHL!-hE;o+3@A7J=9&fPu17jwrHpT6VweLscWxoe6)vUT6hc*kx} zozYHC>`ty(Si|yOc^C%Iu>zb_urnN*jUGJOWt_eSuiF5+5=wHf{^UDa@Sa&Jue$53 zv>%kNjNY#vMmOT;EN^uTVDMuSXZy(C6&6BZcRzSFdr==b^W-=55*L!hw~rk;L#jdJ z?{QW#m^MKAC=fmnqa$W%MjHP?mF6{ILVJ}2a^<^_BL@(6Xvg?H0-~EKZq#hX4}(wE z^C!a_nn;kS8c6obYd}}*K9;u<(!H^03%0%r^tlL7P4OL*Sj|7HA5}(@gED(3q(CX0 zXdh46s=irDlXuhH9h~u4y@Hsw#+FTl$WnM8Z938kBN zo&Ayar4u$<8p8uVqC#lFyz3voi+k2zLmtgK{*muW(l0cAYzIo0KYj{nRi!|kePe`| z;MNMY(-XyL)L$dm8M8P4*zBLE3?_*}FZ?C_mMLta+0Va_ByhoEqk#E~n*zZEc?oXR zT>vIt9>6!wghC(5!>IHE^`&kUs~PbSDY`m~n!Wlcx+}}HwU2=FvO=k>lcG3odX(Rm za8<1~^t^zV#hbehOq-FgPp>F>h-VEKX7#_4)R6obe-ZO_0|Ov@wtEj+|yMAB|l{6h2}A7-X5EvV@=ijP5+L2It~!guHHfttBvD zG&m1_w4(!@vc$>o*V;cuv2^G7z`bP9b!{xcYm8UB95K{u{hO9#3fr4XQIte&>H#i9+C@FX9Q>YmNe5ik z2y_*f&|~m!R6cVY+_6b9XnLR!Z zkH-68e?$90dHmN$z-&HaMtVVLV^VaxFAoF%F@_N+Siecdj(X6cQ3%&UXraVJ_Okpc0OgLS%?;T66Zzr&SjGgCxm7B4P7KEC!{az_SLLCTR zf&{1RzDihS`zomo3+}~ympKV-3R|nxMv&SGZOI(t*z4hRK-U+0q}nXFm5k*NXV%xpdtGQp89DZ?{>v(fWemHJM(a)^*H1(|1ltqeG5*hrY77D{^!U`F z41qy~#B~47Q8_Dcb2r{I!Oh@iS}I0pLo%(w{}2JpvVT^iOXI5~P)WOP zG?c888iTy3nV&nj*#h}ym6THlIdc0t*ot>+@Idu=Xmr`e1v0!_AQkLL10)^1YG^0o zRMtP`VmUIWNn(j~)kMVOPDvY8RoX-w~ZtVAR(bx+m~u z3(8$e0buq`a^>m0EG@1LyvU=ffg$t6F#h=;KQ*(6_L6{JNh5e4Y_>5xl^g-^T`U1UGXb z(=g0r_+@z?sIfMjfS8a337_&u*vbD~?;P6%t~7v4gv2U5ZlDIjy=+Db0Xw=5I`P|8 z5=bYZJ;X$RuK-45#$R#wvU~YwO0ffdMuUtH@WkNO6;#ueiKo9iL796z65S1 z{}?#X-SpPl7jhgrxD)TO3w-$MP_!QW&QTJWFx`%*48ww%ExE`GCsigv`p7326Zs-W zG5q13q`)dj${-4X6Dx0I{*Ei@PiA~Avs#g%llLuvY-fwYIGPp~niGtE_ z9MB_vail;>P6jvM1ADLoRku8JhCeq>)N2eQ?sZV3j{Szhk*?}xv0+$XJ z0`3O&Cisu7rmn#0!Q3HZW5j=~t$URuF&U`(a-^4fCGv0Jm3s^IZcR{bwTqS9DAa;Q z@Db4LL2Wq;2d8+0n=kHTuK)#-x@&N2#d9`YQx=n-BPZ)_9pDPsyxOu5qpPGxu{99d zOjdbzTo3MkkEp^IjVv8!XOAPcH_YdLeMsQ zr?6eU?b`cTbs_$!Q9>I_{&+~qJ7@r}fjctIXAL7LvP+E>RJ);i8~7Lseqq8a?S41zEqYG0sBUU%Cz45b17H!GSue`(&%ft!2(FQM?l00000 literal 0 HcmV?d00001 diff --git a/appinfo.json b/appinfo.json index 6a5ddb1..1d026bf 100755 --- a/appinfo.json +++ b/appinfo.json @@ -1,6 +1,6 @@ { "id": "com.othelloventures.feedspider", - "version": "0.9.3", + "version": "0.9.5", "vendor": "Othello Ventures", "type": "web", "main": "index.html", diff --git a/resources/de_de/strings.json b/resources/de_de/strings.json index e80c511..130970f 100755 --- a/resources/de_de/strings.json +++ b/resources/de_de/strings.json @@ -22,6 +22,7 @@ "Default Accounts": "Standard-Konten", "Email": "E-Mail", "Facebook": "Facebook", + "Feedly": "Feedly", "Feeds": "Feeds", "Folders": "Ordner", "General": "Allgemein", @@ -40,6 +41,7 @@ "Login failed. Try again.": "Login fehlgeschlagen. Versuchen Sie es erneut.", "Login": "Einloggen", "Logout": "Ausloggen", + "Manual Sort Not Available": "Manuelle sortierung nicht verfügbar", "Mark read as you scroll": "Scrollen markiert als gelesen", "Medium Font": "mittlere Schrift", "No": "Nein", @@ -65,6 +67,7 @@ "Spaz": "Spaz", "Starred": "Markiert", "Starring Not Available": "Nicht markiert verfügbar", + "Subscribers": "Teilnehmer", "Subscriptions": "Abonnements", "The Old Reader": "The Old Reader", "Twitter": "Twitter", @@ -72,6 +75,7 @@ "Unshare": "Empfehlung entfernen", "URL": "URL", "URL Copied": "URL Kopierte", + "Website": "Webseite", "Yes": "Ja", "5 Minutes": "5 Minuten", diff --git a/resources/en_us/strings.json b/resources/en_us/strings.json index 8082038..57c37c3 100755 --- a/resources/en_us/strings.json +++ b/resources/en_us/strings.json @@ -22,6 +22,7 @@ "Default Accounts": "Default Accounts", "Email": "Email", "Facebook": "Facebook", + "Feedly": "Feedly", "Feeds": "Feeds", "Folders": "Folders", "General": "General", @@ -40,6 +41,7 @@ "Login failed. Try again.": "Login failed. Try again.", "Login": "Login", "Logout": "Logout", + "Manual Sort Not Available": "Manual Sort Not Available", "Mark read as you scroll": "Mark read as you scroll", "Medium Font": "Medium Font", "No": "No", @@ -65,6 +67,7 @@ "Spaz": "Spaz", "Starred": "Starred", "Starring Not Available": "Starring not available", + "Subscribers": "Subscribers", "Subscriptions": "Subscriptions", "The Old Reader": "The Old Reader", "Twitter": "Twitter", @@ -72,6 +75,7 @@ "Unshare": "Unshare", "URL": "URL", "URL Copied": "URL Copied", + "Website": "Website", "Yes": "Yes", "5 Minutes": "5 Minutes", diff --git a/resources/zh_cn/strings.json b/resources/zh_cn/strings.json index 3886e6c..666e433 100644 --- a/resources/zh_cn/strings.json +++ b/resources/zh_cn/strings.json @@ -22,6 +22,7 @@ "Default Accounts": "默认账户", "Email": "邮件", "Facebook": "Facebook", + "Feedly": "Feedly", "Feeds": "条目设置", "Folders": "目录设置", "General": "常规设置", @@ -40,6 +41,7 @@ "Login failed. Try again.": "登录失败,请重试.", "Login": "登录", "Logout": "注销", + "Manual Sort Not Available": "手动排序不可用", "Mark read as you scroll": "滚动后标记为已读", "Medium Font": "中字体", "No": "否", @@ -65,6 +67,7 @@ "Spaz": "Spaz", "Starred": "加星标条目", "Starring Not Available": "加星不可用", + "Subscribers": "用户", "Subscriptions": "订阅条目", "The Old Reader": "The Old Reader", "Twitter": "Twitter", @@ -72,6 +75,7 @@ "Unshare": "不共享", "URL": "链接", "URL Copied": "复制网址", + "Website": "网站", "Yes": "是", "5 Minutes": "5 分钟", diff --git a/sources.json b/sources.json index 64c672a..1f4db23 100755 --- a/sources.json +++ b/sources.json @@ -22,6 +22,7 @@ {"source": "app/models/search.js"}, {"source": "app/models/tor-api.js"}, {"source": "app/models/ino-api.js"}, + {"source": "app/models/feedly-api.js"}, {"source": "app/lib/ajax.js"}, {"source": "app/lib/instapaper.js"}, @@ -45,5 +46,6 @@ {"source": "app/assistants/notification-feeds-assistant.js"}, {"source": "app/assistants/folder-assistant.js"}, {"source": "app/assistants/configure-sharing-assistant.js"}, - {"source": "app/assistants/instapaper-credentials-assistant.js"} + {"source": "app/assistants/instapaper-credentials-assistant.js"}, + {"scenes": "oauth", "source": "app/assistants/oauth-assistant.js"} ] From 9cd57ae3cf4bcb9bb2836e4f1f65d623c04c6a21 Mon Sep 17 00:00:00 2001 From: Brent Hunter Date: Thu, 19 Dec 2013 09:31:00 -0800 Subject: [PATCH 12/34] Build 1.0.0 - added support for Feedly (production) - fixed a bug that would cause FeedSpider to keep requesting a new Feedly token - added token validation checks on Feedly PUT and DELETE requests --- app/models/feedly-api.js | 110 +++++++++++++++++++-------------- app/views/help/help-scene.html | 9 +++ appinfo.json | 2 +- 3 files changed, 72 insertions(+), 49 deletions(-) diff --git a/app/models/feedly-api.js b/app/models/feedly-api.js index a0bd209..748dc70 100644 --- a/app/models/feedly-api.js +++ b/app/models/feedly-api.js @@ -462,62 +462,76 @@ var FeedlyApi = Class.create({ } }, - //UPDATED 0.9.5 + //UPDATED 1.0.0 _ajaxPut: function(params, url, success, failure) { - try { - var req = new XMLHttpRequest() - req.open("PUT", url, true) - req.setRequestHeader('Content-Type', 'application/json') - req.setRequestHeader('Authorization', "OAuth " + this.credentials.accessToken) - req.send(JSON.stringify(params)) - req.onreadystatechange = function() { - if(req.readyState === 4 && req.status === 200){ - success - } - else if(req.readyState === 4 && req.status !== 200){ - failure - } - else - { - return - } - } + if (this._checkTokenExpiry) + { + try { + var req = new XMLHttpRequest() + req.open("PUT", url, true) + req.setRequestHeader('Content-Type', 'application/json') + req.setRequestHeader('Authorization', "OAuth " + this.credentials.accessToken) + req.send(JSON.stringify(params)) + req.onreadystatechange = function() { + if(req.readyState === 4 && req.status === 200){ + success + } + else if(req.readyState === 4 && req.status !== 200){ + failure + } + else + { + return + } + } + } + catch (e) { + Log.debug('_ajaxPut failed! Error:' + e) + failure + } } - catch (e) { - Log.debug('_ajaxPut failed! Error:' + e) - failure + else + { + failure } }, - //UPDATED 0.9.5 + //UPDATED 1.0.0 _ajaxDelete: function(url, success, failure) { - try { - var req = new XMLHttpRequest() - req.open("DELETE", url, true) - req.setRequestHeader('Authorization', "OAuth " + this.credentials.accessToken) - req.send() - req.onreadystatechange = function() { - if(req.readyState === 4 && req.status === 200){ - success - } - else if(req.readyState === 4 && req.status !== 200){ - failure - } - else - { - return - } - } + if (this._checkTokenExpiry) + { + try { + var req = new XMLHttpRequest() + req.open("DELETE", url, true) + req.setRequestHeader('Authorization', "OAuth " + this.credentials.accessToken) + req.send() + req.onreadystatechange = function() { + if(req.readyState === 4 && req.status === 200){ + success + } + else if(req.readyState === 4 && req.status !== 200){ + failure + } + else + { + return + } + } + } + catch (e) { + Log.debug('_ajaxDelete failed! Error:' + e) + failure + } } - catch (e) { - Log.debug('_ajaxDelete failed! Error:' + e) - failure + else + { + failure } }, - //UPDATED 0.9.5 + //UPDATED 1.0.0 _checkTokenExpiry: function() { - if (new Date(expiryDate) > new Date()) + if (new Date(this.credentials.tokenExpiry) > new Date()) { return true } @@ -601,6 +615,6 @@ var FeedlyApi = Class.create({ } }) -FeedlyApi.BASE_URL = "https://sandbox.feedly.com/v3/"; -FeedlyApi.CLIENT_ID = "sandbox264"; -FeedlyApi.CLIENT_SECRET = "HQ88RW5P2O5HZHPLXM1QY2Z7"; \ No newline at end of file +FeedlyApi.BASE_URL = "https://cloud.feedly.com/v3/"; +FeedlyApi.CLIENT_ID = "feedspider"; +FeedlyApi.CLIENT_SECRET = "FE01DA2G93CK87ZP8NW6T5WYG862"; \ No newline at end of file diff --git a/app/views/help/help-scene.html b/app/views/help/help-scene.html index 6ad3c6d..ff0be8a 100755 --- a/app/views/help/help-scene.html +++ b/app/views/help/help-scene.html @@ -33,6 +33,15 @@
    History
+
+
1.0.0
+
    +
  • added support for Feedly (production)
  • +
  • fixed a bug that would cause FeedSpider to keep requesting a new Feedly token
  • +
  • added token validation checks on Feedly PUT and DELETE requests
  • +
+
+
0.9.5
    diff --git a/appinfo.json b/appinfo.json index 1d026bf..c184f55 100755 --- a/appinfo.json +++ b/appinfo.json @@ -1,6 +1,6 @@ { "id": "com.othelloventures.feedspider", - "version": "0.9.5", + "version": "1.0.0", "vendor": "Othello Ventures", "type": "web", "main": "index.html", From 59a963f06815f08d4154fed31441f5eafb15d3dd Mon Sep 17 00:00:00 2001 From: Brent Hunter Date: Thu, 19 Dec 2013 11:49:51 -0800 Subject: [PATCH 13/34] Build 1.0.1 - Added BazQux Reader Support --- app/assistants/credentials-assistant.js | 5 +- app/models/api.js | 5 + app/models/bq-api.js | 519 ++++++++++++++++++++++++ app/views/help/help-scene.html | 8 + appinfo.json | 2 +- resources/de_de/strings.json | 1 + resources/en_us/strings.json | 1 + resources/zh_cn/strings.json | 1 + sources.json | 1 + 9 files changed, 540 insertions(+), 3 deletions(-) create mode 100644 app/models/bq-api.js diff --git a/app/assistants/credentials-assistant.js b/app/assistants/credentials-assistant.js index 0d00363..d77ce0e 100755 --- a/app/assistants/credentials-assistant.js +++ b/app/assistants/credentials-assistant.js @@ -14,9 +14,10 @@ var CredentialsAssistant = Class.create(BaseAssistant, { } this.serviceChoices = [ - {label: $L("The Old Reader"), value: "tor"}, + {label: $L("BazQux"), value: "bq"}, + {label: $L("Feedly"), value: "feedly"}, {label: $L("InoReader"), value: "ino"}, - {label: $L("Feedly"), value: "feedly"} + {label: $L("The Old Reader"), value: "tor"} ] }, diff --git a/app/models/api.js b/app/models/api.js index b7edd3c..9b2d5e3 100755 --- a/app/models/api.js +++ b/app/models/api.js @@ -14,6 +14,11 @@ var Api = Class.create({ this.appApi = new InoApi() this.appApi.login(credentials, success, failure) } + else if (credentials.service == "bq") + { + this.appApi = new BQApi() + this.appApi.login(credentials, success, failure) + } else if (credentials.service == "feedly") { this.appApi = new FeedlyApi() diff --git a/app/models/bq-api.js b/app/models/bq-api.js new file mode 100644 index 0000000..f412e75 --- /dev/null +++ b/app/models/bq-api.js @@ -0,0 +1,519 @@ +var BQApi = Class.create({ + //UPDATED 1.0.1 + login: function(credentials, success, failure) { + var authSuccess = function(response) { + var authMatch = response.responseText.match(/Auth\=(.*)/) + this.auth = authMatch ? authMatch[1] : '' + success(this.auth) + }.bind(this) + + new Ajax.Request("https://www.bazqux.com/accounts/ClientLogin", { + method: "post", + parameters: {Email: credentials.email, Passwd: credentials.password}, + onSuccess: authSuccess, + onFailure: failure + }) + }, + + //UPDATED 1.0.1 + getTags: function(success, failure) { + new Ajax.Request(BQApi.BASE_URL + "tag/list", { + method: "get", + parameters: {output: "json"}, + requestHeaders: this._requestHeaders(), + onFailure: failure, + onSuccess: function(response) {success(response.responseText.evalJSON().tags)} + }) + }, + + //UPDATED 1.0.1 + getSortOrder: function(success, failure) { + new Ajax.Request(BQApi.BASE_URL + "preference/stream/list", { + method: "get", + parameters: {output: "json"}, + requestHeaders: this._requestHeaders(), + onFailure: failure, + onSuccess: function(response) { + var prefs = response.responseText.evalJSON() + var sortOrder = {} + + if(prefs && prefs.streamprefs) { + $H(prefs.streamprefs).each(function(pair) { + pair.key = pair.key.gsub(/user\/\d+\//, "user/-/") + + $A(pair.value).each(function(pref) { + if("subscription-ordering" == pref.id) { + sortOrder[pair.key] = new SortOrder(pref.value) + } + }) + }) + } + + success(sortOrder) + } + }) + }, + + //UPDATED 1.0.1 + setSortOrder: function(sortOrder, stream) { + this._getEditToken(function(token) { + var parameters = { + T: token, + s: stream || "user/-/state/com.google/root", + k: "subscription-ordering", + v: sortOrder + } + + new Ajax.Request(BQApi.BASE_URL + "preference/stream/set", { + method: "post", + parameters: parameters, + requestHeaders: this._requestHeaders() + }) + }.bind(this)) + }, + + //UPDATED 1.0.1 + unsubscribe: function(feed) { + if(feed.constructor == Folder) { + this.removeLabel(feed) + } + else { + this._getEditToken(function(token) { + var parameters = { + T: token, + s: feed.id, + ac: "unsubscribe", + t: feed.title + } + + new Ajax.Request(BQApi.BASE_URL + "subscription/edit", { + method: "post", + parameters: parameters, + requestHeaders: this._requestHeaders(), + onSuccess: function() {Mojo.Event.send(document, "SubscriptionDeleted", {id: feed.id, count: feed.unreadCount})} + }) + }.bind(this)) + } + }, + + //UPDATED 1.0.1 + removeLabel: function(folder) { + this._getEditToken(function(token) { + var parameters = { + T: token, + s: folder.id, + t: folder.title + } + + new Ajax.Request(BQApi.BASE_URL + "disable-tag", { + method: "post", + parameters: parameters, + requestHeaders: this._requestHeaders(), + onSuccess: function() {Mojo.Event.send(document, "FolderDeleted", {id: folder.id})} + }) + }.bind(this)) + }, + + //NOT CURRENTLY SUPPORTED BY API + searchSubscriptions: function(query, success, failure) { + failure() +// var self = this +// +// new Ajax.Request(BQApi.BASE_URL + "feed-finder", { +// method: "get", +// parameters: {q: query, output: "json"}, +// requestHeaders: this._requestHeaders(), +// onFailure: failure, +// onSuccess: function(response) { +// var subscriptions = response.responseText.evalJSON().items +// success(subscriptions) +// } +// }) + }, + + //UPDATED 1.0.1 + addSubscription: function(url, success, failure) { + this._getEditToken(function(token) { + var parameters = { + T: token, + quickadd: url + } + + new Ajax.Request(BQApi.BASE_URL + "subscription/quickadd", { + method: "post", + parameters: parameters, + requestHeaders: this._requestHeaders(), + onFailure: failure, + onSuccess: function(response) { + var json = response.responseText.evalJSON() + + if(json.streamId) { + success() + } + else { + failure() + } + } + }) + }.bind(this)) + }, + + //UPDATED 1.0.1 + getAllSubscriptions: function(success, failure) { + var self = this + + new Ajax.Request(BQApi.BASE_URL + "subscription/list", { + method: "get", + parameters: {output: "json"}, + requestHeaders: this._requestHeaders(), + onFailure: failure, + onSuccess: function(response) { + var subscriptions = response.responseText.evalJSON().subscriptions + self.cacheTitles(subscriptions) + success(subscriptions) + } + }) + }, + + //UPDATED 1.0.1 + cacheTitles: function(subscriptions) { + var self = this + self.titles = {} + + subscriptions.each(function(subscription) { + self.titles[subscription.id] = subscription.title + }) + }, + + //UPDATED 1.0.1 + titleFor: function(id) { + return this.titles[id] + }, + + //UPDATED 1.0.1 + getUnreadCounts: function(success, failure) { + new Ajax.Request(BQApi.BASE_URL + "unread-count", { + method: "get", + parameters: {output: "json"}, + requestHeaders: this._requestHeaders(), + onFailure: failure, + onSuccess: function(response) { + var json = response.responseText.evalJSON() + + if(json.denied) { + failure() + } + else { + success(json.unreadcounts) + } + } + }) + }, + + //UPDATED 1.0.1 + getAllArticles: function(continuation, success, failure) { + this._getArticles( + "user/-/state/com.google/reading-list", + Preferences.hideReadArticles() ? "user/-/state/com.google/read" : null, + continuation, + success, + failure + ) + }, + + //UPDATED 1.0.1 + getAllStarred: function(continuation, success, failure) { + this._getArticles( + "user/-/state/com.google/starred", + null, + continuation, + success, + failure + ) + }, + + //NOT CURRENTLY SUPPORTED BY API + getAllShared: function(continuation, success, failure) { + this._getArticles( + "user/-/state/com.google/broadcast", + null, + continuation, + success, + failure + ) + }, + + //UPDATED 1.0.1 + getAllArticlesFor: function(id, continuation, success, failure) { + this._getArticles( + id, + Preferences.hideReadArticles() ? "user/-/state/com.google/read" : null, + continuation, + success, + failure + ) + }, + + //UPDATED 1.0.1 + _getArticles: function(id, exclude, continuation, success, failure) { + var parameters = {output: "json", n: 40} + + if(id != "user/-/state/com.google/starred" && + id != "user/-/state/com.google/broadcast" && + Preferences.isOldestFirst()) { + parameters.r = "o" + } + + if(continuation) { + parameters.c = continuation + } + + if(exclude) { + parameters.xt = exclude + } + + new Ajax.Request(BQApi.BASE_URL + "stream/contents/" + escape(id), { + method: "get", + parameters: parameters, + requestHeaders: this._requestHeaders(), + onFailure: failure, + onSuccess: function(response) { + var articles = response.responseText.evalJSON() + success(articles.items, articles.id, articles.continuation) + } + }) + }, + + //UPDATED 1.0.1 + markAllRead: function(id, success, failure) { + this._getEditToken( + function(token) { + var parameters = { + T: token, + s: id + } + + new Ajax.Request(BQApi.BASE_URL + "mark-all-as-read", { + method: "post", + parameters: parameters, + requestHeaders: this._requestHeaders(), + onSuccess: success, + onFailure: failure + }) + }.bind(this), + + failure + ) + }, + + //NOT CURRENTLY SUPPORTED BY API + search: function(query, id, success, failure) { + var parameters = { + q: query, + num: 50, + output: "json" + } + + if(id) { + parameters.s = id + } + + new Ajax.Request(BQApi.BASE_URL + "search/items/ids", { + method: "get", + parameters: parameters, + requestHeaders: this._requestHeaders(), + onSuccess: this.searchItemsFound.bind(this, success, failure), + onFailure: failure + }) + }, + + //NOT CURRENTLY SUPPORTED BY API + searchItemsFound: function(success, failure, response) { + var self = this + var ids = response.responseText.evalJSON().results + + if(ids.length) { + self._getEditToken( + function(token) { + var parameters = { + T: token, + i: ids.map(function(n) {return n.id}) + } + + new Ajax.Request(BQApi.BASE_URL + "stream/items/contents", { + method: "post", + parameters: parameters, + requestHeaders: self._requestHeaders(), + onFailure: failure, + onSuccess: function(response) { + var articles = response.responseText.evalJSON() + success(articles.items, articles.id, articles.continuation) + } + }) + } + ) + } + else { + success([], "", false) + } + }, + + //NOT CURRENTLY SUPPORTED BY API + mapSearchResults: function(response) { + console.log(response.responseText) + }, + + //UPDATED 1.0.1 + setArticleRead: function(articleId, subscriptionId, success, failure) { + this._editTag( + articleId, + subscriptionId, + "user/-/state/com.google/read", + null, + success, + failure + ) + }, + + //UPDATED 1.0.1 + setArticleNotRead: function(articleId, subscriptionId, success, failure, sticky) { + this._editTag( + articleId, + subscriptionId, + null, + "user/-/state/com.google/read", + success, + failure + ) + }, + + //NOT CURRENTLY SUPPORTED BY API + setArticleShared: function(articleId, subscriptionId, success, failure) { + this._editTag( + articleId, + subscriptionId, + "user/-/state/com.google/broadcast", + null, + success, + failure + ) + }, + + //NOT CURRENTLY SUPPORTED BY API + setArticleNotShared: function(articleId, subscriptionId, success, failure) { + this._editTag( + articleId, + subscriptionId, + null, + "user/-/state/com.google/broadcast", + success, + failure + ) + }, + + //UPDATED 1.0.1 + setArticleStarred: function(articleId, subscriptionId, success, failure) { + this._editTag( + articleId, + subscriptionId, + "user/-/state/com.google/starred", + null, + success, + failure + ) + }, + + //UPDATED 1.0.1 + setArticleNotStarred: function(articleId, subscriptionId, success, failure) { + this._editTag( + articleId, + subscriptionId, + null, + "user/-/state/com.google/starred", + success, + failure + ) + }, + + //UPDATED 1.0.1 + _editTag: function(articleId, subscriptionId, addTag, removeTag, success, failure) { + Log.debug("editing tag for article id = " + articleId + " and subscription id = " + subscriptionId) + + this._getEditToken( + function(token) { + var parameters = { + T: token, + i: articleId, + s: subscriptionId + } + + if(addTag) parameters.a = addTag + if(removeTag) parameters.r = removeTag + + new Ajax.Request(BQApi.BASE_URL + "edit-tag", { + method: "post", + parameters: parameters, + requestHeaders: this._requestHeaders(), + onSuccess: success, + onFailure: failure + }) + }.bind(this), + + failure + ) + }, + + //UPDATED 1.0.1 + _requestHeaders: function() { + return {Authorization:"GoogleLogin auth=" + this.auth} + }, + + //UPDATED 1.0.1 + _getEditToken: function(success, failure) { + if(this.editToken && (new Date().getTime() - this.editTokenTime < 120000)) { + Log.debug("using last edit token - " + this.editToken) + success(this.editToken) + } + else { + new Ajax.Request(BQApi.BASE_URL + "token", { + method: "get", + requestHeaders: {Authorization:"GoogleLogin auth=" + this.auth}, + onFailure: failure, + onSuccess: function(response) { + this.editToken = response.responseText + this.editTokenTime = new Date().getTime() + Log.debug("retrieved edit token - " + this.editToken) + success(this.editToken) + }.bind(this) + }) + } + }, + + //UPDATED 1.0.1 + supportsAllArticles: function() { + return true + }, + + //UPDATED 1.0.1 + supportsStarred: function() { + return true + }, + + //UPDATED 1.0.1 + supportsShared: function() { + return false + }, + + //UPDATED 1.0.1 + supportsSearch: function() { + return false + }, + + //UPDATED 1.0.1 + supportsManualSort: function() { + return true + } + +}) + +BQApi.BASE_URL = "https://www.bazqux.com/reader/api/0/" \ No newline at end of file diff --git a/app/views/help/help-scene.html b/app/views/help/help-scene.html index ff0be8a..8a511ae 100755 --- a/app/views/help/help-scene.html +++ b/app/views/help/help-scene.html @@ -33,6 +33,14 @@
    History
+
+
1.0.1
+
    +
  • added support for BazQux Reader
  • +
  • re-ordered service choices for login
  • +
+
+
1.0.0
    diff --git a/appinfo.json b/appinfo.json index c184f55..0f241f1 100755 --- a/appinfo.json +++ b/appinfo.json @@ -1,6 +1,6 @@ { "id": "com.othelloventures.feedspider", - "version": "1.0.0", + "version": "1.0.1", "vendor": "Othello Ventures", "type": "web", "main": "index.html", diff --git a/resources/de_de/strings.json b/resources/de_de/strings.json index 130970f..cb20125 100755 --- a/resources/de_de/strings.json +++ b/resources/de_de/strings.json @@ -10,6 +10,7 @@ "Article shared": "Artikel empfohlen", "Article unshared": "Empfehlung entfernt", "Articles": "Artikel", + "BazQux": "BazQux Reader", "Browser": "Browser", "by #{author}": "von #{author}", "by #{vendor}": "von #{vendor}", diff --git a/resources/en_us/strings.json b/resources/en_us/strings.json index 57c37c3..4755b49 100755 --- a/resources/en_us/strings.json +++ b/resources/en_us/strings.json @@ -10,6 +10,7 @@ "Article shared": "Article shared", "Article unshared": "Article unshared", "Articles": "Articles", + "BazQux": "BazQux Reader", "Browser": "Browser", "by #{author}": "by #{author}", "by #{vendor}": "by #{vendor}", diff --git a/resources/zh_cn/strings.json b/resources/zh_cn/strings.json index 666e433..e63ae38 100644 --- a/resources/zh_cn/strings.json +++ b/resources/zh_cn/strings.json @@ -10,6 +10,7 @@ "Article shared": "共享条文", "Article unshared": "未共享条文", "Articles": "条文设置", + "BazQux": "BazQux Reader", "Browser": "浏览器", "by #{author}": "by #{author}", "by #{vendor}": "by #{vendor}", diff --git a/sources.json b/sources.json index 1f4db23..deffe8e 100755 --- a/sources.json +++ b/sources.json @@ -23,6 +23,7 @@ {"source": "app/models/tor-api.js"}, {"source": "app/models/ino-api.js"}, {"source": "app/models/feedly-api.js"}, + {"source": "app/models/bq-api.js"}, {"source": "app/lib/ajax.js"}, {"source": "app/lib/instapaper.js"}, From 688cd3c635c11c373509c232478d153234175598 Mon Sep 17 00:00:00 2001 From: Brent Hunter Date: Fri, 20 Dec 2013 19:21:36 -0800 Subject: [PATCH 14/34] 1.0.2 - Version bump for App Catalog --- app/views/help/help-scene.html | 11 ++++++++++- appinfo.json | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/app/views/help/help-scene.html b/app/views/help/help-scene.html index 8a511ae..eec0caf 100755 --- a/app/views/help/help-scene.html +++ b/app/views/help/help-scene.html @@ -15,8 +15,10 @@
    FeedSpider is a fork of Semicolon Apps' excellent Google Reader app Feeder, adapted to work with Google Reader compatible applications.

    It currently supports:
    @@ -33,6 +35,13 @@
    History
+
+
1.0.2
+
    +
  • version bump for App Catalog
  • +
+
+
1.0.1
    diff --git a/appinfo.json b/appinfo.json index 0f241f1..3ccea13 100755 --- a/appinfo.json +++ b/appinfo.json @@ -1,6 +1,6 @@ { "id": "com.othelloventures.feedspider", - "version": "1.0.1", + "version": "1.0.2", "vendor": "Othello Ventures", "type": "web", "main": "index.html", From d7462ab2ea710ec48f6e421fc01ff1e0a8d82e79 Mon Sep 17 00:00:00 2001 From: Brent Hunter Date: Sat, 21 Dec 2013 00:39:51 -0800 Subject: [PATCH 15/34] 1.0.3 - Fixed bug related to how Javascript handles dates in 1.4.5. --- app/assistants/oauth-assistant.js | 2 +- app/models/feedly-api.js | 12 ++++++++++-- appinfo.json | 2 +- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/app/assistants/oauth-assistant.js b/app/assistants/oauth-assistant.js index 97cb62e..aa75dac 100755 --- a/app/assistants/oauth-assistant.js +++ b/app/assistants/oauth-assistant.js @@ -161,7 +161,7 @@ OauthAssistant.prototype.codeToken = function(code) { var expiryDate = new Date(); expiryDate.setSeconds(expiryDate.getSeconds() + responseJSON.expires_in); - this.credentials.tokenExpiry = expiryDate; + this.credentials.tokenExpiry = expiryDate.getTime(); this.controller.stageController.swapScene(this.callbackScene, this.credentials); } diff --git a/app/models/feedly-api.js b/app/models/feedly-api.js index 748dc70..9fad1b7 100644 --- a/app/models/feedly-api.js +++ b/app/models/feedly-api.js @@ -10,6 +10,14 @@ var FeedlyApi = Class.create({ } else { + this.credentials.password = null + this.credentials.server = null + this.credentials.id = null + this.credentials.refreshToken = null + this.credentials.accessToken = null + this.credentials.tokenType = null + this.credentials.plan = null + this.credentials.clear() failure() return } @@ -529,9 +537,9 @@ var FeedlyApi = Class.create({ } }, - //UPDATED 1.0.0 + //UPDATED 1.0.3 _checkTokenExpiry: function() { - if (new Date(this.credentials.tokenExpiry) > new Date()) + if (new Date(this.credentials.tokenExpiry).getTime() > new Date().getTime()) { return true } diff --git a/appinfo.json b/appinfo.json index 3ccea13..4666317 100755 --- a/appinfo.json +++ b/appinfo.json @@ -1,6 +1,6 @@ { "id": "com.othelloventures.feedspider", - "version": "1.0.2", + "version": "1.0.3", "vendor": "Othello Ventures", "type": "web", "main": "index.html", From 834cc9c61ef75b1538721191b032a32d6647d281 Mon Sep 17 00:00:00 2001 From: Brent Hunter Date: Sat, 21 Dec 2013 00:52:21 -0800 Subject: [PATCH 16/34] 1.0.3 - forgot to update help scene --- app/views/help/help-scene.html | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/views/help/help-scene.html b/app/views/help/help-scene.html index eec0caf..285bafc 100755 --- a/app/views/help/help-scene.html +++ b/app/views/help/help-scene.html @@ -35,6 +35,13 @@
    History
+
+
1.0.3
+
    +
  • fixed bugs related to Feedly login in webOS 1.4.5
  • +
+
+
1.0.2
    From a9f605fe90a54aa1e04045612e7fac42019605cf Mon Sep 17 00:00:00 2001 From: Brent Hunter Date: Tue, 24 Dec 2013 01:04:07 -0800 Subject: [PATCH 17/34] 1.1.0 - Added Tiny Tiny RSS Support --- app/assistants/app-assistant.js | 2 +- app/assistants/credentials-assistant.js | 36 +- app/assistants/home-assistant.js | 1 + app/assistants/login-assistant.js | 2 +- app/assistants/oauth-assistant.js | 2 +- app/models/all-subscriptions.js | 21 +- app/models/api.js | 5 + app/models/article.js | 37 +- app/models/credentials.js | 9 + app/models/folder.js | 9 +- app/models/folders.js | 2 +- app/models/subscription.js | 4 +- app/models/ttrss-api.js | 612 +++++++++++++++++++ app/views/credentials/credentials-scene.html | 13 + app/views/help/help-scene.html | 7 + appinfo.json | 2 +- resources/de_de/strings.json | 2 + resources/en_us/strings.json | 2 + resources/zh_cn/strings.json | 2 + sources.json | 1 + 20 files changed, 740 insertions(+), 31 deletions(-) create mode 100755 app/models/ttrss-api.js diff --git a/app/assistants/app-assistant.js b/app/assistants/app-assistant.js index d934e6e..8c54ad2 100755 --- a/app/assistants/app-assistant.js +++ b/app/assistants/app-assistant.js @@ -44,7 +44,7 @@ var AppAssistant = Class.create({ $A(counts).each(function(count) { if(count.count && Preferences.wantsNotificationFor(count.id)) { - unreadCount += count.count + unreadCount += count.count || count.counter } }) diff --git a/app/assistants/credentials-assistant.js b/app/assistants/credentials-assistant.js index d77ce0e..f8fef57 100755 --- a/app/assistants/credentials-assistant.js +++ b/app/assistants/credentials-assistant.js @@ -6,18 +6,13 @@ var CredentialsAssistant = Class.create(BaseAssistant, { this.showMessage = showMessage this.button = {buttonLabel: $L("Login")} this.hideLogout = true - this.showFields = true - - if (credentials.service == "feedly") - { - this.showFields = false - } this.serviceChoices = [ {label: $L("BazQux"), value: "bq"}, {label: $L("Feedly"), value: "feedly"}, {label: $L("InoReader"), value: "ino"}, - {label: $L("The Old Reader"), value: "tor"} + {label: $L("The Old Reader"), value: "tor"}, + {label: $L("Tiny Tiny RSS"), value: "ttrss"} ] }, @@ -29,15 +24,17 @@ var CredentialsAssistant = Class.create(BaseAssistant, { this.controller.update("email-label", $L("Username")) this.controller.update("password-label", $L("Password")) this.controller.update("service-label", $L("Service")) + this.controller.update("server-label", $L("Server URL")) this.controller.update("error-message", $L("Login failed. Try again.")) }, activate: function($super, changes) { - $super(changes) + $super(changes) this.controller.get("password").mojo.setConsumesEnterKey(false) + this.controller.get("server").mojo.setConsumesEnterKey(false) this.controller.get("login-failure")[this.showMessage ? "show" : "hide"]() - this.controller.get("email-group")[this.showFields ? "show" : "hide"]() - this.controller.get("password-group")[this.showFields ? "show" : "hide"]() + var initializeFields = {value: this.credentials.service} + this.setService(initializeFields) }, cleanup: function($super) { @@ -49,6 +46,7 @@ var CredentialsAssistant = Class.create(BaseAssistant, { this.controller.setupWidget("service", {modelProperty: "service", choices: this.serviceChoices}, this.credentials) this.controller.setupWidget("email", {modelProperty: "email", changeOnKeyPress: true, autoFocus: true, textCase: Mojo.Widget.steModeLowerCase}, this.credentials) this.controller.setupWidget("password", {modelProperty: "password", changeOnKeyPress: true}, this.credentials) + this.controller.setupWidget("server", {modelProperty: "server", changeOnKeyPress: true, textCase: Mojo.Widget.steModeLowerCase}, this.credentials) this.controller.setupWidget("login", {type: Mojo.Widget.activityButton}, this.button) }, @@ -57,19 +55,27 @@ var CredentialsAssistant = Class.create(BaseAssistant, { this.login = this.login.bind(this) this.controller.listen("password", Mojo.Event.propertyChange, this.propertyChanged) + this.controller.listen("server", Mojo.Event.propertyChange, this.propertyChanged) this.controller.listen("service", Mojo.Event.propertyChange, this.setService = this.setService.bind(this)) this.controller.listen("login", Mojo.Event.tap, this.login) }, cleanupListeners: function() { this.controller.stopListening("password", Mojo.Event.propertyChange, this.propertyChanged) + this.controller.stopListening("server", Mojo.Event.propertyChange, this.propertyChanged) this.controller.stopListening("login", Mojo.Event.tap, this.login) this.controller.stopListening("service", Mojo.Event.propertyChange, this.setService) }, propertyChanged: function(event) { if(Mojo.Char.enter === event.originalEvent.keyCode) { - this.login() + if ((event.property === "password" && this.controller.get("server-group").style.display === "none") || event.property === "server") + { + this.login() + } + else if ((event.property === "password" && this.controller.get("server-group").style.display !== "none")) { + this.controller.get("server").mojo.focus() + } } }, @@ -82,11 +88,19 @@ var CredentialsAssistant = Class.create(BaseAssistant, { { this.controller.get("email-group")["hide"]() this.controller.get("password-group")["hide"]() + this.controller.get("server-group")["hide"]() + } + else if (propertyChangeEvent.value == "ttrss") + { + this.controller.get("email-group")["show"]() + this.controller.get("password-group")["show"]() + this.controller.get("server-group")["show"]() } else { this.controller.get("email-group")["show"]() this.controller.get("password-group")["show"]() + this.controller.get("server-group")["hide"]() } }, }) diff --git a/app/assistants/home-assistant.js b/app/assistants/home-assistant.js index 676ac70..17beb1d 100755 --- a/app/assistants/home-assistant.js +++ b/app/assistants/home-assistant.js @@ -122,6 +122,7 @@ var HomeAssistant = Class.create(BaseAssistant, { if("logout" == command) { var creds = new Credentials() creds.password = null + creds.server = null creds.id = null creds.refreshToken = null creds.accessToken = null diff --git a/app/assistants/login-assistant.js b/app/assistants/login-assistant.js index 1f48942..3025f1c 100755 --- a/app/assistants/login-assistant.js +++ b/app/assistants/login-assistant.js @@ -20,7 +20,7 @@ var LoginAssistant = Class.create(BaseAssistant, { parameters: {}, onSuccess: function(response) { - if((this.credentials.email && this.credentials.password) || this.credentials.service == "feedly") { + if((this.credentials.service !== "ttrss" && this.credentials.email && this.credentials.password) || (this.credentials.service === "ttrss" && this.credentials.email && this.credentials.password && this.credentials.server) || this.credentials.service === "feedly") { if(this.triedLogin) { Log.debug("ALREADY TRIED LOGGING IN, WHAT MAKES YOU THINK ITS GOING TO WORK NOW") } diff --git a/app/assistants/oauth-assistant.js b/app/assistants/oauth-assistant.js index aa75dac..f10b1ed 100755 --- a/app/assistants/oauth-assistant.js +++ b/app/assistants/oauth-assistant.js @@ -268,7 +268,7 @@ OauthAssistant.prototype.loadProgress = function(event) { } } catch(e) { - Mojo.Log.logException(e, e.description); + Log.debug(e.description); } }; OauthAssistant.prototype._updateLoadProgress = function(image) { diff --git a/app/models/all-subscriptions.js b/app/models/all-subscriptions.js index 65c5f12..8a20a4a 100755 --- a/app/models/all-subscriptions.js +++ b/app/models/all-subscriptions.js @@ -48,12 +48,27 @@ var AllSubscriptions = Class.create(SubscriptionContainer, { self.api.getUnreadCounts( function(counts) { counts.each(function(count) { - if(count.id.startsWith("feed")) { - self.incrementUnreadCountBy(count.count) + if(count.id.toString().startsWith("feed") || (count.id > 0 && count.kind !== "cat")){ + + if (count.count !== undefined) + { + self.incrementUnreadCountBy(count.count) + } + else + { + self.incrementUnreadCountBy(count.counter) + } self.items.each(function(item) { if(item.id == count.id) { - item.setUnreadCount(count.count) + if (count.count !== undefined) + { + item.setUnreadCount(count.count) + } + else + { + item.setUnreadCount(count.counter) + } } if(item.isFolder) { diff --git a/app/models/api.js b/app/models/api.js index 9b2d5e3..25a4191 100755 --- a/app/models/api.js +++ b/app/models/api.js @@ -19,6 +19,11 @@ var Api = Class.create({ this.appApi = new BQApi() this.appApi.login(credentials, success, failure) } + else if (credentials.service == "ttrss") + { + this.appApi = new TTRSSApi() + this.appApi.login(credentials, success, failure) + } else if (credentials.service == "feedly") { this.appApi = new FeedlyApi() diff --git a/app/models/article.js b/app/models/article.js index e56bdf3..e3f0163 100755 --- a/app/models/article.js +++ b/app/models/article.js @@ -3,12 +3,19 @@ var Article = Class.create({ this.api = subscription.api this.id = data.id this.subscription = subscription - this.subscriptionId = data.origin ? data.origin.streamId : subscription.id + if (data.origin) + { + this.subscriptionId = data.origin.streamId + } + else + { + this.subscriptionId = data.feed_id || subscription.id + } this.title = data.title || "No Title" this.author = data.author this.origin = this.api.titleFor(this.subscriptionId) var content = data.content || data.summary || {content: ""} - this.summary = this.cleanUp(content.content) + this.summary = content.content ? this.cleanUp(content.content) : this.cleanUp(content) this.readLocked = data.isReadStateLocked this.isRead = false this.isShared = false @@ -16,12 +23,23 @@ var Article = Class.create({ if(data.tags) { + //This section controls behaviour for Feedly and TTRSS this.setStates(data.tags) if (data.unread !== undefined) { this.isRead = !data.unread } + + if (data.marked !== undefined) + { + this.isStarred = data.marked + } + + if (data.published !== undefined) + { + this.isShared = data.published + } } else if (data.unread !== undefined) { @@ -31,7 +49,8 @@ var Article = Class.create({ { this.setStates(data.categories) } - var pubDate = data.crawlTimeMsec || data.crawled + + var pubDate = data.crawlTimeMsec || data.crawled || data.updated + "000" this.setDates(parseInt(pubDate, 10)) this.setArticleLink(data.alternate) }, @@ -64,29 +83,29 @@ var Article = Class.create({ categories.each(function(category) { if (category.id !== undefined) { - if(category.id.endsWith("/tag/global.read")) { + if(category.id.toString().endsWith("/tag/global.read")) { this.isRead = true } - if(category.id.endsWith("/tag/global.saved")) { + if(category.id.toString().endsWith("/tag/global.saved")) { this.isStarred = true } } else { - if(category.endsWith("/state/com.google/read")) { + if(category.toString().endsWith("/state/com.google/read")) { this.isRead = true } - if(category.endsWith("/state/com.google/kept-unread")) { + if(category.toString().endsWith("/state/com.google/kept-unread")) { this.keepUnread = true } - if(category.endsWith("/state/com.google/starred")) { + if(category.toString().endsWith("/state/com.google/starred")) { this.isStarred = true } - if(category.endsWith("/state/com.google/broadcast")) { + if(category.toString().endsWith("/state/com.google/broadcast")) { this.isShared = true } } diff --git a/app/models/credentials.js b/app/models/credentials.js index 3ffe1e6..077b1d7 100755 --- a/app/models/credentials.js +++ b/app/models/credentials.js @@ -2,6 +2,7 @@ var Credentials = Class.create({ initialize: function() { this.email = this.emailCookie().get() this.password = this.passwordCookie().get() + this.server = this.serverCookie().get() if(this.serviceCookie().get()) { this.service = this.serviceCookie().get() @@ -25,6 +26,9 @@ var Credentials = Class.create({ if (this.password !== undefined){ this.passwordCookie().put(this.password) } + if (this.server !== undefined){ + this.serverCookie().put(this.server) + } if (this.service !== undefined){ this.serviceCookie().put(this.service) } @@ -51,6 +55,7 @@ var Credentials = Class.create({ clear: function() { this.emailCookie().remove() this.passwordCookie().remove() + this.serverCookie().remove() this.serviceCookie().remove() this.idCookie().remove() this.accessTokenCookie().remove() @@ -68,6 +73,10 @@ var Credentials = Class.create({ return this.getCookie("password") }, + serverCookie: function() { + return this.getCookie("server") + }, + serviceCookie: function() { return this.getCookie("service") }, diff --git a/app/models/folder.js b/app/models/folder.js index 3931c00..a901629 100755 --- a/app/models/folder.js +++ b/app/models/folder.js @@ -35,7 +35,14 @@ var Folder = Class.create(ArticleContainer, { addUnreadCount: function(count) { this.subscriptions.items.each(function(subscription) { if(subscription.id == count.id) { - subscription.setUnreadCount(count.count) + if (count.count !== undefined) + { + subscription.setUnreadCount(count.count) + } + else + { + subscription.setUnreadCount(count.counter) + } } }) diff --git a/app/models/folders.js b/app/models/folders.js index 3317fcb..119445b 100755 --- a/app/models/folders.js +++ b/app/models/folders.js @@ -26,7 +26,7 @@ var Folders = Class.create({ function(tags) { tags.each(function(tag) { var folder = self.items.find(function(item) {return item.id == tag.id}) - if(folder) folder.sortId = tag.sortid + if(folder) folder.sortId = tag.sortid || tag.order_id }) success() diff --git a/app/models/subscription.js b/app/models/subscription.js index 21e83b2..887c194 100755 --- a/app/models/subscription.js +++ b/app/models/subscription.js @@ -6,8 +6,8 @@ var Subscription = Class.create(ArticleContainer, { this.icon = "rss" this.divideBy = $L("Subscriptions") this.canMarkAllRead = true - this.sortId = data.sortid - this.categories = data.categories + this.sortId = data.sortid || data.order_id + this.categories = data.categories || data.cat_id }, belongsToFolder: function() { diff --git a/app/models/ttrss-api.js b/app/models/ttrss-api.js new file mode 100755 index 0000000..a145fd3 --- /dev/null +++ b/app/models/ttrss-api.js @@ -0,0 +1,612 @@ +var TTRSSApi = Class.create({ + //UPDATED 1.1.0 + login: function(credentials, success, failure) { + //Clean Up Base URL (if necessary) + //Remove whitespace + credentials.server = credentials.server.replace(/^\s\s*/, '').replace(/\s\s*$/, '') + + //Remove trailing slash + credentials.server = credentials.server.replace(/\/$/, ""); + + TTRSSApi.BASE_URL = credentials.server + "/api/" + + var authSuccess = function(response) { + var authResult = response.responseText.evalJSON() + if(authResult.content.error) + { + Log.debug("Login Failure. Error: " + authResult.content.error) + failure() + } + else if (!authResult.content.api_level || authResult.content.api_level < 7) + { + Log.debug("Login Failure. API Level too low") + failure() + } + else if(authResult.content.session_id) + { + this.auth = authResult.content.session_id + success(this.auth) + } + else + { + failure() + } + }.bind(this) + + var params = { + op: "login", + user: credentials.email, + password: credentials.password + } + + new Ajax.Request(TTRSSApi.BASE_URL, { + method: "post", + postBody: JSON.stringify(params), + onSuccess: authSuccess, + onFailure: failure + }) + }, + + //UPDATED 1.1.0 + getTags: function(success, failure) { + var params = { + sid: this.auth, + op: "getCategories", + include_empty: false + } + + new Ajax.Request(TTRSSApi.BASE_URL, { + method: "post", + postBody: JSON.stringify(params), + onSuccess: function(response){ + success(response.responseText.evalJSON().content) + }, + onFailure: failure, + }) + }, + + //NOT SUPPORTED BY API + getSortOrder: function(success, failure) { + new Ajax.Request(TTRSSApi.BASE_URL + "preference/stream/list", { + method: "get", + parameters: {output: "json"}, + requestHeaders: this._requestHeaders(), + onFailure: failure, + onSuccess: function(response) { + var prefs = response.responseText.evalJSON() + var sortOrder = {} + + if(prefs && prefs.streamprefs) { + $H(prefs.streamprefs).each(function(pair) { + pair.key = pair.key.gsub(/user\/\d+\//, "user/-/") + + $A(pair.value).each(function(pref) { + if("subscription-ordering" == pref.id) { + sortOrder[pair.key] = new SortOrder(pref.value) + } + }) + }) + } + + success(sortOrder) + } + }) + }, + + //NOT SUPPORTED BY API + setSortOrder: function(sortOrder, stream) { + this._getEditToken(function(token) { + var parameters = { + T: token, + s: stream || "user/-/state/com.google/root", + k: "subscription-ordering", + v: sortOrder + } + + new Ajax.Request(TTRSSApi.BASE_URL + "preference/stream/set", { + method: "post", + parameters: parameters, + requestHeaders: this._requestHeaders() + }) + }.bind(this)) + }, + + //UPDATED 1.1.0 + unsubscribe: function(feed) { + if(feed.constructor == Folder) { + Feeder.notify($L("Folder Delete Not Supported")) + Mojo.Event.send(document, "FolderDeleted", {id: feed.id}) + //this.removeLabel(feed) + } + else { + var parameters = { + sid: this.auth, + op: "unsubscribeFeed", + feed_id: feed.id + } + + new Ajax.Request(TTRSSApi.BASE_URL, { + method: "post", + postBody: JSON.stringify(parameters), + onSuccess: function() {Mojo.Event.send(document, "SubscriptionDeleted", {id: feed.id, count: feed.unreadCount})} + }) + } + }, + + //NOT SUPPORTED BY API + removeLabel: function(folder) { + this._getEditToken(function(token) { + var parameters = { + T: token, + s: folder.id, + t: folder.title + } + + new Ajax.Request(TTRSSApi.BASE_URL + "disable-tag", { + method: "post", + parameters: parameters, + requestHeaders: this._requestHeaders(), + onSuccess: function() {Mojo.Event.send(document, "FolderDeleted", {id: folder.id})} + }) + }.bind(this)) + }, + + //NOT SUPPORTED BY API + searchSubscriptions: function(query, success, failure) { + failure() + /*var self = this + + new Ajax.Request(TTRSSApi.BASE_URL + "feed-finder", { + method: "get", + parameters: {q: query, output: "json"}, + requestHeaders: this._requestHeaders(), + onFailure: failure, + onSuccess: function(response) { + var subscriptions = response.responseText.evalJSON().items + success(subscriptions) + } + })*/ + }, + + //UPDATED 1.1.0 + addSubscription: function(url, success, failure) { + var parameters = { + sid: this.auth, + op: "subscribeToFeed", + feed_url: url, + category_id: 0 + } + + new Ajax.Request(TTRSSApi.BASE_URL, { + method: "post", + postBody: JSON.stringify(parameters), + onSuccess: function(response) { + var json = response.responseText.evalJSON() + + if(json.content.status.code == 1) { + success() + } + else { + failure() + } + }, + onFailure: failure, + }) + }, + + //UPDATED 1.1.0 + getAllSubscriptions: function(success, failure) { + var self = this + + var params = { + sid: this.auth, + op: "getCategories", + include_empty: false + } + + new Ajax.Request(TTRSSApi.BASE_URL, { + method: "post", + postBody: JSON.stringify(params), + onSuccess: this._processCategories.bind(this, success, failure), + onFailure: failure + }) + }, + + //UPDATED 1.1.0 + _processCategories: function(success, failure, response) { + var self = this + var categories = response.responseText.evalJSON().content + var subscriptions = [] + var params = { + sid: this.auth, + op: "getFeeds" + } + + categories.each(function(category) { + if (category.id > 0) + { + params.cat_id = category.id + + new Ajax.Request(TTRSSApi.BASE_URL, { + method: "post", + asynchronous: false, + postBody: JSON.stringify(params), + onSuccess: function(response){ + var feeds = response.responseText.evalJSON().content + feeds.each(function(feed) { + feed.categories = [{ + id: category.id, + label: category.title + }] + subscriptions.push(feed) + }) + }, + onFailure: failure + }) + } + }) + + params.cat_id = 0 + + new Ajax.Request(TTRSSApi.BASE_URL, { + method: "post", + postBody: JSON.stringify(params), + onSuccess: function(response){ + var feeds = response.responseText.evalJSON().content + feeds.each(function(feed) { + subscriptions.push(feed) + }) + self.cacheTitles(subscriptions) + success(subscriptions) + }, + onFailure: failure + }) + }, + + //UPDATED 1.1.0 + cacheTitles: function(subscriptions) { + var self = this + self.titles = {} + + subscriptions.each(function(subscription) { + self.titles[subscription.id] = subscription.title + }) + }, + + //UPDATED 1.1.0 + titleFor: function(id) { + return this.titles[id] + }, + + //UPDATED 1.1.0 + getUnreadCounts: function(success, failure) { + var params = { + sid: this.auth, + op: "getCounters", + output_mode: "f" + } + + new Ajax.Request(TTRSSApi.BASE_URL, { + method: "post", + postBody: JSON.stringify(params), + onSuccess: function(response){ + var json = response.responseText.evalJSON() + success(json.content) + }, + onFailure: failure + }) + }, + + //UPDATED 1.1.0 + getAllArticles: function(continuation, success, failure) { + this._getArticles( + -4, + Preferences.hideReadArticles() ? "unread" : "all_articles", + continuation, + success, + failure + ) + }, + + //UPDATED 1.1.0 + getAllStarred: function(continuation, success, failure) { + this._getArticles( + -1, + "all_articles", + continuation, + success, + failure + ) + }, + + //UPDATED 1.1.0 + getAllShared: function(continuation, success, failure) { + this._getArticles( + -2, + "all_articles", + continuation, + success, + failure + ) + }, + + //UPDATED 1.1.0 + getAllArticlesFor: function(id, continuation, success, failure) { + this._getArticles( + id, + Preferences.hideReadArticles() ? "unread" : "all_articles", + continuation, + success, + failure + ) + }, + + //UPDATED 1.1.0 + _getArticles: function(id, exclude, continuation, success, failure) { + + var parameters = { + sid: this.auth, + op: "getHeadlines", + feed_id: id, + limit: 40, + show_content: true, + } + + if(id != -4 && + id != -2 && + Preferences.isOldestFirst()) { + parameters.order_by = "date_reverse" + } + + if(continuation) { + parameters.skip = continuation + } + + if(exclude) { + parameters.view_mode = exclude + } + + new Ajax.Request(TTRSSApi.BASE_URL, { + method: "post", + postBody: JSON.stringify(parameters), + onSuccess: function(response){ + var articles = response.responseText.evalJSON() + if(articles.content.length == 40) + { + if(continuation) + { + continuation = continuation + 40 + } + else + { + continuation = 40 + } + success(articles.content, id, continuation) + } + else + { + success(articles.content, id, false) + } + }, + onFailure: failure, + }) + }, + + //UPDATED 1.1.0 + markAllRead: function(id, success, failure) { + var parameters = { + sid: this.auth, + op: "catchupFeed", + feed_id: id + } + + //NOTE: This particular behaviour works due to how category ids are handled, as opposed to feed ids within the app + //it's a bit of an ugly hack, but there isn't an easy way of determining feed vs category without making significant + //changes elsewhere. + if (id === "user/-/state/com.google/reading-list") + { + //NOTE: This behaviour does not work correctly with 1.12 - it must be a bug on the other end. + parameters.feed_id = -4 + parameters.is_cat = false + } + else if (id.constructor == String) + { + parameters.is_cat = true + } + else if (id.constructor == Number) + { + parameters.is_cat = false + } + + new Ajax.Request(TTRSSApi.BASE_URL, { + method: "post", + postBody: JSON.stringify(parameters), + onSuccess: success, + onFailure: failure + }) + }, + + //NOT SUPPORTED BY API + search: function(query, id, success, failure) { + var parameters = { + q: query, + num: 50, + output: "json" + } + + if(id) { + parameters.s = id + } + + new Ajax.Request(TTRSSApi.BASE_URL + "search/items/ids", { + method: "get", + parameters: parameters, + requestHeaders: this._requestHeaders(), + onSuccess: this.searchItemsFound.bind(this, success, failure), + onFailure: failure + }) + }, + + //NOT SUPPORTED BY API + searchItemsFound: function(success, failure, response) { + var self = this + var ids = response.responseText.evalJSON().results + + if(ids.length) { + self._getEditToken( + function(token) { + var parameters = { + T: token, + i: ids.map(function(n) {return n.id}) + } + + new Ajax.Request(TTRSSApi.BASE_URL + "stream/items/contents", { + method: "post", + parameters: parameters, + requestHeaders: self._requestHeaders(), + onFailure: failure, + onSuccess: function(response) { + var articles = response.responseText.evalJSON() + success(articles.items, articles.id, articles.continuation) + } + }) + } + ) + } + else { + success([], "", false) + } + }, + + //NOT SUPPORTED BY API + mapSearchResults: function(response) { + console.log(response.responseText) + }, + + //UPDATED 1.1.0 + setArticleRead: function(articleId, subscriptionId, success, failure) { + this._editTag( + articleId, + subscriptionId, + null, + 2, + success, + failure + ) + }, + + //UPDATED 1.1.0 + setArticleNotRead: function(articleId, subscriptionId, success, failure, sticky) { + this._editTag( + articleId, + subscriptionId, + 2, + null, + success, + failure + ) + }, + + //UPDATED 1.1.0 + setArticleShared: function(articleId, subscriptionId, success, failure) { + this._editTag( + articleId, + subscriptionId, + 1, + null, + success, + failure + ) + }, + + //UPDATED 1.1.0 + setArticleNotShared: function(articleId, subscriptionId, success, failure) { + this._editTag( + articleId, + subscriptionId, + null, + 1, + success, + failure + ) + }, + + //UPDATED 1.1.0 + setArticleStarred: function(articleId, subscriptionId, success, failure) { + this._editTag( + articleId, + subscriptionId, + 0, + null, + success, + failure + ) + }, + + //UPDATED 1.1.0 + setArticleNotStarred: function(articleId, subscriptionId, success, failure) { + this._editTag( + articleId, + subscriptionId, + null, + 0, + success, + failure + ) + }, + + //UPDATED 1.1.0 + _editTag: function(articleId, subscriptionId, addTag, removeTag, success, failure) { + Log.debug("editing tag for article id = " + articleId + " and subscription id = " + subscriptionId) + + var parameters = { + sid: this.auth, + op: "updateArticle", + article_ids: articleId + } + + if(addTag !== null){ + parameters.mode = 1 + parameters.field = addTag + } + + if(removeTag !== null){ + parameters.mode = 0 + parameters.field = removeTag + } + + new Ajax.Request(TTRSSApi.BASE_URL, { + method: "post", + postBody: JSON.stringify(parameters), + onSuccess: success, + onFailure: failure, + }) + }, + + //UPDATED 1.1.0 + supportsAllArticles: function() { + return true + }, + + //UPDATED 1.1.0 + supportsStarred: function() { + return true + }, + + //UPDATED 1.1.0 + supportsShared: function() { + return true + }, + + //UPDATED 1.1.0 + supportsSearch: function() { + return false + }, + + //UPDATED 1.1.0 + supportsManualSort: function() { + return false + } +}) + +TTRSSApi.BASE_URL = "" \ No newline at end of file diff --git a/app/views/credentials/credentials-scene.html b/app/views/credentials/credentials-scene.html index 8e126f1..9ca1ebb 100755 --- a/app/views/credentials/credentials-scene.html +++ b/app/views/credentials/credentials-scene.html @@ -42,6 +42,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ diff --git a/app/views/help/help-scene.html b/app/views/help/help-scene.html index 285bafc..e59d58a 100755 --- a/app/views/help/help-scene.html +++ b/app/views/help/help-scene.html @@ -35,6 +35,13 @@
History
+
+
1.1.0
+
    +
  • added support for Tiny Tiny RSS
  • +
+
+
1.0.3
    diff --git a/appinfo.json b/appinfo.json index 4666317..dc493a1 100755 --- a/appinfo.json +++ b/appinfo.json @@ -1,6 +1,6 @@ { "id": "com.othelloventures.feedspider", - "version": "1.0.3", + "version": "1.1.0", "vendor": "Othello Ventures", "type": "web", "main": "index.html", diff --git a/resources/de_de/strings.json b/resources/de_de/strings.json index cb20125..44bbba1 100755 --- a/resources/de_de/strings.json +++ b/resources/de_de/strings.json @@ -54,6 +54,7 @@ "Reader": "Reader", "Relego": "Relego", "Search Not Available": "Nicht suche verfügbar", + "Server URL" : "Server-URL", "Service" : "Dienstleistung", "Share": "Empfehlen", "Shared": "Empfohlen", @@ -71,6 +72,7 @@ "Subscribers": "Teilnehmer", "Subscriptions": "Abonnements", "The Old Reader": "The Old Reader", + "Tiny Tiny RSS": "Tiny Tiny RSS", "Twitter": "Twitter", "Unable to add subscription": "Abonnement kann nicht hinzugefügt werden", "Unshare": "Empfehlung entfernen", diff --git a/resources/en_us/strings.json b/resources/en_us/strings.json index 4755b49..aa34005 100755 --- a/resources/en_us/strings.json +++ b/resources/en_us/strings.json @@ -54,6 +54,7 @@ "Reader": "Reader", "Relego": "Relego", "Search Not Available": "Search Not Available", + "Server URL" : "Server URL", "Service" : "Service", "Share": "Share", "Shared": "Shared", @@ -71,6 +72,7 @@ "Subscribers": "Subscribers", "Subscriptions": "Subscriptions", "The Old Reader": "The Old Reader", + "Tiny Tiny RSS": "Tiny Tiny RSS", "Twitter": "Twitter", "Unable to add subscription": "Unable to add subscription", "Unshare": "Unshare", diff --git a/resources/zh_cn/strings.json b/resources/zh_cn/strings.json index e63ae38..b9d6fea 100644 --- a/resources/zh_cn/strings.json +++ b/resources/zh_cn/strings.json @@ -53,6 +53,7 @@ "Read Later": "稍后阅读", "Reader": "阅读器", "Relego": "Relego", + "Server URL" : "服务器URL", "Service" : "服务", "Search Not Available": "搜索不可用", "Share": "共享", @@ -71,6 +72,7 @@ "Subscribers": "用户", "Subscriptions": "订阅条目", "The Old Reader": "The Old Reader", + "Tiny Tiny RSS": "Tiny Tiny RSS", "Twitter": "Twitter", "Unable to add subscription": "无法添加订阅", "Unshare": "不共享", diff --git a/sources.json b/sources.json index deffe8e..48409b3 100755 --- a/sources.json +++ b/sources.json @@ -24,6 +24,7 @@ {"source": "app/models/ino-api.js"}, {"source": "app/models/feedly-api.js"}, {"source": "app/models/bq-api.js"}, + {"source": "app/models/ttrss-api.js"}, {"source": "app/lib/ajax.js"}, {"source": "app/lib/instapaper.js"}, From b7e2122b7366963a38b4135e6a22556460b79103 Mon Sep 17 00:00:00 2001 From: Brent Hunter Date: Thu, 26 Dec 2013 10:07:40 -0800 Subject: [PATCH 18/34] 1.1.1 Fixed ttrss bugs: 1. Properly added url to article header 2. Fix problem when dealing with category feeds. --- app/models/article.js | 9 ++++++++- app/models/ttrss-api.js | 7 ++++++- app/views/help/help-scene.html | 8 ++++++++ appinfo.json | 2 +- 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/app/models/article.js b/app/models/article.js index e3f0163..e6cd214 100755 --- a/app/models/article.js +++ b/app/models/article.js @@ -52,7 +52,14 @@ var Article = Class.create({ var pubDate = data.crawlTimeMsec || data.crawled || data.updated + "000" this.setDates(parseInt(pubDate, 10)) - this.setArticleLink(data.alternate) + if (data.alternate) + { + this.setArticleLink(data.alternate) + } + else + { + this.url = data.link + } }, cleanUp: function(content) { diff --git a/app/models/ttrss-api.js b/app/models/ttrss-api.js index a145fd3..184992e 100755 --- a/app/models/ttrss-api.js +++ b/app/models/ttrss-api.js @@ -341,7 +341,7 @@ var TTRSSApi = Class.create({ ) }, - //UPDATED 1.1.0 + //UPDATED 1.1.1 _getArticles: function(id, exclude, continuation, success, failure) { var parameters = { @@ -364,6 +364,11 @@ var TTRSSApi = Class.create({ if(exclude) { parameters.view_mode = exclude + } + + if (id.constructor == String) + { + parameters.is_cat = true } new Ajax.Request(TTRSSApi.BASE_URL, { diff --git a/app/views/help/help-scene.html b/app/views/help/help-scene.html index e59d58a..fbaaf75 100755 --- a/app/views/help/help-scene.html +++ b/app/views/help/help-scene.html @@ -18,6 +18,7 @@
  • BazQux Reader
  • Feedly
  • InoReader
  • +
  • Tiny Tiny RSS
  • The Old Reader
@@ -35,6 +36,13 @@
History
+
+
1.1.1
+
    +
  • bugfixes for Tiny Tiny RSS
  • +
+
+
1.1.0
    diff --git a/appinfo.json b/appinfo.json index dc493a1..1084fc2 100755 --- a/appinfo.json +++ b/appinfo.json @@ -1,6 +1,6 @@ { "id": "com.othelloventures.feedspider", - "version": "1.1.0", + "version": "1.1.1", "vendor": "Othello Ventures", "type": "web", "main": "index.html", From abb9d8325fb3ca84fbaa5a6badcf957266ef4f8a Mon Sep 17 00:00:00 2001 From: Brent Hunter Date: Sat, 28 Dec 2013 11:11:12 -0800 Subject: [PATCH 19/34] 1.1.2 - Streamlined apis to handle post processing instead of pushing it to other parts of app. - fixed bug in Feedly that caused starred state icon to not be updated in article. --- app/assistants/add-assistant.js | 8 --- app/assistants/app-assistant.js | 2 +- app/models/all-subscriptions.js | 21 +------ app/models/article.js | 97 +++++++-------------------------- app/models/countable.js | 2 +- app/models/feedly-api.js | 58 ++++++++++++++++---- app/models/folders.js | 2 +- app/models/subscription.js | 4 +- app/models/ttrss-api.js | 66 +++++++++++++++++++--- app/views/help/help-scene.html | 8 +++ appinfo.json | 2 +- 11 files changed, 144 insertions(+), 126 deletions(-) diff --git a/app/assistants/add-assistant.js b/app/assistants/add-assistant.js index 67b5ced..79a6bdb 100755 --- a/app/assistants/add-assistant.js +++ b/app/assistants/add-assistant.js @@ -117,18 +117,10 @@ var AddAssistant = Class.create(BaseAssistant, { if(json.content && json.content.content) { subscription.content = json.content.content } - else if (json.website && json.subscribers) - { - subscription.content = $L("Website") + ": " + json.website + ", " + $L("Subscribers") + ": " + json.subscribers - } if(json.feed && json.feed.length && json.feed[0].href) { subscription.url = json.feed[0].href } - else if (json.feedId) - { - subscription.url = json.feedId.substr(5) - } return subscription } diff --git a/app/assistants/app-assistant.js b/app/assistants/app-assistant.js index 8c54ad2..d934e6e 100755 --- a/app/assistants/app-assistant.js +++ b/app/assistants/app-assistant.js @@ -44,7 +44,7 @@ var AppAssistant = Class.create({ $A(counts).each(function(count) { if(count.count && Preferences.wantsNotificationFor(count.id)) { - unreadCount += count.count || count.counter + unreadCount += count.count } }) diff --git a/app/models/all-subscriptions.js b/app/models/all-subscriptions.js index 8a20a4a..8b3232e 100755 --- a/app/models/all-subscriptions.js +++ b/app/models/all-subscriptions.js @@ -48,27 +48,12 @@ var AllSubscriptions = Class.create(SubscriptionContainer, { self.api.getUnreadCounts( function(counts) { counts.each(function(count) { - if(count.id.toString().startsWith("feed") || (count.id > 0 && count.kind !== "cat")){ - - if (count.count !== undefined) - { - self.incrementUnreadCountBy(count.count) - } - else - { - self.incrementUnreadCountBy(count.counter) - } + if(count.id.toString().startsWith("feed") || count.id > 0){ + self.incrementUnreadCountBy(count.count) self.items.each(function(item) { if(item.id == count.id) { - if (count.count !== undefined) - { - item.setUnreadCount(count.count) - } - else - { - item.setUnreadCount(count.counter) - } + item.setUnreadCount(count.count) } if(item.isFolder) { diff --git a/app/models/article.js b/app/models/article.js index e6cd214..76e7cd9 100755 --- a/app/models/article.js +++ b/app/models/article.js @@ -3,63 +3,19 @@ var Article = Class.create({ this.api = subscription.api this.id = data.id this.subscription = subscription - if (data.origin) - { - this.subscriptionId = data.origin.streamId - } - else - { - this.subscriptionId = data.feed_id || subscription.id - } + this.subscriptionId = data.origin ? data.origin.streamId : subscription.id this.title = data.title || "No Title" this.author = data.author this.origin = this.api.titleFor(this.subscriptionId) var content = data.content || data.summary || {content: ""} - this.summary = content.content ? this.cleanUp(content.content) : this.cleanUp(content) + this.summary = this.cleanUp(content.content) this.readLocked = data.isReadStateLocked this.isRead = false this.isShared = false this.isStarred = false - - if(data.tags) - { - //This section controls behaviour for Feedly and TTRSS - this.setStates(data.tags) - - if (data.unread !== undefined) - { - this.isRead = !data.unread - } - - if (data.marked !== undefined) - { - this.isStarred = data.marked - } - - if (data.published !== undefined) - { - this.isShared = data.published - } - } - else if (data.unread !== undefined) - { - this.isRead = !data.unread - } - else - { - this.setStates(data.categories) - } - - var pubDate = data.crawlTimeMsec || data.crawled || data.updated + "000" - this.setDates(parseInt(pubDate, 10)) - if (data.alternate) - { - this.setArticleLink(data.alternate) - } - else - { - this.url = data.link - } + this.setStates(data.categories) + this.setDates(parseInt(data.crawlTimeMsec, 10)) + this.setArticleLink(data.alternate) }, cleanUp: function(content) { @@ -88,34 +44,21 @@ var Article = Class.create({ setStates: function(categories) { categories.each(function(category) { - if (category.id !== undefined) - { - if(category.id.toString().endsWith("/tag/global.read")) { - this.isRead = true - } - - if(category.id.toString().endsWith("/tag/global.saved")) { - this.isStarred = true - } - } - else - { - if(category.toString().endsWith("/state/com.google/read")) { - this.isRead = true - } - - if(category.toString().endsWith("/state/com.google/kept-unread")) { - this.keepUnread = true - } - - if(category.toString().endsWith("/state/com.google/starred")) { - this.isStarred = true - } - - if(category.toString().endsWith("/state/com.google/broadcast")) { - this.isShared = true - } - } + if(category.endsWith("/state/com.google/read")) { + this.isRead = true + } + + if(category.endsWith("/state/com.google/kept-unread")) { + this.keepUnread = true + } + + if(category.endsWith("/state/com.google/starred")) { + this.isStarred = true + } + + if(category.endsWith("/state/com.google/broadcast")) { + this.isShared = true + } }.bind(this)) }, diff --git a/app/models/countable.js b/app/models/countable.js index a0ebe76..1884976 100755 --- a/app/models/countable.js +++ b/app/models/countable.js @@ -14,7 +14,7 @@ var Countable = Class.create({ this.unreadCount = 0 } - this.unreadCountDisplay = count > 999 ? "1000+" : count + this.unreadCountDisplay = count > 9999 ? "10000+" : count this.unreadCountDisplay = count <= 0 ? "" : this.unreadCountDisplay }, diff --git a/app/models/feedly-api.js b/app/models/feedly-api.js index 9fad1b7..8f0ad31 100644 --- a/app/models/feedly-api.js +++ b/app/models/feedly-api.js @@ -111,7 +111,7 @@ var FeedlyApi = Class.create({ this._ajaxDelete(FeedlyApi.BASE_URL + "categories/" + encodeURIComponent(folder.id), function() {Mojo.Event.send(document, "FolderDeleted", {id: folder.id})}, function() {}) }, - //UPDATED 0.9.5 + //UPDATED 1.1.2 searchSubscriptions: function(query, success, failure) { var self = this @@ -121,7 +121,14 @@ var FeedlyApi = Class.create({ requestHeaders: this._requestHeaders(), onFailure: failure, onSuccess: function(response) { + //Post-processing var subscriptions = response.responseText.evalJSON().results + + subscriptions.each(function(subscription) { + subscription.content = {content: $L("Website") + ": " + subscription.website + ", " + $L("Subscribers") + ": " + subscription.subscribers} + subscription.feed = [{href: subscription.feedId.substr(5)}] + }) + success(subscriptions) } }) @@ -250,7 +257,7 @@ var FeedlyApi = Class.create({ ) }, - //UPDATED 0.9.5 + //UPDATED 1.1.2 getAllArticlesFor: function(id, continuation, success, failure) { this._getArticles( id, @@ -284,6 +291,37 @@ var FeedlyApi = Class.create({ onFailure: failure, onSuccess: function(response) { var articles = response.responseText.evalJSON() + + //Do post-processing to conform articles to FeedSpider spec + articles.items.each(function(article) { + //Set article categories + article.categories = [] + if(article.tags){ + article.tags.each(function(tag) { + if (tag.id !== undefined) + { + if(tag.id.endsWith("/tag/global.read")) { + article.categories.push("/state/com.google/read") + } + + if(tag.id.endsWith("/tag/global.saved")) { + article.categories.push("/state/com.google/starred") + } + } + }) + } + if (article.unread !== undefined) + { + if(article.unread == false) + { + article.categories.push("/state/com.google/read") + } + } + + //Set article timestamp + article.crawlTimeMsec = article.crawled + }) + success(articles.items, articles.id, articles.continuation) } }) @@ -470,7 +508,7 @@ var FeedlyApi = Class.create({ } }, - //UPDATED 1.0.0 + //UPDATED 1.1.2 _ajaxPut: function(params, url, success, failure) { if (this._checkTokenExpiry) { @@ -482,10 +520,10 @@ var FeedlyApi = Class.create({ req.send(JSON.stringify(params)) req.onreadystatechange = function() { if(req.readyState === 4 && req.status === 200){ - success + success() } else if(req.readyState === 4 && req.status !== 200){ - failure + failure() } else { @@ -495,7 +533,7 @@ var FeedlyApi = Class.create({ } catch (e) { Log.debug('_ajaxPut failed! Error:' + e) - failure + failure() } } else @@ -504,7 +542,7 @@ var FeedlyApi = Class.create({ } }, - //UPDATED 1.0.0 + //UPDATED 1.1.2 _ajaxDelete: function(url, success, failure) { if (this._checkTokenExpiry) { @@ -515,10 +553,10 @@ var FeedlyApi = Class.create({ req.send() req.onreadystatechange = function() { if(req.readyState === 4 && req.status === 200){ - success + success() } else if(req.readyState === 4 && req.status !== 200){ - failure + failure() } else { @@ -528,7 +566,7 @@ var FeedlyApi = Class.create({ } catch (e) { Log.debug('_ajaxDelete failed! Error:' + e) - failure + failure() } } else diff --git a/app/models/folders.js b/app/models/folders.js index 119445b..3317fcb 100755 --- a/app/models/folders.js +++ b/app/models/folders.js @@ -26,7 +26,7 @@ var Folders = Class.create({ function(tags) { tags.each(function(tag) { var folder = self.items.find(function(item) {return item.id == tag.id}) - if(folder) folder.sortId = tag.sortid || tag.order_id + if(folder) folder.sortId = tag.sortid }) success() diff --git a/app/models/subscription.js b/app/models/subscription.js index 887c194..21e83b2 100755 --- a/app/models/subscription.js +++ b/app/models/subscription.js @@ -6,8 +6,8 @@ var Subscription = Class.create(ArticleContainer, { this.icon = "rss" this.divideBy = $L("Subscriptions") this.canMarkAllRead = true - this.sortId = data.sortid || data.order_id - this.categories = data.categories || data.cat_id + this.sortId = data.sortid + this.categories = data.categories }, belongsToFolder: function() { diff --git a/app/models/ttrss-api.js b/app/models/ttrss-api.js index 184992e..e2cb67e 100755 --- a/app/models/ttrss-api.js +++ b/app/models/ttrss-api.js @@ -47,7 +47,7 @@ var TTRSSApi = Class.create({ }) }, - //UPDATED 1.1.0 + //UPDATED 1.1.2 getTags: function(success, failure) { var params = { sid: this.auth, @@ -59,7 +59,13 @@ var TTRSSApi = Class.create({ method: "post", postBody: JSON.stringify(params), onSuccess: function(response){ - success(response.responseText.evalJSON().content) + //Post-Processing + var tags = response.responseText.evalJSON() + tags.content.each(function(tag) { + tag.sortid = tag.order_id + }) + + success(tags.content) }, onFailure: failure, }) @@ -278,7 +284,7 @@ var TTRSSApi = Class.create({ return this.titles[id] }, - //UPDATED 1.1.0 + //UPDATED 1.1.2 getUnreadCounts: function(success, failure) { var params = { sid: this.auth, @@ -290,8 +296,18 @@ var TTRSSApi = Class.create({ method: "post", postBody: JSON.stringify(params), onSuccess: function(response){ - var json = response.responseText.evalJSON() - success(json.content) + var json = response.responseText.evalJSON() + var feeds = [] + + json.content.each(function(feed) { + if (feed.id > 0 && feed.kind !== "cat") + { + feed.count = feed.counter + feeds.push(feed) + } + }) + + success(feeds) }, onFailure: failure }) @@ -341,9 +357,8 @@ var TTRSSApi = Class.create({ ) }, - //UPDATED 1.1.1 + //UPDATED 1.1.2 _getArticles: function(id, exclude, continuation, success, failure) { - var parameters = { sid: this.auth, op: "getHeadlines", @@ -376,6 +391,43 @@ var TTRSSApi = Class.create({ postBody: JSON.stringify(parameters), onSuccess: function(response){ var articles = response.responseText.evalJSON() + + //Do post-processing to conform articles to FeedSpider spec + articles.content.each(function(article) { + //Set article origin + article.origin = {streamId : article.feed_id} + + //Set article content + article.content = {content: article.content} + + //Set article categories + article.categories = [] + if (!article.unread) + { + article.categories.push("/state/com.google/read") + } + + if (article.marked) + { + article.categories.push("/state/com.google/starred") + } + + if (article.published) + { + article.categories.push("/state/com.google/broadcast") + } + + //Set article link + article.alternate = [{ + type: "html", + href: article.link + }] + + //Set article timestamp + article.crawlTimeMsec = article.updated + "000" + }) + + //Load more articles (if there are more to load) if(articles.content.length == 40) { if(continuation) diff --git a/app/views/help/help-scene.html b/app/views/help/help-scene.html index fbaaf75..9a21255 100755 --- a/app/views/help/help-scene.html +++ b/app/views/help/help-scene.html @@ -36,6 +36,14 @@
    History
+
+
1.1.2
+
    +
  • streamlined apis
  • +
  • fixed bug in Feedly regarding starring and un-starring articles
  • +
+
+
1.1.1
    diff --git a/appinfo.json b/appinfo.json index 1084fc2..122dbf3 100755 --- a/appinfo.json +++ b/appinfo.json @@ -1,6 +1,6 @@ { "id": "com.othelloventures.feedspider", - "version": "1.1.1", + "version": "1.1.2", "vendor": "Othello Ventures", "type": "web", "main": "index.html", From d2ebd243c2504601f6895b74fda58acdca9f3919 Mon Sep 17 00:00:00 2001 From: Brent Hunter Date: Sat, 4 Jan 2014 09:00:48 -0800 Subject: [PATCH 20/34] 1.1.3 - replaced JSON parser to stop feeds from hanging - fixed bug in TT-RSS regarding badly sorted aggregate feeds - tweaked how continuation is handled in TTRSS --- app/lib/json2.js | 486 +++++++++++++++++++++++++++++++++ app/models/bq-api.js | 4 +- app/models/feedly-api.js | 4 +- app/models/ino-api.js | 3 +- app/models/tor-api.js | 3 +- app/models/ttrss-api.js | 12 +- app/views/help/help-scene.html | 8 + appinfo.json | 2 +- sources.json | 1 + 9 files changed, 511 insertions(+), 12 deletions(-) create mode 100644 app/lib/json2.js diff --git a/app/lib/json2.js b/app/lib/json2.js new file mode 100644 index 0000000..8d9763a --- /dev/null +++ b/app/lib/json2.js @@ -0,0 +1,486 @@ +/* + json2.js + 2013-05-26 + + Public Domain. + + NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. + + See http://www.JSON.org/js.html + + + This code should be minified before deployment. + See http://javascript.crockford.com/jsmin.html + + USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO + NOT CONTROL. + + + This file creates a global JSON object containing two methods: stringify + and parse. + + JSON.stringify(value, replacer, space) + value any JavaScript value, usually an object or array. + + replacer an optional parameter that determines how object + values are stringified for objects. It can be a + function or an array of strings. + + space an optional parameter that specifies the indentation + of nested structures. If it is omitted, the text will + be packed without extra whitespace. If it is a number, + it will specify the number of spaces to indent at each + level. If it is a string (such as '\t' or ' '), + it contains the characters used to indent at each level. + + This method produces a JSON text from a JavaScript value. + + When an object value is found, if the object contains a toJSON + method, its toJSON method will be called and the result will be + stringified. A toJSON method does not serialize: it returns the + value represented by the name/value pair that should be serialized, + or undefined if nothing should be serialized. The toJSON method + will be passed the key associated with the value, and this will be + bound to the value + + For example, this would serialize Dates as ISO strings. + + Date.prototype.toJSON = function (key) { + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } + + return this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z'; + }; + + You can provide an optional replacer method. It will be passed the + key and value of each member, with this bound to the containing + object. The value that is returned from your method will be + serialized. If your method returns undefined, then the member will + be excluded from the serialization. + + If the replacer parameter is an array of strings, then it will be + used to select the members to be serialized. It filters the results + such that only members with keys listed in the replacer array are + stringified. + + Values that do not have JSON representations, such as undefined or + functions, will not be serialized. Such values in objects will be + dropped; in arrays they will be replaced with null. You can use + a replacer function to replace those with JSON values. + JSON.stringify(undefined) returns undefined. + + The optional space parameter produces a stringification of the + value that is filled with line breaks and indentation to make it + easier to read. + + If the space parameter is a non-empty string, then that string will + be used for indentation. If the space parameter is a number, then + the indentation will be that many spaces. + + Example: + + text = JSON.stringify(['e', {pluribus: 'unum'}]); + // text is '["e",{"pluribus":"unum"}]' + + + text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t'); + // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' + + text = JSON.stringify([new Date()], function (key, value) { + return this[key] instanceof Date ? + 'Date(' + this[key] + ')' : value; + }); + // text is '["Date(---current time---)"]' + + + JSON.parse(text, reviver) + This method parses a JSON text to produce an object or array. + It can throw a SyntaxError exception. + + The optional reviver parameter is a function that can filter and + transform the results. It receives each of the keys and values, + and its return value is used instead of the original value. + If it returns what it received, then the structure is not modified. + If it returns undefined then the member is deleted. + + Example: + + // Parse the text. Values that look like ISO date strings will + // be converted to Date objects. + + myData = JSON.parse(text, function (key, value) { + var a; + if (typeof value === 'string') { + a = +/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); + if (a) { + return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4], + +a[5], +a[6])); + } + } + return value; + }); + + myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) { + var d; + if (typeof value === 'string' && + value.slice(0, 5) === 'Date(' && + value.slice(-1) === ')') { + d = new Date(value.slice(5, -1)); + if (d) { + return d; + } + } + return value; + }); + + + This is a reference implementation. You are free to copy, modify, or + redistribute. +*/ + +/*jslint evil: true, regexp: true */ + +/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply, + call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, + getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, + lastIndex, length, parse, prototype, push, replace, slice, stringify, + test, toJSON, toString, valueOf +*/ + + +// Create a JSON object only if one does not already exist. We create the +// methods in a closure to avoid creating global variables. + +if (typeof JSON2 !== 'object') { + JSON2 = {}; +} + +(function () { + 'use strict'; + + function f(n) { + // Format integers to have at least two digits. + return n < 10 ? '0' + n : n; + } + + if (typeof Date.prototype.toJSON !== 'function') { + + Date.prototype.toJSON = function () { + + return isFinite(this.valueOf()) + ? this.getUTCFullYear() + '-' + + f(this.getUTCMonth() + 1) + '-' + + f(this.getUTCDate()) + 'T' + + f(this.getUTCHours()) + ':' + + f(this.getUTCMinutes()) + ':' + + f(this.getUTCSeconds()) + 'Z' + : null; + }; + + String.prototype.toJSON = + Number.prototype.toJSON = + Boolean.prototype.toJSON = function () { + return this.valueOf(); + }; + } + + var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, + gap, + indent, + meta = { // table of character substitutions + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }, + rep; + + + function quote(string) { + +// If the string contains no control characters, no quote characters, and no +// backslash characters, then we can safely slap some quotes around it. +// Otherwise we must also replace the offending characters with safe escape +// sequences. + + escapable.lastIndex = 0; + return escapable.test(string) ? '"' + string.replace(escapable, function (a) { + var c = meta[a]; + return typeof c === 'string' + ? c + : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }) + '"' : '"' + string + '"'; + } + + + function str(key, holder) { + +// Produce a string from holder[key]. + + var i, // The loop counter. + k, // The member key. + v, // The member value. + length, + mind = gap, + partial, + value = holder[key]; + +// If the value has a toJSON method, call it to obtain a replacement value. + + if (value && typeof value === 'object' && + typeof value.toJSON === 'function') { + value = value.toJSON(key); + } + +// If we were called with a replacer function, then call the replacer to +// obtain a replacement value. + + if (typeof rep === 'function') { + value = rep.call(holder, key, value); + } + +// What happens next depends on the value's type. + + switch (typeof value) { + case 'string': + return quote(value); + + case 'number': + +// JSON numbers must be finite. Encode non-finite numbers as null. + + return isFinite(value) ? String(value) : 'null'; + + case 'boolean': + case 'null': + +// If the value is a boolean or null, convert it to a string. Note: +// typeof null does not produce 'null'. The case is included here in +// the remote chance that this gets fixed someday. + + return String(value); + +// If the type is 'object', we might be dealing with an object or an array or +// null. + + case 'object': + +// Due to a specification blunder in ECMAScript, typeof null is 'object', +// so watch out for that case. + + if (!value) { + return 'null'; + } + +// Make an array to hold the partial results of stringifying this object value. + + gap += indent; + partial = []; + +// Is the value an array? + + if (Object.prototype.toString.apply(value) === '[object Array]') { + +// The value is an array. Stringify every element. Use null as a placeholder +// for non-JSON values. + + length = value.length; + for (i = 0; i < length; i += 1) { + partial[i] = str(i, value) || 'null'; + } + +// Join all of the elements together, separated with commas, and wrap them in +// brackets. + + v = partial.length === 0 + ? '[]' + : gap + ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' + : '[' + partial.join(',') + ']'; + gap = mind; + return v; + } + +// If the replacer is an array, use it to select the members to be stringified. + + if (rep && typeof rep === 'object') { + length = rep.length; + for (i = 0; i < length; i += 1) { + if (typeof rep[i] === 'string') { + k = rep[i]; + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + } else { + +// Otherwise, iterate through all of the keys in the object. + + for (k in value) { + if (Object.prototype.hasOwnProperty.call(value, k)) { + v = str(k, value); + if (v) { + partial.push(quote(k) + (gap ? ': ' : ':') + v); + } + } + } + } + +// Join all of the member texts together, separated with commas, +// and wrap them in braces. + + v = partial.length === 0 + ? '{}' + : gap + ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' + : '{' + partial.join(',') + '}'; + gap = mind; + return v; + } + } + +// If the JSON object does not yet have a stringify method, give it one. + + if (typeof JSON2.stringify !== 'function') { + JSON2.stringify = function (value, replacer, space) { + +// The stringify method takes a value and an optional replacer, and an optional +// space parameter, and returns a JSON text. The replacer can be a function +// that can replace values, or an array of strings that will select the keys. +// A default replacer method can be provided. Use of the space parameter can +// produce text that is more easily readable. + + var i; + gap = ''; + indent = ''; + +// If the space parameter is a number, make an indent string containing that +// many spaces. + + if (typeof space === 'number') { + for (i = 0; i < space; i += 1) { + indent += ' '; + } + +// If the space parameter is a string, it will be used as the indent string. + + } else if (typeof space === 'string') { + indent = space; + } + +// If there is a replacer, it must be a function or an array. +// Otherwise, throw an error. + + rep = replacer; + if (replacer && typeof replacer !== 'function' && + (typeof replacer !== 'object' || + typeof replacer.length !== 'number')) { + throw new Error('JSON2.stringify'); + } + +// Make a fake root object containing our value under the key of ''. +// Return the result of stringifying the value. + + return str('', {'': value}); + }; + } + + +// If the JSON object does not yet have a parse method, give it one. + + if (typeof JSON2.parse !== 'function') { + JSON2.parse = function (text, reviver) { + +// The parse method takes a text and an optional reviver function, and returns +// a JavaScript value if the text is a valid JSON text. + + var j; + + function walk(holder, key) { + +// The walk method is used to recursively walk the resulting structure so +// that modifications can be made. + + var k, v, value = holder[key]; + if (value && typeof value === 'object') { + for (k in value) { + if (Object.prototype.hasOwnProperty.call(value, k)) { + v = walk(value, k); + if (v !== undefined) { + value[k] = v; + } else { + delete value[k]; + } + } + } + } + return reviver.call(holder, key, value); + } + + +// Parsing happens in four stages. In the first stage, we replace certain +// Unicode characters with escape sequences. JavaScript handles many characters +// incorrectly, either silently deleting them, or treating them as line endings. + + text = String(text); + cx.lastIndex = 0; + if (cx.test(text)) { + text = text.replace(cx, function (a) { + return '\\u' + + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }); + } + +// In the second stage, we run the text against regular expressions that look +// for non-JSON patterns. We are especially concerned with '()' and 'new' +// because they can cause invocation, and '=' because it can cause mutation. +// But just to be safe, we want to reject all unexpected forms. + +// We split the second stage into 4 regexp operations in order to work around +// crippling inefficiencies in IE's and Safari's regexp engines. First we +// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we +// replace all simple value tokens with ']' characters. Third, we delete all +// open brackets that follow a colon or comma or that begin the text. Finally, +// we look to see that the remaining characters are only whitespace or ']' or +// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval. + + if (/^[\],:{}\s]*$/ + .test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@') + .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']') + .replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { + +// In the third stage we use the eval function to compile the text into a +// JavaScript structure. The '{' operator is subject to a syntactic ambiguity +// in JavaScript: it can begin a block or an object literal. We wrap the text +// in parens to eliminate the ambiguity. + + j = eval('(' + text + ')'); + +// In the optional fourth stage, we recursively walk the new structure, passing +// each name/value pair to a reviver function for possible transformation. + + return typeof reviver === 'function' + ? walk({'': j}, '') + : j; + } + +// If the text is not JSON parseable, then a SyntaxError is thrown. + + throw new SyntaxError('JSON2.parse'); + }; + } +}()); \ No newline at end of file diff --git a/app/models/bq-api.js b/app/models/bq-api.js index f412e75..47f0e32 100644 --- a/app/models/bq-api.js +++ b/app/models/bq-api.js @@ -254,7 +254,7 @@ var BQApi = Class.create({ ) }, - //UPDATED 1.0.1 + //UPDATED 1.1.3 _getArticles: function(id, exclude, continuation, success, failure) { var parameters = {output: "json", n: 40} @@ -278,7 +278,7 @@ var BQApi = Class.create({ requestHeaders: this._requestHeaders(), onFailure: failure, onSuccess: function(response) { - var articles = response.responseText.evalJSON() + var articles = JSON2.parse(response.responseText) success(articles.items, articles.id, articles.continuation) } }) diff --git a/app/models/feedly-api.js b/app/models/feedly-api.js index 8f0ad31..26932f6 100644 --- a/app/models/feedly-api.js +++ b/app/models/feedly-api.js @@ -268,7 +268,7 @@ var FeedlyApi = Class.create({ ) }, - //UPDATED 0.9.5 + //UPDATED 1.1.3 _getArticles: function(id, exclude, continuation, success, failure) { var parameters = {output: "json", count: 40} @@ -290,7 +290,7 @@ var FeedlyApi = Class.create({ requestHeaders: this._requestHeaders(), onFailure: failure, onSuccess: function(response) { - var articles = response.responseText.evalJSON() + var articles = JSON2.parse(response.responseText) //Do post-processing to conform articles to FeedSpider spec articles.items.each(function(article) { diff --git a/app/models/ino-api.js b/app/models/ino-api.js index 9ee61af..51d9c79 100644 --- a/app/models/ino-api.js +++ b/app/models/ino-api.js @@ -237,6 +237,7 @@ var InoApi = Class.create({ ) }, + //UPDATED 1.1.3 _getArticles: function(id, exclude, continuation, success, failure) { var parameters = {output: "json", n: 40} @@ -260,7 +261,7 @@ var InoApi = Class.create({ requestHeaders: this._requestHeaders(), onFailure: failure, onSuccess: function(response) { - var articles = response.responseText.evalJSON() + var articles = JSON2.parse(response.responseText) success(articles.items, articles.id, articles.continuation) } }) diff --git a/app/models/tor-api.js b/app/models/tor-api.js index 0ce1e10..44d5c1b 100755 --- a/app/models/tor-api.js +++ b/app/models/tor-api.js @@ -239,6 +239,7 @@ var TorApi = Class.create({ ) }, + //UPDATED 1.1.3 _getArticles: function(id, exclude, continuation, success, failure) { var parameters = {output: "json", n: 40} @@ -262,7 +263,7 @@ var TorApi = Class.create({ requestHeaders: this._requestHeaders(), onFailure: failure, onSuccess: function(response) { - var articles = response.responseText.evalJSON() + var articles = JSON2.parse(response.responseText) success(articles.items, articles.id, articles.continuation) } }) diff --git a/app/models/ttrss-api.js b/app/models/ttrss-api.js index e2cb67e..d31fe16 100755 --- a/app/models/ttrss-api.js +++ b/app/models/ttrss-api.js @@ -357,7 +357,7 @@ var TTRSSApi = Class.create({ ) }, - //UPDATED 1.1.2 + //UPDATED 1.1.3 _getArticles: function(id, exclude, continuation, success, failure) { var parameters = { sid: this.auth, @@ -371,6 +371,8 @@ var TTRSSApi = Class.create({ id != -2 && Preferences.isOldestFirst()) { parameters.order_by = "date_reverse" + } else { + parameters.order_by = "feed_dates" } if(continuation) { @@ -390,7 +392,7 @@ var TTRSSApi = Class.create({ method: "post", postBody: JSON.stringify(parameters), onSuccess: function(response){ - var articles = response.responseText.evalJSON() + var articles = JSON2.parse(response.responseText) //Do post-processing to conform articles to FeedSpider spec articles.content.each(function(article) { @@ -428,15 +430,15 @@ var TTRSSApi = Class.create({ }) //Load more articles (if there are more to load) - if(articles.content.length == 40) + if(articles.content.length == parameters.limit) { if(continuation) { - continuation = continuation + 40 + continuation = continuation + parameters.limit } else { - continuation = 40 + continuation = parameters.limit } success(articles.content, id, continuation) } diff --git a/app/views/help/help-scene.html b/app/views/help/help-scene.html index 9a21255..68fee21 100755 --- a/app/views/help/help-scene.html +++ b/app/views/help/help-scene.html @@ -36,6 +36,14 @@
    History
+
+
1.1.3
+
    +
  • replaced JSON parser to stop feeds from hanging
  • +
  • fixed bug in TT-RSS regarding badly sorted aggregate feeds
  • +
+
+
1.1.2
    diff --git a/appinfo.json b/appinfo.json index 122dbf3..e4c52f6 100755 --- a/appinfo.json +++ b/appinfo.json @@ -1,6 +1,6 @@ { "id": "com.othelloventures.feedspider", - "version": "1.1.2", + "version": "1.1.3", "vendor": "Othello Ventures", "type": "web", "main": "index.html", diff --git a/sources.json b/sources.json index 48409b3..218c4ee 100755 --- a/sources.json +++ b/sources.json @@ -28,6 +28,7 @@ {"source": "app/lib/ajax.js"}, {"source": "app/lib/instapaper.js"}, + {"source": "app/lib/json2.js"}, {"source": "app/lib/log.js"}, {"source": "app/lib/sharing.js"}, From 71b185c0d18b15104bda5ddc68c1f986fda008fc Mon Sep 17 00:00:00 2001 From: Brent Hunter Date: Fri, 24 Jan 2014 12:02:11 -0800 Subject: [PATCH 21/34] 1.2.0 - added support for AOL Reader - added support for the "Fresh" and "Archived" feeds in Tiny Tiny RSS - added support for mixes in Feedly --- app/assistants/credentials-assistant.js | 3 +- app/assistants/login-assistant.js | 2 +- app/assistants/oauth-assistant.js | 19 +- app/assistants/preferences-assistant.js | 12 + app/models/all-sources.js | 14 +- app/models/aol-api.js | 647 +++++++++++++++++++ app/models/api.js | 21 + app/models/archived.js | 23 + app/models/bq-api.js | 34 + app/models/feedly-api.js | 138 ++-- app/models/fresh.js | 23 + app/models/ino-api.js | 34 + app/models/preferences.js | 9 + app/models/tor-api.js | 34 + app/models/ttrss-api.js | 34 + app/views/help/help-scene.html | 10 + app/views/preferences/preferences-scene.html | 16 + appinfo.json | 2 +- images/archived.png | Bin 0 -> 3778 bytes images/fresh.png | Bin 0 -> 3626 bytes resources/de_de/strings.json | 5 + resources/en_us/strings.json | 5 + resources/zh_cn/strings.json | 5 + sources.json | 3 + stylesheets/feedspider.css | 12 + 25 files changed, 1056 insertions(+), 49 deletions(-) create mode 100644 app/models/aol-api.js create mode 100755 app/models/archived.js create mode 100755 app/models/fresh.js create mode 100644 images/archived.png create mode 100644 images/fresh.png diff --git a/app/assistants/credentials-assistant.js b/app/assistants/credentials-assistant.js index f8fef57..d95d50d 100755 --- a/app/assistants/credentials-assistant.js +++ b/app/assistants/credentials-assistant.js @@ -8,6 +8,7 @@ var CredentialsAssistant = Class.create(BaseAssistant, { this.hideLogout = true this.serviceChoices = [ + {label: $L("AOL Reader"), value: "aol"}, {label: $L("BazQux"), value: "bq"}, {label: $L("Feedly"), value: "feedly"}, {label: $L("InoReader"), value: "ino"}, @@ -84,7 +85,7 @@ var CredentialsAssistant = Class.create(BaseAssistant, { }, setService: function(propertyChangeEvent) { - if (propertyChangeEvent.value == "feedly") + if (propertyChangeEvent.value == "feedly" || propertyChangeEvent.value == "aol") { this.controller.get("email-group")["hide"]() this.controller.get("password-group")["hide"]() diff --git a/app/assistants/login-assistant.js b/app/assistants/login-assistant.js index 3025f1c..eabacf8 100755 --- a/app/assistants/login-assistant.js +++ b/app/assistants/login-assistant.js @@ -20,7 +20,7 @@ var LoginAssistant = Class.create(BaseAssistant, { parameters: {}, onSuccess: function(response) { - if((this.credentials.service !== "ttrss" && this.credentials.email && this.credentials.password) || (this.credentials.service === "ttrss" && this.credentials.email && this.credentials.password && this.credentials.server) || this.credentials.service === "feedly") { + if((this.credentials.service !== "ttrss" && this.credentials.email && this.credentials.password) || (this.credentials.service === "ttrss" && this.credentials.email && this.credentials.password && this.credentials.server) || this.credentials.service === "feedly" || this.credentials.service === "aol" ) { if(this.triedLogin) { Log.debug("ALREADY TRIED LOGGING IN, WHAT MAKES YOU THINK ITS GOING TO WORK NOW") } diff --git a/app/assistants/oauth-assistant.js b/app/assistants/oauth-assistant.js index f10b1ed..806e946 100755 --- a/app/assistants/oauth-assistant.js +++ b/app/assistants/oauth-assistant.js @@ -16,6 +16,7 @@ function OauthAssistant(oauthConfig) { this.url = ''; this.requested_token = ''; this.exchangingToken = false; + this.service = oauthConfig.service; } OauthAssistant.prototype.setup = function() { @@ -67,7 +68,12 @@ OauthAssistant.prototype.setup = function() { } OauthAssistant.prototype.urlChanged = function(event) { - if (!Mojo.Environment.DeviceInfo.keyboardAvailable && (event.url.indexOf("accounts.google.com") !== -1 || event.url.indexOf("public-api.wordpress.com") !== -1 || event.url.indexOf("http://api.twitter.com") !== -1)) + if (!Mojo.Environment.DeviceInfo.keyboardAvailable && + (event.url.indexOf("accounts.google.com") !== -1 || + event.url.indexOf("public-api.wordpress.com") !== -1 || + event.url.indexOf("http://api.twitter.com") !== -1 || + event.url.indexOf("api.screenname.aol.com") !== -1 || + event.url.indexOf("facebook.com") !== -1)) { this.controller.window.PalmSystem.setManualKeyboardEnabled(true); this.controller.window.PalmSystem.allowResizeOnPositiveSpaceChange(true); @@ -93,6 +99,10 @@ OauthAssistant.prototype.mimeFailure = function(event) { Log.debug(this.TAG + ': code is ' + code); this.codeToken(code); } + else + { + this.controller.stageController.swapScene("credentials", new Credentials(), true); + } } } @@ -108,7 +118,8 @@ OauthAssistant.prototype.requestGrant = function() { var url = this.authorizeUrl + '?client_id=' + this.client_id + '&redirect_uri=' + this.redirect_uri + '&response_type=' + this.response_type; if(scope != '') url = url + '&scope=' + scope; - + if(this.service === 'aol') url = url + '&supportedIdType=facebook,google,twitter'; + this.controller.get('browser').mojo.clearCookies(); this.controller.get('browser').mojo.openURL(url); }; @@ -116,7 +127,7 @@ OauthAssistant.prototype.requestGrant = function() { OauthAssistant.prototype.codeToken = function(code) { this.exchangingToken = true; this.url = this.accessTokenUrl; - this.code = code.replace(/[^\da-zA-Z]/g, ''); + this.code = code; //code.replace(/[^\da-zA-Z]/g, ''); this.method = this.accessTokenMethod; var postParams = { client_id: this.client_id, @@ -152,7 +163,7 @@ OauthAssistant.prototype.codeToken = function(code) { this.credentials = new Credentials(); this.credentials.email = false; this.credentials.password = false; - this.credentials.service = "feedly"; + this.credentials.service = this.service; this.credentials.id = responseJSON.id; this.credentials.refreshToken = responseJSON.refresh_token; this.credentials.accessToken = responseJSON.access_token; diff --git a/app/assistants/preferences-assistant.js b/app/assistants/preferences-assistant.js index 6f20a18..14cce2a 100755 --- a/app/assistants/preferences-assistant.js +++ b/app/assistants/preferences-assistant.js @@ -56,6 +56,7 @@ var PreferencesAssistant = Class.create(BaseAssistant, { this.notificationInterval = {value: Preferences.notificationInterval()} this.notificationFeeds = {value: Preferences.anyOrSelectedFeedsForNotifications()} this.notificationFeedSelection = {buttonLabel: $L("Select Feeds")} + this.feedlySortEngagement = {value: Preferences.isFeedlySortEngagement()} this.originalAllowLandscape = Preferences.allowLandscape() this.originalSortOrder = Preferences.isOldestFirst() @@ -65,6 +66,7 @@ var PreferencesAssistant = Class.create(BaseAssistant, { this.originalFeedSortOrder = Preferences.isManualFeedSort() this.originalTheme = Preferences.getTheme() this.originalNotificationInterval = Preferences.notificationInterval() + this.originalFeedlySortEngagement = Preferences.isFeedlySortEngagement() }, setup: function($super) { @@ -91,6 +93,7 @@ var PreferencesAssistant = Class.create(BaseAssistant, { this.controller.setupWidget("notification-interval", this.intervalChoices, this.notificationInterval) this.controller.setupWidget("notification-feeds", this.notificationFeedsChoices, this.notificationFeeds) this.controller.setupWidget("notification-feed-selection", {}, this.notificationFeedSelection) + this.controller.setupWidget("feedly-sort-engagement", {}, this.feedlySortEngagement) }, addListeners: function() { @@ -110,6 +113,7 @@ var PreferencesAssistant = Class.create(BaseAssistant, { this.controller.listen("notification-feeds", Mojo.Event.propertyChange, this.setNotificationFeeds = this.setNotificationFeeds.bind(this)) this.controller.listen("notification-feed-selection", Mojo.Event.tap, this.selectFeeds = this.selectFeeds.bind(this)) // this.controller.listen("lefties", Mojo.Event.hold, this.weLoveLefties = this.weLoveLefties.bind(this)) + this.controller.listen("feedly-sort-engagement", Mojo.Event.propertyChange, this.setFeedlySortEngagement = this.setFeedlySortEngagement.bind(this)) }, updateLabels: function() { @@ -128,6 +132,8 @@ var PreferencesAssistant = Class.create(BaseAssistant, { // this.controller.get("debug-log-label").update($L("Debug Log")) this.controller.get("mark-read-scroll-label").update($L("Mark read as you scroll")) this.controller.get("notifications-label").update($L("Notifications")) + this.controller.get("feedly-label").update($L("Feedly Options")) + this.controller.get("feedly-sort-engagement-label").update($L("Show most engaging articles only")) }, cleanup: function($super) { @@ -147,6 +153,7 @@ var PreferencesAssistant = Class.create(BaseAssistant, { this.controller.stopListening("notification-feeds", Mojo.Event.propertyChange, this.setNotificationFeeds) this.controller.stopListening("notification-feed-selection", Mojo.Event.tap, this.selectFeeds) // this.controller.stopListening("lefties", Mojo.Event.hold, this.weLoveLefties) + this.controller.stopListening("feedly-sort-engagement", Mojo.Event.propertyChange, this.setFeedlySortEngagement) }, showAndHideStuff: function() { @@ -171,6 +178,10 @@ var PreferencesAssistant = Class.create(BaseAssistant, { } } }, + + setFeedlySortEngagement: function() { + Preferences.setFeedlySortEngagement(this.feedlySortEngagement.value) + }, setAllowLandscape: function() { Preferences.setAllowLandscape(this.allowLandscape.value) @@ -256,6 +267,7 @@ var PreferencesAssistant = Class.create(BaseAssistant, { if (this.originalFontSize != Preferences.fontSize()) changes.fontSizeChanged = true if (this.originalFeedSortOrder != Preferences.isManualFeedSort()) changes.feedSortOrderChanged = true if (this.originalTheme != Preferences.getTheme()) changes.themeChanged = true + if (this.originalFeedlySortEngagement != Preferences.isFeedlySortEngagement()) changes.sortOrderChanged = true this.controller.stageController.popScene(changes) }, diff --git a/app/models/all-sources.js b/app/models/all-sources.js index c204254..a46d111 100755 --- a/app/models/all-sources.js +++ b/app/models/all-sources.js @@ -7,6 +7,12 @@ var AllSources = Class.create({ this.all = new AllArticles(api) this.stickySources.items.push(this.all) } + + if (api.supportsFresh()) + { + this.fresh = new Fresh(api) + this.stickySources.items.push(this.fresh) + } if (api.supportsStarred()) { @@ -19,7 +25,13 @@ var AllSources = Class.create({ this.shared = new Shared(api) this.stickySources.items.push(this.shared) } - + + if (api.supportsArchived()) + { + this.archived = new Archived(api) + this.stickySources.items.push(this.archived) + } + this.subscriptions = new AllSubscriptions(api) this.subscriptionSources = {items: []} }, diff --git a/app/models/aol-api.js b/app/models/aol-api.js new file mode 100644 index 0000000..a6b457c --- /dev/null +++ b/app/models/aol-api.js @@ -0,0 +1,647 @@ +var AolApi = Class.create({ + //UPDATED 1.2.0 + login: function(credentials, success, failure, controller) { + if (credentials.accessToken != null && credentials.refreshToken != null && credentials.tokenExpiry != null) + { + this.credentials = credentials + if (this._checkTokenExpiry()) + { + success(this.credentials.accessToken) + } + else + { + this.credentials.password = null + this.credentials.server = null + this.credentials.id = null + this.credentials.refreshToken = null + this.credentials.accessToken = null + this.credentials.tokenType = null + this.credentials.plan = null + this.credentials.clear() + failure() + return + } + } + else + { + var oauthConfig={ + callbackScene:'login', //Name of the assistant to be called on the OAuth Success + authorizeUrl: AolApi.AUTH_URL + 'authorize', + accessTokenUrl: AolApi.AUTH_URL + 'access_token', + accessTokenMethod:'POST', // Optional - 'GET' by default if not specified + client_id: AolApi.CLIENT_ID, + client_secret: AolApi.CLIENT_SECRET, + redirect_uri:'urn:ietf:wg:oauth:2.0:oob', // Optional - 'oob' by default if not specified + response_type:'code', // now only support code + scope: ['reader'], //for example, this is instagram scope + service: credentials.service + }; + controller.stageController.pushScene('oauth',oauthConfig); + } + }, + + //UPDATED 1.2.0 + getTags: function(success, failure) { + new Ajax.Request(AolApi.BASE_URL + "tag/list", { + method: "get", + parameters: this._requestHeaders(), + onFailure: failure, + onSuccess: function(response) {success(response.responseText.evalJSON().tags)} + }) + }, + + //NOT SUPPORTED BY API + getSortOrder: function(success, failure) { + failure() + /* new Ajax.Request(AolApi.BASE_URL + "preference/stream/list", { + method: "get", + parameters: {output: "json"}, + requestHeaders: this._requestHeaders(), + onFailure: failure, + onSuccess: function(response) { + var prefs = response.responseText.evalJSON() + var sortOrder = {} + + if(prefs && prefs.streamprefs) { + $H(prefs.streamprefs).each(function(pair) { + pair.key = pair.key.gsub(/user\/\d+\//, "user/-/") + + $A(pair.value).each(function(pref) { + if("subscription-ordering" == pref.id) { + sortOrder[pair.key] = new SortOrder(pref.value) + } + }) + }) + } + + success(sortOrder) + } + })*/ + }, + + //NOT SUPPORTED BY API + setSortOrder: function(sortOrder, stream) { + failure() + /*this._getEditToken(function(token) { + var parameters = { + T: token, + s: stream || "user/-/state/com.google/root", + k: "subscription-ordering", + v: sortOrder + } + + new Ajax.Request(AolApi.BASE_URL + "preference/stream/set", { + method: "post", + parameters: parameters, + requestHeaders: this._requestHeaders() + }) + }.bind(this))*/ + }, + + //UPDATED 1.2.0 + unsubscribe: function(feed) { + if(feed.constructor == Folder) { + this.removeLabel(feed) + } + else { + var parameters = { + access_token: this._getAuthToken(), + s: feed.id, + ac: "unsubscribe" + } + + new Ajax.Request(AolApi.BASE_URL + "subscription/edit", { + method: "post", + parameters: parameters, + onSuccess: function() {Mojo.Event.send(document, "SubscriptionDeleted", {id: feed.id, count: feed.unreadCount})} + }) + } + }, + + //UPDATED 1.2.0 + removeLabel: function(folder) { + var parameters = { + access_token: this._getAuthToken(), + s: folder.id + } + + new Ajax.Request(AolApi.BASE_URL + "disable-tag", { + method: "post", + parameters: parameters, + onSuccess: function() {Mojo.Event.send(document, "FolderDeleted", {id: folder.id})} + }) + }, + + //NOT SUPPORTED BY API + searchSubscriptions: function(query, success, failure) { + failure() + /*var self = this + + new Ajax.Request(AolApi.BASE_URL + "feed-finder", { + method: "get", + parameters: {q: query, output: "json"}, + requestHeaders: this._requestHeaders(), + onFailure: failure, + onSuccess: function(response) { + var subscriptions = response.responseText.evalJSON().items + success(subscriptions) + } + })*/ + }, + + //UPDATED 1.2.0 + addSubscription: function(url, success, failure) { + var parameters = { + access_token: this._getAuthToken(), + quickadd: url + } + + new Ajax.Request(AolApi.BASE_URL + "subscription/quickadd", { + method: "post", + parameters: parameters, + onFailure: failure, + onSuccess: function(response) { + var json = response.responseText.evalJSON() + + if(json.streamId) { + success() + } + else { + failure() + } + } + }) + }, + + //UPDATED 1.2.0 + getAllSubscriptions: function(success, failure) { + var self = this + + new Ajax.Request(AolApi.BASE_URL + "subscription/list", { + method: "get", + parameters: this._requestHeaders(), + onFailure: failure, + onSuccess: function(response) { + var subscriptions = response.responseText.evalJSON().subscriptions + self.cacheTitles(subscriptions) + success(subscriptions) + } + }) + }, + + //UPDATED 1.2.0 + cacheTitles: function(subscriptions) { + var self = this + self.titles = {} + + subscriptions.each(function(subscription) { + self.titles[subscription.id] = subscription.title + }) + }, + + //UPDATED 1.2.0 + titleFor: function(id) { + return this.titles[id] + }, + + //UPDATED 1.2.0 + getUnreadCounts: function(success, failure) { + new Ajax.Request(AolApi.BASE_URL + "unread-count", { + method: "get", + parameters: this._requestHeaders(), + onFailure: failure, + onSuccess: function(response) { + var json = response.responseText.evalJSON() + + if(json.denied) { + failure() + } + else { + success(json.unreadcounts) + } + } + }) + }, + + //UPDATED 1.2.0 + getAllArticles: function(continuation, success, failure) { + this._getArticles( + "user/-/state/reading-list", + Preferences.hideReadArticles() ? "user/-/state/read" : null, + continuation, + success, + failure + ) + }, + + //UPDATED 1.2.0 + getAllStarred: function(continuation, success, failure) { + this._getArticles( + "user/-/state/starred", + null, + continuation, + success, + failure + ) + }, + + //NOT SUPPORTED BY API + getAllShared: function(continuation, success, failure) { + failure() + /*this._getArticles( + "user/-/state/com.google/broadcast", + null, + continuation, + success, + failure + )*/ + }, + + //UPDATED 1.2.0 + getAllFresh: function(continuation, success, failure) { + /*this._getArticles( + -3, + "all_articles", + continuation, + success, + failure + )*/ + }, + + //UPDATED 1.2.0 + getAllArchived: function(continuation, success, failure) { + /*this._getArticles( + -0, + "all_articles", + continuation, + success, + failure + )*/ + }, + + //UPDATED 1.2.0 + getAllArticlesFor: function(id, continuation, success, failure) { + this._getArticles( + id, + Preferences.hideReadArticles() ? "user/-/state/read" : null, + continuation, + success, + failure + ) + }, + + //UPDATED 1.2.0 + _getArticles: function(id, exclude, continuation, success, failure) { + var parameters = {output: "json", n: 40, access_token: this._getAuthToken()} + + if(id != "user/-/state/starred" && + Preferences.isOldestFirst()) { + parameters.r = "o" + } + + if(continuation) { + parameters.c = continuation + } + + if(exclude) { + parameters.xt = exclude + } + + new Ajax.Request(AolApi.BASE_URL + "stream/contents/" + escape(id), { + method: "get", + parameters: parameters, + requestHeaders: this._requestHeaders(), + onFailure: failure, + onSuccess: function(response) { + var articles = JSON2.parse(response.responseText) + + //Post Processing + articles.items.each(function(item){ + item.crawlTimeMsec = item.crawlTimeMsec.substring(0, item.crawlTimeMsec.length - 3) + item.alternate.each(function(alternate){ + alternate.type = "html" + }) + item.categories.each(function(category) { + if(category.endsWith("/state/read")) { + item.categories.push("/state/com.google/read") + } + + if(category.endsWith("/state/kept-unread")) { + item.categories.push("/state/com.google/kept-unread") + } + + if(category.endsWith("/state/starred")) { + item.categories.push("/state/com.google/starred") + } + }) + }) + + //Load more articles (if there are more to load) + if(articles.items.length == parameters.n) + { + success(articles.items, articles.id, articles.continuation) + } + else + { + success(articles.items, articles.id, false) + } + } + }) + }, + + //UPDATED 1.2.0 + markAllRead: function(id, success, failure) { + if (id === "user/-/state/com.google/reading-list") { + id = "user/-/state/reading-list" + } + + var parameters = { + access_token: this._getAuthToken(), + s: id + } + + new Ajax.Request(AolApi.BASE_URL + "mark-all-as-read", { + method: "post", + parameters: parameters, + onSuccess: success, + onFailure: failure + }) + }, + + //NOT SUPPORTED BY API + search: function(query, id, success, failure) { + failure() + /*var parameters = { + q: query, + num: 50, + output: "json" + } + + if(id) { + parameters.s = id + } + + new Ajax.Request(AolApi.BASE_URL + "search/items/ids", { + method: "get", + parameters: parameters, + requestHeaders: this._requestHeaders(), + onSuccess: this.searchItemsFound.bind(this, success, failure), + onFailure: failure + })*/ + }, + + //NOT SUPPORTED BY API + searchItemsFound: function(success, failure, response) { + failure() + /*var self = this + var ids = response.responseText.evalJSON().results + + if(ids.length) { + self._getEditToken( + function(token) { + var parameters = { + T: token, + i: ids.map(function(n) {return n.id}) + } + + new Ajax.Request(AolApi.BASE_URL + "stream/items/contents", { + method: "post", + parameters: parameters, + requestHeaders: self._requestHeaders(), + onFailure: failure, + onSuccess: function(response) { + var articles = response.responseText.evalJSON() + success(articles.items, articles.id, articles.continuation) + } + }) + } + ) + } + else { + success([], "", false) + }*/ + }, + + //UPDATED 1.2.0 + mapSearchResults: function(response) { + console.log(response.responseText) + }, + + //UPDATED 1.2.0 + setArticleRead: function(articleId, subscriptionId, success, failure) { + this._editTag( + articleId, + subscriptionId, + "user/-/state/read", + null, + success, + failure + ) + }, + + //UPDATED 1.2.0 + setArticleNotRead: function(articleId, subscriptionId, success, failure, sticky) { + this._editTag( + articleId, + subscriptionId, + null, + "user/-/state/read", + success, + failure + ) + }, + + //NOT SUPPORTED BY API + setArticleShared: function(articleId, subscriptionId, success, failure) { + failure() + /*this._editTag( + articleId, + subscriptionId, + "user/-/state/com.google/broadcast", + null, + success, + failure + )*/ + }, + + //NOT SUPPORTED BY API + setArticleNotShared: function(articleId, subscriptionId, success, failure) { + failure() + /*this._editTag( + articleId, + subscriptionId, + null, + "user/-/state/com.google/broadcast", + success, + failure + )*/ + }, + + //UPDATED 1.2.0 + setArticleStarred: function(articleId, subscriptionId, success, failure) { + this._editTag( + articleId, + subscriptionId, + "user/-/state/starred", + null, + success, + failure + ) + }, + + //UPDATED 1.2.0 + setArticleNotStarred: function(articleId, subscriptionId, success, failure) { + this._editTag( + articleId, + subscriptionId, + null, + "user/-/state/starred", + success, + failure + ) + }, + + //UPDATED 1.2.0 + _editTag: function(articleId, subscriptionId, addTag, removeTag, success, failure) { + Log.debug("editing tag for article id = " + articleId + " and subscription id = " + subscriptionId) + var parameters = { + access_token: this._getAuthToken(), + ac: "edit", + i: articleId + } + + if(addTag) parameters.a = addTag + if(removeTag) parameters.r = removeTag + + new Ajax.Request(AolApi.BASE_URL + "edit-tag", { + method: "post", + parameters: parameters, + onSuccess: success, + onFailure: failure + }) + }, + + //UPDATED 1.2.0 + _requestHeaders: function() { + return {access_token: this._getAuthToken()} + }, + + //UPDATED 1.2.0 + _getAuthToken: function() { + if (this._checkTokenExpiry()) + { + return this.credentials.accessToken + } + else + { + return "OAuth Error" + } + }, + + //UPDATED 1.2.0 + _serialize: function(obj) { + var str = [] + for(var p in obj) + { + if (obj.hasOwnProperty(p)) { + str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])) + } + } + return str.join("&") + }, + + //UPDATED 1.2.0 + _checkTokenExpiry: function() { + if (new Date(this.credentials.tokenExpiry).getTime() > new Date().getTime()) + { + return true + } + else + { + var parameters = { + grant_type: "refresh_token", + refresh_token: this.credentials.refreshToken, + client_id: AolApi.CLIENT_ID, + client_secret: AolApi.CLIENT_SECRET + } + + try { + var req = new XMLHttpRequest() + req.open("POST", AolApi.AUTH_URL + "access_token", false) + req.setRequestHeader("Content-type", "application/x-www-form-urlencoded") + req.send(this._serialize(parameters)) + if(req.readyState === 4 && req.status === 200){ + response = req.responseText.evalJSON() + if (response) { + if (response.access_token) { + this.credentials.accessToken = response.access_token + } + if (response.expires_in) { + var expiryDate = new Date(); + expiryDate.setSeconds(expiryDate.getSeconds() + response.expires_in); + this.credentials.tokenExpiry = expiryDate + } + if (response.token_type) { + this.credentials.tokenType = response.token_type + } + this.credentials.save() + return true + } + else + { + return false + } + } + else + { + throw req.responseText + } + } + catch (e) { + Log.debug('_checkTokenExpiry failed! Error:' + e) + return false + } + } + }, + + //UPDATED 1.2.0 + supportsAllArticles: function() { + return true + }, + + //UPDATED 1.2.0 + supportsArchived: function() { + return false + }, + + //UPDATED 1.2.0 + supportsFresh: function() { + return false + }, + + //UPDATED 1.2.0 + supportsStarred: function() { + return true + }, + + //UPDATED 1.2.0 + supportsShared: function() { + return false + }, + + //UPDATED 1.2.0 + supportsSearch: function() { + return false + }, + + //UPDATED 1.2.0 + supportsManualSort: function() { + return false + } + +}) + +AolApi.BASE_URL = "https://reader.aol.com/reader/api/0/" +AolApi.AUTH_URL = "https://api.screenname.aol.com/auth/" +AolApi.CLIENT_ID = "fe1CgOIzMHHjg_5E"; +AolApi.CLIENT_SECRET = "2fHTh5uZTiUOsy-gs_l3"; \ No newline at end of file diff --git a/app/models/api.js b/app/models/api.js index 25a4191..7ed5ecf 100755 --- a/app/models/api.js +++ b/app/models/api.js @@ -29,6 +29,11 @@ var Api = Class.create({ this.appApi = new FeedlyApi() this.appApi.login(credentials, success, failure, controller) } + else if (credentials.service == "aol") + { + this.appApi = new AolApi() + this.appApi.login(credentials, success, failure, controller) + } else { // No supported service to log into @@ -91,6 +96,14 @@ var Api = Class.create({ getAllShared: function(continuation, success, failure) { this.appApi.getAllShared(continuation, success, failure) }, + + getAllFresh: function(continuation, success, failure) { + this.appApi.getAllFresh(continuation, success, failure) + }, + + getAllArchived: function(continuation, success, failure) { + this.appApi.getAllArchived(continuation, success, failure) + }, getAllArticlesFor: function(id, continuation, success, failure) { this.appApi.getAllArticlesFor(id, continuation, success, failure) @@ -140,6 +153,14 @@ var Api = Class.create({ return this.appApi.supportsAllArticles() }, + supportsFresh: function() { + return this.appApi.supportsFresh() + }, + + supportsArchived: function() { + return this.appApi.supportsArchived() + }, + supportsStarred: function() { return this.appApi.supportsStarred() }, diff --git a/app/models/archived.js b/app/models/archived.js new file mode 100755 index 0000000..63d0f70 --- /dev/null +++ b/app/models/archived.js @@ -0,0 +1,23 @@ +var Archived = Class.create(ArticleContainer, { + initialize: function($super, api) { + $super(api) + this.id = "user/-/state/com.google/archived" + this.title = $L("Archived") + this.icon = "archived" + this.sticky = true + this.divideBy = "Home" + this.hideDivider = "hide-divider" + this.showOrigin = true + this.canMarkAllRead = false + }, + + makeApiCall: function(continuation, success, failure) { + this.api.getAllArchived(continuation, success, failure) + }, + + articleRead: function(subscriptionId) { + }, + + articleNotRead: function(subscriptionId) { + } +}) diff --git a/app/models/bq-api.js b/app/models/bq-api.js index 47f0e32..4692091 100644 --- a/app/models/bq-api.js +++ b/app/models/bq-api.js @@ -243,6 +243,30 @@ var BQApi = Class.create({ ) }, + //UPDATED 1.2.0 + getAllFresh: function(continuation, success, failure) { + failure() + /*this._getArticles( + -3, + "all_articles", + continuation, + success, + failure + )*/ + }, + + //UPDATED 1.2.0 + getAllArchived: function(continuation, success, failure) { + failure() + /*this._getArticles( + -0, + "all_articles", + continuation, + success, + failure + )*/ + }, + //UPDATED 1.0.1 getAllArticlesFor: function(id, continuation, success, failure) { this._getArticles( @@ -494,6 +518,16 @@ var BQApi = Class.create({ return true }, + //UPDATED 1.2.0 + supportsArchived: function() { + return false + }, + + //UPDATED 1.2.0 + supportsFresh: function() { + return false + }, + //UPDATED 1.0.1 supportsStarred: function() { return true diff --git a/app/models/feedly-api.js b/app/models/feedly-api.js index 26932f6..e49123a 100644 --- a/app/models/feedly-api.js +++ b/app/models/feedly-api.js @@ -1,5 +1,5 @@ var FeedlyApi = Class.create({ - //UPDATED 0.9.5 + //UPDATED 1.2.0 login: function(credentials, success, failure, controller) { if (credentials.id != null && credentials.accessToken != null && credentials.refreshToken != null && credentials.tokenExpiry != null) { @@ -33,7 +33,8 @@ var FeedlyApi = Class.create({ client_secret: FeedlyApi.CLIENT_SECRET, redirect_uri:'urn:ietf:wg:oauth:2.0:oob', // Optional - 'oob' by default if not specified response_type:'code', // now only support code - scope: ['https://cloud.feedly.com/subscriptions'] + scope: ['https://cloud.feedly.com/subscriptions'], + service: credentials.service }; controller.stageController.pushScene('oauth',oauthConfig); } @@ -257,6 +258,30 @@ var FeedlyApi = Class.create({ ) }, + //UPDATED 1.2.0 + getAllFresh: function(continuation, success, failure) { + failure() + /*this._getArticles( + -3, + "all_articles", + continuation, + success, + failure + )*/ + }, + + //UPDATED 1.2.0 + getAllArchived: function(continuation, success, failure) { + failure() + /*this._getArticles( + -0, + "all_articles", + continuation, + success, + failure + )*/ + }, + //UPDATED 1.1.2 getAllArticlesFor: function(id, continuation, success, failure) { this._getArticles( @@ -268,7 +293,7 @@ var FeedlyApi = Class.create({ ) }, - //UPDATED 1.1.3 + //UPDATED 1.2.0 _getArticles: function(id, exclude, continuation, success, failure) { var parameters = {output: "json", count: 40} @@ -283,48 +308,69 @@ var FeedlyApi = Class.create({ if(exclude) { parameters.unreadOnly = exclude } - - new Ajax.Request(FeedlyApi.BASE_URL + "streams/" + encodeURIComponent(id) + "/contents", { - method: "get", - parameters: parameters, - requestHeaders: this._requestHeaders(), - onFailure: failure, - onSuccess: function(response) { - var articles = JSON2.parse(response.responseText) - - //Do post-processing to conform articles to FeedSpider spec - articles.items.each(function(article) { - //Set article categories - article.categories = [] - if(article.tags){ - article.tags.each(function(tag) { - if (tag.id !== undefined) - { - if(tag.id.endsWith("/tag/global.read")) { - article.categories.push("/state/com.google/read") - } - - if(tag.id.endsWith("/tag/global.saved")) { - article.categories.push("/state/com.google/starred") - } - } - }) - } - if (article.unread !== undefined) - { - if(article.unread == false) + + if (Preferences.isFeedlySortEngagement() && !id.endsWith("/tag/global.saved")){ + parameters.count = 20 + + new Ajax.Request(FeedlyApi.BASE_URL + "mixes/" + encodeURIComponent(id) + "/contents", { + method: "get", + parameters: parameters, + requestHeaders: this._requestHeaders(), + onFailure: failure, + onSuccess: function(response) { + var articles = JSON2.parse(response.responseText) + this._processArticles(articles, success) + }.bind(this) + }) + } + else + { + new Ajax.Request(FeedlyApi.BASE_URL + "streams/" + encodeURIComponent(id) + "/contents", { + method: "get", + parameters: parameters, + requestHeaders: this._requestHeaders(), + onFailure: failure, + onSuccess: function(response) { + var articles = JSON2.parse(response.responseText) + this._processArticles(articles, success) + }.bind(this) + }) + } + }, + + //UPDATED 1.2.0 + _processArticles: function(articles, success) { + //Do post-processing to conform articles to FeedSpider spec + articles.items.each(function(article) { + //Set article categories + article.categories = [] + if(article.tags){ + article.tags.each(function(tag) { + if (tag.id !== undefined) { - article.categories.push("/state/com.google/read") + if(tag.id.endsWith("/tag/global.read")) { + article.categories.push("/state/com.google/read") + } + + if(tag.id.endsWith("/tag/global.saved")) { + article.categories.push("/state/com.google/starred") + } } + }) + } + if (article.unread !== undefined) + { + if(article.unread == false) + { + article.categories.push("/state/com.google/read") } + } - //Set article timestamp - article.crawlTimeMsec = article.crawled - }) - - success(articles.items, articles.id, articles.continuation) - } - }) + //Set article timestamp + article.crawlTimeMsec = article.crawled + }) + + success(articles.items, articles.id, articles.continuation) }, //UPDATED 0.9.5 @@ -640,6 +686,16 @@ var FeedlyApi = Class.create({ return true }, + //UPDATED 1.2.0 + supportsArchived: function() { + return false + }, + + //UPDATED 1.2.0 + supportsFresh: function() { + return false + }, + //UPDATED 0.9.5 supportsStarred: function() { return true diff --git a/app/models/fresh.js b/app/models/fresh.js new file mode 100755 index 0000000..8674e52 --- /dev/null +++ b/app/models/fresh.js @@ -0,0 +1,23 @@ +var Fresh = Class.create(ArticleContainer, { + initialize: function($super, api) { + $super(api) + this.id = "user/-/state/com.google/fresh" + this.title = $L("Fresh") + this.icon = "fresh" + this.sticky = true + this.divideBy = "Home" + this.hideDivider = "hide-divider" + this.showOrigin = true + this.canMarkAllRead = false + }, + + makeApiCall: function(continuation, success, failure) { + this.api.getAllFresh(continuation, success, failure) + }, + + articleRead: function(subscriptionId) { + }, + + articleNotRead: function(subscriptionId) { + } +}) diff --git a/app/models/ino-api.js b/app/models/ino-api.js index 51d9c79..46e9a0e 100644 --- a/app/models/ino-api.js +++ b/app/models/ino-api.js @@ -227,6 +227,30 @@ var InoApi = Class.create({ ) }, + //UPDATED 1.2.0 + getAllFresh: function(continuation, success, failure) { + failure() + /*this._getArticles( + -3, + "all_articles", + continuation, + success, + failure + )*/ + }, + + //UPDATED 1.2.0 + getAllArchived: function(continuation, success, failure) { + failure() + /*this._getArticles( + -0, + "all_articles", + continuation, + success, + failure + )*/ + }, + getAllArticlesFor: function(id, continuation, success, failure) { this._getArticles( id, @@ -463,6 +487,16 @@ var InoApi = Class.create({ return true }, + //UPDATED 1.2.0 + supportsArchived: function() { + return false + }, + + //UPDATED 1.2.0 + supportsFresh: function() { + return false + }, + supportsStarred: function() { return true }, diff --git a/app/models/preferences.js b/app/models/preferences.js index 6f54ab9..905c3f2 100755 --- a/app/models/preferences.js +++ b/app/models/preferences.js @@ -18,6 +18,15 @@ Preferences = { INSTAPAPER_USERNAME: "q-instapaper-username", INSTAPAPER_PASSWORD: "r-instapaper-password", LEFTY_FRIENDLY: "s-lefty-friendly", + FEEDLY_SORT_ENGAGEMENT: "feedly-sort-engagement", + + isFeedlySortEngagement: function() { + return this.getCookie(this.FEEDLY_SORT_ENGAGEMENT, false) + }, + + setFeedlySortEngagement: function(isFeedlySortEngagement) { + this.setCookie(this.FEEDLY_SORT_ENGAGEMENT, isFeedlySortEngagement) + }, isLeftyFriendly: function() { return this.getCookie(this.LEFTY_FRIENDLY, false) diff --git a/app/models/tor-api.js b/app/models/tor-api.js index 44d5c1b..73e79ac 100755 --- a/app/models/tor-api.js +++ b/app/models/tor-api.js @@ -229,6 +229,30 @@ var TorApi = Class.create({ ) }, + //UPDATED 1.2.0 + getAllFresh: function(continuation, success, failure) { + failure() + /*this._getArticles( + -3, + "all_articles", + continuation, + success, + failure + )*/ + }, + + //UPDATED 1.2.0 + getAllArchived: function(continuation, success, failure) { + failure() + /*this._getArticles( + -0, + "all_articles", + continuation, + success, + failure + )*/ + }, + getAllArticlesFor: function(id, continuation, success, failure) { this._getArticles( id, @@ -482,6 +506,16 @@ var TorApi = Class.create({ return true }, + //UPDATED 1.2.0 + supportsArchived: function() { + return false + }, + + //UPDATED 1.2.0 + supportsFresh: function() { + return false + }, + supportsStarred: function() { return true }, diff --git a/app/models/ttrss-api.js b/app/models/ttrss-api.js index d31fe16..298c5f2 100755 --- a/app/models/ttrss-api.js +++ b/app/models/ttrss-api.js @@ -346,6 +346,28 @@ var TTRSSApi = Class.create({ ) }, + //UPDATED 1.2.0 + getAllFresh: function(continuation, success, failure) { + this._getArticles( + -3, + "all_articles", + continuation, + success, + failure + ) + }, + + //UPDATED 1.2.0 + getAllArchived: function(continuation, success, failure) { + this._getArticles( + -0, + "all_articles", + continuation, + success, + failure + ) + }, + //UPDATED 1.1.0 getAllArticlesFor: function(id, continuation, success, failure) { this._getArticles( @@ -368,7 +390,9 @@ var TTRSSApi = Class.create({ } if(id != -4 && + id != -3 && id != -2 && + id != 0 && Preferences.isOldestFirst()) { parameters.order_by = "date_reverse" } else { @@ -647,6 +671,16 @@ var TTRSSApi = Class.create({ return true }, + //UPDATED 1.2.0 + supportsArchived: function() { + return true + }, + + //UPDATED 1.2.0 + supportsFresh: function() { + return true + }, + //UPDATED 1.1.0 supportsStarred: function() { return true diff --git a/app/views/help/help-scene.html b/app/views/help/help-scene.html index 68fee21..fa8d75c 100755 --- a/app/views/help/help-scene.html +++ b/app/views/help/help-scene.html @@ -15,6 +15,7 @@
    FeedSpider is a fork of Semicolon Apps' excellent Google Reader app Feeder, adapted to work with Google Reader compatible applications.

    It currently supports:
    +
    +
    1.2.0
    +
      +
    • added support for AOL Reader
    • +
    • added support for the "Fresh" and "Archived" feeds in Tiny Tiny RSS
    • +
    • added support for mixes in Feedly
    • +
    +
    +
    1.1.3
      diff --git a/app/views/preferences/preferences-scene.html b/app/views/preferences/preferences-scene.html index d43a247..72f0b10 100755 --- a/app/views/preferences/preferences-scene.html +++ b/app/views/preferences/preferences-scene.html @@ -143,6 +143,22 @@
+
+
Feedly Options
+
+
+
+ + + + + +
+
+
+
+
+