Mhurd has submitted this change and it was merged.

Change subject: References click backend: detect link clicks and send data over 
bridge
......................................................................


References click backend: detect link clicks and send data over bridge

* fixed URI encoding in bridge communication
* copied refs extraction JS code from Android patch in progress
  at https://gerrit.wikimedia.org/r/#/c/146086/4
* sends array of HTML strings for adjacent items, and index of clicked one
* now with refs.js actually added
* added linkId and linkText arrays with the id and text of the link
* fixed link encoding bug that was hidden by old bridge bug;
  was causing all non-ASCII links to fail due to suddenly being
  given the URL-encoded data when expecting raw string!
* skips over whitespace but not other text or elements between
  adjacent refs
* skips over whitespace but not other text or elements between
  adjacent refs
* skips over whitespace but not other text or elements between
  adjacent refs
* skips over whitespace but not other text or elements between
  adjacent refs
* skips over whitespace but not other text or elements between
  adjacent refs
* skips over whitespace but not other text or elements between
  adjacent refs
* skips over whitespace but not other text or elements between
  adjacent refs
* skips over whitespace but not other text or elements between
  adjacent refs

Change-Id: I0e4ec802e7e493a2d0caa759affa8e7ed3eaa8b6
---
M wikipedia/View Controllers/WebView/WebViewController.m
M wikipedia/assets/bundle.js
M wikipedia/mw-bridge/CommunicationBridge.m
M www/js/bridge.js
M www/js/listeners.js
A www/js/refs.js
6 files changed, 267 insertions(+), 10 deletions(-)

Approvals:
  Mhurd: Verified; Looks good to me, approved



diff --git a/wikipedia/View Controllers/WebView/WebViewController.m 
b/wikipedia/View Controllers/WebView/WebViewController.m
index 1ab10f4..59bc476 100644
--- a/wikipedia/View Controllers/WebView/WebViewController.m
+++ b/wikipedia/View Controllers/WebView/WebViewController.m
@@ -816,7 +816,8 @@
             // Ensure the menu is visible when navigating to new page.
             [weakSelf animateTopAndBottomMenuReveal];
         
-            NSString *title = [href substringWithRange:NSMakeRange(6, 
href.length - 6)];
+            NSString *encodedTitle = [href substringWithRange:NSMakeRange(6, 
href.length - 6)];
+            NSString *title = [encodedTitle stringByRemovingPercentEncoding];
             MWPageTitle *pageTitle = [MWPageTitle titleWithString:title];
 
             [weakSelf navigateToPage: pageTitle
@@ -896,6 +897,10 @@
         // Used because UIWebView is difficult to attach one-finger touch 
events to.
         [weakSelf tocHide];
     }];
+    
+    [self.bridge addListener:@"referenceClicked" withBlock:^(NSString 
*messageType, NSDictionary *payload) {
+        NSLog(@"referenceClicked: %@", payload);
+    }];
 
     self.unsafeToScroll = NO;
     self.scrollOffset = CGPointZero;
diff --git a/wikipedia/assets/bundle.js b/wikipedia/assets/bundle.js
index f0f7d23..f32e907 100644
--- a/wikipedia/assets/bundle.js
+++ b/wikipedia/assets/bundle.js
@@ -24,7 +24,7 @@
 
 Bridge.prototype.sendMessage = function( messageType, payload ) {
     var messagePack = { type: messageType, payload: payload };
-    var url = "x-wikipedia-bridge:" + JSON.stringify( messagePack );
+    var url = "x-wikipedia-bridge:" + encodeURIComponent( JSON.stringify( 
messagePack ) );
     
     // quick iframe version based on http://stackoverflow.com/a/6508343/82439
     // fixme can this be an XHR instead? check Cordova current state
@@ -97,6 +97,7 @@
 var bridge = require("./bridge");
 var wikihacks = require("./wikihacks");
 var transformer = require("./transformer");
+var refs = require("./refs");
 
 //TODO: move makeTablesNotBlockIfSafeToDoSo, hideAudioTags and 
reduceWeirdWebkitMargin out into own js object.
 
@@ -239,7 +240,10 @@
 
     if ( anchorTarget && (anchorTarget.tagName === "A") ) {
         var href = anchorTarget.getAttribute( "href" );
-        if ( href[0] === "#" ) {
+        if ( refs.isReference( href ) ) {
+            // Handle reference links with a popup view instead of scrolling 
about!
+            refs.sendNearbyReferences( anchorTarget );
+        } else if ( href[0] === "#" ) {
             // If it is a link to an anchor in the current page, just scroll 
to it
             document.getElementById( href.substring( 1 ) ).scrollIntoView();
         } else {
@@ -279,7 +283,7 @@
 
 document.addEventListener("touchend", touchEnd, "false");
 
-},{"./bridge":1,"./transformer":5,"./wikihacks":7}],4:[function(require,module,exports){
+},{"./bridge":1,"./refs":5,"./transformer":6,"./wikihacks":8}],4:[function(require,module,exports){
 
 var bridge = require("./bridge");
 var elementLocation = require("./elementLocation");
@@ -288,6 +292,128 @@
 window.elementLocation = elementLocation;
 
 },{"./bridge":1,"./elementLocation":2}],5:[function(require,module,exports){
+var bridge = require("./bridge");
+
+function isReference( href ) {
+    return ( href.slice( 0, 10 ) === "#cite_note" );
+}
+
+function goDown( element ) {
+    return element.getElementsByTagName( "A" )[0];
+}
+
+/**
+ * Skip over whitespace but not other elements
+ */
+function skipOverWhitespace( skipFunc ) {
+    return (function(element) {
+        do {
+            element = skipFunc( element );
+            if (element && element.nodeType == Node.TEXT_NODE) {
+                if (element.textContent.match(/^\s+$/)) {
+                    // Ignore empty whitespace
+                    continue;
+                } else {
+                    break;
+                }
+            } else {
+                // found an element or ran out
+                break;
+            }
+        } while (true);
+        return element;
+    });
+}
+
+var goLeft = skipOverWhitespace( function( element ) {
+    return element.previousSibling;
+});
+
+var goRight = skipOverWhitespace( function( element ) {
+    return element.nextSibling;
+});
+
+function hasReferenceLink( element ) {
+    try {
+        return isReference( goDown( element ).getAttribute( "href" ) );
+    } catch (e) {
+        return false;
+    }
+}
+
+function collectRefText( sourceNode ) {
+    var href = sourceNode.getAttribute( "href" );
+    var targetId = href.slice(1);
+    var targetNode = document.getElementById( targetId );
+    if ( targetNode === null ) {
+        console.log("reference target not found: " + targetId);
+        return "";
+    }
+
+    // preferably without the back link
+    var refTexts = targetNode.getElementsByClassName( "reference-text" );
+    if ( refTexts.length > 0 ) {
+        targetNode = refTexts[0];
+    }
+
+    return targetNode.innerHTML;
+}
+
+function collectRefLink( sourceNode ) {
+    var node = sourceNode;
+    while (!node.classList || !node.classList.contains('reference')) {
+        node = node.parentNode;
+        if (!node) {
+            return '';
+        }
+    }
+    return node.id;
+}
+
+function sendNearbyReferences( sourceNode ) {
+    var refsIndex = 0;
+    var refs = [];
+    var linkId = [];
+    var linkText = [];
+    var curNode = sourceNode;
+
+    // handle clicked ref:
+    refs.push( collectRefText( curNode ) );
+    linkId.push( collectRefLink( curNode ) );
+    linkText.push( curNode.textContent );
+
+    // go left:
+    curNode = sourceNode.parentElement;
+    while ( hasReferenceLink( goLeft( curNode ) ) ) {
+        refsIndex += 1;
+        curNode = goLeft( curNode );
+        refs.unshift( collectRefText( goDown ( curNode ) ) );
+        linkId.unshift( collectRefLink( curNode ) );
+        linkText.unshift( curNode.textContent );
+    }
+
+    // go right:
+    curNode = sourceNode.parentElement;
+    while ( hasReferenceLink( goRight( curNode ) ) ) {
+        curNode = goRight( curNode );
+        refs.push( collectRefText( goDown ( curNode ) ) );
+        linkId.push( collectRefLink( curNode ) );
+        linkText.push( curNode.textContent );
+    }
+
+    // Special handling for references
+    bridge.sendMessage( 'referenceClicked', {
+        "refs": refs,
+        "refsIndex": refsIndex,
+        "linkId": linkId,
+        "linkText": linkText
+    } );
+}
+
+exports.isReference = isReference;
+exports.sendNearbyReferences = sendNearbyReferences;
+
+},{"./bridge":1}],6:[function(require,module,exports){
 function Transformer() {
 }
 
@@ -311,7 +437,7 @@
 
 module.exports = new Transformer();
 
-},{}],6:[function(require,module,exports){
+},{}],7:[function(require,module,exports){
 var transformer = require("./transformer");
 
 // Move infobox to the bottom of the lead section
@@ -345,7 +471,7 @@
        return content;
 } );
 
-},{"./transformer":5}],7:[function(require,module,exports){
+},{"./transformer":6}],8:[function(require,module,exports){
 
 // this doesn't seem to work on iOS?
 exports.makeTablesNotBlockIfSafeToDoSo = function() {
@@ -444,4 +570,4 @@
     }
 }
 
-},{}]},{},[1,2,3,4,5,6,7])
\ No newline at end of file
+},{}]},{},[1,2,3,4,5,6,7,8])
\ No newline at end of file
diff --git a/wikipedia/mw-bridge/CommunicationBridge.m 
b/wikipedia/mw-bridge/CommunicationBridge.m
index ccfcccb..0c74f08 100644
--- a/wikipedia/mw-bridge/CommunicationBridge.m
+++ b/wikipedia/mw-bridge/CommunicationBridge.m
@@ -127,7 +127,9 @@
     NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding];
     NSError *err;
     NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data 
options:0 error:&err];
-    // fixme throw an exception?
+    if (err) {
+        NSLog(@"JSON ERROR %@", err);
+    }
     return dict;
 }
 
diff --git a/www/js/bridge.js b/www/js/bridge.js
index 57e3204..c55c30f 100644
--- a/www/js/bridge.js
+++ b/www/js/bridge.js
@@ -23,7 +23,7 @@
 
 Bridge.prototype.sendMessage = function( messageType, payload ) {
     var messagePack = { type: messageType, payload: payload };
-    var url = "x-wikipedia-bridge:" + JSON.stringify( messagePack );
+    var url = "x-wikipedia-bridge:" + encodeURIComponent( JSON.stringify( 
messagePack ) );
     
     // quick iframe version based on http://stackoverflow.com/a/6508343/82439
     // fixme can this be an XHR instead? check Cordova current state
diff --git a/www/js/listeners.js b/www/js/listeners.js
index a2fef82..e9c4d27 100644
--- a/www/js/listeners.js
+++ b/www/js/listeners.js
@@ -1,6 +1,7 @@
 var bridge = require("./bridge");
 var wikihacks = require("./wikihacks");
 var transformer = require("./transformer");
+var refs = require("./refs");
 
 //TODO: move makeTablesNotBlockIfSafeToDoSo, hideAudioTags and 
reduceWeirdWebkitMargin out into own js object.
 
@@ -143,7 +144,10 @@
 
     if ( anchorTarget && (anchorTarget.tagName === "A") ) {
         var href = anchorTarget.getAttribute( "href" );
-        if ( href[0] === "#" ) {
+        if ( refs.isReference( href ) ) {
+            // Handle reference links with a popup view instead of scrolling 
about!
+            refs.sendNearbyReferences( anchorTarget );
+        } else if ( href[0] === "#" ) {
             // If it is a link to an anchor in the current page, just scroll 
to it
             document.getElementById( href.substring( 1 ) ).scrollIntoView();
         } else {
diff --git a/www/js/refs.js b/www/js/refs.js
new file mode 100644
index 0000000..566a686
--- /dev/null
+++ b/www/js/refs.js
@@ -0,0 +1,120 @@
+var bridge = require("./bridge");
+
+function isReference( href ) {
+    return ( href.slice( 0, 10 ) === "#cite_note" );
+}
+
+function goDown( element ) {
+    return element.getElementsByTagName( "A" )[0];
+}
+
+/**
+ * Skip over whitespace but not other elements
+ */
+function skipOverWhitespace( skipFunc ) {
+    return (function(element) {
+        do {
+            element = skipFunc( element );
+            if (element && element.nodeType == Node.TEXT_NODE) {
+                if (element.textContent.match(/^\s+$/)) {
+                    // Ignore empty whitespace
+                    continue;
+                } else {
+                    break;
+                }
+            } else {
+                // found an element or ran out
+                break;
+            }
+        } while (true);
+        return element;
+    });
+}
+
+var goLeft = skipOverWhitespace( function( element ) {
+    return element.previousSibling;
+});
+
+var goRight = skipOverWhitespace( function( element ) {
+    return element.nextSibling;
+});
+
+function hasReferenceLink( element ) {
+    try {
+        return isReference( goDown( element ).getAttribute( "href" ) );
+    } catch (e) {
+        return false;
+    }
+}
+
+function collectRefText( sourceNode ) {
+    var href = sourceNode.getAttribute( "href" );
+    var targetId = href.slice(1);
+    var targetNode = document.getElementById( targetId );
+    if ( targetNode === null ) {
+        console.log("reference target not found: " + targetId);
+        return "";
+    }
+
+    // preferably without the back link
+    var refTexts = targetNode.getElementsByClassName( "reference-text" );
+    if ( refTexts.length > 0 ) {
+        targetNode = refTexts[0];
+    }
+
+    return targetNode.innerHTML;
+}
+
+function collectRefLink( sourceNode ) {
+    var node = sourceNode;
+    while (!node.classList || !node.classList.contains('reference')) {
+        node = node.parentNode;
+        if (!node) {
+            return '';
+        }
+    }
+    return node.id;
+}
+
+function sendNearbyReferences( sourceNode ) {
+    var refsIndex = 0;
+    var refs = [];
+    var linkId = [];
+    var linkText = [];
+    var curNode = sourceNode;
+
+    // handle clicked ref:
+    refs.push( collectRefText( curNode ) );
+    linkId.push( collectRefLink( curNode ) );
+    linkText.push( curNode.textContent );
+
+    // go left:
+    curNode = sourceNode.parentElement;
+    while ( hasReferenceLink( goLeft( curNode ) ) ) {
+        refsIndex += 1;
+        curNode = goLeft( curNode );
+        refs.unshift( collectRefText( goDown ( curNode ) ) );
+        linkId.unshift( collectRefLink( curNode ) );
+        linkText.unshift( curNode.textContent );
+    }
+
+    // go right:
+    curNode = sourceNode.parentElement;
+    while ( hasReferenceLink( goRight( curNode ) ) ) {
+        curNode = goRight( curNode );
+        refs.push( collectRefText( goDown ( curNode ) ) );
+        linkId.push( collectRefLink( curNode ) );
+        linkText.push( curNode.textContent );
+    }
+
+    // Special handling for references
+    bridge.sendMessage( 'referenceClicked', {
+        "refs": refs,
+        "refsIndex": refsIndex,
+        "linkId": linkId,
+        "linkText": linkText
+    } );
+}
+
+exports.isReference = isReference;
+exports.sendNearbyReferences = sendNearbyReferences;

-- 
To view, visit https://gerrit.wikimedia.org/r/148247
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I0e4ec802e7e493a2d0caa759affa8e7ed3eaa8b6
Gerrit-PatchSet: 8
Gerrit-Project: apps/ios/wikipedia
Gerrit-Branch: master
Gerrit-Owner: Brion VIBBER <br...@wikimedia.org>
Gerrit-Reviewer: Mhurd <mh...@wikimedia.org>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to