Browse Source

#WOA-2354|Resolved|Improve link dialog

Yevhen Pavliuk 8 years ago
parent
commit
7961a91f28
5 changed files with 91 additions and 41 deletions
  1. 58
    8
      src/RichTextEditor.js
  2. 3
    1
      src/RichTextToolbar.js
  3. 7
    0
      src/WebviewMessageHandler.js
  4. 5
    1
      src/const.js
  5. 18
    31
      src/editor.html

+ 58
- 8
src/RichTextEditor.js View File

32
     this.state = {
32
     this.state = {
33
       listeners: [],
33
       listeners: [],
34
       showLinkDialog: false,
34
       showLinkDialog: false,
35
+      linkInitialUrl: '',
35
       linkTitle: '',
36
       linkTitle: '',
36
       linkUrl: '',
37
       linkUrl: '',
37
       keyboardHeight: 0
38
       keyboardHeight: 0
107
             }
108
             }
108
           }
109
           }
109
           break;
110
           break;
111
+        case messages.SELECTED_TEXT_RESPONSE:
112
+          if (this.selectedTextResolve) {
113
+            this.selectedTextResolve(message.data);
114
+            this.selectedTextResolve = undefined;
115
+            this.selectedTextReject = undefined;
116
+            if (this.pendingSelectedText) {
117
+              clearTimeout(this.pendingSelectedText);
118
+              this.pendingSelectedText = undefined;
119
+            }
120
+          }
121
+          break;
110
         case messages.ZSS_INITIALIZED:
122
         case messages.ZSS_INITIALIZED:
111
           if (this.props.customCSS) {
123
           if (this.props.customCSS) {
112
             this.setCustomCSS(this.props.customCSS);
124
             this.setCustomCSS(this.props.customCSS);
117
           this.setContentHTML(this.props.initialContentHTML);
129
           this.setContentHTML(this.props.initialContentHTML);
118
           this.props.editorInitializedCallback && this.props.editorInitializedCallback();
130
           this.props.editorInitializedCallback && this.props.editorInitializedCallback();
119
 
131
 
132
+          break;
133
+        case messages.LINK_TOUCHED:
134
+          this.prepareInsert();
135
+          const {title, url} = message.data;
136
+          this.showLinkDialog(title, url);
120
           break;
137
           break;
121
         case messages.LOG:
138
         case messages.LOG:
122
           console.log('FROM ZSS', message.data);
139
           console.log('FROM ZSS', message.data);
180
   _hideModal() {
197
   _hideModal() {
181
     this.setState({
198
     this.setState({
182
       showLinkDialog: false,
199
       showLinkDialog: false,
200
+      linkInitialUrl: '',
183
       linkTitle: '',
201
       linkTitle: '',
184
       linkUrl: ''
202
       linkUrl: ''
185
     })
203
     })
186
   }
204
   }
187
 
205
 
188
   _renderModalButtons() {
206
   _renderModalButtons() {
189
-    const insertDisabled = this.state.linkTitle.trim().length <= 0 || this.state.linkUrl.trim().length <= 0;
207
+    const insertUpdateDisabled = this.state.linkTitle.trim().length <= 0 || this.state.linkUrl.trim().length <= 0;
190
     const containerPlatformStyle = PlatfomIOS ? {justifyContent: 'space-between'} : {paddingTop: 15};
208
     const containerPlatformStyle = PlatfomIOS ? {justifyContent: 'space-between'} : {paddingTop: 15};
191
     const buttonPlatformStyle = PlatfomIOS ? {flex: 1, height: 45, justifyContent: 'center'} : {};
209
     const buttonPlatformStyle = PlatfomIOS ? {flex: 1, height: 45, justifyContent: 'center'} : {};
192
     return (
210
     return (
197
             style={buttonPlatformStyle}
215
             style={buttonPlatformStyle}
198
         >
216
         >
199
           <Text style={[styles.button, {paddingRight: 10}]}>
217
           <Text style={[styles.button, {paddingRight: 10}]}>
200
-            {PlatfomIOS ? 'Cancel' : 'CANCEL'}
218
+            {this._upperCaseButtonTextIfNeeded('Cancel')}
201
           </Text>
219
           </Text>
202
         </TouchableOpacity>
220
         </TouchableOpacity>
203
         <TouchableOpacity
221
         <TouchableOpacity
204
             onPress={() => {
222
             onPress={() => {
205
-              this.insertLink(this.state.linkUrl, this.state.linkTitle);
223
+              if (this._linkIsNew()) {
224
+                this.insertLink(this.state.linkUrl, this.state.linkTitle);
225
+              } else {
226
+                this.updateLink(this.state.linkUrl, this.state.linkTitle);
227
+              }
206
               this._hideModal();
228
               this._hideModal();
207
             }}
229
             }}
208
-            disabled={insertDisabled}
230
+            disabled={insertUpdateDisabled}
209
             style={buttonPlatformStyle}
231
             style={buttonPlatformStyle}
210
         >
232
         >
211
-          <Text style={[styles.button, {opacity: insertDisabled ? 0.5 : 1}]}>
212
-            {PlatfomIOS ? 'Insert' : 'INSERT'}
233
+          <Text style={[styles.button, {opacity: insertUpdateDisabled ? 0.5 : 1}]}>
234
+            {this._upperCaseButtonTextIfNeeded(this._linkIsNew() ? 'Insert' : 'Update')}
213
           </Text>
235
           </Text>
214
         </TouchableOpacity>
236
         </TouchableOpacity>
215
       </View>
237
       </View>
216
     );
238
     );
217
   }
239
   }
218
 
240
 
241
+  _linkIsNew() {
242
+    return !this.state.linkInitialUrl;
243
+  }
244
+
245
+  _upperCaseButtonTextIfNeeded(buttonText) {
246
+    return PlatfomIOS ? buttonText : buttonText.toLowerCase();
247
+  }
248
+
219
   render() {
249
   render() {
220
     //in release build, external html files in Android can't be required, so they must be placed in the assets folder and accessed via uri
250
     //in release build, external html files in Android can't be required, so they must be placed in the assets folder and accessed via uri
221
     const pageSource = PlatfomIOS ? require('./editor.html') : { uri: 'file:///android_asset/editor.html' };
251
     const pageSource = PlatfomIOS ? require('./editor.html') : { uri: 'file:///android_asset/editor.html' };
259
   //-------------------------------------------------------------------------------
289
   //-------------------------------------------------------------------------------
260
   //--------------- Public API
290
   //--------------- Public API
261
 
291
 
262
-  showLinkDialog() {
292
+  showLinkDialog(optionalTitle = '', optionalUrl = '') {
263
     this.setState({
293
     this.setState({
294
+      linkInitialUrl: optionalUrl,
295
+      linkTitle: optionalTitle,
296
+      linkUrl: optionalUrl,
264
       showLinkDialog: true
297
       showLinkDialog: true
265
     });
298
     });
266
   }
299
   }
364
   }
397
   }
365
 
398
 
366
   insertLink(url, title) {
399
   insertLink(url, title) {
367
-
368
     this._sendAction(actions.insertLink, {url, title});
400
     this._sendAction(actions.insertLink, {url, title});
369
   }
401
   }
370
 
402
 
403
+  updateLink(url, title) {
404
+    this._sendAction(actions.updateLink, {url, title});
405
+  }
406
+
371
   insertImage(url, alt) {
407
   insertImage(url, alt) {
372
     this._sendAction(actions.insertImage, {url, alt});
408
     this._sendAction(actions.insertImage, {url, alt});
373
     this.prepareInsert(); //This must be called BEFORE insertImage. But WebViewBridge uses a stack :/
409
     this.prepareInsert(); //This must be called BEFORE insertImage. But WebViewBridge uses a stack :/
471
     });
507
     });
472
   }
508
   }
473
 
509
 
510
+  async getSelectedText() {
511
+    return new Promise((resolve, reject) => {
512
+      this.selectedTextResolve = resolve;
513
+      this.selectedTextReject = reject;
514
+      this._sendAction(actions.getSelectedText);
515
+
516
+      this.pendingSelectedText = setTimeout(() => {
517
+        if (this.selectedTextReject) {
518
+          this.selectedTextReject('timeout')
519
+        }
520
+      }, 5000);
521
+    });
522
+  }
523
+
474
   setTitleFocusHandler(callbackHandler) {
524
   setTitleFocusHandler(callbackHandler) {
475
     this.titleFocusHandler = callbackHandler;
525
     this.titleFocusHandler = callbackHandler;
476
     this._sendAction(actions.setTitleFocusHandler);
526
     this._sendAction(actions.setTitleFocusHandler);

+ 3
- 1
src/RichTextToolbar.js View File

167
         if(this.props.onPressAddLink) {
167
         if(this.props.onPressAddLink) {
168
           this.props.onPressAddLink();
168
           this.props.onPressAddLink();
169
         } else {
169
         } else {
170
-          this.state.editor.showLinkDialog();
170
+          this.state.editor.getSelectedText().then(selectedText => {
171
+            this.state.editor.showLinkDialog(selectedText);
172
+          });
171
         }
173
         }
172
         break;
174
         break;
173
       case actions.insertImage:
175
       case actions.insertImage:

+ 7
- 0
src/WebviewMessageHandler.js View File

73
         case '${actions.insertLink}':
73
         case '${actions.insertLink}':
74
           zss_editor.insertLink(action.data.url, action.data.title);
74
           zss_editor.insertLink(action.data.url, action.data.title);
75
           break;
75
           break;
76
+        case '${actions.updateLink}':
77
+          zss_editor.updateLink(action.data.url, action.data.title);
78
+          break;
76
         case '${actions.insertImage}':
79
         case '${actions.insertImage}':
77
           zss_editor.insertImage(action.data.url, action.data.alt);
80
           zss_editor.insertImage(action.data.url, action.data.alt);
78
           break;
81
           break;
118
         case '${actions.setContentFocusHandler}':
121
         case '${actions.setContentFocusHandler}':
119
           zss_editor.setContentFocusHandler();
122
           zss_editor.setContentFocusHandler();
120
           break;
123
           break;
124
+        case '${actions.getSelectedText}':
125
+          var selectedText = getSelection().toString();
126
+          WebViewBridge.send(JSON.stringify({type: '${messages.SELECTED_TEXT_RESPONSE}', data: selectedText}));
127
+          break;
121
         case '${actions.focusContent}':
128
         case '${actions.focusContent}':
122
           zss_editor.focusContent();
129
           zss_editor.focusContent();
123
           break;
130
           break;

+ 5
- 1
src/const.js View File

4
   getTitleHtml: 'GET_TITLE_HTML',
4
   getTitleHtml: 'GET_TITLE_HTML',
5
   getTitleText: 'GET_TITLE_TEXT',
5
   getTitleText: 'GET_TITLE_TEXT',
6
   getContentHtml: 'GET_CONTENT_HTML',
6
   getContentHtml: 'GET_CONTENT_HTML',
7
+  getSelectedText: 'GET_SELECTED_TEXT',
7
   blurTitleEditor: 'BLUR_TITLE_EDITOR',
8
   blurTitleEditor: 'BLUR_TITLE_EDITOR',
8
   blurContentEditor: 'BLUR_CONTENT_EDITOR',
9
   blurContentEditor: 'BLUR_CONTENT_EDITOR',
9
   focusTitle: 'FOCUS_TITLE',
10
   focusTitle: 'FOCUS_TITLE',
27
   insertBulletsList: 'unorderedList',
28
   insertBulletsList: 'unorderedList',
28
   insertOrderedList: 'orderedList',
29
   insertOrderedList: 'orderedList',
29
   insertLink: 'INST_LINK',
30
   insertLink: 'INST_LINK',
31
+  updateLink: 'UPDATE_LINK',
30
   insertImage: 'INST_IMAGE',
32
   insertImage: 'INST_IMAGE',
31
   setSubscript: 'subscript',
33
   setSubscript: 'subscript',
32
   setSuperscript: 'superscript',
34
   setSuperscript: 'superscript',
56
   LOG: 'LOG',
58
   LOG: 'LOG',
57
   TITLE_FOCUSED: 'TITLE_FOCUSED',
59
   TITLE_FOCUSED: 'TITLE_FOCUSED',
58
   CONTENT_FOCUSED: 'CONTENT_FOCUSED',
60
   CONTENT_FOCUSED: 'CONTENT_FOCUSED',
59
-  SELECTION_CHANGE: 'SELECTION_CHANGE'
61
+  SELECTION_CHANGE: 'SELECTION_CHANGE',
62
+  SELECTED_TEXT_RESPONSE: 'SELECTED_TEXT_RESPONSE',
63
+  LINK_TOUCHED: 'LINK_TOUCHED'
60
 };
64
 };

+ 18
- 31
src/editor.html View File

851
 				}
851
 				}
852
 			}
852
 			}
853
 
853
 
854
+			function setCurrentEditingLinkOnTouch() {
855
+				$('#zss_editor_content').delegate('a', 'touchend', function(event) {
856
+					var anchor = $(event.currentTarget);
857
+
858
+					zss_editor.currentEditingLink = anchor;
859
+
860
+					var link = {
861
+						title: anchor.text(),
862
+						url: anchor.attr('href')
863
+					};
864
+					WebViewBridge.send(JSON.stringify({type: 'LINK_TOUCHED', data: link}))
865
+				});
866
+			}
867
+
854
 			function preventLinkFollowingOnTouch() {
868
 			function preventLinkFollowingOnTouch() {
855
 				$('#zss_editor_content').delegate('a', 'touchend', function(event) {
869
 				$('#zss_editor_content').delegate('a', 'touchend', function(event) {
856
 					event.preventDefault();
870
 					event.preventDefault();
886
 				setupTouchEndFocus('zss_editor_title');
900
 				setupTouchEndFocus('zss_editor_title');
887
 				setupTouchEndFocus('zss_editor_content');
901
 				setupTouchEndFocus('zss_editor_content');
888
 
902
 
903
+				setCurrentEditingLinkOnTouch();
889
 				preventLinkFollowingOnTouch();
904
 				preventLinkFollowingOnTouch();
890
 
905
 
891
 				whenPastingInsertAsPlainText('zss_editor_title');
906
 				whenPastingInsertAsPlainText('zss_editor_title');
1219
 
1234
 
1220
 				zss_editor.restorerange();
1235
 				zss_editor.restorerange();
1221
 				var sel = document.getSelection();
1236
 				var sel = document.getSelection();
1222
-				console.log(sel);
1223
-				if (sel.toString().length != 0) {
1224
-					if (sel.rangeCount) {
1225
-
1226
-						var el = document.createElement("a");
1227
-						el.setAttribute("href", url);
1228
-						el.setAttribute("title", title);
1229
-
1230
-						var range = sel.getRangeAt(0).cloneRange();
1231
-						range.surroundContents(el);
1232
-						sel.removeAllRanges();
1233
-						sel.addRange(range);
1234
-					}
1235
-				}
1236
-				else
1237
-				{
1238
-					document.execCommand("insertHTML",false,"<a href='"+url+"'>"+title+"</a>");
1239
-				}
1237
+				sel.deleteFromDocument();
1238
+				document.execCommand("insertHTML",false,"<a href='"+url+"'>"+title+"</a>");
1240
 
1239
 
1241
 				zss_editor.enabledEditingItems();
1240
 				zss_editor.enabledEditingItems();
1242
 			}
1241
 			}
1248
 				if (zss_editor.currentEditingLink) {
1247
 				if (zss_editor.currentEditingLink) {
1249
 					var c = zss_editor.currentEditingLink;
1248
 					var c = zss_editor.currentEditingLink;
1250
 					c.attr('href', url);
1249
 					c.attr('href', url);
1251
-					c.attr('title', title);
1250
+					c.text(title);
1252
 				}
1251
 				}
1253
 				zss_editor.enabledEditingItems();
1252
 				zss_editor.enabledEditingItems();
1254
 
1253
 
1493
 						items.push('fonts');
1492
 						items.push('fonts');
1494
 					}
1493
 					}
1495
 
1494
 
1496
-					// Link
1497
-					if (nodeName == 'a') {
1498
-						zss_editor.currentEditingLink = t;
1499
-						var title = t.attr('title');
1500
-						items.push('link:'+t.attr('href'));
1501
-						if (t.attr('title') !== undefined) {
1502
-							items.push('link-title:'+t.attr('title'));
1503
-						}
1504
-
1505
-					} else {
1506
-						zss_editor.currentEditingLink = null;
1507
-					}
1508
 					// Blockquote
1495
 					// Blockquote
1509
 					if (nodeName == 'blockquote') {
1496
 					if (nodeName == 'blockquote') {
1510
 						items.push('indent');
1497
 						items.push('indent');