Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge pull request #54 from twitter/use_displayurl_in_entity

Modify linkToUrl() to use display_url and expanded_url in the input entity, add unit tests for new linkTextWithEntity() function.
  • Loading branch information...
commit 87ca503835b2b3546bfd65afb5ce75c654f53844 2 parents 2f0043e + fcd597e
@couch couch authored
Showing with 128 additions and 71 deletions.
  1. +55 −3 test/tests.js
  2. +1 −1  test/twitter-text-conformance
  3. +72 −67 twitter-text.js
View
58 test/tests.js
@@ -142,6 +142,19 @@ test("twttr.txt.autolink", function() {
ok(autoLinkResult.match(/>http:\/\//), 'Include the head of expanded_url');
ok(autoLinkResult.match(/span style='font-size:0'/), 'Obey invisibleTagAttrs');
+ autoLinkResult = twttr.txt.autoLinkEntities("http://t.co/0JG5Mcq",
+ [{
+ "url": "http://t.co/0JG5Mcq",
+ "display_url": "blog.twitter.com/2011/05/twitte…",
+ "expanded_url": "http://blog.twitter.com/2011/05/twitter-for-mac-update.html",
+ "indices": [0, 19]
+ }]
+ );
+ ok(autoLinkResult.match(/<a href="http:\/\/t.co\/0JG5Mcq"[^>]+>/), 'autoLinkEntities: Use t.co URL as link target');
+ ok(autoLinkResult.match(/>blog.twitter.com\/2011\/05\/twitte.*…</), 'autoLinkEntities: Use display url from entities');
+ ok(autoLinkResult.match(/r-for-mac-update.html</), 'autoLinkEntities: Include the tail of expanded_url');
+ ok(autoLinkResult.match(/>http:\/\//), 'autoLinkEntities: Include the head of expanded_url');
+
// Insert the HTML into the document and verify that, if copied and pasted, it would get the expanded_url.
var div = document.createElement('div');
div.innerHTML = autoLinkResult;
@@ -164,11 +177,12 @@ test("twttr.txt.autolink", function() {
});
ok(picTwitter.match(/<a href="http:\/\/t.co\/0JG5Mcq"[^>]+>/), 'Use t.co URL as link target');
ok(picTwitter.match(/>pic.twitter.com\/xyz</), 'Use display url from url entities');
- ok(!picTwitter.match(/foo\/statuses/), 'Don\'t include the tail of expanded_url');
+ ok(picTwitter.match(/title="http:\/\/twitter.com\/foo\/statuses\/123\/photo\/1"/), 'Use expanded url as title');
+ ok(!picTwitter.match(/foo\/statuses</), 'Don\'t include the tail of expanded_url');
// urls with invalid character
var invalidChars = ['\u202A', '\u202B', '\u202C', '\u202D', '\u202E'];
- for (i = 0; i < invalidChars.length; i++) {
+ for (var i = 0; i < invalidChars.length; i++) {
equal(twttr.txt.extractUrls("http://twitt" + invalidChars[i] + "er.com").length, 0, 'Should not extract URL with invalid character');
}
@@ -177,8 +191,46 @@ test("twttr.txt.autolink", function() {
"Autolink hashtag/mentionURL w/ Supplementary character");
});
+test("twttr.txt.linkTextWithEntity", function() {
+ var result = twttr.txt.linkTextWithEntity({
+ "url": "http://t.co/abcde",
+ "display_url": "twitter.com",
+ "expanded_url": "http://twitter.com/"},
+ {invisibleTagAttrs: "class='invisible'"});
+ same(result,
+ "<span class='tco-ellipsis'><span class='invisible'>&nbsp;</span></span><span class='invisible'>http://</span><span class='js-display-url'>twitter.com</span><span class='invisible'>/</span><span class='tco-ellipsis'><span class='invisible'>&nbsp;</span></span>",
+ "Entire display_url is in expanded_url");
+
+ result = twttr.txt.linkTextWithEntity({
+ "url": "http://t.co/abcde",
+ "display_url": "twitter.com…",
+ "expanded_url": "http://twitter.com/abcdefg"},
+ {invisibleTagAttrs: "class='invisible'"});
+ same(result,
+ "<span class='tco-ellipsis'><span class='invisible'>&nbsp;</span></span><span class='invisible'>http://</span><span class='js-display-url'>twitter.com</span><span class='invisible'>/abcdefg</span><span class='tco-ellipsis'><span class='invisible'>&nbsp;</span>…</span>",
+ "display_url ends with …");
+
+ result = twttr.txt.linkTextWithEntity({
+ "url": "http://t.co/abcde",
+ "display_url": "…tter.com/abcdefg",
+ "expanded_url": "http://twitter.com/abcdefg"},
+ {invisibleTagAttrs: "class='invisible'"});
+ same(result,
+ "<span class='tco-ellipsis'>…<span class='invisible'>&nbsp;</span></span><span class='invisible'>http://twi</span><span class='js-display-url'>tter.com/abcdefg</span><span class='invisible'></span><span class='tco-ellipsis'><span class='invisible'>&nbsp;</span></span>",
+ "display_url begins with …");
+
+ result = twttr.txt.linkTextWithEntity({
+ "url": "http://t.co/abcde",
+ "display_url": "pic.twitter.com/xyz",
+ "expanded_url": "http://twitter.com/foo/statuses/123/photo/1"},
+ {invisibleTagAttrs: "class='invisible'"});
+ same(result,
+ "pic.twitter.com/xyz",
+ "display_url and expanded_url are on different domains");
+});
+
test("twttr.txt.extractMentionsOrListsWithIndices", function() {
- var invalid_chars = ['!', '@', '#', '$', '%', '&', '*']
+ var invalid_chars = ['!', '@', '#', '$', '%', '&', '*'];
for (var i = 0; i < invalid_chars.length; i++) {
c = invalid_chars[i];
2  test/twitter-text-conformance
@@ -1 +1 @@
-Subproject commit e9cf44045a67afa075ebdc585b9a7d0017c7d7d7
+Subproject commit a653afa54a2e246758bf991797e04b4d7f253489
View
139 twitter-text.js
@@ -440,78 +440,16 @@ if (typeof twttr === "undefined" || twttr === null) {
var url = entity.url;
var displayUrl = url;
var linkText = twttr.txt.htmlEscape(displayUrl);
+
// If the caller passed a urlEntities object (provided by a Twitter API
// response with include_entities=true), we use that to render the display_url
// for each URL instead of it's underlying t.co URL.
- if (options.urlEntities && options.urlEntities[url] && options.urlEntities[url].display_url) {
- var displayUrl = options.urlEntities[url].display_url;
- var expandedUrl = options.urlEntities[url].expanded_url;
+ var urlEntity = (options.urlEntities && options.urlEntities[url]) || entity;
+ if (urlEntity.display_url) {
if (!options.title) {
- options.title = expandedUrl;
- }
-
- // Goal: If a user copies and pastes a tweet containing t.co'ed link, the resulting paste
- // should contain the full original URL (expanded_url), not the display URL.
- //
- // Method: Whenever possible, we actually emit HTML that contains expanded_url, and use
- // font-size:0 to hide those parts that should not be displayed (because they are not part of display_url).
- // Elements with font-size:0 get copied even though they are not visible.
- // Note that display:none doesn't work here. Elements with display:none don't get copied.
- //
- // Additionally, we want to *display* ellipses, but we don't want them copied. To make this happen we
- // wrap the ellipses in a tco-ellipsis class and provide an onCopy handler that sets display:none on
- // everything with the tco-ellipsis class.
- //
- // Exception: pic.twitter.com images, for which expandedUrl = "https://twitter.com/#!/username/status/1234/photo/1
- // For those URLs, display_url is not a substring of expanded_url, so we don't do anything special to render the elided parts.
- // For a pic.twitter.com URL, the only elided part will be the "https://", so this is fine.
-
- var displayUrlSansEllipses = displayUrl.replace(//g, ""); // We have to disregard ellipses for matching
- // Note: we currently only support eliding parts of the URL at the beginning or the end.
- // Eventually we may want to elide parts of the URL in the *middle*. If so, this code will
- // become more complicated. We will probably want to create a regexp out of display URL,
- // replacing every ellipsis with a ".*".
- if (expandedUrl.indexOf(displayUrlSansEllipses) != -1) {
- var displayUrlIndex = expandedUrl.indexOf(displayUrlSansEllipses);
- var v = {
- displayUrlSansEllipses: displayUrlSansEllipses,
- // Portion of expandedUrl that precedes the displayUrl substring
- beforeDisplayUrl: expandedUrl.substr(0, displayUrlIndex),
- // Portion of expandedUrl that comes after displayUrl
- afterDisplayUrl: expandedUrl.substr(displayUrlIndex + displayUrlSansEllipses.length),
- precedingEllipsis: displayUrl.match(/^/) ? "" : "",
- followingEllipsis: displayUrl.match(/$/) ? "" : ""
- };
- $.each(v, function(index, value) {
- v[index] = twttr.txt.htmlEscape(value);
- });
- // As an example: The user tweets "hi http://longdomainname.com/foo"
- // This gets shortened to "hi http://t.co/xyzabc", with display_url = "…nname.com/foo"
- // This will get rendered as:
- // <span class='tco-ellipsis'> <!-- This stuff should get displayed but not copied -->
- // …
- // <!-- There's a chance the onCopy event handler might not fire. In case that happens,
- // we include an &nbsp; here so that the … doesn't bump up against the URL and ruin it.
- // The &nbsp; is inside the tco-ellipsis span so that when the onCopy handler *does*
- // fire, it doesn't get copied. Otherwise the copied text would have two spaces in a row,
- // e.g. "hi http://longdomainname.com/foo".
- // <span style='font-size:0'>&nbsp;</span>
- // </span>
- // <span style='font-size:0'> <!-- This stuff should get copied but not displayed -->
- // http://longdomai
- // </span>
- // <span class='js-display-url'> <!-- This stuff should get displayed *and* copied -->
- // nname.com/foo
- // </span>
- // <span class='tco-ellipsis'> <!-- This stuff should get displayed but not copied -->
- // <span style='font-size:0'>&nbsp;</span>
- // …
- // </span>
- v['invisible'] = options.invisibleTagAttrs;
- linkText = stringSupplant("<span class='tco-ellipsis'>#{precedingEllipsis}<span #{invisible}>&nbsp;</span></span><span #{invisible}>#{beforeDisplayUrl}</span><span class='js-display-url'>#{displayUrlSansEllipses}</span><span #{invisible}>#{afterDisplayUrl}</span><span class='tco-ellipsis'><span #{invisible}>&nbsp;</span>#{followingEllipsis}</span>", v);
- } else {
- linkText = displayUrl;
+ options.htmlAttrs = (options.htmlAttrs || "") + " title=\"" + urlEntity.expanded_url + "\"";
}
+ linkText = twttr.txt.linkTextWithEntity(urlEntity, options);
}
var d = {
@@ -523,6 +461,73 @@ if (typeof twttr === "undefined" || twttr === null) {
return stringSupplant("<a href=\"#{url}\"#{htmlAttrs}>#{linkText}</a>", d);
};
+ twttr.txt.linkTextWithEntity = function (entity, options) {
+ var displayUrl = entity.display_url;
+ var expandedUrl = entity.expanded_url;
+
+ // Goal: If a user copies and pastes a tweet containing t.co'ed link, the resulting paste
+ // should contain the full original URL (expanded_url), not the display URL.
+ //
+ // Method: Whenever possible, we actually emit HTML that contains expanded_url, and use
+ // font-size:0 to hide those parts that should not be displayed (because they are not part of display_url).
+ // Elements with font-size:0 get copied even though they are not visible.
+ // Note that display:none doesn't work here. Elements with display:none don't get copied.
+ //
+ // Additionally, we want to *display* ellipses, but we don't want them copied. To make this happen we
+ // wrap the ellipses in a tco-ellipsis class and provide an onCopy handler that sets display:none on
+ // everything with the tco-ellipsis class.
+ //
+ // Exception: pic.twitter.com images, for which expandedUrl = "https://twitter.com/#!/username/status/1234/photo/1
+ // For those URLs, display_url is not a substring of expanded_url, so we don't do anything special to render the elided parts.
+ // For a pic.twitter.com URL, the only elided part will be the "https://", so this is fine.
+
+ var displayUrlSansEllipses = displayUrl.replace(//g, ""); // We have to disregard ellipses for matching
+ // Note: we currently only support eliding parts of the URL at the beginning or the end.
+ // Eventually we may want to elide parts of the URL in the *middle*. If so, this code will
+ // become more complicated. We will probably want to create a regexp out of display URL,
+ // replacing every ellipsis with a ".*".
+ if (expandedUrl.indexOf(displayUrlSansEllipses) != -1) {
+ var displayUrlIndex = expandedUrl.indexOf(displayUrlSansEllipses);
+ var v = {
+ displayUrlSansEllipses: displayUrlSansEllipses,
+ // Portion of expandedUrl that precedes the displayUrl substring
+ beforeDisplayUrl: expandedUrl.substr(0, displayUrlIndex),
+ // Portion of expandedUrl that comes after displayUrl
+ afterDisplayUrl: expandedUrl.substr(displayUrlIndex + displayUrlSansEllipses.length),
+ precedingEllipsis: displayUrl.match(/^/) ? "" : "",
+ followingEllipsis: displayUrl.match(/$/) ? "" : ""
+ };
+ $.each(v, function(index, value) {
+ v[index] = twttr.txt.htmlEscape(value);
+ });
+ // As an example: The user tweets "hi http://longdomainname.com/foo"
+ // This gets shortened to "hi http://t.co/xyzabc", with display_url = "…nname.com/foo"
+ // This will get rendered as:
+ // <span class='tco-ellipsis'> <!-- This stuff should get displayed but not copied -->
+ // …
+ // <!-- There's a chance the onCopy event handler might not fire. In case that happens,
+ // we include an &nbsp; here so that the … doesn't bump up against the URL and ruin it.
+ // The &nbsp; is inside the tco-ellipsis span so that when the onCopy handler *does*
+ // fire, it doesn't get copied. Otherwise the copied text would have two spaces in a row,
+ // e.g. "hi http://longdomainname.com/foo".
+ // <span style='font-size:0'>&nbsp;</span>
+ // </span>
+ // <span style='font-size:0'> <!-- This stuff should get copied but not displayed -->
+ // http://longdomai
+ // </span>
+ // <span class='js-display-url'> <!-- This stuff should get displayed *and* copied -->
+ // nname.com/foo
+ // </span>
+ // <span class='tco-ellipsis'> <!-- This stuff should get displayed but not copied -->
+ // <span style='font-size:0'>&nbsp;</span>
+ // …
+ // </span>
+ v['invisible'] = options.invisibleTagAttrs;
+ return stringSupplant("<span class='tco-ellipsis'>#{precedingEllipsis}<span #{invisible}>&nbsp;</span></span><span #{invisible}>#{beforeDisplayUrl}</span><span class='js-display-url'>#{displayUrlSansEllipses}</span><span #{invisible}>#{afterDisplayUrl}</span><span class='tco-ellipsis'><span #{invisible}>&nbsp;</span>#{followingEllipsis}</span>", v);
+ }
+ return displayUrl;
+ };
+
twttr.txt.autoLinkEntities = function(text, entities, options) {
options = clone(options || {});
Please sign in to comment.
Something went wrong with that request. Please try again.