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