author | Giulio Cesare Solaroli <giulio.cesare@clipperz.com> | 2013-10-02 07:59:30 (UTC) |
---|---|---|
committer | Giulio Cesare Solaroli <giulio.cesare@clipperz.com> | 2013-10-02 07:59:30 (UTC) |
commit | 1180b7b195157aaeb4f0d5380e0c886bbd06c2e2 (patch) (side-by-side diff) | |
tree | 709e33a09d9325d382aabaf0a0828e20ebdb96db /frontend/delta/js/Clipperz/PM | |
parent | 20bea94ab6b91c85b171dcf86baba0a64169d508 (diff) | |
download | clipperz-1180b7b195157aaeb4f0d5380e0c886bbd06c2e2.zip clipperz-1180b7b195157aaeb4f0d5380e0c886bbd06c2e2.tar.gz clipperz-1180b7b195157aaeb4f0d5380e0c886bbd06c2e2.tar.bz2 |
Updated /delta
Switched from less to scss. Still no build script to update the final CSS, though.
Added preliminary support for storing account data on browser's local storage for offline viewing. No public backend currently support this feature.
11 files changed, 420 insertions, 42 deletions
diff --git a/frontend/delta/js/Clipperz/PM/DataModel/DevicePreferences.js b/frontend/delta/js/Clipperz/PM/DataModel/DevicePreferences.js new file mode 100644 index 0000000..ff3b33f --- a/dev/null +++ b/frontend/delta/js/Clipperz/PM/DataModel/DevicePreferences.js @@ -0,0 +1,90 @@ +/* + +Copyright 2008-2013 Clipperz Srl + +This file is part of Clipperz, the online password manager. +For further information about its features and functionalities please +refer to http://www.clipperz.com. + +* Clipperz is free software: you can redistribute it and/or modify it + under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + +* Clipperz is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Affero General Public License for more details. + +* You should have received a copy of the GNU Affero General Public + License along with Clipperz. If not, see http://www.gnu.org/licenses/. + +*/ + +Clipperz.PM.DataModel.DevicePreferences = function (args) { + args = args || {}; + + this._data = null; + + Clipperz.PM.DataModel.DevicePreferences.superclass.constructor.apply(this, arguments); + + return this; +} + +Clipperz.Base.extend(Clipperz.PM.DataModel.DevicePreferences, Object, { + + toString: function () { + return "Clipperz.PM.DataModel.DevicePreferences"; + }, + + //------------------------------------------------------------------------- + + shouldStoreDataLocally: function () { + return (localStorage.getItem('shouldStoreDataLocally') === 'true'); + }, + + setShouldStoreDataLocally: function (aValue) { + localStorage.setItem('shouldStoreDataLocally', aValue); + }, + + //------------------------------------------------------------------------- + + setAccountDataWityResponse: function (aResponse) { + localStorage.setItem('clipperz_dump_data', aResponse['data']); + localStorage.setItem('clipperz_dump_version', aResponse['version']); + localStorage.setItem('clipperz_dump_date', new Date()); + + this._data = null; + }, + + accountData: function () { + if (this._data == null) { + var data; + + data = localStorage.getItem('clipperz_dump_data'); + if (data != null) { + this._data = JSON.parse(data); + } + } + + return this._data; + }, + + latestDownload: function () { + var result; + var date; + + date = localStorage.getItem('clipperz_dump_date'); + if (date != null) { + result = new Date(date); + } else { + result = null; + } + + return result; + }, + + //========================================================================= + __syntaxFix__: "syntax fix" +}); + diff --git a/frontend/delta/js/Clipperz/PM/Proxy.js b/frontend/delta/js/Clipperz/PM/Proxy.js index 2ac684a..71c784f 100644 --- a/frontend/delta/js/Clipperz/PM/Proxy.js +++ b/frontend/delta/js/Clipperz/PM/Proxy.js @@ -123,64 +123,65 @@ Clipperz.PM.Proxy.prototype = MochiKit.Base.update(null, { 'message': function (someParameters) { return this.processMessage('message', someParameters, 'MESSAGE'); }, 'logout': function (someParameters) { return this.processMessage('logout', someParameters, 'MESSAGE'); }, //========================================================================= 'processMessage': function (aFunctionName, someParameters, aRequestType) { var deferredResult; deferredResult = new Clipperz.Async.Deferred("Proxy.processMessage", {trace:false}); deferredResult.addMethod(this, 'payToll', aRequestType); deferredResult.addMethod(this, 'sendMessage', aFunctionName); deferredResult.addMethod(this, 'setTollCallback'); deferredResult.callback(someParameters); return deferredResult; }, //========================================================================= '_sendMessage': function (aFunctionName, aVersion, someParameters) { throw Clipperz.Base.exception.AbstractMethod; }, 'sendMessage': function (aFunctionName, someParameters) { var deferredResult; +console.log("PROXY.sendMessage", aFunctionName, someParameters); // TODO: read actual application version for a property set at build time deferredResult = new Clipperz.Async.Deferred("Proxy.sendMessage", {trace:false}); deferredResult.addMethod(this, '_sendMessage', aFunctionName, 'fake-app-version'); deferredResult.addErrback(MochiKit.Base.method(this, 'handleError')); deferredResult.callback(someParameters); return deferredResult; }, //------------------------------------------------------------------------- 'handleError': function (anError) { if (anError['message'] == 'Wrong application version') { anError['isPermanent'] = true; } return anError; }, //========================================================================= 'isReadOnly': function () { return false; }, 'canRegisterNewUsers': function () { return true; }, //========================================================================= __syntaxFix__: "syntax fix" }); diff --git a/frontend/delta/js/Clipperz/PM/Proxy/Proxy.JSON.js b/frontend/delta/js/Clipperz/PM/Proxy/Proxy.JSON.js index 1638d99..6deee3d 100755 --- a/frontend/delta/js/Clipperz/PM/Proxy/Proxy.JSON.js +++ b/frontend/delta/js/Clipperz/PM/Proxy/Proxy.JSON.js @@ -28,59 +28,59 @@ if (typeof(Clipperz.PM) == 'undefined') { Clipperz.PM = {}; } Clipperz.PM.Proxy.JSON = function(args) { Clipperz.PM.Proxy.JSON.superclass.constructor.call(this, args); this._url = args.url || Clipperz.Base.exception.raise('MandatoryParameter'); return this; } Clipperz.Base.extend(Clipperz.PM.Proxy.JSON, Clipperz.PM.Proxy, { 'toString': function() { return "Clipperz.PM.Proxy.JSON"; }, //========================================================================= 'url': function () { return this._url; }, //========================================================================= '_sendMessage': function(aFunctionName, aVersion, someParameters) { var deferredResult; var parameters; parameters = { method: aFunctionName, version: aVersion, parameters: Clipperz.Base.serializeJSON(someParameters) }; - - deferredResult = new Clipperz.Async.Deferred("Proxy.JSON.sendMessage", {trace:false}); +console.log("PROXY.JSON._sendMessage", parameters); + deferredResult = new Clipperz.Async.Deferred("Proxy.JSON._sendMessage", {trace:false}); deferredResult.addCallbackPass(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'remoteRequestSent'); deferredResult.addCallback(MochiKit.Async.doXHR, this.url(), { method:'POST', sendContent:MochiKit.Base.queryString(parameters), headers:{"Content-Type":"application/x-www-form-urlencoded"} }); deferredResult.addCallbackPass(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'remoteRequestReceived'); deferredResult.addCallback(MochiKit.Base.itemgetter('responseText')); deferredResult.addCallback(Clipperz.Base.evalJSON); deferredResult.addCallback(function (someValues) { if (someValues['result'] == 'EXCEPTION') { throw someValues['message']; } return someValues; }) deferredResult.callback(); return deferredResult; }, //========================================================================= __syntaxFix__: "syntax fix" }); diff --git a/frontend/delta/js/Clipperz/PM/Proxy/Proxy.Offline.LocalStorageDataStore.js b/frontend/delta/js/Clipperz/PM/Proxy/Proxy.Offline.LocalStorageDataStore.js index a3c238c..3f16f70 100644 --- a/frontend/delta/js/Clipperz/PM/Proxy/Proxy.Offline.LocalStorageDataStore.js +++ b/frontend/delta/js/Clipperz/PM/Proxy/Proxy.Offline.LocalStorageDataStore.js @@ -1,65 +1,67 @@ /* Copyright 2008-2013 Clipperz Srl This file is part of Clipperz, the online password manager. For further information about its features and functionalities please refer to http://www.clipperz.com. * Clipperz is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. * Clipperz is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. * You should have received a copy of the GNU Affero General Public License along with Clipperz. If not, see http://www.gnu.org/licenses/. */ try { if (typeof(Clipperz.PM.Proxy.Offline.DataStore) == 'undefined') { throw ""; }} catch (e) { throw "Clipperz.PM.Proxy.Offline.LocalStorageDataStore depends on Clipperz.PM.Proxy.Offline.DataStore!"; } //============================================================================= Clipperz.PM.Proxy.Offline.LocalStorageDataStore = function(args) { args = args || {}; - this._data = args.data || (typeof(_clipperz_dump_data_) != 'undefined' ? _clipperz_dump_data_ : null); +// this._data = args.data || (typeof(_clipperz_dump_data_) != 'undefined' ? _clipperz_dump_data_ : null); + this._data = JSON.parse(localStorage.getItem('clipperz_dump_data')); + this._isReadOnly = (typeof(args.readOnly) == 'undefined' ? true : args.readOnly); this._shouldPayTolls = args.shouldPayTolls || false; this._tolls = {}; this._currentStaticConnection = null; // Clipperz.PM.Proxy.Offline.LocalStorageDataStore.superclass.constructor.apply(this, arguments); return this; } Clipperz.Base.extend(Clipperz.PM.Proxy.Offline.LocalStorageDataStore, Clipperz.PM.Proxy.Offline.DataStore, { //========================================================================= '_knock': function(aConnection, someParameters) { var result; result = { toll: this.getTollForRequestType(someParameters['requestType']) } return result; }, //------------------------------------------------------------------------- '_registration': function(aConnection, someParameters) { throw Clipperz.PM.Proxy.Offline.DataStore.exception.ReadOnly; }, //------------------------------------------------------------------------- diff --git a/frontend/delta/js/Clipperz/PM/UI/Components/CardDetail.js b/frontend/delta/js/Clipperz/PM/UI/Components/CardDetail.js index df514a2..12ddce3 100644 --- a/frontend/delta/js/Clipperz/PM/UI/Components/CardDetail.js +++ b/frontend/delta/js/Clipperz/PM/UI/Components/CardDetail.js @@ -8,135 +8,188 @@ refer to http://www.clipperz.com. * Clipperz is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. * Clipperz is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. * You should have received a copy of the GNU Affero General Public License along with Clipperz. If not, see http://www.gnu.org/licenses/. */ Clipperz.PM.UI.Components.CardDetail = React.createClass({ getDefaultProps: function () { return { // searchDelay: 0.3 } }, propTypes: { card: React.PropTypes.object.isRequired }, getInitialState: function () { return { // showSearch: false, // searchTimer: null, + unmaskedFields: new Clipperz.Set(), starred: false }; }, handleDirectLoginClick: function (aDirectLoginReference, anEvent) { MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'runDirectLogin', {record:this.props.card['reference'], directLogin:aDirectLoginReference}); }, + toggleFieldVisibility: function (aField, anEvent) { + var unmaskedFields; + var fieldReference; + + unmaskedFields = this.state['unmaskedFields']; + fieldReference = aField['reference'] + if (unmaskedFields.contains(fieldReference)) { + unmaskedFields.remove(fieldReference) + } else { + unmaskedFields.add(fieldReference) + } + + this.setState({'unmaskedFields': unmaskedFields}); + }, + + handleGoAction: function (aField, anEvent) { + var newWindow; + + newWindow = MochiKit.DOM.currentWindow().open(aField['value'], '_blank'); + newWindow.focus(); + }, + + handleEmailAction: function (aField, anEvent) { + MochiKit.DOM.currentWindow().location = 'mailto:' + aField['value']; + }, + //========================================================================= normalizeFieldValue: function (aValue) { var result = []; var rows = aValue.split('\n'); for (var i = 0; i < rows.length; i++) { if (i > 0) { result.push(React.DOM.br()); } result.push(rows[i].replace(/[\s]/g, '\u00A0')); } return result; }, - renderField: function (aField) { -//console.log("FIELD", aField); - var actionLabel; + renderFieldActionButton: function (aField) { +// var actionLabel; + var result; if (aField['actionType'] == 'URL') { - actionLabel = "go"; + result = React.DOM.div({className:'actionWrapper', onClick:MochiKit.Base.method(this, 'handleGoAction', aField)}, [ + React.DOM.a({className:aField['actionType']}, "go") + ]); } else if (aField['actionType'] == 'PASSWORD') { - actionLabel = "locked"; + var icon; + + if (this.state['unmaskedFields'].contains(aField['reference'])) { + icon = "unlocked"; + } else { + icon = "locked"; + } + result = React.DOM.div({className:'actionWrapper', onClick:MochiKit.Base.method(this, 'toggleFieldVisibility', aField)}, [ + React.DOM.a({className:aField['actionType']}, icon) + ]); } else if (aField['actionType'] == 'EMAIL') { - actionLabel = "email"; + result = React.DOM.div({className:'actionWrapper', onClick:MochiKit.Base.method(this, 'handleEmailAction', aField)}, [ + React.DOM.a({className:aField['actionType']}, "email") + ]); } else { - actionLabel = ""; + result = null; + } + + return result; + }, + + renderField: function (aField) { +//console.log("FIELD", aField); + var fieldExtraClass; + + fieldExtraClass = aField['actionType']; + if (this.state['unmaskedFields'].contains(aField['reference'])) { + fieldExtraClass = fieldExtraClass + ' unlocked'; } - return React.DOM.div({className:'listItem ' + aField['actionType']}, [ + return React.DOM.div({className:'listItem ' + fieldExtraClass, key:aField['reference']}, [ React.DOM.div({className:'fieldWrapper'}, [ React.DOM.div({className:'fieldInnerWrapper'}, [ React.DOM.div({className:'labelWrapper'}, React.DOM.span({className:'label'}, aField['label'])), - React.DOM.div({className:'valueWrapper'}, React.DOM.span({className:'value ' + aField['actionType']}, this.normalizeFieldValue(aField['value']))) + React.DOM.div({className:'valueWrapper'}, React.DOM.span({className:'value ' + fieldExtraClass}, this.normalizeFieldValue(aField['value']))) ]) ]), - React.DOM.div({className:'actionWrapper'}, [ - React.DOM.div({className:aField['actionType']}, actionLabel) - ]) + this.renderFieldActionButton(aField) +// React.DOM.div({className:'actionWrapper'}, [ +// React.DOM.div({className:aField['actionType']}, actionLabel) +// ]) ]); }, renderDirectLogin: function (aDirectLogin) { //console.log("DIRECT LOGIN", aDirectLogin); return React.DOM.div({className:'listItem', onClick:MochiKit.Base.method(this, 'handleDirectLoginClick', aDirectLogin['reference'])}, [ React.DOM.div({className:'labelWrapper'}, React.DOM.span({className:'label'}, aDirectLogin['label'])), React.DOM.div({className:'faviconWrapper'}, React.DOM.img({className:'favicon', src:aDirectLogin['favicon']})), React.DOM.div({className:'directLoginLinkWrapper'}, React.DOM.span({className:'directLoginLink'}, "go")) ]); }, handleBackClick: function (anEvent) { - window.history.back(); +// window.history.back(); + MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'goBack'); }, handleStarClick: function (anEvent) { this.setState({starred: !this.state['starred']}); }, //========================================================================= render: function () { var card = this.props.card; - var starredStatus = (this.state['starred'] ? "starred" : "unstarred"); +// var starredStatus = (this.state['starred'] ? "starred" : "unstarred"); if ((typeof(card['fields']) != 'undefined') && (card['notes'] != '')) { card['fields'].push({ 'actionType': 'NOTES', 'isHidden': false, 'label': "notes", 'reference': "notes", 'value': card['notes'] }) } return React.DOM.div({className:'cardDetail'}, [ React.DOM.div({className:'header'}, [ React.DOM.div({className:'titleWrapper'}, React.DOM.div({className:'title'}, card.title)), -// React.DOM.div({className:'titleWrapper'}, React.DOM.div({className:'title'}, card.title + ' ' + card.title + ' ' + card.title + ' ' + card.title)), React.DOM.div({className:'backWrapper'}, React.DOM.a({className:'button back', onClick:this.handleBackClick}, "back")), - React.DOM.div({className:'starWrapper'}, React.DOM.a({className:'star', onClick:this.handleStarClick}, starredStatus)) +// React.DOM.div({className:'starWrapper'}, React.DOM.a({className:'star', onClick:this.handleStarClick}, starredStatus)) ]), React.DOM.div({className:'content'}, [ card.fields ? React.DOM.div({className:'fields'}, MochiKit.Base.map(this.renderField, card.fields)) : null, card.directLogins ? React.DOM.div({className:'directLogins'}, MochiKit.Base.map(this.renderDirectLogin, card.directLogins)): null ]), React.DOM.div({className:'footer'}, [ /* // React.DOM.a({className:'cancel'}, "cancel"), // React.DOM.a({className:'save'}, "save") React.DOM.a({className:'cancel button'}, "failed"), React.DOM.a({className:'save button'}, "done") */ ]) ]); } //========================================================================= }); diff --git a/frontend/delta/js/Clipperz/PM/UI/Components/CardList.js b/frontend/delta/js/Clipperz/PM/UI/Components/CardList.js index 66d20f1..5a44a4a 100644 --- a/frontend/delta/js/Clipperz/PM/UI/Components/CardList.js +++ b/frontend/delta/js/Clipperz/PM/UI/Components/CardList.js @@ -68,94 +68,101 @@ Clipperz.PM.UI.Components.CardList = React.createClass({ this.state['searchTimer'].cancel(); } if (searchText != this.state['searchText']) { this.state['searchText'] = searchText; this.state['searchTimer'] = MochiKit.Async.callLater(this.props['searchDelay'], MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'searchCards', searchText); } }, focusOnSearchField: function () { console.log("focusOnSearchField", this.refs['searchField']); this.refs['searchField'].getDOMNode.focus(); }, searchBox: function () { var result; if (this.state.showSearch) { result = React.DOM.div({className:'searchBox'}, [ React.DOM.div(null, [ React.DOM.input({type:'search', placeholder:"search", ref:'searchField', onChange:this.updateSearchText}) ]) ]); } else { result = null; } return result; }, //========================================================================= + showPreferences: function (anEvent) { + MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'showPreferences', anEvent); + }, + + //========================================================================= + cardItem: function (aRecordReference) { var reference = aRecordReference['_reference']; var selectedCard = (reference == this.props.selectedCard); - return React.DOM.div({className:'listItem', onClick:MochiKit.Base.method(this, 'handleClickOnCardDetail', reference)}, [ + // TODO: verify if it is possible to put the onClick handler on the container 'div', instead of adding it to each 'div' item. + return React.DOM.div({className:'listItem', key:reference, onClick:MochiKit.Base.method(this, 'handleClickOnCardDetail', reference)}, [ React.DOM.div({className:'labelWrapper'}, React.DOM.span({className:'label'}, aRecordReference.label)), // React.DOM.div({className:'labelWrapper'}, React.DOM.span({className:'label'}, aRecordReference.label + ' ' + aRecordReference.label + ' ' + aRecordReference.label + ' ' + aRecordReference.label + ' ' + aRecordReference.label)), React.DOM.div({className:'faviconWrapper'}, aRecordReference.favicon ? React.DOM.img({className:'favicon', src:aRecordReference.favicon}) : React.DOM.div({className:'favicon'}, '\u00A0')), React.DOM.div({className:'detailLinkWrapper'}, React.DOM.span({className:'detailLink ' + (selectedCard ? 'icon-spin' : '')}, (selectedCard ? "loading" : "detail"))) ]); }, handleClickOnCardDetail: function (aRecordReference, anEvent) { MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'showRecord', aRecordReference); }, cardListItems: function () { var list; var result; list = this.props['cardList']; if (typeof(list) != 'undefined') { result = MochiKit.Base.map(MochiKit.Base.method(this, 'cardItem'), list); } else { result = null; } return result; }, //========================================================================= handleChange: function (anEvent) { // var refs = this.refs; // var refName = MochiKit.Base.filter(function (aRefName) { return refs[aRefName].getDOMNode() == anEvent.target}, MochiKit.Base.keys(this.refs))[0]; // var newState = {}; // // newState[refName] = event.target.value; // this.setState(newState); }, //========================================================================= render: function() { return React.DOM.div(null, [ React.DOM.div({className:'header'}, [ React.DOM.a({className:'account'}, 'clipperz'), React.DOM.div({className:'features'}, [ - React.DOM.a({className:'addCard'}, 'add'), +// React.DOM.a({className:'addCard'}, 'add'), React.DOM.a({className:'search ' + (this.state.showSearch ? 'selected' : ''), onClick:this.toggleSearch}, 'search'), - React.DOM.a({className:'settings'}, 'settings') + React.DOM.a({className:'settings', onClick:this.showPreferences}, 'settings') ]), // this.searchBox() ]), this.searchBox(), React.DOM.div({className:'content cardList'}, this.cardListItems()), ]); } //========================================================================= }); diff --git a/frontend/delta/js/Clipperz/PM/UI/Components/Checkbox.js b/frontend/delta/js/Clipperz/PM/UI/Components/Checkbox.js new file mode 100644 index 0000000..9538063 --- a/dev/null +++ b/frontend/delta/js/Clipperz/PM/UI/Components/Checkbox.js @@ -0,0 +1,44 @@ +/* + +Copyright 2008-2013 Clipperz Srl + +This file is part of Clipperz, the online password manager. +For further information about its features and functionalities please +refer to http://www.clipperz.com. + +* Clipperz is free software: you can redistribute it and/or modify it + under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + +* Clipperz is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Affero General Public License for more details. + +* You should have received a copy of the GNU Affero General Public + License along with Clipperz. If not, see http://www.gnu.org/licenses/. + +*/ + +Clipperz.PM.UI.Components.Checkbox = React.createClass({ +// http://development.tobypitman.com/iphoneCheckboxes/iphoneCheckboxes2.html + + propTypes: { + 'checked': React.PropTypes.bool.isRequired, + 'id': React.PropTypes.string.isRequired, + 'eventHandler': React.PropTypes.func.isRequired + }, + + //========================================================================= + + render: function () { + return React.DOM.div({className:'checkbox', onClick:this.props['eventHandler']}, [ + React.DOM.input({name:this.props['id'], id:this.props['id'], value:this.props['id'], type:'checkbox', checked:this.props['checked']}), + React.DOM.label({className:'check', 'for':this.props['id']}), + React.DOM.label({className:'info', 'for':this.props['id']}, "enable local storage") + ]); + } + + //========================================================================= +}); diff --git a/frontend/delta/js/Clipperz/PM/UI/Components/LoginForm.js b/frontend/delta/js/Clipperz/PM/UI/Components/LoginForm.js index 2b5b4a4..801549f 100644 --- a/frontend/delta/js/Clipperz/PM/UI/Components/LoginForm.js +++ b/frontend/delta/js/Clipperz/PM/UI/Components/LoginForm.js @@ -63,65 +63,65 @@ Clipperz.PM.UI.Components.LoginForm = React.createClass({ handleCredentialSubmit: function (event) { event.preventDefault(); this.refs['passphrase'].getDOMNode().blur(); var credentials = { 'username': this.refs['username'].getDOMNode().value, 'passphrase': this.refs['passphrase'].getDOMNode().value } MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'doLogin', credentials); }, handleRegistrationLinkClick: function (event) { event.preventDefault(); MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'showRegistrationForm'); }, //------------------------------------------------------------------------- shouldEnableLoginButton: function () { var result; return ( ((this.state['username'] != '') && (this.state['passphrase'] != '')) || (this.state['pin'] != '') ) && !this.props['disabled']; }, loginForm: function () { registrationLink = React.DOM.div({'className':'registrationLink'}, [ - React.DOM.a({'onClick':this.handleRegistrationLinkClick}, "Need an account") + React.DOM.a({'onClick':this.handleRegistrationLinkClick}, "Sign up") ]); return React.DOM.div({'className':'loginForm credentials'},[ React.DOM.form({onChange: this.handleChange, onSubmit:this.handleCredentialSubmit}, [ React.DOM.div(null,[ React.DOM.label({'for':'name'}, "username"), React.DOM.input({'type':'text', 'name':'name', 'ref':'username', 'placeholder':"username", 'key':'username', 'autoCapitalize':'none'}), React.DOM.label({'for':'passphrase'}, "passphrase"), React.DOM.input({'type':'password', 'name':'passphrase', 'ref':'passphrase', 'placeholder':"passphrase", 'key':'passphrase'}) ]), React.DOM.button({'type':'submit', 'disabled':!this.shouldEnableLoginButton(), 'className':'button'}, "login") ]), this.props.isNewUserRegistrationAvailable ? registrationLink : null ]); }, handlePINSubmit: function (event) { event.preventDefault(); this.refs['pin'].getDOMNode().blur(); var credentials = { pin: this.refs['pin'].getDOMNode().value } MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'doLogin', credentials); }, pinForm: function () { return React.DOM.div({'className':'loginForm pin'},[ React.DOM.form({onChange: this.handleChange, onSubmit:this.handlePINSubmit}, [ React.DOM.div(null,[ React.DOM.label({'for':'pin'}, "pin"), diff --git a/frontend/delta/js/Clipperz/PM/UI/Components/Overlay.js b/frontend/delta/js/Clipperz/PM/UI/Components/Overlay.js index cc4a06c..cb5f81a 100644 --- a/frontend/delta/js/Clipperz/PM/UI/Components/Overlay.js +++ b/frontend/delta/js/Clipperz/PM/UI/Components/Overlay.js @@ -65,58 +65,59 @@ Clipperz.Base.extend(Clipperz.PM.UI.Components.Overlay, Object, { }, 'failed': function (aMessage, aDelayBeforeHiding) { this.completed(this.showFailIcon, aMessage, aDelayBeforeHiding); }, //------------------------------------------------------------------------- 'resetStatus': function () { MochiKit.Style.showElement(this.element()); MochiKit.Style.showElement(this.getElement('spinner')); MochiKit.Style.hideElement(this.getElement('done')); MochiKit.Style.hideElement(this.getElement('failed')); }, 'setMessage': function (aMessage) { if (typeof(aMessage) != 'undefined') { this.getElement('title').innerHTML = aMessage; } }, 'completed': function (aFunctionToShowResult, aMessage, aDelayBeforeHiding) { var delay = aDelayBeforeHiding || this.defaultDelay(); this.hideSpinner(); MochiKit.Base.bind(aFunctionToShowResult, this)(); this.setMessage(aMessage); MochiKit.Async.callLater(delay, MochiKit.Base.bind(this.hide, this)) }, 'hide': function () { - MochiKit.DOM.removeElementClass(this.element(), 'ios-overlay-show'); - MochiKit.DOM.addElementClass(this.element(), 'ios-overlay-hide'); - MochiKit.Async.callLater(1, MochiKit.Style.hideElement, this.element()); + var element = this.element(); + MochiKit.DOM.removeElementClass(element, 'ios-overlay-show'); + MochiKit.DOM.addElementClass(element, 'ios-overlay-hide'); + MochiKit.Async.callLater(1, MochiKit.Style.hideElement, element); }, 'hideSpinner': function () { MochiKit.Style.hideElement(this.getElement('spinner')); }, 'showDoneIcon': function () { MochiKit.Style.showElement(this.getElement('done')); }, 'showFailIcon': function () { MochiKit.Style.showElement(this.getElement('failed')); }, //------------------------------------------------------------------------- 'defaultDelay': function () { return this._defaultDelay; }, //------------------------------------------------------------------------- __syntaxFix__: "syntax fix" }); diff --git a/frontend/delta/js/Clipperz/PM/UI/Components/PreferencePage.js b/frontend/delta/js/Clipperz/PM/UI/Components/PreferencePage.js new file mode 100644 index 0000000..822acc2 --- a/dev/null +++ b/frontend/delta/js/Clipperz/PM/UI/Components/PreferencePage.js @@ -0,0 +1,88 @@ +/* + +Copyright 2008-2013 Clipperz Srl + +This file is part of Clipperz, the online password manager. +For further information about its features and functionalities please +refer to http://www.clipperz.com. + +* Clipperz is free software: you can redistribute it and/or modify it + under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + +* Clipperz is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Affero General Public License for more details. + +* You should have received a copy of the GNU Affero General Public + License along with Clipperz. If not, see http://www.gnu.org/licenses/. + +*/ + +Clipperz.PM.UI.Components.PreferencePage = React.createClass({ + + getDefaultProps: function () { + return { + } + }, + + propTypes: { +// card: React.PropTypes.object.isRequired +// checked: React.PropTypes.boolean.isRequired + }, + + getInitialState: function () { +// return { +// shouldStoreDataLocally: false +// }; + }, + + handleBackClick: function (anEvent) { + MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'goBack'); + }, + + toggleShouldStoreDataLocally: function (anEvent) { +// this.setState({shouldStoreDataLocally: !this.state['shouldStoreDataLocally']}); + Clipperz.PM.DataModel.devicePreferences.setShouldStoreDataLocally(!Clipperz.PM.DataModel.devicePreferences.shouldStoreDataLocally()); + this.setState({}); + }, + + shouldStoreDataLocally: function () { + return Clipperz.PM.DataModel.devicePreferences.shouldStoreDataLocally(); + }, + + syncNow: function (anEvent) { + MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'synchronizeLocalData'); + }, + + //========================================================================= + + render: function () { + return React.DOM.div({className:'preferences'}, [ + React.DOM.div({className:'header'}, [ + React.DOM.div({className:'titleWrapper'}, React.DOM.div({className:'title'}, "Preferences")), + React.DOM.div({className:'backWrapper'}, React.DOM.a({className:'button back', onClick:this.handleBackClick}, "back")), + ]), + React.DOM.div({className:'content'}, [ + React.DOM.form(null, [ + React.DOM.div({className:'section'}, [ + React.DOM.h4(null, "Local storage"), + React.DOM.p(null, "Store you account data locally for offline viewing"), + new Clipperz.PM.UI.Components.Checkbox({'id':'shouldStoreLocally_checkbox', 'checked':this.shouldStoreDataLocally(), 'eventHandler':this.toggleShouldStoreDataLocally}), + this.shouldStoreDataLocally() ? React.DOM.div({className:'syncInfo'}, [ +// React.DOM.h5(null, "data were never synchronized before"), + React.DOM.a({className:'button', onClick:this.syncNow}, "Sync now") + ]) : null + ]) + ]) + ]), + React.DOM.div({className:'footer'}, [ + + ]) + ]); + } + + //========================================================================= +}); diff --git a/frontend/delta/js/Clipperz/PM/UI/MainController.js b/frontend/delta/js/Clipperz/PM/UI/MainController.js index da7540e..20ff041 100644 --- a/frontend/delta/js/Clipperz/PM/UI/MainController.js +++ b/frontend/delta/js/Clipperz/PM/UI/MainController.js @@ -1,186 +1,246 @@ /* Copyright 2008-2013 Clipperz Srl This file is part of Clipperz, the online password manager. For further information about its features and functionalities please refer to http://www.clipperz.com. * Clipperz is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. * Clipperz is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. * You should have received a copy of the GNU Affero General Public License along with Clipperz. If not, see http://www.gnu.org/licenses/. */ Clipperz.Base.module('Clipperz.PM.UI'); Clipperz.PM.UI.MainController = function() { var pages; - this._proxy = null; +// this._proxy = null; this._user = null; this._filter = ''; // this._currentPage = 'loadingPage'; this._pageStack = ['loadingPage']; this._overlay = new Clipperz.PM.UI.Components.Overlay(); pages = { 'loginPage': new Clipperz.PM.UI.Components.LoginForm(), 'registrationPage': new Clipperz.PM.UI.Components.RegistrationWizard(), 'cardListPage': new Clipperz.PM.UI.Components.CardList(), 'cardDetailPage': new Clipperz.PM.UI.Components.CardDetail({card: {}}), + 'preferencePage': new Clipperz.PM.UI.Components.PreferencePage(), 'errorPage': new Clipperz.PM.UI.Components.ErrorPage({message:''}) }; MochiKit.Base.map(function (anId) {React.renderComponent(pages[anId], MochiKit.DOM.getElement(anId))}, MochiKit.Base.keys(pages)); this._pages = pages; this.registerForNotificationCenterEvents(); + MochiKit.Signal.connect(MochiKit.DOM.currentDocument(), 'onselectionchange', this, 'selectionChangeHandler'); return this; } MochiKit.Base.update(Clipperz.PM.UI.MainController.prototype, { toString: function () { return "Clipperz.PM.UI.MainController"; }, //========================================================================= overlay: function () { return this._overlay; }, loginForm: function () { return this._loginForm; }, registrationWizard: function () { return this._registrationWizard; }, //========================================================================= isOnline: function() { return navigator.onLine; +// return false; }, hasLocalData: function() { - return false; +// return false; + return (Clipperz.PM.DataModel.devicePreferences.accountData() != null); }, loginMode: function () { // PIN is set using this command: // Clipperz.PM.PIN.setCredentialsWithPIN('1234', {'username':'joe', 'passphrase':'clipperz'}); return Clipperz.PM.PIN.isSet() ? 'PIN' : 'CREDENTIALS'; }, //========================================================================= pages: function () { return this._pages; }, pageStack: function () { return this._pageStack; }, //========================================================================= + showOfflineError: function () { +console.log("THE BROWSER IS OFFLINE"); + }, + selectInitialProxy: function () { if (this.isOnline()) { - this._proxy = Clipperz.PM.Proxy.defaultProxy; +// this._proxy = Clipperz.PM.Proxy.defaultProxy; } else { if (this.hasLocalData()) { - this._proxy = new Clipperz.PM.Proxy.Offline({dataStore: new Clipperz.PM.Proxy.Offline.LocalStorageDataStore(), shouldPayTolls:false}); +// this._proxy = new Clipperz.PM.Proxy.Offline({dataStore: new Clipperz.PM.Proxy.Offline.LocalStorageDataStore(), shouldPayTolls:false}); + Clipperz.PM.Proxy.defaultProxy = new Clipperz.PM.Proxy.Offline({dataStore: new Clipperz.PM.Proxy.Offline.LocalStorageDataStore(), shouldPayTolls:false}); } else { this.showOfflineError(); } } }, - proxy: function () { - return this._proxy; - }, +// proxy: function () { +// return this._proxy; +// }, //========================================================================= registerForNotificationCenterEvents: function () { - var events = ['doLogin', 'registerNewUser', 'showRegistrationForm', 'goBack', 'showRecord', 'searchCards', 'runDirectLogin']; + var events = [ + 'doLogin', + 'registerNewUser', + 'showRegistrationForm', + 'goBack', + 'showRecord', + 'searchCards', + 'showPreferences', + 'runDirectLogin', + 'synchronizeLocalData' + ]; var self = this; MochiKit.Base.map(function (anEvent) { MochiKit.Signal.connect(Clipperz.Signal.NotificationCenter, anEvent, MochiKit.Base.method(self, anEvent)); }, events); // MochiKit.Signal.connect(window, 'onpopstate', MochiKit.Base.method(this, 'historyGoBack')); MochiKit.Signal.connect(window, 'onbeforeunload', MochiKit.Base.method(this, 'shouldExitApp')); }, //------------------------------------------------------------------------- + selectionChangeHandler: function (anEvent) { + var selection; + var selectionRange; + var selectionNode; + var valueElement; +// other hints: http://www.bearpanther.com/2013/05/27/easy-text-selection-in-mobile-safari/ +// SELECTION: https://developer.mozilla.org/en-US/docs/Web/API/Selection +// RANGE: https://developer.mozilla.org/en-US/docs/Web/API/Range +// NODE TYPES: https://developer.mozilla.org/en-US/docs/Web/API/Node.nodeType + + selection = MochiKit.DOM.currentWindow().getSelection(); +//console.log("-- selection", selection); + selectionRange = selection.getRangeAt(0); + selectionNode = selectionRange.startContainer.childNodes[selectionRange.startOffset]; +//console.log("-- selectionNode", selectionNode); + + if (selectionNode != undefined) { + valueElement = MochiKit.DOM.getFirstElementByTagAndClassName('*', 'value', selectionNode); +//console.log("-- valueElement", valueElement); + } + + if ((valueElement != null) && (valueElement != selectionNode)) { + var range; + range = MochiKit.DOM.currentDocument().createRange(); + range.selectNodeContents(valueElement); + selection.removeAllRanges(); + selection.addRange(range); + + anEvent.preventDefault(); + anEvent.stopPropagation(); + +//console.log("updated selection", MochiKit.DOM.currentWindow().getSelection()); + } +//console.log("-----------"); + }, + + //------------------------------------------------------------------------- + run: function (parameters) { var shouldShowRegistrationForm; + var canRegisterNewUsers; + + canRegisterNewUsers = Clipperz.PM.Proxy.defaultProxy.canRegisterNewUsers(); this.selectInitialProxy(); - shouldShowRegistrationForm = parameters['shouldShowRegistrationForm'] && this.proxy().canRegisterNewUsers(); - this.pages()['loginPage'].setProps({'mode':this.loginMode(), 'isNewUserRegistrationAvailable': this.proxy().canRegisterNewUsers()}); + shouldShowRegistrationForm = parameters['shouldShowRegistrationForm'] && canRegisterNewUsers; + this.pages()['loginPage'].setProps({'mode':this.loginMode(), 'isNewUserRegistrationAvailable':canRegisterNewUsers}); if (shouldShowRegistrationForm) { this.showRegistrationForm(); } else { this.showLoginForm(); } this.overlay().done("", 0.5); }, //------------------------------------------------------------------------- showLoginForm: function () { var loginFormPage; loginFormPage = this.pages()['loginPage']; - loginFormPage.setProps({'mode':this.loginMode(), 'isNewUserRegistrationAvailable': this.proxy().canRegisterNewUsers()}); + loginFormPage.setProps({'mode':this.loginMode(), 'isNewUserRegistrationAvailable':Clipperz.PM.Proxy.defaultProxy.canRegisterNewUsers()}); this.moveInPage(this.currentPage(), 'loginPage'); MochiKit.Async.callLater(0.5, MochiKit.Base.method(loginFormPage, 'setInitialFocus')); }, showRegistrationForm: function () { var currentPage; var registrationPage; currentPage = this.currentPage(); registrationPage = this.pages()['registrationPage']; this.setCurrentPage('loginPage'); registrationPage.setProps({}); this.moveInPage(currentPage, 'registrationPage'); MochiKit.Async.callLater(0.5, MochiKit.Base.method(registrationPage, 'setInitialFocus')); }, //========================================================================= doLogin: function (event) { var credentials; var getPassphraseDelegate; var user; user = null; this.overlay().show("logging in"); this.pages()['loginPage'].setProps({disabled:true}); if ('pin' in event) { credentials = Clipperz.PM.PIN.credentialsWithPIN(event['pin']); } else { credentials = event; @@ -294,111 +354,124 @@ MochiKit.Base.update(Clipperz.PM.UI.MainController.prototype, { var deferredResult; deferredResult = new Clipperz.Async.Deferred('MainController.showRecordList', {trace:false}); deferredResult.addMethod(this, 'allCardInfo'); deferredResult.addMethod(this, 'filterCards'); deferredResult.addMethod(this, 'sortCards'); deferredResult.addCallback(MochiKit.Base.bind(function (someRecordInfo) { this.pages()['cardListPage'].setProps({cardList: someRecordInfo}); }, this)); deferredResult.callback(); return deferredResult; }, filter: function () { return this._filter; }, setFilter: function (aValue) { this._filter = aValue; }, searchCards: function (someParameters) { //console.log("SEARCH CARDS", someParameters); this.setFilter(someParameters); this.showRecordList(); }, //========================================================================= runApplication: function () { MochiKit.Signal.connect(window, 'onpopstate', MochiKit.Base.method(this, 'historyGoBack')); +/// TODO: remove this TEST HACK this.moveInPage(this.currentPage(), 'cardListPage'); return this.showRecordList(); + +// this.moveInPage(this.currentPage(), 'preferencePage'); }, showRecord: function (aRecordReference) { //console.log("Show Record", aRecordReference); var deferredResult; this.pages()['cardListPage'].setProps({selectedCard:aRecordReference}); deferredResult = new Clipperz.Async.Deferred('MainController.runApplication', {trace:false}); -// deferredResult.addMethod(this.user(), 'getRecord', aRecordReference['_reference']); deferredResult.addMethod(this.user(), 'getRecord', aRecordReference); deferredResult.addMethodcaller('content'); deferredResult.addCallback(MochiKit.Base.bind(function (aCard) { //console.log("CARD DETAILS", aCard); this.pages()['cardDetailPage'].setProps({card: aCard}); this.pages()['cardListPage'].setProps({selectedCard: null}); }, this)); deferredResult.addMethod(this, 'moveInPage', this.currentPage(), 'cardDetailPage', true); deferredResult.callback(); return deferredResult; }, runDirectLogin: function (someParameters) { -console.log("RUN DIRECT LOGIN", someParameters); +//console.log("RUN DIRECT LOGIN", someParameters); var deferredResult; -// this.pages()['cardListPage'].setProps({selectedCard:aRecordReference}); deferredResult = new Clipperz.Async.Deferred('MainController.runDirectLogin', {trace:false}); -// deferredResult.addMethod(this.user(), 'getRecord', aRecordReference['_reference']); deferredResult.addMethod(this.user(), 'getRecord', someParameters['record']); deferredResult.addMethodcaller('directLoginWithReference', someParameters['directLogin']); deferredResult.addCallback(Clipperz.PM.UI.DirectLoginRunner.openDirectLogin); deferredResult.callback(); return deferredResult; }, shouldExitApp: function (anEvent) { -console.log("SHOULD EXIT APP"); +//console.log("SHOULD EXIT APP"); anEvent.preventDefault(); anEvent.stopPropagation(); }, //========================================================================= + showPreferences: function (anEvent) { + var deferredResult; + + this.pages()['preferencePage'].setProps({}); + deferredResult = new Clipperz.Async.Deferred('MainController.showPreferences', {trace:false}); + deferredResult.addMethod(this, 'moveInPage', this.currentPage(), 'preferencePage', true); + deferredResult.callback(); + + return deferredResult; + }, + + //========================================================================= + genericErrorHandler: function (anEvent, anError) { var errorMessage; var result; result = anError; errorMessage = "login failed"; if (anError['isPermanent'] === true) { this.pages()['errorPage'].setProps({message:anError.message}); this.moveInPage(this.currentPage(), 'errorPage'); errorMessage = "failure"; } else { if ('pin' in anEvent) { errorCount = Clipperz.PM.PIN.recordFailedAttempt(); if (errorCount == -1) { errorMessage = "PIN resetted"; } } } this.overlay().failed(errorMessage, 1); return result; }, //========================================================================= slidePage: function (fromPage, toPage, direction) { var fromPosition; var toPosition; if (direction == "LEFT") { fromPosition = 'right'; @@ -451,41 +524,60 @@ console.log("SHOULD EXIT APP"); }, setCurrentPage: function (aPage) { this.pageStack().unshift(aPage); }, moveInPage: function (fromPage, toPage, addToHistory) { var shouldAddItemToHistory; shouldAddItemToHistory = typeof(addToHistory) == 'undefined' ? false : addToHistory; this.slidePage(MochiKit.DOM.getElement(fromPage), MochiKit.DOM.getElement(toPage), 'LEFT'); this.setCurrentPage(toPage); if (shouldAddItemToHistory) { //console.log("ADD ITEM TO HISTORY"); //console.log("ADD ITEM TO HISTORY - window", window); //console.log("ADD ITEM TO HISTORY - window.history", window.history); window.history.pushState({'fromPage': fromPage, 'toPage': toPage}); //# window.history.pushState(); //console.log("ADDED ITEM TO HISTORY"); } else { //console.log("Skip HISTORY"); } }, moveOutPage: function (fromPage, toPage) { this.slidePage(MochiKit.DOM.getElement(fromPage), MochiKit.DOM.getElement(toPage), 'RIGHT'); this.setCurrentPage(toPage); }, //========================================================================= + + synchronizeLocalData: function (anEvent) { + var deferredResult; + + deferredResult = new Clipperz.Async.Deferred('MainController.synchronizeLocalData', {trace:true}); +// deferredResult.addMethod(this.proxy(), 'message', 'downloadAccountData', {}); + deferredResult.addMethod(this.user().connection(), 'message', 'downloadAccountData', {}); + deferredResult.addCallback(function (aResult) { + Clipperz.PM.DataModel.devicePreferences.setAccountDataWityResponse(aResult); +// localStorage.setItem('clipperz_dump_data', aResult['data']); +// localStorage.setItem('clipperz_dump_version', aResult['version']); +// localStorage.setItem('clipperz_dump_date', new Date()); + }) + deferredResult.callback(); + + return deferredResult; + }, + + //========================================================================= /* wrongAppVersion: function (anError) { // this.pages()['errorPage'].setProps({message:anError.message}); // this.moveInPage('errorPage', this.currentPage()); }, */ //========================================================================= __syntaxFix__: "syntax fix" }); |