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 | |||
@@ -37,6 +37,7 @@ Clipperz.PM.UI.Components.CardDetail = React.createClass({ | |||
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 | }, |
@@ -45,6 +46,32 @@ Clipperz.PM.UI.Components.CardDetail = React.createClass({ | |||
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) { |
@@ -61,30 +88,56 @@ Clipperz.PM.UI.Components.CardDetail = React.createClass({ | |||
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 | ||
@@ -98,7 +151,8 @@ Clipperz.PM.UI.Components.CardDetail = React.createClass({ | |||
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) { |
@@ -109,7 +163,7 @@ Clipperz.PM.UI.Components.CardDetail = React.createClass({ | |||
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'] }) |
@@ -118,9 +172,8 @@ Clipperz.PM.UI.Components.CardDetail = React.createClass({ | |||
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, |
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 | |||
@@ -97,11 +97,18 @@ console.log("focusOnSearchField", this.refs['searchField']); | |||
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')), |
@@ -146,9 +153,9 @@ console.log("focusOnSearchField", this.refs['searchField']); | |||
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 | ]), |
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 | |||
@@ -92,14 +92,14 @@ Clipperz.PM.UI.Components.LoginForm = React.createClass({ | |||
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") |
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 | |||
@@ -94,9 +94,10 @@ Clipperz.Base.extend(Clipperz.PM.UI.Components.Overlay, Object, { | |||
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 () { |
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 | }); | ||