diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..86c8a7d --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.swp +*.ipk +*.*~ +dropbox-auth.js diff --git a/app/LICENSE-2.0.txt b/app/LICENSE-2.0.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/app/LICENSE-2.0.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/app/appinfo.json b/app/appinfo.json new file mode 100644 index 0000000..97e0d55 --- /dev/null +++ b/app/appinfo.json @@ -0,0 +1,22 @@ +{ + "id": "com.monkeystew.todotxtenyo.beta", + "version": "0.1.0", + "vendor": "Monkeystew", + "type": "web", + "main": "index.html", + "title": "Todo.txt Enyo beta", + "icon": "icon.png", + "uiRevision": 2, + "universalSearch": { + "search": { + "displayName":"Todo.txt Enyo", + "url":"com.monkeystew.todotxtenyo.beta", + "launchParam":"search" + }, + "action": { + "displayName":"New todo.txt task", + "url":"com.monkeystew.todotxtenyo.beta", + "launchParam":"addTodoText" + } + } +} diff --git a/app/depends.js b/app/depends.js new file mode 100644 index 0000000..7cbadb1 --- /dev/null +++ b/app/depends.js @@ -0,0 +1,11 @@ +enyo.depends( + "source/TodoTxt.js", + "source/TodoList.js", + "source/TodoEdit.js", + "source/TodoPrefs.js", + "source/dropbox-auth.js", + "source/Dropbox.js", + "source/oauth.js", + "source/sha1.js", + "source/styles.css" +); diff --git a/app/framework_config.json b/app/framework_config.json new file mode 100644 index 0000000..9624b26 --- /dev/null +++ b/app/framework_config.json @@ -0,0 +1,3 @@ +{ + "logLevel": 99 +} diff --git a/app/icon.png b/app/icon.png new file mode 100644 index 0000000..abc3357 Binary files /dev/null and b/app/icon.png differ diff --git a/app/images/menu-icon-new.png b/app/images/menu-icon-new.png new file mode 100644 index 0000000..46919d9 Binary files /dev/null and b/app/images/menu-icon-new.png differ diff --git a/app/images/menu-icon-prefs.png b/app/images/menu-icon-prefs.png new file mode 100644 index 0000000..f703d7e Binary files /dev/null and b/app/images/menu-icon-prefs.png differ diff --git a/app/images/menu-icon-sync.png b/app/images/menu-icon-sync.png new file mode 100644 index 0000000..31c508c Binary files /dev/null and b/app/images/menu-icon-sync.png differ diff --git a/app/index.html b/app/index.html new file mode 100644 index 0000000..bb93b50 --- /dev/null +++ b/app/index.html @@ -0,0 +1,25 @@ + + + + Todo.txt Enyo + + + + + + + + diff --git a/app/mock/todoTxt_getConn.json b/app/mock/todoTxt_getConn.json new file mode 100644 index 0000000..b1c3a51 --- /dev/null +++ b/app/mock/todoTxt_getConn.json @@ -0,0 +1,4 @@ +{ +"returnValue": true, +"isInternetConnectionAvailable": false +} diff --git a/app/mock/todoTxt_makeDir.json b/app/mock/todoTxt_makeDir.json new file mode 100644 index 0000000..c44f3a7 --- /dev/null +++ b/app/mock/todoTxt_makeDir.json @@ -0,0 +1 @@ +{ "returnValue": true } diff --git a/app/mock/todoTxt_readFile.json b/app/mock/todoTxt_readFile.json new file mode 100644 index 0000000..835a97b --- /dev/null +++ b/app/mock/todoTxt_readFile.json @@ -0,0 +1,5 @@ +{ "returnValue": true, + "path": "/dummy", + "content": "x TEST ENTRY 2\n(C) Alpha TEST ENTRY 1\n(A) Beta TEST ENTRY 3\nTEST ENTRY 4\n(B) Some TEST ENTRY 5\n+TestProject1 @Bug this is my next bug for testing\n+someotherp testing some other one\n" + +} diff --git a/app/mock/todoTxt_writeFile.json b/app/mock/todoTxt_writeFile.json new file mode 100644 index 0000000..b463932 --- /dev/null +++ b/app/mock/todoTxt_writeFile.json @@ -0,0 +1,5 @@ +{ +"returnValue": true, +"path": "no dummy", +"bytes": "no bytes either" +} diff --git a/app/source/Dropbox.js b/app/source/Dropbox.js new file mode 100644 index 0000000..48158bd --- /dev/null +++ b/app/source/Dropbox.js @@ -0,0 +1,223 @@ +/* + * Copyright 2012 Morgan McMillian + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +enyo.kind({ + name: "Dropbox", + kind: enyo.Control, + published: { + token: "", + tokenSecret: "", + requestToken: "", + requestSecret: "", + authorized: false + }, + events: { + "onAuthSuccess": "", + "onAuthFailure": "", + "onGetFileSuccess": "", + "onGetFileFailure": "", + "onPutFileSuccess": "", + "onPutFileFailure": "", + "onMetadataSuccess": "", + "onMetadataFailure": "" + }, + components: [ + {name: "authPopup", kind: "ModalDialog", components: [ + {content: "Please click DONE to complete the process."}, + {kind:"Button", caption:"DONE", onclick:"requestAccessToken"} + ]}, + {name: "launch", kind: "PalmService", + service: "palm://com.palm.applicationManager", + method: "launch", onSuccess: "launchSuccess", + onFailure: "launchFailed" + }, + {name: "webSrv", kind: "WebService" } + ], + + create: function() { + this.inherited(arguments); + + this.contentURL = "https://api-content.dropbox.com/1"; + this.apiURL = "https://api.dropbox.com/1"; + this.mainURL = "https://www.dropbox.com/1"; + + /* valid values are either "dropbox" or "sandbox" + * + * See the following URL for the additional details. + * https://www.dropbox.com/developers/reference/api + */ + this.root = "dropbox"; + + /* The following two properties are contained in the + * dropbox-auth.js file. Dropbox will NOT work unless you + * update the file to contain the correct values. + */ + this.consumerKey = consumerKey; + this.consumerSecret = consumerSecret; + }, + + /* url - URL needed for a web service call + * params - (optional) Object containing paramters for the request + * body - (required for files_put) request body to be submitted + * method - method required for the request (GET, POST, PUT) + * onSuccess - callback function when web service call is successful + * onFailure - callback function when web service call has failed + */ + dropboxCall: function(url, params, body, method, onSuccess, onFailure) { + + var token = (this.authorized) ? this.token : this.requestToken; + var secret = (this.authorized) ? this.tokenSecret : this.requestSecret; + + var accessor = { + consumerKey: this.consumerKey, + consumerSecret: this.consumerSecret, + token: token, + tokenSecret: secret + }; + + var message = { + action: url, + method: method, + parameters: params + }; + + OAuth.completeRequest(message, accessor); + OAuth.SignatureMethod.sign(message, accessor); + var post = OAuth.formEncode(message.parameters); + + if (url.match(/files_put/)) { + requrl = url + "?" + post; + this.$.webSrv.setContentType("text/plain"); + } else { + body = post; + requrl = url; + } + + this.$.webSrv.setUrl(requrl); + this.$.webSrv.setMethod(method); + this.$.webSrv.onSuccess = onSuccess; + this.$.webSrv.onFailure = onFailure; + this.$.webSrv.call(body); + }, + + /* ******************** OAuth Functions ******************** */ + + validateAccess: function() { + if (this.token.length > 0 && this.tokenSecret.length > 0) { + var url = this.apiURL + "/account/info"; + this.dropboxCall(url, {}, "", "POST", + "doAuthSuccess", "doAuthFailure"); + } else { + this.$.authPopup.openAtCenter(); + this.startTokenRequest(); + } + }, + + startTokenRequest: function() { + var url = this.apiURL + "/oauth/request_token"; + this.dropboxCall(url, {}, "", "POST", + "processRequestToken", "doAuthFailure"); + }, + + processRequestToken: function(inSender, inResponse, inRequest) { + if (inResponse) { + var url = this.mainURL + "/oauth/authorize?" + inResponse; + + var tokens = inResponse.split("&"); + this.requestSecret = tokens[0].split("=")[1]; + this.requestToken = tokens[1].split("=")[1]; + + console.log("...launching browser window..."); + this.$.launch.call({ + "id": "com.palm.app.browser", + "params": {"target": url} + }); + } + }, + + requestAccessToken: function() { + var url = this.apiURL + "/oauth/access_token"; + this.dropboxCall(url, {}, "", "POST", + "processAccessToken", "doAuthFailure"); + }, + + processAccessToken: function(inSender, inResponse, inRequest) { + var tokens = inResponse.split("&"); + var accessSecret = tokens[0].split("=")[1]; + var accessToken = tokens[1].split("=")[1]; + + this.token = accessToken; + this.tokenSecret = accessSecret; + console.log("..authorized.."); + //console.log(this.token); + //console.log(this.tokenSecret); + this.authorized = true; + this.doAuthSuccess(); + this.$.authPopup.close(); + }, + + + /* ******************** Dropbox REST API ******************** */ + + + /* file - Relative path of file to be uploaded. + * data - File contents to be uploaded. + * params - (optional) Object containing additional parameters. + * + * Returns the metadata for the uploaded file. + * + * See the following URL for the additional details + * https://www.dropbox.com/developers/reference/api#files_put + */ + putFile: function(file, data, params) { + var url = this.contentURL+"/files_put/"+this.root+file; + this.dropboxCall(url, params, data, "PUT", + "doPutFileSuccess", "doPutFileFailure"); + }, + + /* file - Relative path of file to be retrieved + * rev - (optional) File revision to be retrieved. Defaults to + * the most recent revision. + * + * Returns the file contents at the requested revision. The + * HTTP response contains the content metadata in JSON format + * within an x-dropbox-metadata header. + * + * See the following URL for the additional details. + * https://www.dropbox.com/developers/reference/api#files_put + */ + getFile: function(file, rev) { + var url = this.contentURL+"/files/"+this.root+file; + var params = { rev: rev }; + this.dropboxCall(url, params, "", "GET", + "doGetFileSuccess", "doGetFileFailure"); + }, + + /* path - path to a file or folder + * params - (optional) Object containing additional parameters. + * + * Returns metadata for requested file or folder + * + * See the following URL for the additional details + * https://www.dropbox.com/developers/reference/api#metadata + */ + getMetadata: function(path, params) { + var url = this.apiURL+"/metadata/"+this.root+path; + this.dropboxCall(url, params, "", "GET", + "doMetadataSuccess", "doMetadataFailure"); + } + +}); diff --git a/app/source/TodoEdit.js b/app/source/TodoEdit.js new file mode 100644 index 0000000..dc7b24a --- /dev/null +++ b/app/source/TodoEdit.js @@ -0,0 +1,40 @@ +/* + * Copyright 2012 Morgan McMillian + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +enyo.kind({ + name: "TodoEdit", + kind: enyo.VFlexBox, + events: { + "onClose": "", + "onSave": "" + }, + components: [ + + {flex: 1, kind: "Scroller", components: [ + {kind: "RichText", name: "tododetail"}, + ]}, + {name: "editToolbar", kind: "Toolbar", pack: "justify", className: "enyo-toolbar-light", + components: [ + {flex: 1, kind: "Button", caption: "Cancel", + onclick: "doClose", align: "left"}, + {flex: 1, kind: "Button", caption: "Save", + onclick: "doSave", align: "right"} + ] + } + + ] + +}); diff --git a/app/source/TodoList.js b/app/source/TodoList.js new file mode 100644 index 0000000..b922aff --- /dev/null +++ b/app/source/TodoList.js @@ -0,0 +1,363 @@ +/* + * Copyright 2012 Morgan McMillian + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +enyo.kind({ + name: "TodoList", + kind: enyo.VFlexBox, + published: { + replaceItem: false, + completeItem: false, + sortOrder: "pri", + cacheChanges: "NO", + searchFilter: null, + sortedList: undefined + }, + events: { + "onEdit": "", + "onPrefs": "", + "onReload": "" + }, + components: [ + + {name: "todoPopup", kind: "ModalDialog", components: [ + {kind: "Button", caption: "Complete", onclick: "completeTodoItem"}, + {kind: "Button", caption: "Prioritize", onclick: "showPriList"}, + {kind: "Button", caption: "Update", onclick: "updateTodoItem"}, + {kind: "Button", caption: "Delete", onclick: "deleteTodoItem"}, + {kind: "Button", caption: "Dismiss", onclick: "closePopup"} + ]}, + {name: "completedPopup", kind: "ModalDialog", components: [ + {kind: "Button", caption: "Undo Complete", onclick: "undoCompleteTodoItem"}, + {kind: "Button", caption: "Delete", onclick: "deleteTodoItem"}, + {kind: "Button", caption: "Dismiss", onclick: "closePopup"} + ]}, + {name: "priorityPopup", kind: "ModalDialog", components: [ + {kind: "HtmlContent", content: "Select priority"}, + {kind: "RadioGroup", name: "priGroup", onChange: "setPriority", components: [ + {caption: "-", value: "-"}, + {caption: "A", value: "A"}, + {caption: "B", value: "B"}, + {caption: "C", value: "C"}, + {caption: "D", value: "D"}, + {caption: "E", value: "E"} + ]}, + {kind: "Button", caption: "Dismiss", onclick: "closePopup"} + ]}, + {kind: "SearchInput", name: "searchbox", onchange: "searchList", onCancel: "clearSearch"}, + {flex: 1, kind: "Scroller", name: "scroller", components: [ + {kind: "VirtualRepeater", name: "todoList", + onSetupRow: "getTodoList", onclick: "todoTap", + components: [ + {kind: "SwipeableItem", onConfirm: "deleteTodo", + layoutKind: enyo.HFlexLayout, tapHighlight: true, name: "todoRow", + components: [ + {name: "lineNum", className: "left-col"}, + {name: "priority", className: "mid-col"}, + {flex: 1, name: "todoItem"} + ] + } + ] + } + ]}, + {name: "listToolbar", kind: "Toolbar", pack: "justify", className: "enyo-toolbar-light", + components: [ + {flex: 1}, + //{kind: "Button", caption: "Filter"}, + //TODO: consider moving to the top + {kind: "ListSelector", value: "pri", + onChange: "changeSort", items: [ + {caption: "Priority", value: "pri"}, + {caption: "ID Ascending", value: "asc"}, + {caption: "ID Descending", value: "dsc"}, + {caption: "Text (A-Z)", value: "az"} + ]}, + {icon: "images/menu-icon-new.png", + onclick: "doEdit", align: "right"}, + {icon: "images/menu-icon-sync.png", + onclick: "doReload", align: "right"}, + {icon: "images/menu-icon-prefs.png", + onclick: "doPrefs", align: "right"} + ] + }, + + ], + + getTodoList: function(inSender, inIndex) { + + if (this.sortedList == undefined) { + this.sortedList = this.owner.todoList.slice(0); + this.changeSort(null, this.sortOrder, null); + } + + var r = this.sortedList[inIndex]; + + if (r) { + var filtered = false; + var s = r.detail.replace(/^x\s/,""); + var s = s.replace(/^\([A-E]\)\s/,""); + var filter = new RegExp(this.searchFilter, "i"); + if (this.search && s.match(filter)) { + filtered = true; + } else if (!this.search) { + filtered = true; + } + if (this.owner.preferences["lineNumbers"]) { + this.$.lineNum.setContent(r.num); + } + this.$.priority.setContent(r.pri); + var pri = r.detail.replace(/^\(([A-E])\)\s.*$/,"$1"); + if (r.pri == "(A) ") this.$.priority.addClass("pri-a"); + if (r.pri == "(B) ") this.$.priority.addClass("pri-b"); + if (r.pri == "(C) ") this.$.priority.addClass("pri-c"); + this.$.todoItem.setContent(s); + if (r.detail.match(/^x\s/)) { + this.$.todoItem.addStyles("text-decoration:line-through;"); + this.$.todoItem.addClass("completed-item"); + } + if (inIndex == this.selectedIndex) { + this.$.todoRow.addClass("selected-item"); + } + if (!filtered) { + this.$.todoRow.hide(); + } + return true; + } + }, + + todoTap: function(inSender, inEvent) { + this.selectedIndex = inEvent.rowIndex; + if (this.selectedIndex != undefined) { + this.selectedId = this.sortedList[this.selectedIndex].num-1; + } + var r = this.sortedList[inEvent.rowIndex]; + + if (r) { + var completed = r.detail.match(/^x\s/); + var quick = this.owner.preferences["quickComplete"]; + if (completed) { + this.$.completedPopup.openAtCenter(); + } else if (quick) { + this.completeTodoItem(); + } else { + this.$.todoPopup.openAtCenter(); + } + } + this.$.todoList.render(); + }, + + completeTodoItem: function() { + this.completeItem = true; + var dfmt = new enyo.g11n.DateFmt({date:"yyyy-MM-dd"}); + this.cacheChanges = "START"; + this.clearPriority(); + this.owner.$.editView.$.tododetail.setValue( + "x " + dfmt.format(new Date()) + " " + + this.owner.todoList[this.selectedId].detail + ); + this.replaceItem = true; + this.cacheChanges = "COMMIT"; + this.owner.addTodo(); + this.completeItem = false; + this.$.todoPopup.close(); + }, + + undoCompleteTodoItem: function() { + this.completeItem = true; + this.owner.$.editView.$.tododetail.setValue( + this.owner.todoList[this.selectedId].detail.replace(/^x\s[0-9]{4}-[0-9]{2}-[0-9]{2}\s/,"") + ); + this.replaceItem = true; + this.owner.addTodo(); + this.completeItem = false; + this.closePopup(); + }, + + updateTodoItem: function() { + this.owner.$.editView.$.tododetail.setValue(this.owner.todoList[this.selectedId].detail); + this.replaceItem = true; + this.owner.showEditView(); + this.$.todoPopup.close(); + }, + + addTodo: function() { + var task = new Object(); + task.num = this.owner.todoList.length + 1; + task.pri = null; + task.detail = this.owner.$.editView.$.tododetail.getValue(); + task.detail = task.detail.replace(/(<\/?[A-Za-z][A-Za-z0-9]*>)+/g," "); + if (this.owner.preferences["dateTasks"] && !this.completeItem) { + var dfmt = new enyo.g11n.DateFmt({date:"yyyy-MM-dd"}); + task.detail = dfmt.format(new Date()) + " " + task.detail; + } + if (this.owner.preferences["storage"] != "none" && this.cacheChanges != "YES" && this.cacheChanges != "COMMIT") { + console.log("saving backup"); + this.owner.saveFile( + this.owner.preferences["filepath"]+".bak", this.owner.todoList); + if (this.cacheChanges == "START") { + this.cacheChanges = "YES" + } + } + if (this.replaceItem) { + this.owner.todoList[this.selectedId].detail = task.detail; + this.owner.todoList[this.selectedId].pri = task.detail.match(/^\([A-E]\)\s/); + this.replaceItem = false; + } else { + this.owner.todoList.push(task); + } + this.listRefresh(); + if (this.owner.preferences["storage"] != "none" && this.cacheChanges != "START" && this.cacheChanges != "YES") { + console.log("saving list"); + this.owner.saveFile( + this.owner.preferences["filepath"], this.owner.todoList); + if (this.cacheChanges == "COMMIT") { + this.cacheChanges = "NO" + } + } + this.owner.closeView(); + }, + + deleteTodoItem: function() { + this.deleteTodo(null, this.selectedId); + this.closePopup(); + }, + + deleteTodo: function(inSender, inIndex) { + if (this.owner.preferences["storage"] != "none") { + this.owner.saveFile( + this.owner.preferences["filepath"]+".bak", this.owner.todoList); + } + this.owner.todoList.splice(inIndex, 1); + this.listRefresh(); + if (this.owner.preferences["storage"] != "none") { + this.owner.saveFile( + this.owner.preferences["filepath"], this.owner.todoList); + } + }, + + closePopup: function() { + this.$.todoPopup.close(); + this.$.completedPopup.close(); + this.$.priorityPopup.close(); + this.selectedIndex = null; + this.selectedId = null; + this.$.todoList.render(); + }, + + showPriList: function() { + var r = this.sortedList[this.selectedIndex]; + + this.$.todoPopup.close(); + this.$.priorityPopup.openAtCenter(); + if (r.detail.match(/^\(([A-E])\)\s/)) { + var pri = r.detail.replace(/^\(([A-E])\)\s.*$/,"$1"); + this.$.priGroup.setValue(pri); + } else { + this.$.priGroup.setValue(pri); + } + }, + + setPriority: function(inSender) { + var val = inSender.getValue(); + this.clearPriority(); + if (val.match(/[A-E]/)) { + this.owner.$.editView.$.tododetail.setValue( + "(" + val + ") " + this.owner.todoList[this.selectedId].detail); + this.replaceItem = true; + this.addTodo(); + } + this.$.priorityPopup.close(); + }, + + clearPriority: function() { + this.owner.$.editView.$.tododetail.setValue( + this.owner.todoList[this.selectedId].detail.replace(/^\([A-E]\)\s/,"") + ); + this.replaceItem = true; + this.owner.addTodo(); + }, + + searchList: function(inSender) { + this.searchFilter = inSender.getValue(); + this.searchFilter = this.searchFilter.replace(/\+/,"\\+"); + //console.log(this.searchFilter); + this.search = true; + this.$.scroller.scrollIntoView(0,0); + this.$.todoList.render(); + }, + + clearSearch: function() { + this.searchFilter = null; + this.search = false; + this.$.todoList.render(); + }, + + changeSort: function(inSender, inValue, inOldValue) { + this.sortOrder = inValue; + if (inValue == "pri") { + this.sortedList.sort(function (a,b) { + if (!a["pri"] && a["detail"].match(/^x\s/)) { + checka = "-"; + } else if (!a["pri"]) { + checka = "+"; + } else { + checka = a["pri"]; + } + if (!b["pri"] && b["detail"].match(/^x\s/)) { + checkb = "-"; + } else if (!b["pri"]) { + checkb = "+"; + } else { + checkb = b["pri"]; + } + if (checka < checkb) return -1; + if (checka > checkb) return 1; + return 0; + }); + this.$.todoList.render(); + } else if (inValue == "asc") { + this.sortedList.sort(function (a,b) { + if (a["num"] < b["num"]) return -1; + if (a["num"] > b["num"]) return 1; + return 0; + }); + this.$.todoList.render(); + } else if (inValue == "dsc") { + this.sortedList.sort(function (a,b) { + if (a["num"] > b["num"]) return -1; + if (a["num"] < b["num"]) return 1; + return 0; + }); + this.$.todoList.render(); + } else if (inValue == "az") { + this.sortedList.sort(function (a,b) { + var checka = a["detail"].replace(/^\([A-E]\)\s/,""); + var checkb = b["detail"].replace(/^\([A-E]\)\s/,""); + if (checka < checkb) return -1; + if (checka > checkb) return 1; + return 0; + }); + this.$.todoList.render(); + } else { + console.log("wait what?"); + } + }, + + listRefresh: function() { + this.sortedList = undefined; + this.$.todoList.render(); + } + +}); diff --git a/app/source/TodoPrefs.js b/app/source/TodoPrefs.js new file mode 100644 index 0000000..8149bde --- /dev/null +++ b/app/source/TodoPrefs.js @@ -0,0 +1,193 @@ +/* + * Copyright 2012 Morgan McMillian + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +enyo.kind({ + name: "TodoPrefs", + kind: enyo.VFlexBox, + events: { + "onClose": "", + "onAbout": "", + "onPrefReset": "" + }, + components: [ + + {flex: 1, kind: "Scroller", components: [ + {kind: "VFlexBox", className: "box-center", components: [ + {kind: "RowGroup", caption: "todo.txt", components: [ + {kind: "Item", layoutKind: "HFlexLayout", + components: [ + {content: "Show line numbers"}, + {kind: "Spacer"}, + {kind: "CheckBox", name: "lineNumbers", + preferenceProperty: "lineNumbers", + onChange: "setPreference"} + ]}, + {kind: "Item", layoutKind: "HFlexLayout", + components: [ + {content: "Date new tasks"}, + {kind: "Spacer"}, + {kind: "CheckBox", name: "dateTasks", + preferenceProperty: "dateTasks", + onChange: "setPreference"} + ]}, + {kind: "Item", layoutKind: "HFlexLayout", + components: [ + {content: "Quick complete"}, + {kind: "Spacer"}, + {kind: "CheckBox", name: "quickComplete", + preferenceProperty: "quickComplete", + onChange: "setPreference"} + ]}, + {kind: "Item", layoutKind: "HFlexLayout", + components: [ + {content: "Work offline"}, + {kind: "Spacer"}, + {kind: "CheckBox", name: "offline", + preferenceProperty: "offline", + onChange: "setPreference"} + ]} + ]}, + {kind: "RowGroup", caption: "storage", components: [ + {kind: "Item", layoutKind: "HFlexLayout", + components: [ + {flex: 1, kind: "ListSelector", name: "storage", + preferenceProperty: "storage", + onChange: "setPreference", + items: [ + {caption: "No Storage", value: "none"}, + {caption: "Internal Storage", value: "file"}, + {caption: "Dropbox", value: "dropbox"} + ]} + ]}, + {kind: "Item", layoutKind: "HFlexLayout", + name: "filepathselect", showing: true, + components: [ + {flex: 1, kind: "Input", name: "filepath", + preferenceProperty: "filepath", + hint: "file path", + disabled: true + } + ]}, + {kind: "Item", layoutKind: "HFlexLayout", + name: "dboxpathselect", showing: true, + components: [ + {flex: 1, kind: "Input", name: "dboxpath", + preferenceProperty: "dboxpath", + hint: "Dropbox path", + onchange: "setPreference", + disabled: false + } + ]}, + {kind: "Item", layoutKind: "HFlexLayout", + name: "dboxlogout", showing: false, + components: [ + {flex: 1, kind: "Button", + caption: "Log out of Dropbox", + className: "enyo-button-negative", + onclick: "clearDropbox" + } + ]} + ]}, + {kind: "Button", caption: "About", onclick: "doAbout", + className: "enyo-button-dark" + }, + {kind: "Button", caption: "Reset Prefs", onclick: "doPrefReset", + className: "enyo-button-dark" + } + ]} + ]}, + {name: "prefToolbar", kind: "Toolbar", pack: "justify", className: "enyo-toolbar-light", + components: [ + {kind: "Spacer"}, + {flex: 1, kind: "Button", caption: "Done", + onclick: "doClose", align: "center"}, + {kind: "Spacer"} + ] + }, + {name: "todoFilePicker", kind: "FilePicker", fileType: ["document"], + onPickFile: "fileSelected", allowMultiSelect: false} + + ], + + setPreference: function(inSender, inEvent, inValue) { + //var value = (inSender.kind === "CheckBox") ? inSender.getChecked() : inValue; + if (inSender.kind === "CheckBox") { + value = inSender.getChecked(); + } else if (inSender.kind === "Input") { + value = inValue; + } else { + value = inEvent; + } + + this.owner.preferences[inSender.preferenceProperty] = value; + + if (inSender.preferenceProperty == "storage") { + if (value == "file") { + //this.$.todoFilePicker.pickFile(); + var mypath = "/media/internal/todo/todo.txt" + this.owner.preferences["filepath"] = mypath; + this.$.filepath.setValue(mypath); + this.$.filepath.render(); + this.$.dboxlogout.hide(); + this.owner.preferences["offline"] = true; + this.owner.$.preferenceView.$.offline.setChecked(true); + this.owner.refreshTodo(); + } else if (value == "dropbox") { + this.owner.$.dropbox.validateAccess(); + } else { + this.owner.preferences["filepath"] = ""; + this.$.filepath.setValue(""); + this.$.filepath.render(); + this.$.dboxlogout.hide(); + this.owner.preferences["offline"] = true; + this.owner.$.preferenceView.$.offline.setChecked(true); + } + } else if (inSender.preferenceProperty == "dboxpath") { + //console.log(this.owner.preferences["dboxpath"]); + this.owner.dropboxRefresh = true; + this.owner.refreshTodo(); + } + + localStorage.setItem("TodoPreferences", JSON.stringify(this.owner.preferences)); + this.owner.$.listView.$.todoList.render(); + }, + + fileSelected: function(inSender, inResponse) { + //console.log(inResponse[0].fullPath); + value = inResponse[0].fullPath; + if (value) { + this.owner.preferences["filepath"] = value; + this.$.filepath.setValue(value); + this.$.filepath.render(); + this.owner.refreshTodo(); + } + }, + + clearDropbox: function() { + this.owner.preferences["dboxuid"] = ""; + this.owner.preferences["dboxtoken"] = ""; + this.owner.preferences["dboxsecret"] = ""; + this.owner.preferences["dboxname"] = ""; + this.owner.preferences["dboxrev"] = ""; + this.owner.preferences["storage"] = "file"; + this.owner.preferences["offline"] = true; + this.owner.$.preferenceView.$.offline.setChecked(true); + localStorage.setItem("TodoPreferences", JSON.stringify(this.owner.preferences)); + this.$.storage.setValue("file"); + this.$.dboxlogout.hide(); + } + +}); diff --git a/app/source/TodoTxt.js b/app/source/TodoTxt.js new file mode 100644 index 0000000..90257c1 --- /dev/null +++ b/app/source/TodoTxt.js @@ -0,0 +1,358 @@ +/* + * Copyright 2012 Morgan McMillian + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +enyo.kind({ + name: "TodoTxt", + kind: enyo.VFlexBox, + published: { + launchParams: null + }, + components: [ + + {kind: "AppMenu", components: [ + {caption: "Preferences", onclick: "showPrefView"}, + {caption: "About", onclick: "showAbout"} + ]}, + {kind: "Popup", name: "about", layoutKind: "VFlexLayout", + contentHeight: "100%", height: "80%", width: "80%", + components: [ + {name: "aboutTitle", content: ""}, + {content: "by Morgan McMillian"}, + {kind: "Divider", caption: "Version History"}, + {flex: 1, kind: "Scroller", components: [ + {kind: "HtmlContent", className: "ver-history", + srcId: "history"} + ]}, + {kind: "Button", caption: "Done", onclick: "closeAbout" } + ]}, + {kind: "PageHeader", pack: "justify", components: [ + {kind: "HtmlContent", content: "Todo.txt Enyo [beta]"}, + {flex: 1}, + {name: "viewTitle", kind: "HtmlContent", + className: "todo-view-title", content: ""} + ]}, + {flex: 1, kind: "Pane", onSelectView: "viewSelected", components: [ + {name: "listView", kind: "TodoList", onEdit: "showEditView", + onPrefs: "showPrefView", onReload: "refreshTodo" + }, + {name: "editView", kind: "TodoEdit", + onClose: "closeView", onSave: "addTodo" + }, + {name: "preferenceView", kind: "TodoPrefs", + onClose: "closeView", onAbout: "showAbout", + onPrefReset: "resetPreferences" + } + ]}, + {name: "dropbox", kind: "Dropbox", + onAuthSuccess: "enableDropbox", + onAuthFailure: "disableDropbox", + onGetFileSuccess: "loadDropbox", + onGetFileFailure: "fail", + onPutFileSuccess: "dboxPutFileSuccess", + onPutFileFailure: "fail" + }, + {name: "readFile", kind: "PalmService", + service: "palm://com.monkeystew.todotxtenyo.beta.service/", + method: "readfile", + onSuccess: "parseFile", onFailure: "doNothing" + }, + {name: "writeFile", kind: "PalmService", + service: "palm://com.monkeystew.todotxtenyo.beta.service/", + method: "writefile", onSuccess: "saveSuccess" + }, + {name: "makeDir", kind: "PalmService", + service: "palm://com.monkeystew.todotxtenyo.beta.service/", + method: "makedir", onSuccess: "dirCreated", + onFailure: "doNothing" + }, + {name: "getConn", kind: "PalmService", + service: "palm://com.palm.connectionmanager/", + method: "getstatus", + onSuccess: "handleConnectionChange", + subscribe: true + } + + ], + + ready: function() { + this.$.getConn.call(); + + this.preferences = localStorage.getItem("TodoPreferences"); + if (this.preferences == undefined) { + this.resetPreferences(); + } else { + this.preferences = JSON.parse(this.preferences); + } + + if (this.preferences["storage"] == "dropbox") { + if (this.preferences["dboxtoken"]) { + this.$.dropbox.setToken( + this.preferences["dboxtoken"] + ); + this.$.dropbox.setTokenSecret( + this.preferences["dboxsecret"] + ); + this.$.dropbox.setAuthorized(true); + this.$.preferenceView.$.dboxlogout.show(); + this.$.preferenceView.$.dboxpathselect.show(); + this.$.preferenceView.$.filepathselect.hide(); + } + } + + //TODO: need to derive a better method for introducing new + //preferences into the application + + if (this.preferences["dboxpath"] == undefined) { + this.preferences["dboxpath"] = "/todo"; + localStorage.setItem("TodoPreferences", JSON.stringify(this.preferences)); + } + + if (this.preferences["offline"] == true) { + this.$.viewTitle.setContent("[offline]"); + } + + this.$.makeDir.call({ path: "/media/internal/todo" }); + + this.todoList = []; + this.refreshTodo(); + + }, + + launchParamsChanged: function() { + if (this.launchParams) { + if (this.launchParams.addTodoText) { + this.$.editView.$.tododetail.setValue( + this.launchParams.addTodoText + ); + this.showEditView(); + } else if (this.launchParams.search) { + this.$.listView.$.searchbox.setValue( + this.launchParams.search + ); + this.$.listView.$.searchbox.fire("onchange"); + } + } + }, + + viewSelected: function(inSender, inView) { + if (inView == this.$.listView) { + if (this.preferences["offline"] == true) { + this.$.viewTitle.setContent("[offline]"); + } else { + this.$.viewTitle.setContent(""); + } + } else if (inView == this.$.preferenceView) { + this.$.viewTitle.setContent("preferences"); + } + }, + + refreshTodo: function() { + if (this.preferences["storage"] == "file") { + this.getLocalFile(); + } else if (this.preferences["storage"] == "dropbox") { + if (this.preferences["offline"] == false) { + var dboxpath = this.preferences["dboxpath"]; + this.$.dropbox.getFile(dboxpath+"/todo.txt"); + } else { + console.log("working offline, loading local copy instead"); + this.getLocalFile(); + } + } + }, + + addTodo: function() { + this.$.listView.addTodo(); + }, + + showEditView: function() { + this.$.pane.selectViewByName("editView"); + if (this.$.listView.getReplaceItem()) { + this.$.viewTitle.setContent("update task"); + } else { + this.$.viewTitle.setContent("add task"); + } + this.$.editView.$.tododetail.forceFocus(); + }, + + showPrefView: function() { + for ( item in this.preferences ) { + var component = eval("this.$.preferenceView.$."+item); + if (component != undefined) { + if (component.kind === "CheckBox") { + component.setChecked(this.preferences[component.preferenceProperty]); + } else { + component.setValue(this.preferences[component.preferenceProperty]); + } + } + } + this.$.pane.selectViewByName("preferenceView"); + }, + + showAbout: function() { + this.$.about.openAtCenter(); + this.$.aboutTitle.setContent( + "Todo.txt Enyo [beta] v" + enyo.fetchAppInfo().version); + this.$.about.render(); + }, + + closeAbout: function() { + this.$.about.close(); + }, + + closeView: function() { + this.$.editView.$.tododetail.setValue(""); + this.$.pane.selectViewByName("listView"); + }, + + parseFile: function(path, file) { + var todofile = file.content.split("\n"); + this.todoList = []; + for (line in todofile) { + if (todofile[line]) { + if (typeof line == "string") { + line = parseInt(line, 10); + } + var task = new Object(); + task.num = line + 1; + task.pri = todofile[line].match(/^\([A-E]\)\s/); + task.detail = todofile[line]; + this.todoList.push(task); + } + } + this.$.listView.setSortedList(undefined); + this.$.listView.$.todoList.render(); + console.log("row count: " + this.todoList.length); + }, + + saveFile: function(path, list) { + data = ""; + for (item in list) { + data = data + list[item].detail + "\n"; + } + this.$.writeFile.call({ + path: path, content: data }); + + if (this.preferences["storage"] == "dropbox" && + this.preferences["offline"] == false && + this.dropboxRefresh == false) { + var filename = path.match(/todo\.txt.*/); + filename = this.preferences["dboxpath"]+"/"+filename; + //var params = { overwrite: true }; + this.$.dropbox.putFile(filename, data); + } else if (this.dropboxRefresh == true) { + this.dropboxRefresh = false; + } + }, + + saveSuccess: function(inSender, inEvent) { + if (inEvent.error) { + console.log("error: "+inEvent.error.message); + } else { + enyo.windows.addBannerMessage(inEvent.path + " saved", "{}"); + console.log(inEvent.path + " saved..."); + console.log(inEvent.bytes + " bytes..."); + } + }, + + dboxPutFileSuccess: function(inSender, inResponse) { + console.log(inResponse.path + " uploaded to Dropbox"); + console.log("at revision " + inResponse.revision); + console.log(inResponse.size + " bytes..."); + }, + + doNothing: function(inSender) { + console.log("errrr"); + console.log(":: " + inSender); + }, + + dirCreated: function(inSender, inEvent) { + if (inEvent.error) { + console.log("error: " + inEvent.error.message); + } + }, + + getLocalFile: function() { + this.$.readFile.call({ path: this.preferences["filepath"] }); + }, + + loadDropbox: function(inSender, inResponse, inRequest) { + var file = new Object(); + file.content = inResponse; + this.parseFile(null, file); + this.dropboxRefresh = true; + this.saveFile(this.preferences["filepath"], this.todoList); + }, + + resetPreferences: function() { + this.preferences = new Object(); + this.preferences["storage"] = "file"; + this.preferences["offline"] = true; + this.preferences["filepath"] = "/media/internal/todo/todo.txt"; + this.preferences["dboxpath"] = "/todo"; + this.$.preferenceView.$.offline.setChecked(true); + this.$.preferenceView.$.dboxlogout.hide(); + this.$.preferenceView.$.dboxpathselect.hide(); + this.$.preferenceView.$.filepathselect.show(); + localStorage.setItem("TodoPreferences", JSON.stringify(this.preferences)); + }, + + handleConnectionChange: function(inSender, inResponse) { + var r = inResponse.isInternetConnectionAvailable; + + if (r) { + console.log("went online..."); + } else { + console.log("went offline..."); + if (this.preferences["offline"] == false) { + enyo.windows.addBannerMessage("offline mode", "{}"); + this.preferences["offline"] = true; + this.$.preferenceView.$.offline.setChecked(true); + } + } + }, + + fail: function(inSender, inResponse, inRequest) { + console.log("error"); + console.log(JSON.stringify(inResponse)); + enyo.windows.addBannerMessage(inResponse.error, "{}"); + }, + + enableDropbox: function() { + console.log("authentication successful, enabling dropbox"); + var token = this.$.dropbox.getToken(); + var secret = this.$.dropbox.getTokenSecret(); + this.preferences["dboxtoken"] = token; + this.preferences["dboxsecret"] = secret; + this.preferences["offline"] = false; + localStorage.setItem("TodoPreferences", JSON.stringify(this.preferences)); + this.$.preferenceView.$.dboxlogout.show(); + this.$.preferenceView.$.dboxpathselect.show(); + this.$.preferenceView.$.filepathselect.hide(); + this.dropboxRefresh = true; + this.refreshTodo(); + }, + + disableDropbox: function() { + this.$.dropbox.setToken(""); + this.$.dropbox.setTokenSecret(""); + this.$.dropbox.setAuthorized(false); + this.preferences["storage"] = "file"; + this.preferences["dboxtoken"] = ""; + this.preferences["dboxsecret"] = ""; + localStorage.setItem("TodoPreferences", JSON.stringify(this.preferences)); + } + +}); diff --git a/app/source/dropbox-auth.js.sample b/app/source/dropbox-auth.js.sample new file mode 100644 index 0000000..c0aed71 --- /dev/null +++ b/app/source/dropbox-auth.js.sample @@ -0,0 +1,9 @@ + +/* Dropbox will NOT work unless you replace the following two +* properties with the appropriate values for your registered +* application and rename the file to dropbox-auth.js or you +* will be able to package and run this correctly. +*/ + +var consumerKey = ""; +var consumerSecret = ""; diff --git a/app/source/oauth.js b/app/source/oauth.js new file mode 100644 index 0000000..295569a --- /dev/null +++ b/app/source/oauth.js @@ -0,0 +1,551 @@ +/* + * Copyright 2008 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* Here's some JavaScript software for implementing OAuth. + + This isn't as useful as you might hope. OAuth is based around + allowing tools and websites to talk to each other. However, + JavaScript running in web browsers is hampered by security + restrictions that prevent code running on one website from + accessing data stored or served on another. + + Before you start hacking, make sure you understand the limitations + posed by cross-domain XMLHttpRequest. + + On the bright side, some platforms use JavaScript as their + language, but enable the programmer to access other web sites. + Examples include Google Gadgets, and Microsoft Vista Sidebar. + For those platforms, this library should come in handy. +*/ + +// The HMAC-SHA1 signature method calls b64_hmac_sha1, defined by +// http://pajhome.org.uk/crypt/md5/sha1.js + +/* An OAuth message is represented as an object like this: + {method: "GET", action: "http://server.com/path", parameters: ...} + + The parameters may be either a map {name: value, name2: value2} + or an Array of name-value pairs [[name, value], [name2, value2]]. + The latter representation is more powerful: it supports parameters + in a specific sequence, or several parameters with the same name; + for example [["a", 1], ["b", 2], ["a", 3]]. + + Parameter names and values are NOT percent-encoded in an object. + They must be encoded before transmission and decoded after reception. + For example, this message object: + {method: "GET", action: "http://server/path", parameters: {p: "x y"}} + ... can be transmitted as an HTTP request that begins: + GET /path?p=x%20y HTTP/1.0 + (This isn't a valid OAuth request, since it lacks a signature etc.) + Note that the object "x y" is transmitted as x%20y. To encode + parameters, you can call OAuth.addToURL, OAuth.formEncode or + OAuth.getAuthorization. + + This message object model harmonizes with the browser object model for + input elements of an form, whose value property isn't percent encoded. + The browser encodes each value before transmitting it. For example, + see consumer.setInputs in example/consumer.js. + */ + +/* This script needs to know what time it is. By default, it uses the local + clock (new Date), which is apt to be inaccurate in browsers. To do + better, you can load this script from a URL whose query string contains + an oauth_timestamp parameter, whose value is a current Unix timestamp. + For example, when generating the enclosing document using PHP: + +