Diffstat (limited to 'frontend/delta/js/Clipperz/PM/UI/Components') (more/less context) (ignore whitespace changes)
6 files changed, 218 insertions, 25 deletions
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 | |||
@@ -1,142 +1,195 @@ | |||
1 | /* | 1 | /* |
2 | 2 | ||
3 | Copyright 2008-2013 Clipperz Srl | 3 | Copyright 2008-2013 Clipperz Srl |
4 | 4 | ||
5 | This file is part of Clipperz, the online password manager. | 5 | This file is part of Clipperz, the online password manager. |
6 | For further information about its features and functionalities please | 6 | For further information about its features and functionalities please |
7 | refer to http://www.clipperz.com. | 7 | refer to http://www.clipperz.com. |
8 | 8 | ||
9 | * Clipperz is free software: you can redistribute it and/or modify it | 9 | * Clipperz is free software: you can redistribute it and/or modify it |
10 | under the terms of the GNU Affero General Public License as published | 10 | under the terms of the GNU Affero General Public License as published |
11 | by the Free Software Foundation, either version 3 of the License, or | 11 | by the Free Software Foundation, either version 3 of the License, or |
12 | (at your option) any later version. | 12 | (at your option) any later version. |
13 | 13 | ||
14 | * Clipperz is distributed in the hope that it will be useful, but | 14 | * Clipperz is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of | 15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | 16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. | 17 | See the GNU Affero General Public License for more details. |
18 | 18 | ||
19 | * You should have received a copy of the GNU Affero General Public | 19 | * You should have received a copy of the GNU Affero General Public |
20 | License along with Clipperz. If not, see http://www.gnu.org/licenses/. | 20 | License along with Clipperz. If not, see http://www.gnu.org/licenses/. |
21 | 21 | ||
22 | */ | 22 | */ |
23 | 23 | ||
24 | Clipperz.PM.UI.Components.CardDetail = React.createClass({ | 24 | Clipperz.PM.UI.Components.CardDetail = React.createClass({ |
25 | 25 | ||
26 | getDefaultProps: function () { | 26 | getDefaultProps: function () { |
27 | return { | 27 | return { |
28 | // searchDelay: 0.3 | 28 | // searchDelay: 0.3 |
29 | } | 29 | } |
30 | }, | 30 | }, |
31 | 31 | ||
32 | propTypes: { | 32 | propTypes: { |
33 | card: React.PropTypes.object.isRequired | 33 | card: React.PropTypes.object.isRequired |
34 | }, | 34 | }, |
35 | 35 | ||
36 | getInitialState: function () { | 36 | getInitialState: function () { |
37 | return { | 37 | return { |
38 | // showSearch: false, | 38 | // showSearch: false, |
39 | // searchTimer: null, | 39 | // searchTimer: null, |
40 | unmaskedFields: new Clipperz.Set(), | ||
40 | starred: false | 41 | starred: false |
41 | }; | 42 | }; |
42 | }, | 43 | }, |
43 | 44 | ||
44 | handleDirectLoginClick: function (aDirectLoginReference, anEvent) { | 45 | handleDirectLoginClick: function (aDirectLoginReference, anEvent) { |
45 | MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'runDirectLogin', {record:this.props.card['reference'], directLogin:aDirectLoginReference}); | 46 | MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'runDirectLogin', {record:this.props.card['reference'], directLogin:aDirectLoginReference}); |
46 | }, | 47 | }, |
47 | 48 | ||
49 | toggleFieldVisibility: function (aField, anEvent) { | ||
50 | var unmaskedFields; | ||
51 | var fieldReference; | ||
52 | |||
53 | unmaskedFields = this.state['unmaskedFields']; | ||
54 | fieldReference = aField['reference'] | ||
55 | if (unmaskedFields.contains(fieldReference)) { | ||
56 | unmaskedFields.remove(fieldReference) | ||
57 | } else { | ||
58 | unmaskedFields.add(fieldReference) | ||
59 | } | ||
60 | |||
61 | this.setState({'unmaskedFields': unmaskedFields}); | ||
62 | }, | ||
63 | |||
64 | handleGoAction: function (aField, anEvent) { | ||
65 | var newWindow; | ||
66 | |||
67 | newWindow = MochiKit.DOM.currentWindow().open(aField['value'], '_blank'); | ||
68 | newWindow.focus(); | ||
69 | }, | ||
70 | |||
71 | handleEmailAction: function (aField, anEvent) { | ||
72 | MochiKit.DOM.currentWindow().location = 'mailto:' + aField['value']; | ||
73 | }, | ||
74 | |||
48 | //========================================================================= | 75 | //========================================================================= |
49 | 76 | ||
50 | normalizeFieldValue: function (aValue) { | 77 | normalizeFieldValue: function (aValue) { |
51 | varresult = []; | 78 | varresult = []; |
52 | varrows = aValue.split('\n'); | 79 | varrows = aValue.split('\n'); |
53 | 80 | ||
54 | for (var i = 0; i < rows.length; i++) { | 81 | for (var i = 0; i < rows.length; i++) { |
55 | if (i > 0) { | 82 | if (i > 0) { |
56 | result.push(React.DOM.br()); | 83 | result.push(React.DOM.br()); |
57 | } | 84 | } |
58 | result.push(rows[i].replace(/[\s]/g, '\u00A0')); | 85 | result.push(rows[i].replace(/[\s]/g, '\u00A0')); |
59 | } | 86 | } |
60 | 87 | ||
61 | return result; | 88 | return result; |
62 | }, | 89 | }, |
63 | 90 | ||
64 | renderField: function (aField) { | 91 | renderFieldActionButton: function (aField) { |
65 | //console.log("FIELD", aField); | 92 | // varactionLabel; |
66 | varactionLabel; | 93 | var result; |
67 | 94 | ||
68 | if (aField['actionType'] == 'URL') { | 95 | if (aField['actionType'] == 'URL') { |
69 | actionLabel = "go"; | 96 | result = React.DOM.div({className:'actionWrapper', onClick:MochiKit.Base.method(this, 'handleGoAction', aField)}, [ |
97 | React.DOM.a({className:aField['actionType']}, "go") | ||
98 | ]); | ||
70 | } else if (aField['actionType'] == 'PASSWORD') { | 99 | } else if (aField['actionType'] == 'PASSWORD') { |
71 | actionLabel = "locked"; | 100 | var icon; |
101 | |||
102 | if (this.state['unmaskedFields'].contains(aField['reference'])) { | ||
103 | icon = "unlocked"; | ||
104 | } else { | ||
105 | icon = "locked"; | ||
106 | } | ||
107 | result =React.DOM.div({className:'actionWrapper', onClick:MochiKit.Base.method(this, 'toggleFieldVisibility', aField)}, [ | ||
108 | React.DOM.a({className:aField['actionType']}, icon) | ||
109 | ]); | ||
72 | } else if (aField['actionType'] == 'EMAIL') { | 110 | } else if (aField['actionType'] == 'EMAIL') { |
73 | actionLabel = "email"; | 111 | result =React.DOM.div({className:'actionWrapper', onClick:MochiKit.Base.method(this, 'handleEmailAction', aField)}, [ |
112 | React.DOM.a({className:aField['actionType']}, "email") | ||
113 | ]); | ||
74 | } else { | 114 | } else { |
75 | actionLabel = ""; | 115 | result = null; |
116 | } | ||
117 | |||
118 | return result; | ||
119 | }, | ||
120 | |||
121 | renderField: function (aField) { | ||
122 | //console.log("FIELD", aField); | ||
123 | var fieldExtraClass; | ||
124 | |||
125 | fieldExtraClass = aField['actionType']; | ||
126 | if (this.state['unmaskedFields'].contains(aField['reference'])) { | ||
127 | fieldExtraClass = fieldExtraClass + ' unlocked'; | ||
76 | } | 128 | } |
77 | 129 | ||
78 | returnReact.DOM.div({className:'listItem ' + aField['actionType']}, [ | 130 | returnReact.DOM.div({className:'listItem ' + fieldExtraClass, key:aField['reference']}, [ |
79 | React.DOM.div({className:'fieldWrapper'}, [ | 131 | React.DOM.div({className:'fieldWrapper'}, [ |
80 | React.DOM.div({className:'fieldInnerWrapper'}, [ | 132 | React.DOM.div({className:'fieldInnerWrapper'}, [ |
81 | React.DOM.div({className:'labelWrapper'}, React.DOM.span({className:'label'}, aField['label'])), | 133 | React.DOM.div({className:'labelWrapper'}, React.DOM.span({className:'label'}, aField['label'])), |
82 | React.DOM.div({className:'valueWrapper'}, React.DOM.span({className:'value ' + aField['actionType']}, this.normalizeFieldValue(aField['value']))) | 134 | React.DOM.div({className:'valueWrapper'}, React.DOM.span({className:'value ' + fieldExtraClass}, this.normalizeFieldValue(aField['value']))) |
83 | ]) | 135 | ]) |
84 | ]), | 136 | ]), |
85 | React.DOM.div({className:'actionWrapper'}, [ | 137 | this.renderFieldActionButton(aField) |
86 | React.DOM.div({className:aField['actionType']}, actionLabel) | 138 | // React.DOM.div({className:'actionWrapper'}, [ |
87 | ]) | 139 | // React.DOM.div({className:aField['actionType']}, actionLabel) |
140 | // ]) | ||
88 | ]); | 141 | ]); |
89 | }, | 142 | }, |
90 | 143 | ||
91 | renderDirectLogin: function (aDirectLogin) { | 144 | renderDirectLogin: function (aDirectLogin) { |
92 | //console.log("DIRECT LOGIN", aDirectLogin); | 145 | //console.log("DIRECT LOGIN", aDirectLogin); |
93 | returnReact.DOM.div({className:'listItem', onClick:MochiKit.Base.method(this, 'handleDirectLoginClick', aDirectLogin['reference'])}, [ | 146 | returnReact.DOM.div({className:'listItem', onClick:MochiKit.Base.method(this, 'handleDirectLoginClick', aDirectLogin['reference'])}, [ |
94 | React.DOM.div({className:'labelWrapper'}, React.DOM.span({className:'label'}, aDirectLogin['label'])), | 147 | React.DOM.div({className:'labelWrapper'}, React.DOM.span({className:'label'}, aDirectLogin['label'])), |
95 | React.DOM.div({className:'faviconWrapper'}, React.DOM.img({className:'favicon', src:aDirectLogin['favicon']})), | 148 | React.DOM.div({className:'faviconWrapper'}, React.DOM.img({className:'favicon', src:aDirectLogin['favicon']})), |
96 | React.DOM.div({className:'directLoginLinkWrapper'}, React.DOM.span({className:'directLoginLink'}, "go")) | 149 | React.DOM.div({className:'directLoginLinkWrapper'}, React.DOM.span({className:'directLoginLink'}, "go")) |
97 | ]); | 150 | ]); |
98 | }, | 151 | }, |
99 | 152 | ||
100 | handleBackClick: function (anEvent) { | 153 | handleBackClick: function (anEvent) { |
101 | window.history.back(); | 154 | // window.history.back(); |
155 | MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'goBack'); | ||
102 | }, | 156 | }, |
103 | 157 | ||
104 | handleStarClick: function (anEvent) { | 158 | handleStarClick: function (anEvent) { |
105 | this.setState({starred: !this.state['starred']}); | 159 | this.setState({starred: !this.state['starred']}); |
106 | }, | 160 | }, |
107 | 161 | ||
108 | //========================================================================= | 162 | //========================================================================= |
109 | 163 | ||
110 | render: function () { | 164 | render: function () { |
111 | var card = this.props.card; | 165 | var card = this.props.card; |
112 | var starredStatus = (this.state['starred'] ? "starred" : "unstarred"); | 166 | // var starredStatus = (this.state['starred'] ? "starred" : "unstarred"); |
113 | 167 | ||
114 | if ((typeof(card['fields']) != 'undefined') && (card['notes'] != '')) { | 168 | if ((typeof(card['fields']) != 'undefined') && (card['notes'] != '')) { |
115 | card['fields'].push({ 'actionType': 'NOTES', 'isHidden': false, 'label': "notes", 'reference': "notes", 'value': card['notes'] }) | 169 | card['fields'].push({ 'actionType': 'NOTES', 'isHidden': false, 'label': "notes", 'reference': "notes", 'value': card['notes'] }) |
116 | } | 170 | } |
117 | 171 | ||
118 | returnReact.DOM.div({className:'cardDetail'}, [ | 172 | returnReact.DOM.div({className:'cardDetail'}, [ |
119 | React.DOM.div({className:'header'}, [ | 173 | React.DOM.div({className:'header'}, [ |
120 | React.DOM.div({className:'titleWrapper'}, React.DOM.div({className:'title'}, card.title)), | 174 | React.DOM.div({className:'titleWrapper'}, React.DOM.div({className:'title'}, card.title)), |
121 | // React.DOM.div({className:'titleWrapper'}, React.DOM.div({className:'title'}, card.title + ' ' + card.title + ' ' + card.title + ' ' + card.title)), | ||
122 | React.DOM.div({className:'backWrapper'}, React.DOM.a({className:'button back', onClick:this.handleBackClick}, "back")), | 175 | React.DOM.div({className:'backWrapper'}, React.DOM.a({className:'button back', onClick:this.handleBackClick}, "back")), |
123 | React.DOM.div({className:'starWrapper'}, React.DOM.a({className:'star', onClick:this.handleStarClick}, starredStatus)) | 176 | // React.DOM.div({className:'starWrapper'}, React.DOM.a({className:'star', onClick:this.handleStarClick}, starredStatus)) |
124 | ]), | 177 | ]), |
125 | React.DOM.div({className:'content'}, [ | 178 | React.DOM.div({className:'content'}, [ |
126 | card.fields ? React.DOM.div({className:'fields'}, MochiKit.Base.map(this.renderField, card.fields)) : null, | 179 | card.fields ? React.DOM.div({className:'fields'}, MochiKit.Base.map(this.renderField, card.fields)) : null, |
127 | card.directLogins ? React.DOM.div({className:'directLogins'}, MochiKit.Base.map(this.renderDirectLogin,card.directLogins)): null | 180 | card.directLogins ? React.DOM.div({className:'directLogins'}, MochiKit.Base.map(this.renderDirectLogin,card.directLogins)): null |
128 | ]), | 181 | ]), |
129 | React.DOM.div({className:'footer'}, [ | 182 | React.DOM.div({className:'footer'}, [ |
130 | /* | 183 | /* |
131 | // React.DOM.a({className:'cancel'}, "cancel"), | 184 | // React.DOM.a({className:'cancel'}, "cancel"), |
132 | // React.DOM.a({className:'save'}, "save") | 185 | // React.DOM.a({className:'save'}, "save") |
133 | 186 | ||
134 | React.DOM.a({className:'cancel button'}, "failed"), | 187 | React.DOM.a({className:'cancel button'}, "failed"), |
135 | React.DOM.a({className:'save button'}, "done") | 188 | React.DOM.a({className:'save button'}, "done") |
136 | */ | 189 | */ |
137 | ]) | 190 | ]) |
138 | ]); | 191 | ]); |
139 | } | 192 | } |
140 | 193 | ||
141 | //========================================================================= | 194 | //========================================================================= |
142 | }); | 195 | }); |
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 | |||
@@ -52,110 +52,117 @@ Clipperz.PM.UI.Components.CardList = React.createClass({ | |||
52 | showSearchBox = !this.state.showSearch; | 52 | showSearchBox = !this.state.showSearch; |
53 | 53 | ||
54 | this.setState({showSearch: showSearchBox}); | 54 | this.setState({showSearch: showSearchBox}); |
55 | 55 | ||
56 | if (showSearchBox) { | 56 | if (showSearchBox) { |
57 | MochiKit.Async.callLater(0.1, MochiKit.Base.method(this, 'focusOnSearchField')); | 57 | MochiKit.Async.callLater(0.1, MochiKit.Base.method(this, 'focusOnSearchField')); |
58 | } | 58 | } |
59 | }, | 59 | }, |
60 | 60 | ||
61 | updateSearchText: function (anEvent) { | 61 | updateSearchText: function (anEvent) { |
62 | varsearchText; | 62 | varsearchText; |
63 | 63 | ||
64 | searchText = anEvent.target.value; | 64 | searchText = anEvent.target.value; |
65 | //console.log(">>> updateSearchText", searchText); | 65 | //console.log(">>> updateSearchText", searchText); |
66 | 66 | ||
67 | if ((this.state['searchTimer'] != null) && (searchText != this.state['searchText'])) { | 67 | if ((this.state['searchTimer'] != null) && (searchText != this.state['searchText'])) { |
68 | this.state['searchTimer'].cancel(); | 68 | this.state['searchTimer'].cancel(); |
69 | } | 69 | } |
70 | 70 | ||
71 | if (searchText != this.state['searchText']) { | 71 | if (searchText != this.state['searchText']) { |
72 | this.state['searchText'] = searchText; | 72 | this.state['searchText'] = searchText; |
73 | this.state['searchTimer'] = MochiKit.Async.callLater(this.props['searchDelay'], MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'searchCards', searchText); | 73 | this.state['searchTimer'] = MochiKit.Async.callLater(this.props['searchDelay'], MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'searchCards', searchText); |
74 | } | 74 | } |
75 | }, | 75 | }, |
76 | 76 | ||
77 | focusOnSearchField: function () { | 77 | focusOnSearchField: function () { |
78 | console.log("focusOnSearchField", this.refs['searchField']); | 78 | console.log("focusOnSearchField", this.refs['searchField']); |
79 | this.refs['searchField'].getDOMNode.focus(); | 79 | this.refs['searchField'].getDOMNode.focus(); |
80 | }, | 80 | }, |
81 | 81 | ||
82 | searchBox: function () { | 82 | searchBox: function () { |
83 | var result; | 83 | var result; |
84 | 84 | ||
85 | if (this.state.showSearch) { | 85 | if (this.state.showSearch) { |
86 | result =React.DOM.div({className:'searchBox'}, [ | 86 | result =React.DOM.div({className:'searchBox'}, [ |
87 | React.DOM.div(null, [ | 87 | React.DOM.div(null, [ |
88 | React.DOM.input({type:'search', placeholder:"search", ref:'searchField', onChange:this.updateSearchText}) | 88 | React.DOM.input({type:'search', placeholder:"search", ref:'searchField', onChange:this.updateSearchText}) |
89 | ]) | 89 | ]) |
90 | ]); | 90 | ]); |
91 | } else { | 91 | } else { |
92 | result = null; | 92 | result = null; |
93 | } | 93 | } |
94 | 94 | ||
95 | return result; | 95 | return result; |
96 | }, | 96 | }, |
97 | 97 | ||
98 | //========================================================================= | 98 | //========================================================================= |
99 | 99 | ||
100 | showPreferences: function (anEvent) { | ||
101 | MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'showPreferences', anEvent); | ||
102 | }, | ||
103 | |||
104 | //========================================================================= | ||
105 | |||
100 | cardItem: function (aRecordReference) { | 106 | cardItem: function (aRecordReference) { |
101 | varreference = aRecordReference['_reference']; | 107 | varreference = aRecordReference['_reference']; |
102 | varselectedCard = (reference == this.props.selectedCard); | 108 | varselectedCard = (reference == this.props.selectedCard); |
103 | 109 | ||
104 | returnReact.DOM.div({className:'listItem', onClick:MochiKit.Base.method(this, 'handleClickOnCardDetail', reference)}, [ | 110 | //TODO: verify if it is possible to put the onClick handler on the container 'div', instead of adding it to each 'div' item. |
111 | returnReact.DOM.div({className:'listItem', key:reference, onClick:MochiKit.Base.method(this, 'handleClickOnCardDetail', reference)}, [ | ||
105 | React.DOM.div({className:'labelWrapper'}, React.DOM.span({className:'label'}, aRecordReference.label)), | 112 | React.DOM.div({className:'labelWrapper'}, React.DOM.span({className:'label'}, aRecordReference.label)), |
106 | // React.DOM.div({className:'labelWrapper'}, React.DOM.span({className:'label'}, aRecordReference.label + ' ' + aRecordReference.label + ' ' + aRecordReference.label + ' ' + aRecordReference.label + ' ' + aRecordReference.label)), | 113 | // React.DOM.div({className:'labelWrapper'}, React.DOM.span({className:'label'}, aRecordReference.label + ' ' + aRecordReference.label + ' ' + aRecordReference.label + ' ' + aRecordReference.label + ' ' + aRecordReference.label)), |
107 | React.DOM.div({className:'faviconWrapper'}, aRecordReference.favicon ? React.DOM.img({className:'favicon', src:aRecordReference.favicon}) : React.DOM.div({className:'favicon'}, '\u00A0')), | 114 | React.DOM.div({className:'faviconWrapper'}, aRecordReference.favicon ? React.DOM.img({className:'favicon', src:aRecordReference.favicon}) : React.DOM.div({className:'favicon'}, '\u00A0')), |
108 | React.DOM.div({className:'detailLinkWrapper'}, React.DOM.span({className:'detailLink ' + (selectedCard ? 'icon-spin' : '')}, (selectedCard ? "loading" : "detail"))) | 115 | React.DOM.div({className:'detailLinkWrapper'}, React.DOM.span({className:'detailLink ' + (selectedCard ? 'icon-spin' : '')}, (selectedCard ? "loading" : "detail"))) |
109 | ]); | 116 | ]); |
110 | }, | 117 | }, |
111 | 118 | ||
112 | handleClickOnCardDetail: function (aRecordReference, anEvent) { | 119 | handleClickOnCardDetail: function (aRecordReference, anEvent) { |
113 | MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'showRecord', aRecordReference); | 120 | MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'showRecord', aRecordReference); |
114 | }, | 121 | }, |
115 | 122 | ||
116 | cardListItems: function () { | 123 | cardListItems: function () { |
117 | varlist; | 124 | varlist; |
118 | varresult; | 125 | varresult; |
119 | 126 | ||
120 | list = this.props['cardList']; | 127 | list = this.props['cardList']; |
121 | 128 | ||
122 | if (typeof(list) != 'undefined') { | 129 | if (typeof(list) != 'undefined') { |
123 | result = MochiKit.Base.map(MochiKit.Base.method(this, 'cardItem'), list); | 130 | result = MochiKit.Base.map(MochiKit.Base.method(this, 'cardItem'), list); |
124 | } else { | 131 | } else { |
125 | result = null; | 132 | result = null; |
126 | } | 133 | } |
127 | 134 | ||
128 | return result; | 135 | return result; |
129 | }, | 136 | }, |
130 | 137 | ||
131 | //========================================================================= | 138 | //========================================================================= |
132 | 139 | ||
133 | handleChange: function (anEvent) { | 140 | handleChange: function (anEvent) { |
134 | // varrefs = this.refs; | 141 | // varrefs = this.refs; |
135 | // var refName = MochiKit.Base.filter(function (aRefName) { return refs[aRefName].getDOMNode() == anEvent.target}, MochiKit.Base.keys(this.refs))[0]; | 142 | // var refName = MochiKit.Base.filter(function (aRefName) { return refs[aRefName].getDOMNode() == anEvent.target}, MochiKit.Base.keys(this.refs))[0]; |
136 | // var newState = {}; | 143 | // var newState = {}; |
137 | // | 144 | // |
138 | // newState[refName] = event.target.value; | 145 | // newState[refName] = event.target.value; |
139 | // this.setState(newState); | 146 | // this.setState(newState); |
140 | }, | 147 | }, |
141 | 148 | ||
142 | //========================================================================= | 149 | //========================================================================= |
143 | 150 | ||
144 | render: function() { | 151 | render: function() { |
145 | returnReact.DOM.div(null, [ | 152 | returnReact.DOM.div(null, [ |
146 | React.DOM.div({className:'header'}, [ | 153 | React.DOM.div({className:'header'}, [ |
147 | React.DOM.a({className:'account'}, 'clipperz'), | 154 | React.DOM.a({className:'account'}, 'clipperz'), |
148 | React.DOM.div({className:'features'}, [ | 155 | React.DOM.div({className:'features'}, [ |
149 | React.DOM.a({className:'addCard'}, 'add'), | 156 | // React.DOM.a({className:'addCard'}, 'add'), |
150 | React.DOM.a({className:'search ' + (this.state.showSearch ? 'selected' : ''), onClick:this.toggleSearch}, 'search'), | 157 | React.DOM.a({className:'search ' + (this.state.showSearch ? 'selected' : ''), onClick:this.toggleSearch}, 'search'), |
151 | React.DOM.a({className:'settings'}, 'settings') | 158 | React.DOM.a({className:'settings', onClick:this.showPreferences}, 'settings') |
152 | ]), | 159 | ]), |
153 | // this.searchBox() | 160 | // this.searchBox() |
154 | ]), | 161 | ]), |
155 | this.searchBox(), | 162 | this.searchBox(), |
156 | React.DOM.div({className:'content cardList'}, this.cardListItems()), | 163 | React.DOM.div({className:'content cardList'}, this.cardListItems()), |
157 | ]); | 164 | ]); |
158 | } | 165 | } |
159 | 166 | ||
160 | //========================================================================= | 167 | //========================================================================= |
161 | }); | 168 | }); |
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 @@ | |||
1 | /* | ||
2 | |||
3 | Copyright 2008-2013 Clipperz Srl | ||
4 | |||
5 | This file is part of Clipperz, the online password manager. | ||
6 | For further information about its features and functionalities please | ||
7 | refer to http://www.clipperz.com. | ||
8 | |||
9 | * Clipperz is free software: you can redistribute it and/or modify it | ||
10 | under the terms of the GNU Affero General Public License as published | ||
11 | by the Free Software Foundation, either version 3 of the License, or | ||
12 | (at your option) any later version. | ||
13 | |||
14 | * Clipperz is distributed in the hope that it will be useful, but | ||
15 | WITHOUT ANY WARRANTY; without even the implied warranty of | ||
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | ||
17 | See the GNU Affero General Public License for more details. | ||
18 | |||
19 | * You should have received a copy of the GNU Affero General Public | ||
20 | License along with Clipperz. If not, see http://www.gnu.org/licenses/. | ||
21 | |||
22 | */ | ||
23 | |||
24 | Clipperz.PM.UI.Components.Checkbox = React.createClass({ | ||
25 | //http://development.tobypitman.com/iphoneCheckboxes/iphoneCheckboxes2.html | ||
26 | |||
27 | propTypes: { | ||
28 | 'checked': React.PropTypes.bool.isRequired, | ||
29 | 'id': React.PropTypes.string.isRequired, | ||
30 | 'eventHandler':React.PropTypes.func.isRequired | ||
31 | }, | ||
32 | |||
33 | //========================================================================= | ||
34 | |||
35 | render: function () { | ||
36 | returnReact.DOM.div({className:'checkbox', onClick:this.props['eventHandler']}, [ | ||
37 | React.DOM.input({name:this.props['id'], id:this.props['id'], value:this.props['id'], type:'checkbox', checked:this.props['checked']}), | ||
38 | React.DOM.label({className:'check', 'for':this.props['id']}), | ||
39 | React.DOM.label({className:'info', 'for':this.props['id']}, "enable local storage") | ||
40 | ]); | ||
41 | } | ||
42 | |||
43 | //========================================================================= | ||
44 | }); | ||
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 | |||
@@ -47,104 +47,104 @@ Clipperz.PM.UI.Components.LoginForm = React.createClass({ | |||
47 | }; | 47 | }; |
48 | }, | 48 | }, |
49 | 49 | ||
50 | //========================================================================= | 50 | //========================================================================= |
51 | 51 | ||
52 | handleChange: function (anEvent) { | 52 | handleChange: function (anEvent) { |
53 | varrefs = this.refs; | 53 | varrefs = this.refs; |
54 | var refName = MochiKit.Base.filter(function (aRefName) { return refs[aRefName].getDOMNode() == anEvent.target}, MochiKit.Base.keys(this.refs))[0]; | 54 | var refName = MochiKit.Base.filter(function (aRefName) { return refs[aRefName].getDOMNode() == anEvent.target}, MochiKit.Base.keys(this.refs))[0]; |
55 | var newState = {}; | 55 | var newState = {}; |
56 | 56 | ||
57 | newState[refName] = event.target.value; | 57 | newState[refName] = event.target.value; |
58 | this.setState(newState); | 58 | this.setState(newState); |
59 | }, | 59 | }, |
60 | 60 | ||
61 | //========================================================================= | 61 | //========================================================================= |
62 | 62 | ||
63 | handleCredentialSubmit: function (event) { | 63 | handleCredentialSubmit: function (event) { |
64 | event.preventDefault(); | 64 | event.preventDefault(); |
65 | 65 | ||
66 | this.refs['passphrase'].getDOMNode().blur(); | 66 | this.refs['passphrase'].getDOMNode().blur(); |
67 | 67 | ||
68 | var credentials = { | 68 | var credentials = { |
69 | 'username': this.refs['username'].getDOMNode().value, | 69 | 'username': this.refs['username'].getDOMNode().value, |
70 | 'passphrase': this.refs['passphrase'].getDOMNode().value | 70 | 'passphrase': this.refs['passphrase'].getDOMNode().value |
71 | } | 71 | } |
72 | MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'doLogin', credentials); | 72 | MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'doLogin', credentials); |
73 | }, | 73 | }, |
74 | 74 | ||
75 | handleRegistrationLinkClick: function (event) { | 75 | handleRegistrationLinkClick: function (event) { |
76 | event.preventDefault(); | 76 | event.preventDefault(); |
77 | MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'showRegistrationForm'); | 77 | MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'showRegistrationForm'); |
78 | }, | 78 | }, |
79 | 79 | ||
80 | //------------------------------------------------------------------------- | 80 | //------------------------------------------------------------------------- |
81 | 81 | ||
82 | shouldEnableLoginButton: function () { | 82 | shouldEnableLoginButton: function () { |
83 | var result; | 83 | var result; |
84 | 84 | ||
85 | return( | 85 | return( |
86 | ((this.state['username'] != '') && (this.state['passphrase'] != '')) | 86 | ((this.state['username'] != '') && (this.state['passphrase'] != '')) |
87 | || | 87 | || |
88 | (this.state['pin'] != '') | 88 | (this.state['pin'] != '') |
89 | ) && !this.props['disabled']; | 89 | ) && !this.props['disabled']; |
90 | }, | 90 | }, |
91 | 91 | ||
92 | 92 | ||
93 | loginForm: function () { | 93 | loginForm: function () { |
94 | registrationLink =React.DOM.div({'className':'registrationLink'}, [ | 94 | registrationLink =React.DOM.div({'className':'registrationLink'}, [ |
95 | React.DOM.a({'onClick':this.handleRegistrationLinkClick}, "Need an account") | 95 | React.DOM.a({'onClick':this.handleRegistrationLinkClick}, "Sign up") |
96 | ]); | 96 | ]); |
97 | returnReact.DOM.div({'className':'loginForm credentials'},[ | 97 | returnReact.DOM.div({'className':'loginForm credentials'},[ |
98 | React.DOM.form({onChange: this.handleChange, onSubmit:this.handleCredentialSubmit}, [ | 98 | React.DOM.form({onChange: this.handleChange, onSubmit:this.handleCredentialSubmit}, [ |
99 | React.DOM.div(null,[ | 99 | React.DOM.div(null,[ |
100 | React.DOM.label({'for':'name'}, "username"), | 100 | React.DOM.label({'for' :'name'}, "username"), |
101 | React.DOM.input({'type':'text', 'name':'name', 'ref':'username', 'placeholder':"username", 'key':'username', 'autoCapitalize':'none'}), | 101 | React.DOM.input({'type':'text', 'name':'name', 'ref':'username', 'placeholder':"username", 'key':'username', 'autoCapitalize':'none'}), |
102 | React.DOM.label({'for':'passphrase'}, "passphrase"), | 102 | React.DOM.label({'for' :'passphrase'}, "passphrase"), |
103 | React.DOM.input({'type':'password', 'name':'passphrase', 'ref':'passphrase', 'placeholder':"passphrase", 'key':'passphrase'}) | 103 | React.DOM.input({'type':'password', 'name':'passphrase', 'ref':'passphrase', 'placeholder':"passphrase", 'key':'passphrase'}) |
104 | ]), | 104 | ]), |
105 | React.DOM.button({'type':'submit', 'disabled':!this.shouldEnableLoginButton(), 'className':'button'}, "login") | 105 | React.DOM.button({'type':'submit', 'disabled':!this.shouldEnableLoginButton(), 'className':'button'}, "login") |
106 | ]), | 106 | ]), |
107 | this.props.isNewUserRegistrationAvailable ? registrationLink : null | 107 | this.props.isNewUserRegistrationAvailable ? registrationLink : null |
108 | ]); | 108 | ]); |
109 | }, | 109 | }, |
110 | 110 | ||
111 | handlePINSubmit: function (event) { | 111 | handlePINSubmit: function (event) { |
112 | event.preventDefault(); | 112 | event.preventDefault(); |
113 | 113 | ||
114 | this.refs['pin'].getDOMNode().blur(); | 114 | this.refs['pin'].getDOMNode().blur(); |
115 | 115 | ||
116 | var credentials = { | 116 | var credentials = { |
117 | pin: this.refs['pin'].getDOMNode().value | 117 | pin: this.refs['pin'].getDOMNode().value |
118 | } | 118 | } |
119 | 119 | ||
120 | MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'doLogin', credentials); | 120 | MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'doLogin', credentials); |
121 | }, | 121 | }, |
122 | 122 | ||
123 | pinForm: function () { | 123 | pinForm: function () { |
124 | returnReact.DOM.div({'className':'loginForm pin'},[ | 124 | returnReact.DOM.div({'className':'loginForm pin'},[ |
125 | React.DOM.form({onChange: this.handleChange, onSubmit:this.handlePINSubmit}, [ | 125 | React.DOM.form({onChange: this.handleChange, onSubmit:this.handlePINSubmit}, [ |
126 | React.DOM.div(null,[ | 126 | React.DOM.div(null,[ |
127 | React.DOM.label({'for':'pin'}, "pin"), | 127 | React.DOM.label({'for':'pin'}, "pin"), |
128 | React.DOM.input({'type':'text', 'name':'pin', 'ref':'pin', placeholder:"PIN", 'key':'pin', 'autocapitalize':'none'}) | 128 | React.DOM.input({'type':'text', 'name':'pin', 'ref':'pin', placeholder:"PIN", 'key':'pin', 'autocapitalize':'none'}) |
129 | ]), | 129 | ]), |
130 | React.DOM.button({'type':'submit', 'disabled':this.props.disabled, 'className':'button'}, "login") | 130 | React.DOM.button({'type':'submit', 'disabled':this.props.disabled, 'className':'button'}, "login") |
131 | ]) | 131 | ]) |
132 | ]); | 132 | ]); |
133 | }, | 133 | }, |
134 | 134 | ||
135 | setInitialFocus: function () { | 135 | setInitialFocus: function () { |
136 | if (this.props.mode == 'PIN') { | 136 | if (this.props.mode == 'PIN') { |
137 | this.refs['pin'].getDOMNode().select(); | 137 | this.refs['pin'].getDOMNode().select(); |
138 | } else { | 138 | } else { |
139 | if (this.refs['username'].getDOMNode().value == '') { | 139 | if (this.refs['username'].getDOMNode().value == '') { |
140 | this.refs['username'].getDOMNode().focus(); | 140 | this.refs['username'].getDOMNode().focus(); |
141 | } else{ | 141 | } else{ |
142 | this.refs['passphrase'].getDOMNode().select(); | 142 | this.refs['passphrase'].getDOMNode().select(); |
143 | } | 143 | } |
144 | } | 144 | } |
145 | }, | 145 | }, |
146 | 146 | ||
147 | render: function() { | 147 | render: function() { |
148 | returnnew this.props.template({'innerComponent': this.props.mode == 'PIN' ? this.pinForm() : this.loginForm()}); | 148 | returnnew this.props.template({'innerComponent': this.props.mode == 'PIN' ? this.pinForm() : this.loginForm()}); |
149 | } | 149 | } |
150 | }); | 150 | }); |
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 | |||
@@ -49,74 +49,75 @@ Clipperz.Base.extend(Clipperz.PM.UI.Components.Overlay, Object, { | |||
49 | 49 | ||
50 | 'getElement': function (aClass) { | 50 | 'getElement': function (aClass) { |
51 | return MochiKit.Selector.findChildElements(this.element(), ['.'+aClass])[0]; | 51 | return MochiKit.Selector.findChildElements(this.element(), ['.'+aClass])[0]; |
52 | }, | 52 | }, |
53 | 53 | ||
54 | //------------------------------------------------------------------------- | 54 | //------------------------------------------------------------------------- |
55 | 55 | ||
56 | 'show': function (aMessage) { | 56 | 'show': function (aMessage) { |
57 | this.resetStatus(); | 57 | this.resetStatus(); |
58 | this.setMessage(aMessage); | 58 | this.setMessage(aMessage); |
59 | MochiKit.DOM.removeElementClass(this.element(), 'ios-overlay-hide'); | 59 | MochiKit.DOM.removeElementClass(this.element(), 'ios-overlay-hide'); |
60 | MochiKit.DOM.addElementClass(this.element(), 'ios-overlay-show'); | 60 | MochiKit.DOM.addElementClass(this.element(), 'ios-overlay-show'); |
61 | }, | 61 | }, |
62 | 62 | ||
63 | 'done': function (aMessage, aDelayBeforeHiding) { | 63 | 'done': function (aMessage, aDelayBeforeHiding) { |
64 | this.completed(this.showDoneIcon, aMessage, aDelayBeforeHiding); | 64 | this.completed(this.showDoneIcon, aMessage, aDelayBeforeHiding); |
65 | }, | 65 | }, |
66 | 66 | ||
67 | 'failed': function (aMessage, aDelayBeforeHiding) { | 67 | 'failed': function (aMessage, aDelayBeforeHiding) { |
68 | this.completed(this.showFailIcon, aMessage, aDelayBeforeHiding); | 68 | this.completed(this.showFailIcon, aMessage, aDelayBeforeHiding); |
69 | }, | 69 | }, |
70 | 70 | ||
71 | //------------------------------------------------------------------------- | 71 | //------------------------------------------------------------------------- |
72 | 72 | ||
73 | 'resetStatus': function () { | 73 | 'resetStatus': function () { |
74 | MochiKit.Style.showElement(this.element()); | 74 | MochiKit.Style.showElement(this.element()); |
75 | MochiKit.Style.showElement(this.getElement('spinner')); | 75 | MochiKit.Style.showElement(this.getElement('spinner')); |
76 | MochiKit.Style.hideElement(this.getElement('done')); | 76 | MochiKit.Style.hideElement(this.getElement('done')); |
77 | MochiKit.Style.hideElement(this.getElement('failed')); | 77 | MochiKit.Style.hideElement(this.getElement('failed')); |
78 | }, | 78 | }, |
79 | 79 | ||
80 | 'setMessage': function (aMessage) { | 80 | 'setMessage': function (aMessage) { |
81 | if (typeof(aMessage) != 'undefined') { | 81 | if (typeof(aMessage) != 'undefined') { |
82 | this.getElement('title').innerHTML = aMessage; | 82 | this.getElement('title').innerHTML = aMessage; |
83 | } | 83 | } |
84 | }, | 84 | }, |
85 | 85 | ||
86 | 'completed': function (aFunctionToShowResult, aMessage, aDelayBeforeHiding) { | 86 | 'completed': function (aFunctionToShowResult, aMessage, aDelayBeforeHiding) { |
87 | var delay = aDelayBeforeHiding || this.defaultDelay(); | 87 | var delay = aDelayBeforeHiding || this.defaultDelay(); |
88 | 88 | ||
89 | this.hideSpinner(); | 89 | this.hideSpinner(); |
90 | MochiKit.Base.bind(aFunctionToShowResult, this)(); | 90 | MochiKit.Base.bind(aFunctionToShowResult, this)(); |
91 | this.setMessage(aMessage); | 91 | this.setMessage(aMessage); |
92 | 92 | ||
93 | MochiKit.Async.callLater(delay, MochiKit.Base.bind(this.hide, this)) | 93 | MochiKit.Async.callLater(delay, MochiKit.Base.bind(this.hide, this)) |
94 | }, | 94 | }, |
95 | 95 | ||
96 | 'hide': function () { | 96 | 'hide': function () { |
97 | MochiKit.DOM.removeElementClass(this.element(), 'ios-overlay-show'); | 97 | var element = this.element(); |
98 | MochiKit.DOM.addElementClass(this.element(), 'ios-overlay-hide'); | 98 | MochiKit.DOM.removeElementClass(element, 'ios-overlay-show'); |
99 | MochiKit.Async.callLater(1, MochiKit.Style.hideElement, this.element()); | 99 | MochiKit.DOM.addElementClass(element, 'ios-overlay-hide'); |
100 | MochiKit.Async.callLater(1, MochiKit.Style.hideElement, element); | ||
100 | }, | 101 | }, |
101 | 102 | ||
102 | 'hideSpinner': function () { | 103 | 'hideSpinner': function () { |
103 | MochiKit.Style.hideElement(this.getElement('spinner')); | 104 | MochiKit.Style.hideElement(this.getElement('spinner')); |
104 | }, | 105 | }, |
105 | 106 | ||
106 | 'showDoneIcon': function () { | 107 | 'showDoneIcon': function () { |
107 | MochiKit.Style.showElement(this.getElement('done')); | 108 | MochiKit.Style.showElement(this.getElement('done')); |
108 | }, | 109 | }, |
109 | 110 | ||
110 | 'showFailIcon': function () { | 111 | 'showFailIcon': function () { |
111 | MochiKit.Style.showElement(this.getElement('failed')); | 112 | MochiKit.Style.showElement(this.getElement('failed')); |
112 | }, | 113 | }, |
113 | 114 | ||
114 | //------------------------------------------------------------------------- | 115 | //------------------------------------------------------------------------- |
115 | 116 | ||
116 | 'defaultDelay': function () { | 117 | 'defaultDelay': function () { |
117 | return this._defaultDelay; | 118 | return this._defaultDelay; |
118 | }, | 119 | }, |
119 | 120 | ||
120 | //------------------------------------------------------------------------- | 121 | //------------------------------------------------------------------------- |
121 | __syntaxFix__: "syntax fix" | 122 | __syntaxFix__: "syntax fix" |
122 | }); | 123 | }); |
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 @@ | |||
1 | /* | ||
2 | |||
3 | Copyright 2008-2013 Clipperz Srl | ||
4 | |||
5 | This file is part of Clipperz, the online password manager. | ||
6 | For further information about its features and functionalities please | ||
7 | refer to http://www.clipperz.com. | ||
8 | |||
9 | * Clipperz is free software: you can redistribute it and/or modify it | ||
10 | under the terms of the GNU Affero General Public License as published | ||
11 | by the Free Software Foundation, either version 3 of the License, or | ||
12 | (at your option) any later version. | ||
13 | |||
14 | * Clipperz is distributed in the hope that it will be useful, but | ||
15 | WITHOUT ANY WARRANTY; without even the implied warranty of | ||
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | ||
17 | See the GNU Affero General Public License for more details. | ||
18 | |||
19 | * You should have received a copy of the GNU Affero General Public | ||
20 | License along with Clipperz. If not, see http://www.gnu.org/licenses/. | ||
21 | |||
22 | */ | ||
23 | |||
24 | Clipperz.PM.UI.Components.PreferencePage = React.createClass({ | ||
25 | |||
26 | getDefaultProps: function () { | ||
27 | return { | ||
28 | } | ||
29 | }, | ||
30 | |||
31 | propTypes: { | ||
32 | // card: React.PropTypes.object.isRequired | ||
33 | // checked: React.PropTypes.boolean.isRequired | ||
34 | }, | ||
35 | |||
36 | getInitialState: function () { | ||
37 | // return { | ||
38 | // shouldStoreDataLocally: false | ||
39 | // }; | ||
40 | }, | ||
41 | |||
42 | handleBackClick: function (anEvent) { | ||
43 | MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'goBack'); | ||
44 | }, | ||
45 | |||
46 | toggleShouldStoreDataLocally: function (anEvent) { | ||
47 | // this.setState({shouldStoreDataLocally: !this.state['shouldStoreDataLocally']}); | ||
48 | Clipperz.PM.DataModel.devicePreferences.setShouldStoreDataLocally(!Clipperz.PM.DataModel.devicePreferences.shouldStoreDataLocally()); | ||
49 | this.setState({}); | ||
50 | }, | ||
51 | |||
52 | shouldStoreDataLocally: function () { | ||
53 | return Clipperz.PM.DataModel.devicePreferences.shouldStoreDataLocally(); | ||
54 | }, | ||
55 | |||
56 | syncNow: function (anEvent) { | ||
57 | MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'synchronizeLocalData'); | ||
58 | }, | ||
59 | |||
60 | //========================================================================= | ||
61 | |||
62 | render: function () { | ||
63 | returnReact.DOM.div({className:'preferences'}, [ | ||
64 | React.DOM.div({className:'header'}, [ | ||
65 | React.DOM.div({className:'titleWrapper'}, React.DOM.div({className:'title'}, "Preferences")), | ||
66 | React.DOM.div({className:'backWrapper'}, React.DOM.a({className:'button back', onClick:this.handleBackClick}, "back")), | ||
67 | ]), | ||
68 | React.DOM.div({className:'content'}, [ | ||
69 | React.DOM.form(null, [ | ||
70 | React.DOM.div({className:'section'}, [ | ||
71 | React.DOM.h4(null, "Local storage"), | ||
72 | React.DOM.p(null, "Store you account data locally for offline viewing"), | ||
73 | new Clipperz.PM.UI.Components.Checkbox({'id':'shouldStoreLocally_checkbox', 'checked':this.shouldStoreDataLocally(), 'eventHandler':this.toggleShouldStoreDataLocally}), | ||
74 | this.shouldStoreDataLocally() ? React.DOM.div({className:'syncInfo'}, [ | ||
75 | // React.DOM.h5(null, "data were never synchronized before"), | ||
76 | React.DOM.a({className:'button', onClick:this.syncNow}, "Sync now") | ||
77 | ]) : null | ||
78 | ]) | ||
79 | ]) | ||
80 | ]), | ||
81 | React.DOM.div({className:'footer'}, [ | ||
82 | |||
83 | ]) | ||
84 | ]); | ||
85 | } | ||
86 | |||
87 | //========================================================================= | ||
88 | }); | ||