commit 639cab1bffe5a5dfbe22a31fae25604d04aa9bc3 Author: Gregory Eremin Date: Fri Jun 27 00:09:54 2014 +0700 Initial commit diff --git a/background.js b/background.js new file mode 100644 index 0000000..4887bbc --- /dev/null +++ b/background.js @@ -0,0 +1,262 @@ +(function() { + var SHOW_TIMEOUT = 1000, + LOAD_TIMEOUT = SHOW_TIMEOUT / 4, + STORAGE = {}; + + var links = document.getElementsByTagName('a'); + for (var i = 0; i < links.length; i++) { + var el = links[i], + href = el.getAttribute('href'); + + // Skipping elements that has no href and anchor links + if (href === null || href[0] === '#') { + continue; + } + + STORAGE[i] = {}; + (function(i) { + var el = links[i]; + + el.addEventListener('mouseover', function(e) { + log('The mouse is OVER!'); + + if (STORAGE[i].result === undefined) { + STORAGE[i].load_timeout = after(LOAD_TIMEOUT, function() { + STORAGE[i].xhr = loadPageInfo(el.href, function(res) { + log('Result loaded:', res); + STORAGE[i].result = res; + delete STORAGE[i].xhr; + + var tt = document.getElementById('whats-there-tooltip'); + if (tt === null) { + showTooltip(el, STORAGE[i].result, STORAGE[i].e); + } + }); + }); + } + + STORAGE[i].show_timeout = after(SHOW_TIMEOUT, function() { + log('Time for result!'); + if (STORAGE[i].result !== undefined) { + showTooltip(el, STORAGE[i].result, STORAGE[i].e); + } + }); + }); + + el.addEventListener('mouseout', function(e) { + hideTooltip(); + log('The mouse is OUT!'); + + if (STORAGE[i].xhr !== undefined) { + STORAGE[i].xhr.abort(); + log('XHR aborted!'); + } + window.clearTimeout(STORAGE[i].load_timeout); + window.clearTimeout(STORAGE[i].show_timeout); + }); + + el.addEventListener('mousemove', function(e) { + STORAGE[i].e = e; + }); + })(i); + } + + function showTooltip(el, info, e) { + var tt = document.getElementById('whats-there-tooltip'); + if (tt !== null) { + // Don't show a tooltip if there's one already + return; + } + + if (info.title === undefined && info.description === undefined) { + // Don't show a tooltip if there's no title nor descriptions for the page + return; + } + + var tt = document.createElement('div'), + body = document.getElementsByTagName('body')[0], + bounds = el.getBoundingClientRect(); + + tt.setAttribute('class', 'whats-there-tooltip'); + tt.setAttribute('id', 'whats-there-tooltip'); + + if (info.image_url !== undefined) { + var div = document.createElement('div'); + div.setAttribute('class', 'whats-there-img'); + div.style.backgroundImage = 'url(' + info.image_url + ')'; + tt.appendChild(div); + } + + if (info.title !== undefined) { + var div = document.createElement('div'); + div.setAttribute('class', 'whats-there-title'); + div.innerText = info.title; + tt.appendChild(div); + } + + if (info.description !== undefined) { + var div = document.createElement('div'); + div.setAttribute('class', 'whats-there-description'); + div.innerText = info.description; + tt.appendChild(div); + } + + var site_name; + if (info.site_name === undefined) { + site_name = info.host; + } else { + site_name = info.site_name + ' (' + info.host + ')'; + } + var div = document.createElement('div'); + div.setAttribute('class', 'whats-there-site'); + div.innerText = site_name; + tt.appendChild(div); + + body.appendChild(tt); + moveTooltip(e); + } + + function hideTooltip() { + var tt = document.getElementById('whats-there-tooltip'); + if (tt !== null) { + tt.parentNode.removeChild(tt); + } + } + + function moveTooltip(e) { + var tt = document.getElementById('whats-there-tooltip'), + tt_width = tt.clientWidth, + tt_height = tt.clientHeight, + w_width = window.innerWidth, + w_height = window.innerHeight, + s_top = document.documentElement.scrollTop, + s_left = document.documentElement.scrollLeft, + e_top = e.clientY, + e_left = e.clientX; + + var tt_top = s_top + e_top - tt_height - 20, + tt_left = s_left + e_left - tt_width / 2; + + // Vertical positioning fix + if (tt_top < 10) { + tt_top = 10; + + if (e_left > w_width / 2) { + tt_left = s_left + e_left - tt_width - 20; + } else { + tt_left = s_left + e_left + 20; + } + } else { + // Horizontal positioning fixes + if (e_left - tt_width / 2 - 10 < s_left) { + tt_left = 10; + } else if (tt_left + tt_width + 10 > w_width) { + tt_left = w_width - tt_width - 10; + } + } + + tt.style.top = tt_top + 'px'; + tt.style.left = tt_left + 'px'; + } + + function loadPageInfo(url, callback) { + var xhr = new XMLHttpRequest(); + + xhr.onreadystatechange = function() { + if (this.readyState === this.HEADERS_RECEIVED) { + var ct = this.getResponseHeader('Content-Type'); + + // Aborting page load if it's not HTML + if (ct === null || ct.indexOf('text/html') != 0) { + this.abort(); + } + } else if (this.readyState === this.DONE && this.status === 200) { + var result = parseTags(this.responseText), + tokens = url.split('/'); + + result.protocol = tokens[0]; + result.host = tokens[2]; + if (result.image_url !== undefined) { + result.image_url = makeAbsoluteURL(url, result.image_url); + } + + callback(result); + } + } + + xhr.open('GET', url, true); + xhr.send(null); + + return xhr; + } + + function parseTags(html) { + var parser = new DOMParser(), + result = {}; + + var doc = parser.parseFromString(html, 'text/html'); + + var title = doc.getElementsByTagName('title')[0]; + if (title !== undefined) { + result.title = title.innerText; + } + + var tags = doc.getElementsByTagName('meta'); + for (var i = 0; i < tags.length; i++) { + var el = tags[i], + name = el.getAttribute('name'), + property = el.getAttribute('property'), + content = el.getAttribute('content'), + has_content = (content !== null && content.length > 0); + + if (property === 'og:title' && has_content) { + result.title = content; + } else if (name === 'description' && has_content) { + result.description = content; + } else if (property === 'og:description' && has_content) { + result.description = content; + } else if (property === 'og:image' && has_content) { + result.image_url = content; + } else if (property === 'og:site_name' && has_content) { + result.site_name = content; + } + } + + return result; + } + + function makeAbsoluteURL(page_url, link_url) { + var tokens = link_url.split('/'), + page = document.createElement('a'), + link = document.createElement('a'); + + page.href = page_url; + link.href = link_url; + + if ((tokens[0] === 'http:' || tokens[0] === 'https:') && tokens[1] === '') { + // Assuming it's an absolute URL already + return link_url; + } else if (link_url.slice(0, 2) === '//') { + // Relative protocol + return page.protocol + link_url; + } else { + return [ + page.protocol, + '//', + page.host, + link.pathname, + link.search + ].join('') + } + } + + function after(timeout, func) { + return window.setTimeout(func, timeout); + } + + function log() { + if (false && console) { + console.log.apply(console, arguments); + } + } +})(); diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..3d0ef6c --- /dev/null +++ b/manifest.json @@ -0,0 +1,20 @@ +{ + "manifest_version": 2, + + "name": "Whats There", + "description": "Shows nice link previews based on the page's Open Graph tags", + "version": "1.0", + + "permissions": [ + "http://*/*", + "https://*/*" + ], + "content_scripts": [ + { + "all_frames": true, + "js": [ "background.js" ], + "css": [ "tooltip.css" ], + "matches": [ "\u003Call_urls>" ] + } + ] +} diff --git a/resources/aviasales.jpg b/resources/aviasales.jpg new file mode 100644 index 0000000..cd9076d Binary files /dev/null and b/resources/aviasales.jpg differ diff --git a/resources/playground.html b/resources/playground.html new file mode 100644 index 0000000..19cdc29 --- /dev/null +++ b/resources/playground.html @@ -0,0 +1,41 @@ + + + + Playground + + + + + localhots.xxx + erem.in + facebook.com + bad link + wtf + aviasales.ru + + twitter.com + github.com + travelpayouts.com + hotellook.ru + jetradar.com + youtube.com + habrahabr.ru + + diff --git a/resources/themes_demo.html b/resources/themes_demo.html new file mode 100644 index 0000000..1148f82 --- /dev/null +++ b/resources/themes_demo.html @@ -0,0 +1,47 @@ + + + + Themes Demo + + + + +
+
+
Дешевые авиабилеты онлайн. Поиск билетов на самолет по 728 авиакомпаниям, включая low-cost - Aviasales
+
Aviasales.ru помогает найти авиабилеты дешево. Мы ищем билеты на самолет по ведущим российским и зарубежным авиакассам. С помощью нашего поиска вы найдете лучшие цены на авиабилеты. Мы не продаем авиабилеты, мы помогаем вам найти лучшее предложение.
+
Aviasales.ru (aviasales.ru)
+
+ +
+
Дешевые авиабилеты онлайн. Поиск билетов на самолет по 728 авиакомпаниям, включая low-cost - Aviasales
+
Aviasales.ru помогает найти авиабилеты дешево. Мы ищем билеты на самолет по ведущим российским и зарубежным авиакассам. С помощью нашего поиска вы найдете лучшие цены на авиабилеты. Мы не продаем авиабилеты, мы помогаем вам найти лучшее предложение.
+
Aviasales.ru (aviasales.ru)
+
+ +
+
+
Дешевые авиабилеты онлайн. Поиск билетов на самолет по 728 авиакомпаниям, включая low-cost - Aviasales
+
Aviasales.ru (aviasales.ru)
+
+ +
+
Дешевые авиабилеты онлайн. Поиск билетов на самолет по 728 авиакомпаниям, включая low-cost - Aviasales
+
Aviasales.ru (aviasales.ru)
+
+ +
+
Aviasales.ru помогает найти авиабилеты дешево. Мы ищем билеты на самолет по ведущим российским и зарубежным авиакассам. С помощью нашего поиска вы найдете лучшие цены на авиабилеты. Мы не продаем авиабилеты, мы помогаем вам найти лучшее предложение.
+
Aviasales.ru (aviasales.ru)
+
+ + diff --git a/tooltip.css b/tooltip.css new file mode 100644 index 0000000..7845be5 --- /dev/null +++ b/tooltip.css @@ -0,0 +1,63 @@ +.whats-there-tooltip +{ + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-size: 14px; + line-height: 18px; + + position: absolute; + z-index: 999999; + + overflow: hidden; + + width: 400px; + + border-width: 1px; + border-style: solid; + border-color: #ccc; + border-radius: 5px; + background-color: #fff; + box-shadow: 0 1px 3px 1px rgba(0, 0, 0, .2); +} +.whats-there-img +{ + float: left; + + width: 150px; + height: 150px; + margin-right: 10px; + + background-repeat: no-repeat; + background-position: center center; + background-size: auto 100%; +} +.whats-there-title +{ + font-weight: 600; + + overflow: hidden; + + height: 18px; + margin: 5px 10px; + + white-space: nowrap; + text-overflow: ellipsis; +} +.whats-there-description +{ + font-size: 13px; + line-height: 16px; + + overflow: hidden; + + height: 94px; + margin: 5px 10px; + + color: #666; +} +.whats-there-site +{ + overflow: hidden; + + height: 18px; + margin: 5px 10px; +}