Skip to content
  • Unwatch
  • Fork

/SocialSharePrivacy

HTTPS clone URL

SSH clone URL

Subversion checkout URL

You can clone with HTTPS, SSH, or Subversion.

Clone in Desktop Download ZIP
branch: master
Mathias Panzenböck
804 lines (710 sloc) 25.817 kb
/**
* @license
* jquery.socialshareprivacy.js | 2 Klicks fuer mehr Datenschutz
*
* http://www.heise.de/extras/socialshareprivacy/
* http://www.heise.de/ct/artikel/2-Klicks-fuer-mehr-Datenschutz-1333879.html
*
* Copyright (c) 2011 Hilko Holweg, Sebastian Hilbig, Nicolas Heiringhoff, Juergen Schmidt,
* Heise Zeitschriften Verlag GmbH & Co. KG, http://www.heise.de
*
* Copyright (c) 2012-2013 Mathias Panzenböck
*
* is released under the MIT License http://www.opensource.org/licenses/mit-license.php
*
* Spread the word, link to us if you can.
*/
(function ($, undefined) {
"use strict";
/*
* helper functions
*/
/**
* Build an absolute url using a base url.
* The provided base url has to be a valid absolute url. It will not be validated!
* If no base url is given the document location is used.
* Schemes that behave other than http might not work.
* This function tries to support file:-urls, but might fail in some cases.
* email:-urls aren't supported at all (don't make sense anyway).
*/
function absurl (url, base) {
if (!base) base = document.baseURI || $("html > head > base").last().attr("href") || document.location.href;
if (!url) {
return base;
}
else if (/^[a-z][-+\.a-z0-9]*:/i.test(url)) {
// The scheme actually could contain any kind of alphanumerical unicode
// character, but JavaScript regular expressions don't support unicode
// character classes. Maybe /^[^:]+:/ or even /^.*:/ would be sufficient?
return url;
}
else if (url.slice(0,2) === '//') {
return /^[^:]+:/.exec(base)[0]+url;
}
var ch = url.charAt(0);
if (ch === '/') {
if (/^file:/i.test(base)) {
// file scheme has no hostname
return 'file://'+url;
}
else {
return /^[^:]+:\/*[^\/]+/i.exec(base)[0]+url;
}
}
else if (ch === '#') {
// assume "#" only occures at the end indicating the fragment
return base.replace(/#.*$/,'')+url;
}
else if (ch === '?') {
// assume "?" and "#" only occure at the end indicating the query
// and the fragment
return base.replace(/[\?#].*$/,'')+url;
}
else {
var path;
if (/^file:/i.test(base)) {
path = base.replace(/^file:\/{0,2}/i,'');
base = "file://";
}
else {
var match = /^([^:]+:\/*[^\/]+)(\/.*?)?(\?.*?)?(#.*)?$/.exec(base);
base = match[1];
path = match[2]||"/";
}
path = path.split("/");
path.pop();
if (path.length === 0) {
// Ensure leading "/". Of course this is only valid on
// unix like filesystems. More magic would be needed to
// support other filesystems.
path.push("");
}
path.push(url);
return base+path.join("/");
}
}
function formatNumber (number) {
number = Number(number);
var prefix = "";
var suffix = "";
if (number < 0) {
prefix = "-";
number = -number;
}
if (number === Infinity) {
return prefix + "Infinity";
}
if (number > 9999) {
number = number / 1000;
suffix = "K";
}
number = Math.round(number);
if (number === 0) {
return "0";
}
var buf = [];
while (number > 0) {
var part = String(number % 1000);
number = Math.floor(number / 1000);
if (number) {
while (part.length < 3) {
part = "0"+part;
}
}
buf.unshift(part);
}
return prefix + buf.join(",") + suffix;
}
// helper function that gets the title of the current page
function getTitle (options, uri, settings) {
var title = settings && settings.title;
if (typeof title === "function") {
title = title.call(this, options, uri, settings);
}
if (title) {
return title;
}
var title = $('meta[name="DC.title"]').attr('content');
var creator = $('meta[name="DC.creator"]').attr('content');
if (title && creator) {
return title + ' - ' + creator;
} else {
return title || $('meta[property="og:title"]').attr('content') || $('title').text();
}
}
function getDescription (options, uri, settings) {
var description = settings && settings.description;
if (typeof description === "function") {
description = description.call(this, options, uri, settings);
}
if (description) {
return description;
}
return abbreviateText(
$('meta[name="twitter:description"]').attr('content') ||
$('meta[itemprop="description"]').attr('content') ||
$('meta[name="description"]').attr('content') ||
$.trim($('article, p').first().text()) || $.trim($('body').text()), 3500);
}
var IMAGE_ATTR_MAP = {
META : 'content',
IMG : 'src',
A : 'href',
IFRAME : 'src',
LINK : 'href'
};
// find the largest image of the website
// if no image at all is found use googles favicon service, which
// defaults to a small globe (so there is always some image)
function getImage (options, uri, settings) {
var imgs, img = settings && settings.image;
if (typeof img === "function") {
img = img.call(this, options, uri, settings);
}
if (!img) {
imgs = $('meta[property="image"], meta[property="og:image"], meta[property="og:image:url"], meta[name="twitter:image"], link[rel="image_src"], itemscope *[itemprop="image"]').first();
if (imgs.length > 0) {
img = imgs.attr(IMAGE_ATTR_MAP[imgs[0].nodeName]);
}
}
if (img) {
return absurl(img);
}
imgs = $('img').filter(':visible').filter(function () {
return $(this).parents('.social_share_privacy_area').length === 0;
});
if (imgs.length === 0) {
img = $('link[rel~="shortcut"][rel~="icon"]').attr('href');
if (img) return absurl(img);
return 'http://www.google.com/s2/favicons?'+$.param({domain:location.hostname});
}
imgs.sort(function (lhs, rhs) {
return rhs.offsetWidth * rhs.offsetHeight - lhs.offsetWidth * lhs.offsetHeight;
});
// browser makes src absolute:
return imgs[0].src;
}
// abbreviate at last blank before length and add "\u2026" (horizontal ellipsis)
function abbreviateText (text, length) {
// length of UTF-8 encoded string
if (unescape(encodeURIComponent(text)).length <= length) {
return text;
}
// "\u2026" is actually 3 bytes long in UTF-8
// TODO: if any of the last 3 characters is > 1 byte long this truncates too much
var abbrev = text.slice(0, length - 3);
if (!/\W/.test(text.charAt(length - 3))) {
var match = /^(.*)\s\S*$/.exec(abbrev);
if (match) {
abbrev = match[1];
}
}
return abbrev + "\u2026";
}
var HTML_CHAR_MAP = {
'<': '&lt;',
'>': '&gt;',
'&': '&amp;',
'"': '&quot;',
"'": '&#39;'
};
function escapeHtml (s) {
return s.replace(/[<>&"']/g, function (ch) {
return HTML_CHAR_MAP[ch];
});
}
function getEmbed (options, uri, settings) {
var embed = settings && settings.embed;
if (typeof embed === "function") {
embed = embed.call(this, options, uri, settings);
}
if (embed) {
return embed;
}
embed = ['<iframe scrolling="no" frameborder="0" style="border:none;" allowtransparency="true"'];
var embed_url = $('meta[name="twitter:player"]').attr('content');
if (embed_url) {
var width = $('meta[name="twitter:player:width"]').attr('content');
var height = $('meta[name="twitter:player:height"]').attr('content');
if (width) embed.push(' width="',escapeHtml(width),'"');
if (height) embed.push(' height="',escapeHtml(height),'"');
}
else {
embed_url = uri + options.referrer_track;
}
embed.push(' src="',escapeHtml(embed_url),'"></iframe>');
return embed.join('');
}
// build URI from rel="canonical" or document.location
function getURI (options) {
var uri = document.location.href;
var canonical = $("link[rel=canonical]").attr("href") || $('head meta[property="og:url"]').attr("content");
if (canonical) {
uri = absurl(canonical);
}
else if (options && options.ignore_fragment) {
uri = uri.replace(/#.*$/,'');
}
return uri;
}
function buttonClickHandler (service_name) {
function onclick (event) {
var $container = $(this).parents('li.help_info').first();
var $share = $container.parents('.social_share_privacy_area').first().parent();
var options = $share.data('social-share-privacy-options');
var service = options.services[service_name];
var button_class = service.button_class || service_name;
var uri = options.uri;
if (typeof uri === 'function') {
uri = uri.call($share[0], options);
}
var $switch = $container.find('span.switch');
if ($switch.hasClass('off')) {
$container.addClass('info_off');
$switch.addClass('on').removeClass('off').html(service.txt_on||'\u00a0');
$container.find('img.privacy_dummy').replaceWith(
typeof(service.button) === "function" ?
service.button.call($container.parent().parent()[0],service,uri,options) :
service.button);
$share.trigger({type: 'socialshareprivacy:enable', serviceName: service_name, isClick: !event.isTrigger});
} else {
$container.removeClass('info_off');
$switch.addClass('off').removeClass('on').html(service.txt_off||'\u00a0');
$container.find('.dummy_btn').empty().
append($('<img/>').addClass(button_class+'_privacy_dummy privacy_dummy').
attr({
alt: service.dummy_alt,
src: service.path_prefix + (options.layout === 'line' ?
service.dummy_line_img : service.dummy_box_img)
}).click(onclick));
$share.trigger({type: 'socialshareprivacy:disable', serviceName: service_name, isClick: !event.isTrigger});
}
};
return onclick;
}
// display info-overlays a tiny bit delayed
function enterHelpInfo () {
var $info_wrapper = $(this);
if ($info_wrapper.hasClass('info_off')) return;
var timeout_id = window.setTimeout(function () {
$info_wrapper.addClass('display');
$info_wrapper.removeData('timeout_id');
}, 500);
$info_wrapper.data('timeout_id', timeout_id);
}
function leaveHelpInfo () {
var $info_wrapper = $(this);
var timeout_id = $info_wrapper.data('timeout_id');
if (timeout_id !== undefined) {
window.clearTimeout(timeout_id);
}
$info_wrapper.removeClass('display');
}
function permCheckChangeHandler () {
var $input = $(this);
var $share = $input.parents('.social_share_privacy_area').first().parent();
var options = $share.data('social-share-privacy-options');
if ($input.is(':checked')) {
options.set_perma_option($input.attr('data-service'), options);
$input.parent().addClass('checked');
} else {
options.del_perma_option($input.attr('data-service'), options);
$input.parent().removeClass('checked');
}
}
function enterSettingsInfo () {
var $settings = $(this);
var timeout_id = window.setTimeout(function () {
$settings.find('.settings_info_menu').removeClass('off').addClass('on');
$settings.removeData('timeout_id');
}, 500);
$settings.data('timeout_id', timeout_id);
}
function leaveSettingsInfo () {
var $settings = $(this);
var timeout_id = $settings.data('timeout_id');
if (timeout_id !== undefined) {
window.clearTimeout(timeout_id);
}
$settings.find('.settings_info_menu').removeClass('on').addClass('off');
}
function setPermaOption (service_name, options) {
$.cookie('socialSharePrivacy_'+service_name, 'perma_on', options.cookie_expires, options.cookie_path, options.cookie_domain);
}
function delPermaOption (service_name, options) {
$.cookie('socialSharePrivacy_'+service_name, null, -1, options.cookie_path, options.cookie_domain);
}
function getPermaOption (service_name, options) {
return !!options.get_perma_options(options)[service_name];
}
function getPermaOptions (options) {
var cookies = $.cookie();
var permas = {};
for (var name in cookies) {
var match = /^socialSharePrivacy_(.+)$/.exec(name);
if (match) {
permas[match[1]] = cookies[name] === 'perma_on';
}
}
return permas;
}
// extend jquery with our plugin function
function socialSharePrivacy (options) {
if (typeof options === "string") {
var command = options;
if (arguments.length === 1) {
switch (command) {
case "enable":
this.find('.switch.off').click();
break;
case "disable":
this.find('.switch.on').click();
break;
case "toggle":
this.find('.switch').click();
break;
case "options":
return this.data('social-share-privacy-options');
case "destroy":
this.trigger({type: 'socialshareprivacy:destroy'});
this.children('.social_share_privacy_area').remove();
this.removeData('social-share-privacy-options');
break;
case "enabled":
var enabled = {};
this.each(function () {
var $self = $(this);
var options = $self.data('social-share-privacy-options');
for (var name in options.services) {
enabled[name] = $self.find('.'+(options.services[name].class_name||name)+' .switch').hasClass('on');
}
});
return enabled;
case "disabled":
var disabled = {};
this.each(function () {
var $self = $(this);
var options = $self.data('social-share-privacy-options');
for (var name in options.services) {
disabled[name] = $self.find('.'+(options.services[name].class_name||name)+' .switch').hasClass('off');
}
});
return disabled;
default:
throw new Error("socialSharePrivacy: unknown command: "+command);
}
}
else {
var arg = arguments[1];
switch (command) {
case "enable":
this.each(function () {
var $self = $(this);
var options = $self.data('social-share-privacy-options');
$self.find('.'+(options.services[arg].class_name||arg)+' .switch.off').click();
});
break;
case "disable":
this.each(function () {
var $self = $(this);
var options = $self.data('social-share-privacy-options');
$self.find('.'+(options.services[arg].class_name||arg)+' .switch.on').click();
});
break;
case "toggle":
this.each(function () {
var $self = $(this);
var options = $self.data('social-share-privacy-options');
$self.find('.'+(options.services[arg].class_name||arg)+' .switch').click();
});
break;
case "option":
if (arguments.length > 2) {
var value = {};
value[arg] = arguments[2];
this.each(function () {
$.extend(true, $(this).data('social-share-privacy-options'), value);
});
}
else {
return this.data('social-share-privacy-options')[arg];
}
break;
case "options":
$.extend(true, options, arg);
break;
case "enabled":
var options = this.data('social-share-privacy-options');
return this.find('.'+(options.services[arg].class_name||arg)+' .switch').hasClass('on');
case "disabled":
var options = this.data('social-share-privacy-options');
return this.find('.'+(options.services[arg].class_name||arg)+' .switch').hasClass('off');
default:
throw new Error("socialSharePrivacy: unknown command: "+command);
}
}
return this;
}
return this.each(function () {
// parse options passed via data-* attributes:
var data = {};
if (this.lang) data.language = this.lang;
for (var i = 0, attrs = this.attributes; i < attrs.length; ++ i) {
var attr = attrs[i];
if (/^data-./.test(attr.name)) {
var path = attr.name.slice(5).replace(/-/g,"_").split(".");
var ctx = data, j = 0;
for (; j < path.length-1; ++ j) {
var name = path[j];
if (name in ctx) {
ctx = ctx[name];
if (typeof ctx === "string") {
ctx = (new Function("$", "return ("+ctx+");")).call(this, $);
}
}
else {
ctx = ctx[name] = {};
}
}
var name = path[j];
if (typeof ctx[name] === "object") {
ctx[name] = $.extend(true, (new Function("$", "return ("+attr.value+");")).call(this, $), ctx[name]);
}
else {
ctx[name] = attr.value;
}
}
}
// parse global option values:
if ('cookie_expires' in data) data.cookie_expires = Number(data.cookie_expires);
if ('perma_option' in data) data.perma_option = $.trim(data.perma_option).toLowerCase() === "true";
if ('ignore_fragment' in data) data.ignore_fragment = $.trim(data.ignore_fragment).toLowerCase() === "true";
if ('set_perma_option' in data) {
data.set_perma_option = new Function("service_name", "options", data.set_perma_option);
}
if ('del_perma_option' in data) {
data.del_perma_option = new Function("service_name", "options", data.del_perma_option);
}
if ('get_perma_option' in data) {
data.get_perma_option = new Function("service_name", "options", data.get_perma_option);
}
if ('get_perma_options' in data) {
data.get_perma_options = new Function("options", data.get_perma_options);
}
if ('order' in data) {
data.order = $.trim(data.order);
if (data.order) {
data.order = data.order.split(/\s+/g);
}
else {
delete data.order;
}
}
if (typeof data.services === "string") {
data.services = (new Function("$", "return ("+data.services+");")).call(this, $);
}
if ('options' in data) {
data = $.extend(data, (new Function("$", "return ("+data.options+");")).call(this, $));
delete data.options;
}
if ('services' in data) {
for (var service_name in data.services) {
var service = data.services[service_name];
if (typeof service === "string") {
data.services[service_name] = (new Function("$", "return ("+service+");")).call(this, $);
}
// only values of common options are parsed:
if (typeof service.status === "string") {
service.status = $.trim(service.status).toLowerCase() === "true";
}
if (typeof service.perma_option === "string") {
service.perma_option = $.trim(service.perma_option).toLowerCase() === "true";
}
}
}
// overwrite default values with user settings
var this_options = $.extend(true,{},socialSharePrivacy.settings,options,data);
var order = this_options.order || [];
var dummy_img = this_options.layout === 'line' ? 'dummy_line_img' : 'dummy_box_img';
var any_on = false;
var any_perm = false;
var any_unsafe = false;
var unordered = [];
for (var service_name in this_options.services) {
var service = this_options.services[service_name];
if (service.status) {
any_on = true;
if ($.inArray(service_name, order) === -1) {
unordered.push(service_name);
}
if (service.privacy !== 'safe') {
any_unsafe = true;
if (service.perma_option) {
any_perm = true;
}
}
}
if (!('language' in service)) {
service.language = this_options.language;
}
if (!('path_prefix' in service)) {
service.path_prefix = this_options.path_prefix;
}
if (!('referrer_track' in service)) {
service.referrer_track = '';
}
}
unordered.sort();
order = order.concat(unordered);
// check if at least one service is activated
if (!any_on) {
return;
}
// insert stylesheet into document and prepend target element
if (this_options.css_path) {
var css_path = (this_options.path_prefix||"") + this_options.css_path;
// IE fix (needed for IE < 9 - but done for all IE versions)
if (document.createStyleSheet) {
document.createStyleSheet(css_path);
} else if ($('link[href="'+css_path+'"]').length === 0) {
$('<link/>',{rel:'stylesheet',type:'text/css',href:css_path}).appendTo(document.head);
}
}
// get stored perma options
var permas;
if (this_options.perma_option && any_perm) {
if (this_options.get_perma_options) {
permas = this_options.get_perma_options(this_options);
}
else {
permas = {};
for (var service_name in this_options.services) {
permas[service_name] = this_options.get_perma_option(service_name, this_options);
}
}
}
// canonical uri that will be shared
var uri = this_options.uri;
if (typeof uri === 'function') {
uri = uri.call(this, this_options);
}
var $context = $('<ul class="social_share_privacy_area"></ul>').addClass(this_options.layout);
var $share = $(this);
$share.prepend($context).data('social-share-privacy-options',this_options);
for (var i = 0; i < order.length; ++ i) {
var service_name = order[i];
var service = this_options.services[service_name];
if (service && service.status) {
var class_name = service.class_name || service_name;
var button_class = service.button_class || service_name;
var $help_info;
if (service.privacy === 'safe') {
$help_info = $('<li class="help_info"><div class="info">' +
service.txt_info + '</div><div class="dummy_btn"></div></li>').addClass(class_name);
$help_info.find('.dummy_btn').
addClass(button_class).
append(service.button.call(this,service,uri,this_options));
}
else {
$help_info = $('<li class="help_info"><div class="info">' +
service.txt_info + '</div><span class="switch off">' + (service.txt_off||'\u00a0') +
'</span><div class="dummy_btn"></div></li>').addClass(class_name);
$help_info.find('.dummy_btn').
addClass(button_class).
append($('<img/>').addClass(button_class+'_privacy_dummy privacy_dummy').
attr({
alt: service.dummy_alt,
src: service.path_prefix + service[dummy_img]
}));
$help_info.find('.dummy_btn img.privacy_dummy, span.switch').click(
buttonClickHandler(service_name));
}
$context.append($help_info);
}
}
//
// append Info/Settings-area
//
if (any_unsafe) {
var $settings_info = $('<li class="settings_info"><div class="settings_info_menu off perma_option_off"><a>' +
'<span class="help_info icon"><span class="info">' + this_options.txt_help + '</span></span></a></div></li>');
var $info_link = $settings_info.find('> .settings_info_menu > a').attr('href', this_options.info_link);
if (this_options.info_link_target) {
$info_link.attr("target",this_options.info_link_target);
}
$context.append($settings_info);
$context.find('.help_info').on('mouseenter', enterHelpInfo).on('mouseleave', leaveHelpInfo);
// menu for permanently enabling of service buttons
if (this_options.perma_option && any_perm) {
// define container
var $container_settings_info = $context.find('li.settings_info');
// remove class that fomrats the i-icon, because perma-options are shown
var $settings_info_menu = $container_settings_info.find('.settings_info_menu');
$settings_info_menu.removeClass('perma_option_off');
// append perma-options-icon (.settings) and form (hidden)
$settings_info_menu.append(
'<span class="settings">' + this_options.txt_settings + '</span><form><fieldset><legend>' +
this_options.settings_perma + '</legend></fieldset></form>');
// write services with <input> and <label> and checked state from cookie
var $fieldset = $settings_info_menu.find('form fieldset');
for (var i = 0; i < order.length; ++ i) {
var service_name = order[i];
var service = this_options.services[service_name];
if (service && service.status && service.perma_option && service.privacy !== 'safe') {
var class_name = service.class_name || service_name;
var perma = permas[service_name];
var $field = $('<label><input type="checkbox"' + (perma ? ' checked="checked"/>' : '/>') +
service.display_name + '</label>');
$field.find('input').attr('data-service', service_name);
$fieldset.append($field);
// enable services when cookie set and refresh cookie
if (perma) {
$context.find('li.'+class_name+' span.switch').click();
this_options.set_perma_option(service_name, this_options);
}
}
}
// indicate clickable setings gear
$container_settings_info.find('span.settings').css('cursor', 'pointer');
// show settings menu on hover
$container_settings_info.on('mouseenter', enterSettingsInfo).on('mouseleave', leaveSettingsInfo);
// interaction for <input> to enable services permanently
$container_settings_info.find('fieldset input').on('change', permCheckChangeHandler);
}
}
$share.trigger({type: 'socialshareprivacy:create', options: this_options});
});
};
// expose helper functions:
socialSharePrivacy.absurl = absurl;
socialSharePrivacy.escapeHtml = escapeHtml;
socialSharePrivacy.getTitle = getTitle;
socialSharePrivacy.getImage = getImage;
socialSharePrivacy.getEmbed = getEmbed;
socialSharePrivacy.getDescription = getDescription;
socialSharePrivacy.abbreviateText = abbreviateText;
socialSharePrivacy.formatNumber = formatNumber;
socialSharePrivacy.settings = {
'services' : {},
'info_link' : 'http://panzi.github.io/SocialSharePrivacy/',
'info_link_target' : '',
'txt_settings' : 'Settings',
'txt_help' : 'If you activate these fields via click, data will be sent to a third party (Facebook, Twitter, Google, ...) and stored there. For more details click <em>i</em>.',
'settings_perma' : 'Permanently enable share buttons:',
'layout' : 'line', // possible values: 'line' (~120x20) or 'box' (~58x62)
'set_perma_option' : setPermaOption,
'del_perma_option' : delPermaOption,
'get_perma_options' : getPermaOptions,
'get_perma_option' : getPermaOption,
'perma_option' : !!$.cookie,
'cookie_path' : '/',
'cookie_domain' : document.location.hostname,
'cookie_expires' : 365,
'path_prefix' : '',
'css_path' : "stylesheets/socialshareprivacy.css",
'uri' : getURI,
'language' : 'en',
'ignore_fragment' : true
};
$.fn.socialSharePrivacy = socialSharePrivacy;
}(jQuery));
Jump to Line
Something went wrong with that request. Please try again.