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 index fe9d576..79a6bdb --- 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")) 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 index b87c821..00e16ee --- a/app/assistants/article-assistant.js +++ b/app/assistants/article-assistant.js @@ -64,6 +64,7 @@ var ArticleAssistant = Class.create(BaseAssistant, { setFontSize: function() { var summary = this.controller.get("summary") + summary.removeClassName("tiny") summary.removeClassName("small") summary.removeClassName("medium") summary.removeClassName("large") diff --git a/app/assistants/articles-assistant.js b/app/assistants/articles-assistant.js old mode 100644 new mode 100755 index f84f9ba..ade5359 --- a/app/assistants/articles-assistant.js +++ b/app/assistants/articles-assistant.js @@ -1,6 +1,7 @@ var ArticlesAssistant = Class.create(BaseAssistant, { - initialize: function($super, subscription) { + initialize: function($super, api, subscription) { $super() + this.api = api this.subscription = subscription this.subscription.reset() }, @@ -315,7 +316,14 @@ var ArticlesAssistant = Class.create(BaseAssistant, { }, doSearch: function(query) { - this.controller.stageController.pushScene("articles", 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 old mode 100644 new mode 100755 index 3744dd4..0250bcd --- a/app/assistants/base-assistant.js +++ b/app/assistants/base-assistant.js @@ -269,12 +269,18 @@ var BaseAssistant = Class.create({ }, listenForSearch: function() { - $(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() { - $(this.controller.document).stopObserving("keypress", this.startSearch) + if(this.api.supportsSearch()) + { + $(this.controller.document).stopObserving("keypress", this.startSearch) + } }, scrollToTop: function() { @@ -317,8 +323,11 @@ var BaseAssistant = Class.create({ items.push({label: $L("Hide read feeds"), command: "hide-read-feeds"}); } - items.push({label: $L("Search"), command: "search"}); - + if(this.api.supportsSearch()) + { + items.push({label: $L("Search"), command: "search"}); + } + this.controller.popupSubmenu({ placeNear: this.controller.get("header-text"), items: items, @@ -351,8 +360,12 @@ var BaseAssistant = Class.create({ } items.push({label: $L("Refresh"), command: "refresh"}); - items.push({label: $L("Search"), command: "search"}); + if(this.api.supportsSearch()) + { + items.push({label: $L("Search"), command: "search"}); + } + this.controller.popupSubmenu({ placeNear: this.controller.get("header-text"), items: items, 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 index f1a0502..81cb71b --- a/app/assistants/credentials-assistant.js +++ b/app/assistants/credentials-assistant.js @@ -6,6 +6,16 @@ var CredentialsAssistant = Class.create(BaseAssistant, { this.showMessage = showMessage this.button = {buttonLabel: $L("Login")} 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"}, + {label: $L("OwnCloud News"), value: "oc"}, + {label: $L("The Old Reader"), value: "tor"}, + {label: $L("Tiny Tiny RSS"), value: "ttrss"} + ] }, setup: function($super) { @@ -13,15 +23,20 @@ var CredentialsAssistant = Class.create(BaseAssistant, { this.setupWidgets() this.setupListeners() - this.controller.update("email-label", $L("Email")) + 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"]() + var initializeFields = {value: this.credentials.service} + this.setService(initializeFields) }, cleanup: function($super) { @@ -30,8 +45,10 @@ 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("server", {modelProperty: "server", changeOnKeyPress: true, textCase: Mojo.Widget.steModeLowerCase}, this.credentials) this.controller.setupWidget("login", {type: Mojo.Widget.activityButton}, this.button) }, @@ -39,22 +56,53 @@ var CredentialsAssistant = Class.create(BaseAssistant, { this.propertyChanged = this.propertyChanged.bind(this) this.login = this.login.bind(this) - this.controller.listen("password", Mojo.Event.propertyChange, this.propertyChanged) + 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("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() + } } }, login: function() { this.controller.stageController.swapScene("login", this.credentials) - } + }, + + setService: function(propertyChangeEvent) { + if (propertyChangeEvent.value == "feedly" || propertyChangeEvent.value == "aol") + { + this.controller.get("email-group")["hide"]() + this.controller.get("password-group")["hide"]() + this.controller.get("server-group")["hide"]() + } + else if (propertyChangeEvent.value == "ttrss" || propertyChangeEvent.value == "oc") + { + 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/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 index 9bd7754..789354d --- a/app/assistants/folder-assistant.js +++ b/app/assistants/folder-assistant.js @@ -1,6 +1,7 @@ var FolderAssistant = Class.create(BaseAssistant, { - initialize: function($super, folder) { + initialize: function($super, api, folder) { $super() + this.api = api this.folder = folder this.subscriptions = {items: []} }, @@ -74,7 +75,7 @@ var FolderAssistant = Class.create(BaseAssistant, { }, folderTapped: function(event) { - this.controller.stageController.pushScene("articles", event.item) + this.controller.stageController.pushScene("articles", this.api, event.item) }, folderRendered: function(listWidget, itemModel, itemNode) { @@ -108,7 +109,14 @@ var FolderAssistant = Class.create(BaseAssistant, { }, doSearch: function(query) { - this.controller.stageController.pushScene("articles", 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/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 index e0d6483..17beb1d --- a/app/assistants/home-assistant.js +++ b/app/assistants/home-assistant.js @@ -10,7 +10,6 @@ var HomeAssistant = Class.create(BaseAssistant, { setup: function($super) { $super() - Feeder.Metrix.checkBulletinBoard(this.controller, 20); this.setupLists() this.setupListeners() this.setupSearch() @@ -122,8 +121,14 @@ var HomeAssistant = Class.create(BaseAssistant, { if("logout" == command) { var creds = new Credentials() - creds.password = false - creds.save() + creds.password = null + creds.server = 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) { @@ -165,10 +170,10 @@ var HomeAssistant = Class.create(BaseAssistant, { sourceTapped: function(event) { if(event.item.isFolder && !Preferences.combineFolders()) { - this.controller.stageController.pushScene("folder", event.item) + this.controller.stageController.pushScene("folder", this.api, event.item) } else { - this.controller.stageController.pushScene("articles", event.item) + this.controller.stageController.pushScene("articles", this.api, event.item) } }, @@ -248,6 +253,13 @@ var HomeAssistant = Class.create(BaseAssistant, { }, doSearch: function(query) { - this.controller.stageController.pushScene("articles", 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/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 index e560649..7af07e8 --- a/app/assistants/login-assistant.js +++ b/app/assistants/login-assistant.js @@ -5,14 +5,11 @@ var LoginAssistant = Class.create(BaseAssistant, { this.credentials = credentials || new Credentials() this.api = new Api() this.triedLogin = false - this.hideLogout = true + this.hideLogout = true }, setup: function($super) { $super() - - Log.debug("sending metrix data") - Feeder.Metrix.postDeviceData() }, activate: function($super, changes) { @@ -23,7 +20,7 @@ var LoginAssistant = Class.create(BaseAssistant, { parameters: {}, onSuccess: function(response) { - if(this.credentials.email && this.credentials.password) { + if(((this.credentials.service !== "ttrss" || this.credentials.service !== "oc") && this.credentials.email && this.credentials.password) || ((this.credentials.service === "ttrss" || this.credentials.service === "oc") && 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") } @@ -31,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/notification-feeds-assistant.js b/app/assistants/notification-feeds-assistant.js old mode 100644 new mode 100755 diff --git a/app/assistants/oauth-assistant.js b/app/assistants/oauth-assistant.js new file mode 100755 index 0000000..b07247c --- /dev/null +++ b/app/assistants/oauth-assistant.js @@ -0,0 +1,316 @@ +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; + this.service = oauthConfig.service; +} + +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("twitter.com") !== -1 || + event.url.indexOf("api.screenname.aol.com") !== -1 || + event.url.indexOf("facebook.com") !== -1 || + event.url.indexOf("live.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); + } + else + { + this.controller.stageController.swapScene("credentials", new Credentials(), true); + } + } +} + +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; + if(this.service === 'aol') url = url + '&supportedIdType=facebook,google,twitter'; + + 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; //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 = this.service; + 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.getTime(); + + 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) { + Log.debug(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/assistants/preferences-assistant.js b/app/assistants/preferences-assistant.js old mode 100644 new mode 100755 index 95b3d7d..7b25c69 --- a/app/assistants/preferences-assistant.js +++ b/app/assistants/preferences-assistant.js @@ -9,6 +9,7 @@ var PreferencesAssistant = Class.create(BaseAssistant, { ]} this.fontSizeChoices = {choices: [ + {label: $L("Tiny Font"), value: "tiny"}, {label: $L("Small Font"), value: "small"}, {label: $L("Medium Font"), value: "medium"}, {label: $L("Large Font"), value: "large"} @@ -21,7 +22,8 @@ var PreferencesAssistant = Class.create(BaseAssistant, { this.themeChoices = {choices: [ {label: $L("Grey Theme"), value: "grey"}, - {label: $L("Light Theme"), value: "light"} + {label: $L("Light Theme"), value: "light"}, + {label: $L("Dark Theme"), value: "dark"} ]} this.intervalChoices = {choices: [ @@ -55,6 +57,8 @@ 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.shortenURLs = {value: Preferences.isShortenURLs()} this.originalAllowLandscape = Preferences.allowLandscape() this.originalSortOrder = Preferences.isOldestFirst() @@ -64,6 +68,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) { @@ -90,6 +95,8 @@ 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) + this.controller.setupWidget("shorten-urls", {}, this.shortenURLs) }, addListeners: function() { @@ -109,6 +116,8 @@ 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)) + this.controller.listen("shorten-urls", Mojo.Event.propertyChange, this.setShortenURLs = this.setShortenURLs.bind(this)) }, updateLabels: function() { @@ -127,6 +136,10 @@ 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")) + this.controller.get("sharing-label").update($L("Sharing")) + this.controller.get("shorten-urls-label").update($L("Shorten URLs")) }, cleanup: function($super) { @@ -146,6 +159,8 @@ 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) + this.controller.stopListening("shorten-urls", Mojo.Event.propertyChange, this.setShortenURLs) }, showAndHideStuff: function() { @@ -171,6 +186,14 @@ var PreferencesAssistant = Class.create(BaseAssistant, { } }, + setShortenURLs: function() { + Preferences.setShortenURLs(this.shortenURLs.value) + }, + + setFeedlySortEngagement: function() { + Preferences.setFeedlySortEngagement(this.feedlySortEngagement.value) + }, + setAllowLandscape: function() { Preferences.setAllowLandscape(this.allowLandscape.value) }, @@ -255,6 +278,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/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/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/lib/log.js b/app/lib/log.js old mode 100644 new mode 100755 index 82ef542..74374e6 --- a/app/lib/log.js +++ b/app/lib/log.js @@ -2,9 +2,8 @@ var Log = { items: [], debug: function(message) { - Mojo.Log.info(message) - if(Preferences.isDebugging()) { + Mojo.Log.info(message) this.items.push({message: message}) } } diff --git a/app/lib/sharing.js b/app/lib/sharing.js old mode 100644 new mode 100755 index d5387e4..3b41266 --- a/app/lib/sharing.js +++ b/app/lib/sharing.js @@ -1,13 +1,20 @@ var Sharing = { + + //variables in use up to ax items: [ - {id: "sharing-aa", label: $L("Google"), defaultEnabled: true}, - {id: "sharing-ab", label: $L("Share"), command: "share-with-google", defaultEnabled: true}, + {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("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-ad", label: $L("Project Macaw"), command: "send-to-project-macaw", defaultEnabled: true}, + {id: "sharing-aw", label: $L("Spaz HD"), command: "send-to-spaz-hd", defaultEnabled: false}, + {id: "sharing-ax", label: $L("Spaz Beta"), command: "send-to-spaz-beta", defaultEnabled: false}, + {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}, {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}, @@ -18,9 +25,10 @@ var Sharing = { {id: "sharing-al", label: $L("Spare Time"), command: "send-to-spare-time", defaultEnabled: false}, {id: "sharing-am", label: $L("Instapaper"), command: "send-to-instapaper", defaultEnabled: true}, {id: "sharing-an", label: $L("ReadOnTouch PHONE"), command: "send-to-readontouch-phone", defaultEnabled: false}, - {id: "sharing-ao", label: $L("ReadOnTouch PRO"), command: "send-to-readontouch-pro", defaultEnabled: false} + {id: "sharing-ao", label: $L("ReadOnTouch PRO"), command: "send-to-readontouch-pro", defaultEnabled: false}, + {id: "sharing-ay", label: $L("MoboReader"), command: "send-to-mobo-reader", defaultEnabled: false} ], - + getPopupFor: function(article) { var sortOrder = Preferences.getSharingOptionsSortOrder() console.log(sortOrder) @@ -95,15 +103,19 @@ var Sharing = { case "send-to-readontouch-pro": Sharing.sendToReadontouchPro(article, controller); break; case "send-to-spare-time": Sharing.sendToSpareTime(article, controller); break; case "send-to-relego": Sharing.sendToRelego(article, controller); break; - case "send-to-bad-kitty": Sharing.sendToBadKitty(article, controller); break; - case "send-to-spaz": Sharing.sendToSpaz(article, controller); break; - case "send-to-twee": Sharing.sendToTwee(article, controller); break; - case "send-to-tweed": Sharing.sendToTweed(article, controller); break; - case "send-to-carbon": Sharing.sendToCarbon(article, controller); break; + case "send-to-project-macaw": Sharing.sendToProjectMacaw(article, controller); break; + case "send-to-glimpse": Sharing.sendToGlimpse(article, controller); break; + case "send-to-qp-default": Sharing.sendToQPDefault(article, controller); break; + case "send-to-qp-all": Sharing.sendToQPAll(article, controller); break; case "send-to-email": Sharing.sendToEmail(article, controller); break; 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-browser": Sharing.sendToBrowser(article, controller); break; + case "send-to-spaz-hd": Sharing.sendToSpazHD(article, controller); break; + case "send-to-spaz-beta": Sharing.sendToSpazBeta(article, controller); break; + case "send-to-mobo-reader": Sharing.sendToMoboReader(article, controller); break; case "configure": controller.stageController.pushScene("configure-sharing", Sharing.items) } }, @@ -120,28 +132,59 @@ var Sharing = { }) }, - sendToFacebook: function(article, controller) { - Sharing.sendToApp(controller, $L("Facebook"), "com.palm.app.facebook", {status: article.title + "\n\n" + article.url}) - }, - - sendToBadKitty: function(article, controller) { - Sharing.sendToApp(controller, $L("Bad Kitty"), "com.superinhuman.badkitty", {action: "tweet", tweet: article.title + "\n\n" + article.url}) + sendToFacebook: function(article, controller, shorturl) { + if(Preferences.isShortenURLs() && !shorturl) + { + Sharing.getShortURL(article, controller, article.url, "sendToFacebook") + return + } + + var url = shorturl ? shorturl : article.url + Sharing.sendToApp(controller, $L("Facebook"), "com.palm.app.facebook", {status: article.title + "\n\n" + url}) }, - sendToSpaz: function(article, controller) { - Sharing.sendToApp(controller, $L("Spaz"), "com.funkatron.app.spaz", {action: "prepPost", tweet: article.title + "\n\n" + article.url}) + sendToProjectMacaw: function(article, controller, shorturl) { + if(Preferences.isShortenURLs() && !shorturl) + { + Sharing.getShortURL(article, controller, article.url, "sendToProjectMacaw") + return + } + + var url = shorturl ? shorturl : article.url + Sharing.sendToApp(controller, $L("Project Macaw"), "net.minego.phnx", {action: "tweet", msg: article.title + "\n\n" + url}) }, - sendToTwee: function(article, controller) { - Sharing.sendToApp(controller, $L("Twee"), "com.deliciousmorsel.twee", {tweet: article.title + "\n\n" + article.url}) + sendToGlimpse: function(article, controller, shorturl) { + if(Preferences.isShortenURLs() && !shorturl) + { + Sharing.getShortURL(article, controller, article.url, "sendToGlimpse") + return + } + + var url = shorturl ? shorturl : article.url + Sharing.sendToApp(controller, $L("Glimpse"), "com.ingloriousapps.glimpse", {query: "tweet/" + article.title + "\n\n" + url}) }, - sendToTweed: function(article, controller) { - Sharing.sendToApp(controller, $L("Tweed"), "com.pivotallabs.tweed.us", {newTweet: article.title + "\n\n" + article.url}) + sendToQPDefault: function(article, controller, shorturl) { + if(Preferences.isShortenURLs() && !shorturl) + { + Sharing.getShortURL(article, controller, article.url, "sendToQPDefault") + return + } + + var url = shorturl ? shorturl : article.url + Sharing.sendToApp(controller, $L("Default Accounts"), "com.hedami.quickpost", {quickPost: article.title + "\n\n" + url}) }, - sendToCarbon: function(article, controller) { - Sharing.sendToApp(controller, $L("Carbon"), "com.dotsandlines.carbon", {action: "compose", body: article.title + "\n\n" + article.url}) + sendToQPAll: function(article, controller, shorturl) { + if(Preferences.isShortenURLs() && !shorturl) + { + Sharing.getShortURL(article, controller, article.url, "sendToQPAll") + return + } + + var url = shorturl ? shorturl : article.url + Sharing.sendToApp(controller, $L("All Accounts"), "com.hedami.quickpost", {quickPost: "z " + article.title + "\n\n" + url}) }, sendToInstapaper: function(article, controller) { @@ -168,12 +211,26 @@ var Sharing = { Sharing.sendToApp(controller, $L("Relego"), "com.webosroundup.relego", {action: 'addtorelego', url: article.url, title: article.title}) }, - sendToEmail: function(article, controller) { - Sharing.sendToApp(controller, $L("Email"), "com.palm.app.email", {summary: article.title, text: article.title + "\n\n" + article.url}) + sendToEmail: function(article, controller, shorturl) { + if(Preferences.isShortenURLs() && !shorturl) + { + Sharing.getShortURL(article, controller, article.url, "sendToEmail") + return + } + + var url = shorturl ? shorturl : article.url + Sharing.sendToApp(controller, $L("Email"), "com.palm.app.email", {summary: article.title, text: article.title + "\n\n" + url}) }, - sendToSms: function(article, controller) { - Sharing.sendToApp(controller, $L("Messaging"), "com.palm.app.messaging", {messageText: article.title + "\n\n" + article.url}) + sendToSms: function(article, controller, shorturl) { + if(Preferences.isShortenURLs() && !shorturl) + { + Sharing.getShortURL(article, controller, article.url, "sendToSms") + return + } + + var url = shorturl ? shorturl : article.url + Sharing.sendToApp(controller, $L("Messaging"), "com.palm.app.messaging", {messageText: article.title + "\n\n" + url}) }, sendToNeato: function(article, controller) { @@ -187,6 +244,62 @@ var Sharing = { sendToReadontouchPro: function(article, controller) { Sharing.sendToApp(controller, $L("ReadOnTouch PRO"), "com.sven-ziegler.readontouch", {action: 'addLink', url: article.url, title: article.title}) }, + + + sendToMoboReader: function(article, controller) { + Sharing.sendToApp(controller, $L("MoboReader"), "info.mobo.moboreader", {action: 'addLink', url: article.url, title: article.title}) + }, + + sendToClipboard: function(article, controller) { + controller.stageController.setClipboard(article.url) + Feeder.notify($L("URL Copied")) + }, + + sendToBrowser: function(article, controller, shorturl) { + if(Preferences.isShortenURLs() && !shorturl) + { + Sharing.getShortURL(article, controller, article.url, "sendToBrowser") + return + } + + var url = shorturl ? shorturl : article.url + Sharing.sendToApp(controller, $L("Browser"), "com.palm.app.browser", {target: "https://twitter.com/intent/tweet?text=" + encodeURIComponent(article.title) + "&url=" + encodeURI(url)}) + }, + + sendToSpazHD: function(article, controller, shorturl) { + if(Preferences.isShortenURLs() && !shorturl) + { + Sharing.getShortURL(article, controller, article.url, "sendToSpazHD") + return + } + + var url = shorturl ? shorturl : article.url + Sharing.sendToApp(controller, $L("Spaz HD"), "com.funkatron.app.spaz-hd", {action: "prepPost", tweet: article.title + "\n\n" + url}) + }, + + sendToSpazBeta: function(article, controller, shorturl) { + if(Preferences.isShortenURLs() && !shorturl) + { + Sharing.getShortURL(article, controller, article.url, "sendToSpazBeta") + return + } + + var url = shorturl ? shorturl : article.url + Sharing.sendToApp(controller, $L("Spaz Beta"), "com.funkatron.app.spaz-beta", {action: "prepPost", tweet: article.title + "\n\n" + url}) + }, + + getShortURL: function(article, controller, url, method) + { + new Ajax.Request("http://is.gd/create.php", { + method: "get", + parameters: {format: "json", url: encodeURI(url)}, + onSuccess: function(response){ + shorturl = response.responseText.evalJSON().shorturl + Sharing[method](article, controller, shorturl) + }, + onFailure: function(){Sharing[method](article, controller, url)} + }) + }, sendToApp: function(controller, appName, id, params) { controller.serviceRequest("palm://com.palm.applicationManager", { 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..a46d111 --- a/app/models/all-sources.js +++ b/app/models/all-sources.js @@ -1,10 +1,37 @@ 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.stickySources = {items: []} + + if (api.supportsAllArticles()) + { + 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()) + { + this.starred = new Starred(api) + this.stickySources.items.push(this.starred) + } + + if (api.supportsShared()) + { + 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/all-subscriptions.js b/app/models/all-subscriptions.js old mode 100644 new mode 100755 index 4317de2..8b3232e --- a/app/models/all-subscriptions.js +++ b/app/models/all-subscriptions.js @@ -48,12 +48,12 @@ var AllSubscriptions = Class.create(SubscriptionContainer, { self.api.getUnreadCounts( function(counts) { counts.each(function(count) { - if(count.id.startsWith("feed")) { + if(count.id.toString().startsWith("feed") || count.id > 0){ self.incrementUnreadCountBy(count.count) self.items.each(function(item) { if(item.id == count.id) { - item.setUnreadCount(count.count) + item.setUnreadCount(count.count) } if(item.isFolder) { @@ -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/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 old mode 100644 new mode 100755 index 3e2fbfb..d9a69b8 --- a/app/models/api.js +++ b/app/models/api.js @@ -1,462 +1,184 @@ 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}, - onSuccess: authSuccess, - onFailure: failure - }) - }, - + initialize: function() { + this.appApi = undefined + }, + + login: function(credentials, success, failure, controller) { + if (credentials.service == "tor") + { + this.appApi = new TorApi() + this.appApi.login(credentials, success, failure) + } + else if (credentials.service == "ino") + { + 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 == "ttrss") + { + this.appApi = new TTRSSApi() + this.appApi.login(credentials, success, failure) + } + else if (credentials.service == "feedly") + { + 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 if (credentials.service == "oc") + { + this.appApi = new OCApi() + this.appApi.login(credentials, success, failure, controller) + } + else + { + // No supported service to log into + failure + } + }, + getTags: function(success, failure) { - new Ajax.Request(Api.BASE_URL + "tag/list", { - method: "get", - parameters: {output: "json"}, - requestHeaders: this._requestHeaders(), - onFailure: failure, - onSuccess: function(response) {success(response.responseText.evalJSON().tags)} - }) + this.appApi.getTags(success, failure) }, getSortOrder: function(success, failure) { - new Ajax.Request(Api.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) - } - }) + this.appApi.getSortOrder(success, failure) }, 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(Api.BASE_URL + "preference/stream/set", { - method: "post", - parameters: parameters, - requestHeaders: this._requestHeaders() - }) - }.bind(this)) + this.appApi.setSortOrder(sortOrder, stream) }, 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(Api.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)) - } + this.appApi.unsubscribe(feed) }, removeLabel: function(folder) { - this._getEditToken(function(token) { - var parameters = { - T: token, - s: folder.id, - t: folder.title - } - - new Ajax.Request(Api.BASE_URL + "disable-tag", { - method: "post", - parameters: parameters, - requestHeaders: this._requestHeaders(), - onSuccess: function() {Mojo.Event.send(document, "FolderDeleted", {id: folder.id})} - }) - }.bind(this)) + this.appApi.removeLabel(folder) }, searchSubscriptions: function(query, success, failure) { - var self = this - - new Ajax.Request(Api.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) - } - }) + this.appApi.searchSubscriptions(query, success, failure) }, addSubscription: function(url, success, failure) { - this._getEditToken(function(token) { - var parameters = { - T: token, - quickadd: url - } - - new Ajax.Request(Api.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)) + this.appApi.addSubscription(url, success, failure) }, getAllSubscriptions: function(success, failure) { - var self = this - - new Ajax.Request(Api.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) - } - }) + this.appApi.getAllSubscriptions(success, failure) }, cacheTitles: function(subscriptions) { - var self = this - self.titles = {} - - subscriptions.each(function(subscription) { - self.titles[subscription.id] = subscription.title - }) + this.appApi.cacheTitles(subscriptions) }, titleFor: function(id) { - return this.titles[id] + return this.appApi.titleFor(id) }, getUnreadCounts: function(success, failure) { - new Ajax.Request(Api.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) - } - } - }) + this.appApi.getUnreadCounts(success, failure) }, getAllArticles: function(continuation, success, failure) { - this._getArticles( - "user/-/state/com.google/reading-list", - Preferences.hideReadArticles() ? "user/-/state/com.google/read" : null, - continuation, - success, - failure - ) + this.appApi.getAllArticles(continuation, success, failure) }, getAllStarred: function(continuation, success, failure) { - this._getArticles( - "user/-/state/com.google/starred", - null, - continuation, - success, - failure - ) + this.appApi.getAllStarred(continuation, success, failure) }, getAllShared: function(continuation, success, failure) { - this._getArticles( - "user/-/state/com.google/broadcast", - null, - continuation, - success, - failure - ) + this.appApi.getAllShared(continuation, success, failure) }, - - getAllArticlesFor: function(id, continuation, success, failure) { - this._getArticles( - id, - Preferences.hideReadArticles() ? "user/-/state/com.google/read" : null, - 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) }, - _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(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) - } - }) + getAllArticlesFor: function(id, continuation, success, failure) { + this.appApi.getAllArticlesFor(id, continuation, success, failure) }, markAllRead: function(id, success, failure) { - this._getEditToken( - function(token) { - var parameters = { - T: token, - s: id - } - - new Ajax.Request(Api.BASE_URL + "mark-all-as-read", { - method: "post", - parameters: parameters, - requestHeaders: this._requestHeaders(), - onSuccess: success, - onFailure: failure - }) - }.bind(this), - - failure - ) + this.appApi.markAllRead(id, success, failure) }, search: function(query, id, success, failure) { - var parameters = { - q: query, - num: 50, - output: "json" - } - - if(id) { - parameters.s = id - } - - new Ajax.Request(Api.BASE_URL + "search/items/ids", { - method: "get", - parameters: parameters, - requestHeaders: this._requestHeaders(), - onSuccess: this.searchItemsFound.bind(this, success, failure), - onFailure: failure - }) + this.appApi.search(query, id, success, failure) }, 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(Api.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) - } + this.appApi.searchItemsFound(success, failure, response) }, mapSearchResults: function(response) { - console.log(response.responseText) + this.appApi.mapSearchResults(response) }, setArticleRead: function(articleId, subscriptionId, success, failure) { - this._editTag( - articleId, - subscriptionId, - "user/-/state/com.google/read", - "user/-/state/com.google/kept-unread", - success, - failure - ) + this.appApi.setArticleRead(articleId, subscriptionId, success, failure) }, setArticleNotRead: function(articleId, subscriptionId, success, failure, sticky) { - this._editTag( - articleId, - subscriptionId, - sticky ? "user/-/state/com.google/kept-unread" : null, - "user/-/state/com.google/read", - success, - failure - ) + this.appApi.setArticleNotRead(articleId, subscriptionId, success, failure, sticky) }, setArticleShared: function(articleId, subscriptionId, success, failure) { - this._editTag( - articleId, - subscriptionId, - "user/-/state/com.google/broadcast", - null, - success, - failure - ) + this.appApi.setArticleShared(articleId, subscriptionId, success, failure) }, setArticleNotShared: function(articleId, subscriptionId, success, failure) { - this._editTag( - articleId, - subscriptionId, - null, - "user/-/state/com.google/broadcast", - success, - failure - ) + this.appApi.setArticleNotShared(articleId, subscriptionId, success, failure) }, setArticleStarred: function(articleId, subscriptionId, success, failure) { - this._editTag( - articleId, - subscriptionId, - "user/-/state/com.google/starred", - null, - success, - failure - ) + this.appApi.setArticleStarred(articleId, subscriptionId, success, failure) }, setArticleNotStarred: function(articleId, subscriptionId, success, failure) { - this._editTag( - articleId, - subscriptionId, - null, - "user/-/state/com.google/starred", - success, - failure - ) + this.appApi.setArticleNotStarred(articleId, subscriptionId, success, failure) }, - - _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(Api.BASE_URL + "edit-tag", { - method: "post", - parameters: parameters, - requestHeaders: this._requestHeaders(), - onSuccess: success, - onFailure: failure - }) - }.bind(this), - - failure - ) + + supportsAllArticles: function() { + return this.appApi.supportsAllArticles() }, - - _requestHeaders: function() { - return {Authorization:"GoogleLogin auth=" + this.auth} + + supportsFresh: function() { + return this.appApi.supportsFresh() }, - - _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(Api.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) - }) - } + + supportsArchived: function() { + return this.appApi.supportsArchived() + }, + + supportsStarred: function() { + return this.appApi.supportsStarred() + }, + + supportsShared: function() { + return this.appApi.supportsShared() + }, + + supportsSearch: function() { + return this.appApi.supportsSearch() + }, + + supportsManualSort: function() { + return this.appApi.supportsManualSort() } -}) - -Api.BASE_URL = "http://www.google.com/reader/api/0/" +}) \ No newline at end of file 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/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..76e7cd9 --- a/app/models/article.js +++ b/app/models/article.js @@ -10,6 +10,9 @@ var Article = Class.create({ var content = data.content || data.summary || {content: ""} this.summary = this.cleanUp(content.content) this.readLocked = data.isReadStateLocked + this.isRead = false + this.isShared = false + this.isStarred = false this.setStates(data.categories) this.setDates(parseInt(data.crawlTimeMsec, 10)) this.setArticleLink(data.alternate) @@ -40,26 +43,22 @@ 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/read")) { + this.isRead = true + } - if(category.endsWith("/state/com.google/kept-unread")) { - this.keepUnread = 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/starred")) { + this.isStarred = true + } - if(category.endsWith("/state/com.google/broadcast")) { - this.isShared = true - } + if(category.endsWith("/state/com.google/broadcast")) { + this.isShared = true + } }.bind(this)) }, @@ -115,26 +114,54 @@ 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) { 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 service") success(false) } else { diff --git a/app/models/bq-api.js b/app/models/bq-api.js new file mode 100644 index 0000000..4692091 --- /dev/null +++ b/app/models/bq-api.js @@ -0,0 +1,553 @@ +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.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( + id, + Preferences.hideReadArticles() ? "user/-/state/com.google/read" : null, + continuation, + success, + failure + ) + }, + + //UPDATED 1.1.3 + _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 = JSON2.parse(response.responseText) + 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.2.0 + supportsArchived: function() { + return false + }, + + //UPDATED 1.2.0 + supportsFresh: function() { + return false + }, + + //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/models/countable.js b/app/models/countable.js old mode 100644 new mode 100755 index a0ebe76..1884976 --- 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/credentials.js b/app/models/credentials.js old mode 100644 new mode 100755 index d499e3f..077b1d7 --- a/app/models/credentials.js +++ b/app/models/credentials.js @@ -2,11 +2,67 @@ 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() + } + else + { + 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) + if (this.email !== undefined){ + this.emailCookie().put(this.email) + } + 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) + } + 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.serverCookie().remove() + this.serviceCookie().remove() + this.idCookie().remove() + this.accessTokenCookie().remove() + this.refreshTokenCookie().remove() + this.tokenExpiryCookie().remove() + this.tokenTypeCookie().remove() + this.planCookie().remove() }, emailCookie: function() { @@ -16,6 +72,38 @@ var Credentials = Class.create({ passwordCookie: function() { return this.getCookie("password") }, + + serverCookie: function() { + return this.getCookie("server") + }, + + 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/feeder.js b/app/models/feeder.js old mode 100644 new mode 100755 index 3031b41..ee7619e --- a/app/models/feeder.js +++ b/app/models/feeder.js @@ -1,7 +1,5 @@ var Feeder = Feeder || {} -Feeder.Metrix = new Metrix() - Feeder.notify = function(message) { Mojo.Controller.getAppController().showBanner({messageText: message}, "", "feeder") } diff --git a/app/models/feedly-api.js b/app/models/feedly-api.js new file mode 100644 index 0000000..e49123a --- /dev/null +++ b/app/models/feedly-api.js @@ -0,0 +1,722 @@ +var FeedlyApi = Class.create({ + //UPDATED 1.2.0 + 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 + { + 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: 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'], + service: credentials.service + }; + 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 1.1.2 + 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) { + //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) + } + }) + }, + + //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 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( + id, + Preferences.hideReadArticles() ? true : false, + continuation, + success, + failure + ) + }, + + //UPDATED 1.2.0 + _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 + } + + 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) + { + 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) + }, + + //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 1.1.2 + _ajaxPut: function(params, url, success, failure) { + 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() + } + } + else + { + failure + } + }, + + //UPDATED 1.1.2 + _ajaxDelete: function(url, success, failure) { + 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() + } + } + else + { + failure + } + }, + + //UPDATED 1.0.3 + _checkTokenExpiry: function() { + if (new Date(this.credentials.tokenExpiry).getTime() > new Date().getTime()) + { + 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 1.2.0 + supportsArchived: function() { + return false + }, + + //UPDATED 1.2.0 + supportsFresh: function() { + return false + }, + + //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://cloud.feedly.com/v3/"; +FeedlyApi.CLIENT_ID = "feedspider"; +FeedlyApi.CLIENT_SECRET = "FE01DA2G93CK87ZP8NW6T5WYG862"; \ No newline at end of file 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 index 3931c00..a901629 --- 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 old mode 100644 new mode 100755 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 new file mode 100644 index 0000000..d7a1d9c --- /dev/null +++ b/app/models/ino-api.js @@ -0,0 +1,522 @@ +var InoApi = 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.inoreader.com/accounts/ClientLogin", { + method: "post", + requestHeaders: {AppId: InoApi.APP_ID, AppKey: InoApi.APP_KEY}, + parameters: {Email: credentials.email, Passwd: credentials.password}, + onSuccess: authSuccess, + onFailure: failure + }) + }, + + getTags: function(success, failure) { + new Ajax.Request(InoApi.BASE_URL + "tag/list", { + method: "get", + parameters: {output: "json"}, + requestHeaders: this._requestHeaders(), + onFailure: failure, + onSuccess: function(response) {success(response.responseText.evalJSON().tags)} + }) + }, + + getSortOrder: function(success, failure) { + new Ajax.Request(InoApi.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) + } + }) + }, + + 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(InoApi.BASE_URL + "preference/stream/set", { + method: "post", + parameters: parameters, + requestHeaders: this._requestHeaders() + }) + }.bind(this)) + }, + + 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(InoApi.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)) + } + }, + + removeLabel: function(folder) { + this._getEditToken(function(token) { + var parameters = { + T: token, + s: folder.id, + t: folder.title + } + + new Ajax.Request(InoApi.BASE_URL + "disable-tag", { + method: "post", + parameters: parameters, + requestHeaders: this._requestHeaders(), + onSuccess: function() {Mojo.Event.send(document, "FolderDeleted", {id: folder.id})} + }) + }.bind(this)) + }, + + searchSubscriptions: function(query, success, failure) { + var self = this + + new Ajax.Request(InoApi.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) + } + }) + }, + + addSubscription: function(url, success, failure) { + this._getEditToken(function(token) { + var parameters = { + T: token, + quickadd: url + } + + new Ajax.Request(InoApi.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)) + }, + + getAllSubscriptions: function(success, failure) { + var self = this + + new Ajax.Request(InoApi.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) + } + }) + }, + + cacheTitles: function(subscriptions) { + var self = this + self.titles = {} + + subscriptions.each(function(subscription) { + self.titles[subscription.id] = subscription.title + }) + }, + + titleFor: function(id) { + return this.titles[id] + }, + + getUnreadCounts: function(success, failure) { + new Ajax.Request(InoApi.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) + } + } + }) + }, + + getAllArticles: function(continuation, success, failure) { + this._getArticles( + "user/-/state/com.google/reading-list", + Preferences.hideReadArticles() ? "user/-/state/com.google/read" : null, + continuation, + success, + failure + ) + }, + + getAllStarred: function(continuation, success, failure) { + this._getArticles( + "user/-/state/com.google/starred", + null, + continuation, + success, + failure + ) + }, + + getAllShared: function(continuation, success, failure) { + this._getArticles( + "user/-/state/com.google/broadcast", + null, + continuation, + success, + failure + ) + }, + + //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, + Preferences.hideReadArticles() ? "user/-/state/com.google/read" : null, + continuation, + success, + failure + ) + }, + + //UPDATED 1.1.3 + _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(InoApi.BASE_URL + "stream/contents/" + escape(id), { + method: "get", + parameters: parameters, + requestHeaders: this._requestHeaders(), + onFailure: failure, + onSuccess: function(response) { + var articles = JSON2.parse(response.responseText) + success(articles.items, articles.id, articles.continuation) + } + }) + }, + + markAllRead: function(id, success, failure) { + this._getEditToken( + function(token) { + var parameters = { + T: token, + s: id + } + + new Ajax.Request(InoApi.BASE_URL + "mark-all-as-read", { + method: "post", + parameters: parameters, + requestHeaders: this._requestHeaders(), + onSuccess: success, + onFailure: failure + }) + }.bind(this), + + failure + ) + }, + + search: function(query, id, success, failure) { + var parameters = { + q: query, + num: 50, + output: "json" + } + + if(id) { + parameters.s = id + } + + new Ajax.Request(InoApi.BASE_URL + "search/items/ids", { + method: "get", + parameters: parameters, + requestHeaders: this._requestHeaders(), + onSuccess: this.searchItemsFound.bind(this, success, failure), + onFailure: failure + }) + }, + + 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(InoApi.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) + } + }, + + mapSearchResults: function(response) { + console.log(response.responseText) + }, + + setArticleRead: function(articleId, subscriptionId, success, failure) { + this._editTag( + articleId, + subscriptionId, + "user/-/state/com.google/read", + null, + success, + failure + ) + }, + + setArticleNotRead: function(articleId, subscriptionId, success, failure, sticky) { + this._editTag( + articleId, + subscriptionId, + null, + "user/-/state/com.google/read", + success, + failure + ) + }, + + setArticleShared: function(articleId, subscriptionId, success, failure) { + this._editTag( + articleId, + subscriptionId, + "user/-/state/com.google/broadcast", + null, + success, + failure + ) + }, + + setArticleNotShared: function(articleId, subscriptionId, success, failure) { + this._editTag( + articleId, + subscriptionId, + null, + "user/-/state/com.google/broadcast", + success, + failure + ) + }, + + setArticleStarred: function(articleId, subscriptionId, success, failure) { + this._editTag( + articleId, + subscriptionId, + "user/-/state/com.google/starred", + null, + success, + failure + ) + }, + + setArticleNotStarred: function(articleId, subscriptionId, success, failure) { + this._editTag( + articleId, + subscriptionId, + null, + "user/-/state/com.google/starred", + success, + failure + ) + }, + + _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(InoApi.BASE_URL + "edit-tag", { + method: "post", + parameters: parameters, + requestHeaders: this._requestHeaders(), + onSuccess: success, + onFailure: failure + }) + }.bind(this), + + failure + ) + }, + + _requestHeaders: function() { + return {Authorization:"GoogleLogin auth=" + this.auth, AppId: InoApi.APP_ID, AppKey: InoApi.APP_KEY} + }, + + _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(InoApi.BASE_URL + "token", { + method: "get", + requestHeaders: this._requestHeaders(), + 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) + }) + } + }, + + supportsAllArticles: function() { + return true + }, + + //UPDATED 1.2.0 + supportsArchived: function() { + return false + }, + + //UPDATED 1.2.0 + supportsFresh: function() { + return false + }, + + supportsStarred: function() { + return true + }, + + supportsShared: function() { + return true + }, + + supportsSearch: function() { + return false + }, + + //UPDATED 0.9.5 + supportsManualSort: function() { + return true + } + +}) + +InoApi.BASE_URL = "https://www.inoreader.com/reader/api/0/"; +InoApi.APP_ID = "1000001438"; +InoApi.APP_KEY = "Hea9JkjSNEepktY0s9ss9TUaETgCoBzg"; \ No newline at end of file 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/metrix.js b/app/models/metrix.js deleted file mode 100755 index f26fbf8..0000000 --- a/app/models/metrix.js +++ /dev/null @@ -1 +0,0 @@ -/* This license governs use of the accompanying software ("Software"), and your use of the Software constitutes acceptance of this license. Subject to the restrictions below, you may use the Software for any commercial or noncommercial purpose, including distributing derivative works. SECTION 1: DEFINITIONS A. "Syntactix LLC" refers to Syntactix, LLC, a limited liability corporation organized and operating under the laws of the state of Florida. B. "Metrix" or "Metrix Library" refers to the Metrix Library WebOS Framework, which is a Syntactix LLC software product. C. "SOFTWARE" refers to the source code, compiled binaries, installation files documentation and any other materials provided by Syntactix LLC. SECTION 2: LICENSE You agree that: A. Subject to the terms of this license, the Licensor grants you a non-transferable, non-exclusive, worldwide, royalty-free copyright license to reproduce and redistribute unmodified the SOFTWARE for use within your Palm WebOS application provided that the following conditions are met: (i) All copyright notices are retained. (ii) A copy of this license is retained in the header of each source file of the software. B. You may NOT decompile, disassemble, reverse engineer or otherwise attempt to extract, generate or retrieve source code from any compiled binary provided in the SOFTWARE. C. You will (a) NOT use Syntactix's name, logo, or trademarks in association with distribution of the SOFTWARE or derivative works unless otherwise permitted in writing; and (b) you WILL indemnify, hold harmless, and defend Syntactix from and against any claims or lawsuits, including attorneys fees, that arise or result from the use or distribution of your modifications to the SOFTWARE and any additional software you distribute along with the SOFTWARE. D. The SOFTWARE comes "as is", with no warranties. None whatsoever. This means no express, implied or statutory warranty, including without limitation, warranties of merchantability or fitness for a particular purpose or any warranty of title or non-infringement. E. Neither Syntactix LLC nor its suppliers will be liable for any of those types of damages known as indirect, special, consequential, or incidental related to the SOFTWARE or this license, to the maximum extent the law permits, no matter what legal theory its based on. Also, you must pass this limitation of liability on whenever you distribute the SOFTWARE or derivative works. F. If you sue anyone over patents that you think may apply to the SOFTWARE for a person's use of the SOFTWARE, your license to the SOFTWARE ends automatically. G. The patent rights, if any, granted in this license only apply to the SOFTWARE, not to any derivative works you make. H. The SOFTWARE is subject to U.S. export jurisdiction at the time it is licensed to you, and it may be subject to additional export or import laws in other places. You agree to comply with all such laws and regulations that may apply to the SOFTWARE after delivery of the SOFTWARE to you. I. If you are an agency of the U.S. Government, (i) the SOFTWARE is provided pursuant to a solicitation issued on or after December 1, 1995, is provided with the commercial license rights set forth in this license, and (ii) the SOFTWARE is provided pursuant to a solicitation issued prior to December 1, 1995, is provided with Restricted Rights as set forth in FAR, 48 C.F.R. 52.227-14 (June 1987) or DFAR, 48 C.F.R. 252.227-7013 (Oct 1988), as applicable. J. Your rights under this license end automatically if you breach it in any way. K. This license contains the only rights associated with the SOFTWARE and Syntactix LLC reserves all rights not expressly granted to you in this license. 2010 Syntactix, LLC. All rights reserved. */ var Metrix = Class.create( { initialize: function() { this.cookieName = Mojo.appInfo.id + ".metrix"; this.metrixVersion = "0.3.2"; this.ServiceRequest = new ServiceRequestWrapper(); this.AjaxRequest = new AjaxRequestWrapper(); this.initializeCookie(); }, postDeviceData: function(versionCtrl) { if(versionCtrl === true && this.lastAppRunVersion === Mojo.appInfo.version && this.lastWebosBuild === Mojo.Environment.build) { return; } if(!this.postDeviceDataOnce) //Only post this once per app session. { this.postDeviceDataOnce = true; } else { return; } var idRequest = this.ServiceRequest.request('palm://com.palm.preferences/systemProperties', { method:"Get", parameters:{"key": "com.palm.properties.nduid" }, onSuccess: this.gotDeviceId.bind(this) }); }, gotDeviceId: function(response) { var deviceId = response["com.palm.properties.nduid"]; var appId = Mojo.appInfo.id; var appVersion = Mojo.appInfo.version; var webOsBuildNumber = Mojo.Environment.build; var resolution = Mojo.Environment.DeviceInfo.screenWidth + " x " + Mojo.Environment.DeviceInfo.screenHeight; var deviceName = Mojo.Environment.DeviceInfo.modelNameAscii; var carrierName = Mojo.Environment.DeviceInfo.carrierName; var webOsVersion = Mojo.Environment.DeviceInfo.platformVersion; var companyName = Mojo.appInfo.vendor; var locale = Mojo.Locale.getCurrentLocale(); var request = this.AjaxRequest.request("http://metrix.webosroundup.com/MetrixInterface.asmx/DeviceDataPostV2", { method: "get", evalJSON: "false", parameters: {deviceId: deviceId, companyName: companyName, packageId: appId, appVersion: appVersion, resolution: resolution, webOsBuildNumber: webOsBuildNumber, webOsVersion: webOsVersion, carrier: carrierName, deviceName: deviceName, locale: locale}, onSuccess: this.postDeviceDataSuccess.bind(this), onFailure: this.postDeviceDataFailure, on0: this.postDeviceDataFailure }); }, postDeviceDataSuccess: function(transport) { var temp = transport.responseXML.getElementsByTagName("creationTimestamp"); if(temp.length > 0) { this.creationTimestamp = temp.item(0).textContent; } else { return; } temp = transport.responseXML.getElementsByTagName("lastUpdateTimestamp"); if(temp.length > 0) { this.lastUpdateTimestamp = temp.item(0).textContent; } else { return; } this.lastAppRunVersion = Mojo.appInfo.version; this.lastWebosBuild = Mojo.Environment.build; this.storeCookie(); }, postDeviceDataFailure: function(transport) { Mojo.Log.warn("Metrix Response Failed",transport.status); Mojo.Log.warn("Metrix Error=",transport.responseText); }, initializeCookie: function() { this.cookieData = new Mojo.Model.Cookie(this.cookieName); var oldMetrixCookie = this.cookieData.get(); if(oldMetrixCookie) { if(oldMetrixCookie.metrixVersion === this.metrixVersion) { this.creationTimestamp = oldMetrixCookie.creationTimestamp; this.lastUpdateTimestamp = oldMetrixCookie.lastUpdateTimestamp; this.lastAppRunVersion = oldMetrixCookie.lastAppRunVersion; this.lastWebOsBuild = oldMetrixCookie.lastWebOsBuild; this.lastBulletinTime = oldMetrixCookie.lastBulletinTime; this.bulletinVersion = oldMetrixCookie.bulletinVersion; } else { this.creationTimestamp = oldMetrixCookie.creationTimestamp; this.lastUpdateTimestamp = oldMetrixCookie.lastUpdateTimestamp; this.lastAppRunVersion = oldMetrixCookie.lastAppRunVersion; this.lastWebOsBuild = oldMetrixCookie.lastWebOsBuild; this.lastBulletinTime = oldMetrixCookie.lastBulletinTime; this.bulletinVersion = oldMetrixCookie.bulletinVersion; } } else { this.creationTimestamp = null; this.lastUpdateTimestamp = null; this.lastAppRunVersion = null; this.lastWebOsBuild = null; this.lastBulletinTime = null; this.bulletinVersion = 0; } this.storeCookie(); }, storeCookie: function(lastBulletinTime,bulletinVersion) { if(lastBulletinTime) { this.lastBulletinTime = lastBulletinTime; } if(bulletinVersion) { this.bulletinVersion = bulletinVersion; } this.cookieData.put({ creationTimestamp: this.creationTimestamp, lastUpdateTimestamp: this.lastUpdateTimestamp, lastAppRunVersion: this.lastAppRunVersion, lastWebOsBuild: this.lastWebOsBuild, metrixVersion: this.metrixVersion, lastBulletinTime: this.lastBulletinTime, bulletinVersion: this.bulletinVersion } ); }, isExpired: function(currentUtcTime, daysAllowed) { var daysUtc = daysAllowed * 86400; var result = false; if(this.creationTimestamp !== null) { if((currentUtcTime - this.creationTimestamp) > daysUtc) { result = true; } } else //this.creationTimestamp === null { //some reason we don't have a creationTime use the time passed in and store it. Once the post happens the real time will over write this one this.creationTimestamp = currentUtcTime; this.storeCookie(); } return result; }, checkBulletinBoard: function(controller,minBulletinVersion, forceReview, url) { if(minBulletinVersion) { if(minBulletinVersion > this.bulletinVersion) { this.bulletinVersion = minBulletinVersion; } } if(!forceReview) { forceReview = false; } if(!url) { url = "http://metrix.webosroundup.com/MetrixInterface.asmx/GetBulletinBoard?packageId=" + Mojo.appInfo.id; } var result = this.ServiceRequest.request('palm://com.palm.systemservice/time', { method: 'getSystemTime', parameters: {}, onSuccess: this.bulletinTimeCheck.bind(this,controller, forceReview, url), onFailure: function(){return;} }); }, bulletinTimeCheck: function(controller, forceReview, url, response) { var timeUTC = response.utc; if(response.utc > (this.lastBulletinTime + 86400) || forceReview === true) { var requestBulletin = this.AjaxRequest.request(url, { method: "get", evalJSON: "false", onSuccess: this.checkBulletinSuccess.bind(this, timeUTC,controller, forceReview), onFailure: this.checkBulletinFailure.bind(this), on0: this.checkBulletinFailure.bind(this) }); } }, checkBulletinSuccess: function(timeUTC,controller, forceReview, transport) { var version = transport.responseXML.getElementsByTagName("version").item(0).textContent; var msgArray = []; if(version > this.bulletinVersion || forceReview === true) { var bulletins = transport.responseXML.getElementsByTagName("announcement") for(var i = 0; i < bulletins.length; i++) { msgArray.push({title: bulletins[i].getElementsByTagName("title").item(0).textContent,text: bulletins[i].getElementsByTagName("message").item(0).textContent}); } if(msgArray.length > 0) { controller.showDialog({template: "metrix/displayBulletin-dialog",assistant: new BulletinAssistant(this, controller, msgArray, timeUTC,version),preventCancel:true}); } } }, checkBulletinFailure: function(transport) { }, customCounts: function(valueGroup,valueName,valueData) { var result = 0; if(!valueGroup || !valueName || !valueData || isNaN(valueData)) { return -1; } if(!this.customCountsLock) { this.customCountsLock = window.setTimeout(function(){this.customCountsLock = null;}.bind(this),5000); var url = "http://metrix.webosroundup.com/MetrixInterface.asmx/UpdateCustomCounts"; this.AjaxRequest.request(url, { method: "get", evalJSON: "false", parameters: {packageId: Mojo.appInfo.id, valueGroup: valueGroup, valueName: valueName, valueData: valueData}, onSuccess: this.customCountsSuccess.bind(this), onFailure: this.customCountsFailure.bind(this), on0: this.customCountsFailure.bind(this) }); } else { result = -1 } return result; }, customCountsSuccess: function(transport) { }, customCountsFailure: function(transport) { } }); function BulletinAssistant(sceneAssistant, controller, msgArray, timeUTC, version) { this.sceneAssistant = sceneAssistant; this.sceneAssistant.controller = controller; this.msgArray = msgArray; this.utc = timeUTC; this.version = version; } BulletinAssistant.prototype.setup = function(widget) { this.widget = widget; this.msgTitleElement = this.sceneAssistant.controller.get("bulletinTitle"); this.msgTextElement = this.sceneAssistant.controller.get("msgContent"); this.sceneAssistant.controller.get("bltnAckSelectorHitTarget").observe("mousedown",this.highlighter.bind(this, "bltnAckSelector_source", "bltnAckSelectorTitle","down","txt")); this.sceneAssistant.controller.get("bltnAckSelectorHitTarget").observe("mouseup",this.highlighter.bind(this, "bltnAckSelector_source", "bltnAckSelectorTitle","up","txt")); this.sceneAssistant.controller.get("bltnSnoozeSelectorHitTarget").observe("mousedown",this.highlighter.bind(this, "bltnSnoozeSelector_source", "bltnSnoozeSelectorTitle","down","txt")); this.sceneAssistant.controller.get("bltnSnoozeSelectorHitTarget").observe("mouseup",this.highlighter.bind(this, "bltnSnoozeSelector_source", "bltnSnoozeSelectorTitle","up","txt")); this.sceneAssistant.controller.get("bltnPrevSelectorHitTarget").observe("mousedown",this.highlighter.bind(this, "bltnPrevSelector_source", "imgPrev","down","img")); this.sceneAssistant.controller.get("bltnPrevSelectorHitTarget").observe("mouseup",this.highlighter.bind(this, "bltnPrevSelector_source", "imgPrev","up","img")); this.sceneAssistant.controller.get("bltnNextSelectorHitTarget").observe("mousedown",this.highlighter.bind(this, "bltnNextSelector_source", "imgNext","down","img")); this.sceneAssistant.controller.get("bltnNextSelectorHitTarget").observe("mouseup",this.highlighter.bind(this, "bltnNextSelector_source", "imgNext","up","img")); this.menuBarHandler = this.menuBarTouch.bindAsEventListener(this); this.sceneAssistant.controller.listen("bulletin_view_header", Mojo.Event.tap, this.menuBarHandler); if(this.msgArray.length > 1) { var srcString = this.sceneAssistant.controller.get("imgNext").src; srcString = srcString.substr(0,srcString.length - 7) + "upp.png"; this.sceneAssistant.controller.get("imgNext").src = srcString; } this.msgIndex = 0; this.msgTitleElement.innerHTML = this.msgArray[this.msgIndex].title; this.msgTextElement.innerHTML = this.msgArray[this.msgIndex].text; }; BulletinAssistant.prototype.cleanup = function() { this.sceneAssistant.controller.stopListening("bulletin_view_header", Mojo.Event.tap, this.menuBarHandler); } BulletinAssistant.prototype.highlighter = function(btnSourceElement, btnTitleElement,mouseAction, btnType) { if(mouseAction === "down") { this.sceneAssistant.controller.get(btnSourceElement).style["-webkit-border-image"] = "url(" + Mojo.appPath + "/images/Metrix/header-button-inverse.png) 0 10 0 10 stretch stretch"; if(btnType === "txt") { this.sceneAssistant.controller.get(btnTitleElement).style["color"] = "white"; } else if (btnType === "img") { var srcString = this.sceneAssistant.controller.get(btnTitleElement).src; if(srcString.substr(srcString.length - 7,7) !== "bnk.png") { srcString = srcString.substr(0,srcString.length - 7) + "dwn.png"; this.sceneAssistant.controller.get(btnTitleElement).src = srcString; } } } else if(mouseAction === "up") { this.sceneAssistant.controller.get(btnSourceElement).style["-webkit-border-image"] = "url(" + Mojo.appPath + "/images/Metrix/header-button.png) 0 10 0 10 stretch stretch"; if(btnType === "txt") { this.sceneAssistant.controller.get(btnTitleElement).style.color = "black"; } else if (btnType === "img") { var srcString = this.sceneAssistant.controller.get(btnTitleElement).src; if(srcString.substr(srcString.length - 7,7) !== "bnk.png") { srcString = srcString.substr(0,srcString.length - 7) + "upp.png"; this.sceneAssistant.controller.get(btnTitleElement).src = srcString; } } } }; BulletinAssistant.prototype.activate = function() { }; BulletinAssistant.prototype.handleCommand = function(event) { if(event.type == Mojo.Event.back) { event.stop(); this.sceneAssistant.storeCookie(this.utc, this.version); this.widget.mojo.close(); } }; BulletinAssistant.prototype.menuBarTouch = function(event) { var target = event.target.id; switch(target) { case "bltnAckSelectorHitTarget": this.sceneAssistant.storeCookie(this.utc, this.version); this.widget.mojo.close(); break; case "bltnPrevSelectorHitTarget": if(this.msgIndex > 0) { this.msgIndex--; this.msgTitleElement.innerHTML = this.msgArray[this.msgIndex].title; this.msgTextElement.innerHTML = this.msgArray[this.msgIndex].text; } if(this.msgIndex === 0) { var srcString = this.sceneAssistant.controller.get("imgPrev").src; srcString = srcString.substr(0,srcString.length - 7) + "bnk.png"; this.sceneAssistant.controller.get("imgPrev").src = srcString; } if(this.msgArray.length > 1) { var srcString = this.sceneAssistant.controller.get("imgNext").src; srcString = srcString.substr(0,srcString.length - 7) + "upp.png"; this.sceneAssistant.controller.get("imgNext").src = srcString; } break; case "bltnNextSelectorHitTarget": if(this.msgIndex < this.msgArray.length - 1) { this.msgIndex++; this.msgTitleElement.innerHTML = this.msgArray[this.msgIndex].title; this.msgTextElement.innerHTML = this.msgArray[this.msgIndex].text; } if(this.msgIndex === this.msgArray.length - 1) { var srcString = this.sceneAssistant.controller.get("imgNext").src; srcString = srcString.substr(0,srcString.length - 7) + "bnk.png"; this.sceneAssistant.controller.get("imgNext").src = srcString; } if(this.msgIndex > 0) { var srcString = this.sceneAssistant.controller.get("imgPrev").src; srcString = srcString.substr(0,srcString.length - 7) + "upp.png"; this.sceneAssistant.controller.get("imgPrev").src = srcString; } break; case "bltnSnoozeSelectorHitTarget": this.sceneAssistant.storeCookie(this.utc); this.widget.mojo.close(); break; default: break; } }; Mojo.Log.debug = function() { Mojo.Log._logImplementation(Mojo.Log.LOG_LEVEL_ERROR, $A(arguments)); }; Mojo.Log._logRemoteDownloadImplementation = function(args) { var stringToLog; var formatString = args.shift(); if (formatString) { // make sure the format string is in fact a string formatString = "" + formatString; var nextArgument = function(stringToReplace) { var target; if (stringToReplace === "%%") { return "%"; } target = args.shift(); switch (stringToReplace) { case "%o": return Object.inspect(target); case "%j": return Object.toJSON(target); } return target; }; var resultString = formatString.replace(/%[jsdfio%]/g, nextArgument); stringToLog = [resultString].concat(args).join(" "); /* var loggingFunction, banners = {}; var makeBanners = function(label) { var appTitle = Mojo.appInfo.title || "foo"; var loggingPrefix = label + ": "; return {loggingPrefix: loggingPrefix}; }; if (messageLevel <= Mojo.Log.LOG_LEVEL_ERROR) { loggingFunction = "error"; banners = makeBanners("Error"); } else if (messageLevel <= Mojo.Log.LOG_LEVEL_WARNING) { loggingFunction = "warn"; banners = makeBanners("Warning"); } else { loggingFunction = "info"; banners = makeBanners("Info"); } if (console[loggingFunction]) { if (Mojo.Host.current !== Mojo.Host.browser && banners.loggingPrefix) { stringToLog = banners.loggingPrefix + stringToLog; if (banners.loggingSuffix) { stringToLog += banners.loggingSuffix; } } console[loggingFunction](stringToLog); } */ } return stringToLog; }; \ No newline at end of file diff --git a/app/models/oc-api.js b/app/models/oc-api.js new file mode 100755 index 0000000..f141996 --- /dev/null +++ b/app/models/oc-api.js @@ -0,0 +1,629 @@ +var OCApi = Class.create({ + //UPDATED 1.2.1 + 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(/\/$/, ""); + + OCApi.BASE_URL = credentials.server + "/index.php/apps/news/api/v1-2/" + this.auth = btoa(credentials.email + ":" + credentials.password) + + new Ajax.Request(OCApi.BASE_URL + "version", { + method: "get", + requestHeaders: this._requestHeaders(), + onSuccess: success(this.auth), + onFailure: failure + }) + }, + + //UPDATED 1.2.1 + getTags: function(success, failure) { + new Ajax.Request(OCApi.BASE_URL + "folders", { + method: "get", + requestHeaders: this._requestHeaders(), + onSuccess: function(response){ + //Post-Processing + var tags = response.responseText.evalJSON().folders + var i = 1 + tags.each(function(tag) { + tag.sortid = i + i++ + }) + + success(tags) + }, + onFailure: failure, + }) + }, + + //NOT SUPPORTED BY API + getSortOrder: function(success, failure) { + /*new Ajax.Request(OCApi.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) + } + })*/ + failure() + }, + + //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(OCApi.BASE_URL + "preference/stream/set", { + method: "post", + parameters: parameters, + requestHeaders: this._requestHeaders() + }) + }.bind(this))*/ + }, + + //UPDATED 1.2.1 + unsubscribe: function(feed) { + if(feed.constructor == Folder) { + Mojo.Event.send(document, "FolderDeleted", {id: feed.id}) + this.removeLabel(feed) + } + else { + this._ajaxDelete(OCApi.BASE_URL + "feeds/" + feed.id,function() {Mojo.Event.send(document, "SubscriptionDeleted", {id: feed.id, count: feed.unreadCount})},function() {}) + } + }, + + //UPDATED 1.2.1 + removeLabel: function(folder) { + this._ajaxDelete(OCApi.BASE_URL + "folders/" + folder.id.substr(7),function() {Mojo.Event.send(document, "FolderDeleted", {id: folder.id})},function() {}) + }, + + //NOT SUPPORTED BY API + searchSubscriptions: function(query, success, failure) { + failure() + /*var self = this + + new Ajax.Request(OCApi.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.1 + addSubscription: function(url, success, failure) { + var parameters = { + url: url, + folderId: 0 + } + + new Ajax.Request(OCApi.BASE_URL + "feeds", { + method: "post", + requestHeaders: this._requestHeaders(), + postBody: JSON.stringify(parameters), + onSuccess: success, + onFailure: failure + }) + }, + + //UPDATED 1.2.1 + getAllSubscriptions: function(success, failure) { + var self = this + + new Ajax.Request(OCApi.BASE_URL + "feeds", { + method: "get", + requestHeaders: this._requestHeaders(), + onSuccess: this._processFeeds.bind(this, success, failure), + onFailure: failure + }) + }, + + //UPDATED 1.2.1 + _processFeeds: function(success, failure, response) { + var self = this + var feeds = response.responseText.evalJSON().feeds + var subscriptions = [] + + new Ajax.Request(OCApi.BASE_URL + "folders", { + method: "get", + asynchronous: false, + requestHeaders: this._requestHeaders(), + onSuccess: function(response){ + var tags = response.responseText.evalJSON().folders + self._cacheFolderTitles(tags) + }, + onFailure: failure + }) + + feeds.each(function(feed) { + if (feed.folderId > 0) + { + feed.categories = [{ + id: "folder/" + feed.folderId, + label: self._titleForFolder(feed.folderId) + }] + } + subscriptions.push(feed) + }) + self.cacheTitles(subscriptions) + success(subscriptions) + }, + + //UPDATED 1.2.1 + cacheTitles: function(subscriptions) { + var self = this + self.titles = {} + + subscriptions.each(function(subscription) { + self.titles[subscription.id] = subscription.title + }) + }, + + //UPDATED 1.2.1 + _cacheFolderTitles: function(folders) { + var self = this + self.folderTitles = {} + + folders.each(function(folder) { + self.folderTitles[folder.id] = folder.name + }) + }, + + //UPDATED 1.2.1 + _cacheArticleHashes: function(articles, append) { + var self = this + + if (append == false) + { + self.articleHashes = {} + } + + articles.each(function(article) { + self.articleHashes[article.id] = article.guidHash + }) + }, + + //UPDATED 1.2.1 + titleFor: function(id) { + return this.titles[id] + }, + + //UPDATED 1.2.1 + _titleForFolder: function(id) { + return this.folderTitles[id] + }, + + //UPDATED 1.2.1 + _hashForArticle: function(id) { + return this.articleHashes[id] + }, + + //UPDATED 1.2.1 + getUnreadCounts: function(success, failure) { + new Ajax.Request(OCApi.BASE_URL + "feeds", { + method: "get", + requestHeaders: this._requestHeaders(), + onSuccess: function(response){ + var json = response.responseText.evalJSON() + var feeds = [] + + json.feeds.each(function(feed) { + var feedCount = {} + feedCount.id = feed.id + feedCount.count = feed.unreadCount + feeds.push(feedCount) + }) + success(feeds) + }, + onFailure: failure + }) + }, + + //UPDATED 1.2.1 + getAllArticles: function(continuation, success, failure) { + this._getArticles( + "all", + Preferences.hideReadArticles() ? false : true, + continuation, + success, + failure + ) + }, + + //UPDATED 1.2.1 + getAllStarred: function(continuation, success, failure) { + this._getArticles( + "starred", + true, + continuation, + success, + failure + ) + }, + + //NOT SUPPORTED BY API + getAllShared: function(continuation, success, failure) { + failure() + }, + + //NOT SUPPORTED BY API + getAllFresh: function(continuation, success, failure) { + failure() + }, + + //NOT SUPPORTED BY API + getAllArchived: function(continuation, success, failure) { + failure() + }, + + //UPDATED 1.2.1 + getAllArticlesFor: function(id, continuation, success, failure) { + this._getArticles( + id, + Preferences.hideReadArticles() ? false : true, + continuation, + success, + failure + ) + }, + + //UPDATED 1.2.1 + _getArticles: function(id, exclude, continuation, success, failure) { + var self = this + + var parameters = { + batchSize: 40, + getRead: exclude + } + + if (id.toString().substr(0,7) === "folder/") + { + parameters.type = 1 + parameters.id = id.substr(7) + } + else if (id === "starred") + { + parameters.type = 2 + parameters.id = 0 + } + else if (id === "all") + { + parameters.type = 3 + parameters.id = 0 + } + else + { + parameters.type = 0 + parameters.id = id + } + + if(continuation) { + parameters.offset = continuation + } + + new Ajax.Request(OCApi.BASE_URL + "items", { + method: "get", + parameters: parameters, + requestHeaders: this._requestHeaders(), + onSuccess: function(response){ + var articles = JSON2.parse(response.responseText) + var lastArticleId = 0 + OCApi.NEWESTITEMID = 0 + + if(!continuation && articles.items[0] != undefined) + { + OCApi.NEWESTITEMID = articles.items[0].id + } + + //Do post-processing to conform articles to FeedSpider spec + articles.items.each(function(article) { + //Set article origin + article.origin = {streamId : article.feedId} + + //Set article content + article.content = {content: article.body} + + //Set article categories + article.categories = [] + if (!article.unread) + { + article.categories.push("/state/com.google/read") + } + + if (article.starred) + { + article.categories.push("/state/com.google/starred") + } + + //Set article link + article.alternate = [{ + type: "html", + href: article.url + }] + + //Set article timestamp + article.crawlTimeMsec = article.pubDate + "000" + + //Check article id + if (article.id < lastArticleId || lastArticleId == 0) + { + lastArticleId = article.id + } + }) + + //Store article hashes + if(continuation) + { + self._cacheArticleHashes(articles.items, true) + } + else + { + self._cacheArticleHashes(articles.items, false) + } + + //Load more articles (if there are more to load) + if(articles.items.length == parameters.batchSize) + { + success(articles.items, id, lastArticleId) + } + else + { + success(articles.items, id, false) + } + }, + onFailure: failure, + }) + }, + + //UPDATED 1.2.1 + markAllRead: function(id, success, failure) { + var parameters = { + newestItemId: OCApi.NEWESTITEMID + } + + if (id === "user/-/state/com.google/reading-list") + { + this._ajaxPut(parameters, OCApi.BASE_URL + "items/read", success, failure) + } + else if (id.toString().substr(0,7) === "folder/") + { + this._ajaxPut(parameters, OCApi.BASE_URL + "folders/" + id.substr(7) + "/read", success, failure) + } + else + { + this._ajaxPut(parameters, OCApi.BASE_URL + "feeds/" + id + "/read", success, 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(OCApi.BASE_URL + "search/items/ids", { + method: "get", + parameters: parameters, + requestHeaders: this._requestHeaders(), + onSuccess: this.searchItemsFound.bind(this, success, failure), + onFailure: failure + })*/ + 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(OCApi.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) + }*/ + failure() + }, + + //NOT SUPPORTED BY API + mapSearchResults: function(response) { + //console.log(response.responseText) + }, + + //UPDATED 1.2.1 + setArticleRead: function(articleId, subscriptionId, success, failure) { + this._ajaxPut({}, OCApi.BASE_URL + "items/" + articleId + "/read", success, failure) + }, + + //UPDATED 1.2.1 + setArticleNotRead: function(articleId, subscriptionId, success, failure, sticky) { + this._ajaxPut({}, OCApi.BASE_URL + "items/" + articleId + "/unread", success, failure) + }, + + //NOT SUPPORTED BY API + setArticleShared: function(articleId, subscriptionId, success, failure) { + /*this._editTag( + articleId, + subscriptionId, + 1, + null, + success, + failure + )*/ + failure() + }, + + //NOT SUPPORTED BY API + setArticleNotShared: function(articleId, subscriptionId, success, failure) { + /*this._editTag( + articleId, + subscriptionId, + null, + 1, + success, + failure + )*/ + failure() + }, + + //UPDATED 1.2.1 + setArticleStarred: function(articleId, subscriptionId, success, failure) { + var articleGuid = this._hashForArticle(articleId) + this._ajaxPut({}, OCApi.BASE_URL + "items/" + subscriptionId + "/" + articleGuid + "/star", success, failure) + }, + + //UPDATED 1.2.1 + setArticleNotStarred: function(articleId, subscriptionId, success, failure) { + var articleGuid = this._hashForArticle(articleId) + this._ajaxPut({}, OCApi.BASE_URL + "items/" + subscriptionId + "/" + articleGuid + "/unstar", success, failure) + }, + + //UPDATED 1.2.1 + _requestHeaders: function() { + return {Authorization:"Basic " + this.auth} + }, + + //UPDATED 1.2.1 + _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', "Basic " + this.auth) + 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 1.2.1 + _ajaxDelete: function(url, success, failure) { + try { + var req = new XMLHttpRequest() + req.open("DELETE", url, true) + req.setRequestHeader('Authorization', "Basic " + this.auth) + 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 1.2.1 + supportsAllArticles: function() { + return true + }, + + //UPDATED 1.2.1 + supportsArchived: function() { + return false + }, + + //UPDATED 1.2.1 + supportsFresh: function() { + return false + }, + + //UPDATED 1.2.1 + supportsStarred: function() { + return true + }, + + //UPDATED 1.2.1 + supportsShared: function() { + return false + }, + + //UPDATED 1.2.1 + supportsSearch: function() { + return false + }, + + //UPDATED 1.2.1 + supportsManualSort: function() { + return false + } +}) + +OCApi.BASE_URL = "" +OCApi.NEWESTITEMID = 0 \ No newline at end of file diff --git a/app/models/preferences.js b/app/models/preferences.js old mode 100644 new mode 100755 index 6f54ab9..c747da4 --- a/app/models/preferences.js +++ b/app/models/preferences.js @@ -18,6 +18,24 @@ Preferences = { INSTAPAPER_USERNAME: "q-instapaper-username", INSTAPAPER_PASSWORD: "r-instapaper-password", LEFTY_FRIENDLY: "s-lefty-friendly", + FEEDLY_SORT_ENGAGEMENT: "feedly-sort-engagement", + SHORTEN_URLS: "shorten-urls", + + isShortenURLs: function() { + return this.getCookie(this.SHORTEN_URLS, false) + }, + + setShortenURLs: function(isShortenURLs) { + this.setCookie(this.SHORTEN_URLS, isShortenURLs) + }, + + 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/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 index 1d1b1bf..037c602 --- 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/subscription.js b/app/models/subscription.js old mode 100644 new mode 100755 diff --git a/app/models/tor-api.js b/app/models/tor-api.js new file mode 100755 index 0000000..73e79ac --- /dev/null +++ b/app/models/tor-api.js @@ -0,0 +1,538 @@ +var TorApi = 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://theoldreader.com/reader/api/0/accounts/ClientLogin", { + method: "post", + parameters: {client: "FeedSpider", accountType: "HOSTED_OR_GOOGLE", service: "reader", Email: credentials.email, Passwd: credentials.password}, + onSuccess: authSuccess, + onFailure: failure + }) + }, + + getTags: function(success, failure) { + new Ajax.Request(TorApi.BASE_URL + "tag/list", { + method: "get", + parameters: {output: "json"}, + requestHeaders: this._requestHeaders(), + onFailure: failure, + onSuccess: function(response) {success(response.responseText.evalJSON().tags)} + }) + }, + + getSortOrder: function(success, failure) { + new Ajax.Request(TorApi.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) + } + }) + }, + + 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(TorApi.BASE_URL + "preference/stream/set", { + method: "post", + parameters: parameters, + requestHeaders: this._requestHeaders() + }) + }.bind(this)) + }, + + 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(TorApi.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)) + } + }, + + removeLabel: function(folder) { + this._getEditToken(function(token) { + var parameters = { + T: token, + s: folder.id, + t: folder.title + } + + new Ajax.Request(TorApi.BASE_URL + "disable-tag", { + method: "post", + parameters: parameters, + requestHeaders: this._requestHeaders(), + onSuccess: function() {Mojo.Event.send(document, "FolderDeleted", {id: folder.id})} + }) + }.bind(this)) + }, + + searchSubscriptions: function(query, success, failure) { + var self = this + + new Ajax.Request(TorApi.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) + } + }) + }, + + addSubscription: function(url, success, failure) { + this._getEditToken(function(token) { + var parameters = { + T: token, + quickadd: url + } + + new Ajax.Request(TorApi.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)) + }, + + getAllSubscriptions: function(success, failure) { + var self = this + + new Ajax.Request(TorApi.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) + } + }) + }, + + cacheTitles: function(subscriptions) { + var self = this + self.titles = {} + + subscriptions.each(function(subscription) { + self.titles[subscription.id] = subscription.title + }) + }, + + titleFor: function(id) { + return this.titles[id] + }, + + getUnreadCounts: function(success, failure) { + new Ajax.Request(TorApi.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) + } + } + }) + }, + + getAllArticles: function(continuation, success, failure) { + this._getArticles( + "user/-/state/com.google/reading-list", + Preferences.hideReadArticles() ? "user/-/state/com.google/read" : null, + continuation, + success, + failure + ) + }, + + getAllStarred: function(continuation, success, failure) { + this._getArticles( + "user/-/state/com.google/starred", + null, + continuation, + success, + failure + ) + }, + + getAllShared: function(continuation, success, failure) { + this._getArticles( + "user/-/state/com.google/broadcast", + null, + continuation, + success, + failure + ) + }, + + //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, + Preferences.hideReadArticles() ? "user/-/state/com.google/read" : null, + continuation, + success, + failure + ) + }, + + //UPDATED 1.1.3 + _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(TorApi.BASE_URL2 + escape(id), { + method: "get", + parameters: parameters, + requestHeaders: this._requestHeaders(), + onFailure: failure, + onSuccess: function(response) { + var articles = JSON2.parse(response.responseText) + success(articles.items, articles.id, articles.continuation) + } + }) + + // NOTE: This is the original Google Reader API Logic. The Old Reader API does not properly support using this method + // for accessing "feeds of feeds" such as the all items feed, or "all" feeds within a folder. These must be accessed using + // the Atom feed (https://theoldreader.com/reader/atom/) instead. Once The Old Reader fixes their API, I will look into + // re-implementing the original logic for consistency's sake. + /* + new Ajax.Request(TorApi.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) { + this._getEditToken( + function(token) { + var parameters = { + T: token, + s: id + } + + new Ajax.Request(TorApi.BASE_URL + "mark-all-as-read", { + method: "post", + parameters: parameters, + requestHeaders: this._requestHeaders(), + onSuccess: success, + onFailure: failure + }) + }.bind(this), + + failure + ) + }, + + search: function(query, id, success, failure) { + var parameters = { + q: query, + num: 50, + output: "json" + } + + if(id) { + parameters.s = id + } + + new Ajax.Request(TorApi.BASE_URL + "search/items/ids", { + method: "get", + parameters: parameters, + requestHeaders: this._requestHeaders(), + onSuccess: this.searchItemsFound.bind(this, success, failure), + onFailure: failure + }) + }, + + 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(TorApi.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) + } + }, + + mapSearchResults: function(response) { + console.log(response.responseText) + }, + + setArticleRead: function(articleId, subscriptionId, success, failure) { + this._editTag( + articleId, + subscriptionId, + "user/-/state/com.google/read", + "user/-/state/com.google/kept-unread", + success, + failure + ) + }, + + setArticleNotRead: function(articleId, subscriptionId, success, failure, sticky) { + this._editTag( + articleId, + subscriptionId, + sticky ? "user/-/state/com.google/kept-unread" : null, + "user/-/state/com.google/read", + success, + failure + ) + }, + + setArticleShared: function(articleId, subscriptionId, success, failure) { + /*this._editTag( + articleId, + subscriptionId, + "user/-/state/com.google/broadcast", + null, + success, + failure + )*/ + }, + + setArticleNotShared: function(articleId, subscriptionId, success, failure) { + /*this._editTag( + articleId, + subscriptionId, + null, + "user/-/state/com.google/broadcast", + success, + failure + )*/ + }, + + setArticleStarred: function(articleId, subscriptionId, success, failure) { + this._editTag( + articleId, + subscriptionId, + "user/-/state/com.google/starred", + null, + success, + failure + ) + }, + + setArticleNotStarred: function(articleId, subscriptionId, success, failure) { + this._editTag( + articleId, + subscriptionId, + null, + "user/-/state/com.google/starred", + success, + failure + ) + }, + + _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(TorApi.BASE_URL + "edit-tag", { + method: "post", + parameters: parameters, + requestHeaders: this._requestHeaders(), + onSuccess: success, + onFailure: failure + }) + }.bind(this), + + failure + ) + }, + + _requestHeaders: function() { + return {Authorization:"GoogleLogin auth=" + this.auth} + }, + + _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(TorApi.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) + }) + } + }, + + supportsAllArticles: function() { + return true + }, + + //UPDATED 1.2.0 + supportsArchived: function() { + return false + }, + + //UPDATED 1.2.0 + supportsFresh: function() { + return false + }, + + supportsStarred: function() { + return true + }, + + supportsShared: function() { + return false + }, + + supportsSearch: function() { + return false + }, + + //UPDATED 0.9.5 + supportsManualSort: function() { + return true + } +}) + +TorApi.BASE_URL = "https://theoldreader.com/reader/api/0/" +TorApi.BASE_URL2 = "https://theoldreader.com/reader/atom/" diff --git a/app/models/ttrss-api.js b/app/models/ttrss-api.js new file mode 100755 index 0000000..298c5f2 --- /dev/null +++ b/app/models/ttrss-api.js @@ -0,0 +1,705 @@ +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.2 + 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){ + //Post-Processing + var tags = response.responseText.evalJSON() + tags.content.each(function(tag) { + tag.sortid = tag.order_id + }) + + success(tags.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.2 + 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() + 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 + }) + }, + + //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.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( + id, + Preferences.hideReadArticles() ? "unread" : "all_articles", + continuation, + success, + failure + ) + }, + + //UPDATED 1.1.3 + _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 != -3 && + id != -2 && + id != 0 && + Preferences.isOldestFirst()) { + parameters.order_by = "date_reverse" + } else { + parameters.order_by = "feed_dates" + } + + if(continuation) { + parameters.skip = continuation + } + + if(exclude) { + parameters.view_mode = exclude + } + + if (id.constructor == String) + { + parameters.is_cat = true + } + + new Ajax.Request(TTRSSApi.BASE_URL, { + method: "post", + postBody: JSON.stringify(parameters), + onSuccess: function(response){ + var articles = JSON2.parse(response.responseText) + + //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 == parameters.limit) + { + if(continuation) + { + continuation = continuation + parameters.limit + } + else + { + continuation = parameters.limit + } + 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.2.0 + supportsArchived: function() { + return true + }, + + //UPDATED 1.2.0 + supportsFresh: 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/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..9ca1ebb --- a/app/views/credentials/credentials-scene.html +++ b/app/views/credentials/credentials-scene.html @@ -1,11 +1,22 @@
- +
-
+
+
+
+
+
+
+
+
+
+
+ +
@@ -18,7 +29,7 @@
-
+
@@ -31,6 +42,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ diff --git a/app/views/dashboard/dashboard-scene.html b/app/views/dashboard/dashboard-scene.html old mode 100644 new mode 100755 index 041671f..1d726d4 --- a/app/views/dashboard/dashboard-scene.html +++ b/app/views/dashboard/dashboard-scene.html @@ -2,7 +2,7 @@
0
-
+
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..0e5fe13 --- a/app/views/help/help-scene.html +++ b/app/views/help/help-scene.html @@ -12,18 +12,234 @@
-
Thank you for your purchase. If you have questions or suggestions you can reach us at:
+
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
+
+
1.2.6
+
    +
  • added support for updated InoReader api
  • +
+
+ +
+
1.2.5
+
    +
  • add MoboReader to sharing options (thanks to Garfonso)
  • +
+
+ +
+
1.2.4
+
    +
  • fixed issue with improper cleanup call being made on preferences page
  • +
+
+ +
+
1.2.3
+
    +
  • re-added support for Spaz (available from Mobo Mobile)
  • +
  • added option to shorten URL for social media posts
  • +
  • fixed virtual keyboard not appearing for oauth login
  • +
+
+ +
+
1.2.2
+
    +
  • added Tiny Font option
  • +
+
+ +
+
1.2.1
+
    +
  • added support for OwnCloud News
  • +
+
+ +
+
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
+
    +
  • replaced JSON parser to stop feeds from hanging
  • +
  • fixed bug in TT-RSS regarding badly sorted aggregate feeds
  • +
+
+ +
+
1.1.2
+
    +
  • streamlined apis
  • +
  • fixed bug in Feedly regarding starring and un-starring articles
  • +
+
+ +
+
1.1.1
+
    +
  • bugfixes for Tiny Tiny RSS
  • +
+
+ +
+
1.1.0
+
    +
  • added support for Tiny Tiny RSS
  • +
+
+ +
+
1.0.3
+
    +
  • fixed bugs related to Feedly login in webOS 1.4.5
  • +
+
+ +
+
1.0.2
+
    +
  • version bump for App Catalog
  • +
+
+ +
+
1.0.1
+
    +
  • added support for BazQux Reader
  • +
  • re-ordered service choices for login
  • +
+
+ +
+
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
+
    +
  • 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
+
    +
  • 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
+
    +
  • 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
+
    +
  • bugfixes
  • +
+
+ +
+
0.9.0
+
    +
  • added support for InoReader
  • +
+
+ +
+
0.8.5
+
    +
  • new icon (Thanks RumoredNow!)
  • +
  • added support for Glimpse
  • +
  • added support for Quick Post
  • +
+
+ +
+
0.8.4
+
    +
  • added Dark Theme
  • +
  • removed Metrix tracking (service no longer available)
  • +
+
+ +
+
0.8.3
+
    +
  • changed name from The Old Feeder to FeedSpider
  • +
  • fixed missing Notification Bar icon on phones
  • +
+
+ +
+
0.8.2
+
    +
  • disable the search feature due to API incompatability
  • +
+
+ +
+
0.8.1
+
    +
  • add Project Macaw to sharing options
  • +
  • fix bug when displaying the "All" feed inside a folder
  • +
+
+ +
+
0.8.0
+
    +
  • change app to support The Old Reader after Google Reader shutdown
  • +
+
+
0.7.5
    diff --git a/app/views/home/divider.html b/app/views/home/divider.html old mode 100644 new mode 100755 diff --git a/app/views/home/home-scene.html b/app/views/home/home-scene.html old mode 100644 new mode 100755 index 2b9a650..d8cc7e8 --- a/app/views/home/home-scene.html +++ b/app/views/home/home-scene.html @@ -17,7 +17,7 @@
    - +
    diff --git a/app/views/home/source.html b/app/views/home/source.html old mode 100644 new mode 100755 diff --git a/app/views/instapaper-credentials/instapaper-credentials-scene.html b/app/views/instapaper-credentials/instapaper-credentials-scene.html old mode 100644 new mode 100755 diff --git a/app/views/login/login-scene.html b/app/views/login/login-scene.html old mode 100644 new mode 100755 diff --git a/app/views/metrix/displayBulletin-dialog.html b/app/views/metrix/displayBulletin-dialog.html deleted file mode 100755 index 5878b5c..0000000 --- a/app/views/metrix/displayBulletin-dialog.html +++ /dev/null @@ -1 +0,0 @@ -
    Bulletin Title
    Message Goes Here
    Close
    Snooze
    \ No newline at end of file diff --git a/app/views/notification-feeds/feed.html b/app/views/notification-feeds/feed.html old mode 100644 new mode 100755 diff --git a/app/views/notification-feeds/notification-feeds-scene.html b/app/views/notification-feeds/notification-feeds-scene.html old mode 100644 new mode 100755 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 0000000..5264ade Binary files /dev/null and b/app/views/oauth/progress-indicator.png differ diff --git a/app/views/preferences/preferences-scene.html b/app/views/preferences/preferences-scene.html old mode 100644 new mode 100755 index d43a247..f885003 --- a/app/views/preferences/preferences-scene.html +++ b/app/views/preferences/preferences-scene.html @@ -122,6 +122,22 @@
+
+
Sharing
+
+
+
+ + + + + +
+
+
+
+
+
Notifications
@@ -143,6 +159,22 @@
+
+
Feedly Options
+
+
+
+ + + + + +
+
+
+
+
+