Sleazy Fork is available in English.

BetterFetLife

See website

Ajankohdalta 18.6.2015. Katso uusin versio.

// ==UserScript==
// @name        BetterFetLife
// @namespace   com.fetlife.better
// @include     https://fetlife.com/*
// @version     1.2
// @grant       none
// @description See website
// ==/UserScript==

$(document).ready(function(){
			
	// add custom css
	$('head').append('<style type="text/css">' + bfl_css + '</style>');
	
	// === USER POPUP === //
	
	// create user popup
	$('body').append('							' +
		'	<div id="bfl-user">					' +
		'		<a class="avatar-wrap">			' +
		'			<div class="avatar"></div>	' +
		'		</a>							' +
		'		<a class="name"></a>			' +
		'		<span class="status"></span>	' +
		'		<span class="location"></span>	' +
		'		<span class="photos"></span>	' +
		'	</div>								'
	);
	
	// show a user popup on hover
	$('a[href^="/users/"], a[href^="https://fetlife.com/users/"').hoverIntent(function(){
				
		var linkEl = this;
		var href = $(linkEl).attr('href');
			href = href.replace('https://fetlife.com', '');
		
		// prevent non-user links
		if( href.split('/').length != 3 ) return;
		
		// prevent self-links
		if( $(linkEl).closest('#bfl-user').length > 0 ) return;		
		
		// prevent closing
		clearTimeout(hideUserPopupTimeout);	
		
		// reset the popup
		hideUserPopup();
				
		// show the popup
		$('#bfl-user')
			.addClass('loading')
			.css({
				top: $(linkEl).offset().top + $(linkEl).height() + 8,
				left: $(linkEl).offset().left
			})
			.show();
								
		$.ajax({
			url: href,
            dataType: "html",
			
			// cache is OK
			cache: true,
			
			// prevent 503, fetlife don't liking ajax calls
		    beforeSend: function(xhr) {
		        xhr.setRequestHeader(
		            'X-Requested-With',
		            {
		                toString: function() { return ''; }
		            }
		        );
		    },
			success: function(userDOM){
				
				var userAvatarEl = $(userDOM).find('#main_content a img');
				
				// avatar href
				$('#bfl-user')
					.attr('avatar-href', $(userAvatarEl).attr('src'));
									
				// avatar
				$('#bfl-user .avatar-wrap')
					.attr('href', href)
					
				$('#bfl-user .avatar')
					.css('background-image', 'url(' + $(userAvatarEl).attr('src') + ')')

				// name
				$('#bfl-user .name')
					.attr('href', href)
					.html( $(userAvatarEl).attr('alt') );
				
				// status (age+gender+orientation)
				$('#bfl-user .status')
					.html( $(userDOM).find('#profile h2 .small').html() );
				
				// location
				$('#bfl-user .location')
					.html( $(userDOM).find('#profile h2.bottom + p').html() );
				
				window.userDOM = userDOM;
				
				// photos
				var photos = $(userDOM).find('#profile .container a[href^="/users/"][href*="/pictures"]');
				photos = photos.filter(function(){
					return $(this).find('img').length > 0;
				})
				photos = photos.slice(0,5);
				$('#bfl-user .photos')
					.html('')
					.append( photos );
					
				// friends status
				// remove the link first
				$(userDOM).find('.friends_badge').find('a').remove()
				$('#bfl-user .friends_status')
					.html( $(userDOM).find('.friends_badge').text() );	
					
				$('#bfl-user')
					.removeClass('loading')	
			}
		});
		
	}, function(e){
		
		var linkEl = this;
		var href = $(linkEl).attr('href');
		
		// prevent non-user links
		if( href.split('/').length != 3 ) return;
		
		clearTimeout(hideUserPopupTimeout);
		hideUserPopupTimeout = setTimeout(hideUserPopup, hideUserPopupDelay);
	});
	$('#bfl-user').live('mouseover', function(e){
		clearTimeout(hideUserPopupTimeout);
	});
	$('#bfl-user').live('mouseleave', function(e){
		clearTimeout(hideUserPopupTimeout);
		hideUserPopupTimeout = setTimeout(hideUserPopup, hideUserPopupDelay);
	});

	var hideUserPopupTimeout = setTimeout('', 0);
	var hideUserPopupDelay = 500;
	
	function hideUserPopup() {
		
		$('#bfl-user .avatar').attr('style', '');
		$('#bfl-user .name').html('').attr('href', '');
		$('#bfl-user .status').html('');
		$('#bfl-user .location').html('');
		$('#bfl-user .friends_badge').html('');
		$('#bfl-user .photos').html('');
		$('#bfl-user').removeClass('loading');
		
		$('#bfl-user').hide();
	}
	
	// === IMAGE POPUP === //
	
	// create image popup
	$('body').append('										' +
		'	<div id="bfl-image">							' +
		'		<span class="header">						' +
		'			<span class="title"></span>				' +
		'			<span class="like-wrap">				' +
		'				<span class="like-count"></span>	' +
		'				<span class="like picto">k</span>	' +
		'			</span>									' +
		'		</span>										' +
		'		<a class="image-wrap">						' +
		'			<img class="image" />					' +
		'		</a>										' +
		'	</div>											'
	);
	
	
	$('a[href^="/users/"][href*="/pictures"], a[href^="https://fetlife.com/users/"][href*="/pictures"]').hoverIntent(function(){
				
		var linkEl = this;
		var href = $(this).attr('href');
			href = href.replace('https://fetlife.com', '');
		
		// prevent non-image links
		if( href.split('/').length != 5 ) return;
		
		// prevent self-links
		if( $(linkEl).closest('#bfl-image').length > 0 ) return;
		
		// prevent 'next image'
		if( $(linkEl).children('.fake_img').length > ) return;
		
		// reset the popup
		hideImagePopup();
		
		// show the popup
		var css = {
			top: $(linkEl).offset().top + $(linkEl).height() + 8
		}
		
		if( $(linkEl).offset().left > $(window).width()/2 ) {
			css.right = $(window).width() - $(linkEl).offset().left - $(linkEl).width();
			$('#bfl-image').addClass('alignright');
		} else {
			css.left = $(linkEl).offset().left;
		}
		
		$('#bfl-image')
			.addClass('loading')
			.css(css)
			.show();
		
		$.ajax({
            url: href,
            dataType: "html",
            success: function(html){  
               
                var title = $(html).find('.s.i.caption').text();
                
	            var likeUrl = href.split('/');  
	                likeUrl = likeUrl[ likeUrl.length-1 ];
	                likeUrl = "/pictures/" + likeUrl + "/likes"
				
				// extract the image src                                          
                var src = $(html).find('style').first().html().match(/\(\'(.*?)\'\)/);
                    src = src[0];
                    src = src.replace("('", "");
                    src = src.replace("')", "");
                   					
				$('#bfl-image .title')
					.html(title)
					.attr('title', title)
				
				$('#bfl-image .like-wrap')
					.attr('data-href', likeUrl);
				
				$('#bfl-image .image-wrap')
					.attr('href', href);
					
				$('#bfl-image .image')
					.load(function(){
						$('#bfl-image').removeClass('loading')
					})
					.attr('src', src)
               
				// get amount of likes
                $.ajax({
                    url: likeUrl,
                    dataType: "json",
                    success: function(data) {
                        $('#bfl-image .like-wrap').toggle(data.user_can_like);
                        if( data.is_liked_by_user ) {
                            $('#bfl-image .like-wrap').addClass('liked');
                        }
				
						$('#bfl-image .like-count')
							.html(data.total);
                    }
                });
            }
        });
		
	}, function(e){
		
		var linkEl = this;
		var href = $(linkEl).attr('href');
		
		// prevent non-user links
		if( href.split('/').length != 5 ) return;
		
		clearTimeout(hideImagePopupTimeout);
		hideImagePopupTimeout = setTimeout(hideImagePopup, hideImagePopupDelay);
	});
	$('#bfl-image').live('mouseover', function(e){
		clearTimeout(hideImagePopupTimeout);	
		clearTimeout(hideUserPopupTimeout);
	});
	$('#bfl-image').live('mouseleave', function(e){
		clearTimeout(hideImagePopupTimeout);
		hideImagePopupTimeout = setTimeout(hideImagePopup, hideImagePopupDelay);
	});

	var hideImagePopupTimeout = setTimeout('', 0);
	var hideImagePopupDelay = 500;
	
	function hideImagePopup() {
		
		$('#bfl-image .title').html('').attr('href', '');
		$('#bfl-image .image').attr('src', '');
		$('#bfl-image .like-wrap').attr('data-href', '');
		$('#bfl-image .like-count').html('');
		$('#bfl-image').removeClass('loading');
		$('#bfl-image').removeClass('alignright');
		$('#bfl-image').hide();
	}
	
	$('#bfl-image .like-wrap').live('click', function(){
        var this_ = this;
        $.ajax({
            url: $(this_).data('href') + '/toggle',
            type: 'post',
            success: function(){
                
                if( $(this_).hasClass('liked') ) {
	                $('#bfl-image .like-count').html( parseInt( $('#bfl-image .like-count').html()) - 1 )
                } else {
	                $('#bfl-image .like-count').html( parseInt( $('#bfl-image .like-count').html()) + 1 )
                }
                
                $(this_).toggleClass('liked');
            }
        });
       
        return false;
    });
	
});

var bfl_css = '' +
'	#bfl-user {' +
'		position: absolute;' +
'		z-index: 100;' +
'		display: none;' +
'		padding: 4px;' +
'		min-width: 180px;' +
'		height: 80px;' +
'		padding-left: 92px;' +
'		padding-right: 8px;' +
'		background: #323232;' +
'		border: 3px solid #171717;' +
'	}' +
'	#bfl-user.loading {' +
'		padding-left: 84px;' +
'		padding-right: 4px;' +
'		min-width: 0;' +
'	}' +
'	#bfl-user:before,' +
'	#bfl-image:before {' +
'		position: absolute;' +
'		z-index: 101;' +
'		display: block;' +
'		content: "";' +
'		left: 7px;' +
'		top: -8px;' +
'		border: 8px solid transparent;' +
'		border-bottom-color: #171717;' +
'		border-top-width: 0;' +
'	}' +
'	#bfl-user:after,' +
'	#bfl-image:after {' +
'		position: absolute;' +
'		z-index: 102;' +
'		display: block;' +
'		content: "";' +
'		left: 10px;' +
'		top: -5px;' +
'		border: 5px solid transparent;' +
'		border-bottom-color: #323232;' +
'		border-top-width: 0;' +
'	}' +
'		#bfl-user .avatar {' +
'			position: absolute;' +
'			left: 4px;' +
'			width: 80px;' +
'			height: 80px;' +
'			padding: 0px;' +
'			margin-right: 8px;' +
'			background-color: transparent;' +
'			background-size: cover;' +
'			background-position: center center;' +
'			background-repeat: no-repeat;' +
'		}' +
'		#bfl-user.loading .avatar {' +
'			background-size: auto;' +
'			background-image: url(https://flassets.a.ssl.fastly.net/std/spinners/circle_big.gif);' +
'			margin-right: 0;' +
'		}' +
'		#bfl-user .name {' +
'			white-space: nowrap;' +
'		}'	+
'		#bfl-user .status {' +
'			white-space: nowrap;' +
'			color: #aaa;' +
'		}'	+
'		#bfl-user .location {' +
'			display: block;' +
'			font-size: 12px;' +
'			white-space: nowrap;' +
'		}' +
'		#bfl-user .friends_status {' +
'			float: right;' +
'			font-size: 12px;' +
'		}' +
'		#bfl-user .photos {' +
'			position: absolute;' +
'			right: 4px;' +
'			bottom: 4px;' +
'			font-size: 12px;' +
'		}' +
'			#bfl-user .photos a {' +
'				float: left;' +
'				font-size: 12px;' +
'			}' +
'				#bfl-user .photos a img {' +
'					float: left;' +
'					margin: 2px;' +
'					width: 25px;' +
'					height: 25px;' +
'					padding: 0;' +
'				}' +
'	#bfl-image {' +
'		position: absolute;' +
'		z-index: 100;' +
'		display: none;' +
'		background: #323232;' +
'		border: 3px solid #171717;' +
'		padding: 4px;' +
'	}' +
'	#bfl-image.alignright:before {' +
'		left: auto;' +
'		right: 7px;' +
'	}' +
'	#bfl-image.alignright:after {' +
'		left: auto;' +
'		right: 10px;' +
'	}' +
'	#bfl-image.loading {' +
'		width: 80px;' +
'		height: 80px;' +
'		background: #323232 url(https://flassets.a.ssl.fastly.net/std/spinners/circle_big.gif) no-repeat center center;' +
'	}' +
'		#bfl-image .header {' +
'			position: absolute;' +
'			left: 0;' +
'			right: 0;' +
'			top: 0;' +
'			background: #323232;' +
'			padding: 4px;' +
'			overflow: hidden;' +
'			font-size: 12px;;' +
'		}'	+
'		#bfl-image.loading .header {' +
'			display: none;' +
'		}' +
'			#bfl-image .title {' +
'				float: left;' +
'				width: 80%;' +
'				white-space: nowrap;' +
'				overflow: hidden;' +
'				text-overflow: ellipsis;' +
'			}'	+
'			#bfl-image .like-wrap {' +
'				float: right;' +
'				width: 10%;' +
'				white-space: nowrap;' +
'				text-align: right;' +
'				cursor: pointer;' +
'			}'	+
'			#bfl-image .like-wrap:hover {' +
'				color: #ffffff;' +
'			}'	+
'			#bfl-image .like-wrap:active {' +
'				color: #bbbbbb;' +
'			}'	+
'			#bfl-image .like-wrap.liked {' +
'				color: #DD0000;' +
'			}'	+
'			#bfl-image .like-wrap.liked:hover {' +
'				color: #FF0000;' +
'			}'	+
'			#bfl-image .like-wrap.liked:active {' +
'				color: #BB0000;' +
'			}'	+
'		#bfl-image .image {' +
'			display: block;' +
'			padding: 0;' +
'		}'	+
'		#bfl-image.loading .image {' +
'			opacity: 0;' +
'		}' +

/*!
 * hoverIntent v1.8.0 // 2014.06.29 // jQuery v1.9.1+
 * http://cherne.net/brian/resources/jquery.hoverIntent.html
 *
 * You may use hoverIntent under the terms of the MIT license. Basically that
 * means you are free to use hoverIntent as long as this header is left intact.
 * Copyright 2007, 2014 Brian Cherne
 */
 
/**
* hoverIntent is similar to jQuery's built-in "hover" function except that
* instead of firing the onMouseOver event immediately, hoverIntent checks
* to see if the user's mouse has slowed down (beneath the sensitivity
* threshold) before firing the onMouseOver event.
* 
* hoverIntent r6 // 2011.02.26 // jQuery 1.5.1+
* <http://cherne.net/brian/resources/jquery.hoverIntent.html>
* 
* hoverIntent is currently available for use in all personal or commercial 
* projects under both MIT and GPL licenses. This means that you can choose 
* the license that best suits your project, and use it accordingly.
* 
* // basic usage (just like .hover) receives onMouseOver and onMouseOut functions
* $("ul li").hoverIntent( showNav , hideNav );
* 
* // advanced usage receives configuration object only
* $("ul li").hoverIntent({
*	sensitivity: 7, // number = sensitivity threshold (must be 1 or higher)
*	interval: 100,   // number = milliseconds of polling interval
*	over: showNav,  // function = onMouseOver callback (required)
*	timeout: 0,   // number = milliseconds delay before onMouseOut function call
*	out: hideNav    // function = onMouseOut callback (required)
* });
* 
* @param  f  onMouseOver function || An object with configuration options
* @param  g  onMouseOut function  || Nothing (use configuration options object)
* @author    Brian Cherne brian(at)cherne(dot)net
*/
(function($) {
	$.fn.hoverIntent = function(f,g) {
		// default configuration options
		var cfg = {
			sensitivity: 7,
			interval: 100,
			timeout: 0
		};
		// override configuration options with user supplied object
		cfg = $.extend(cfg, g ? { over: f, out: g } : f );

		// instantiate variables
		// cX, cY = current X and Y position of mouse, updated by mousemove event
		// pX, pY = previous X and Y position of mouse, set by mouseover and polling interval
		var cX, cY, pX, pY;

		// A private function for getting mouse position
		var track = function(ev) {
			cX = ev.pageX;
			cY = ev.pageY;
		};

		// A private function for comparing current and previous mouse position
		var compare = function(ev,ob) {
			ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t);
			// compare mouse positions to see if they've crossed the threshold
			if ( ( Math.abs(pX-cX) + Math.abs(pY-cY) ) < cfg.sensitivity ) {
				$(ob).unbind("mousemove",track);
				// set hoverIntent state to true (so mouseOut can be called)
				ob.hoverIntent_s = 1;
				return cfg.over.apply(ob,[ev]);
			} else {
				// set previous coordinates for next time
				pX = cX; pY = cY;
				// use self-calling timeout, guarantees intervals are spaced out properly (avoids JavaScript timer bugs)
				ob.hoverIntent_t = setTimeout( function(){compare(ev, ob);} , cfg.interval );
			}
		};

		// A private function for delaying the mouseOut function
		var delay = function(ev,ob) {
			ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t);
			ob.hoverIntent_s = 0;
			return cfg.out.apply(ob,[ev]);
		};

		// A private function for handling mouse 'hovering'
		var handleHover = function(e) {
			// copy objects to be passed into t (required for event object to be passed in IE)
			var ev = jQuery.extend({},e);
			var ob = this;

			// cancel hoverIntent timer if it exists
			if (ob.hoverIntent_t) { ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t); }

			// if e.type == "mouseenter"
			if (e.type == "mouseenter") {
				// set "previous" X and Y position based on initial entry point
				pX = ev.pageX; pY = ev.pageY;
				// update "current" X and Y position based on mousemove
				$(ob).bind("mousemove",track);
				// start polling interval (self-calling timeout) to compare mouse coordinates over time
				if (ob.hoverIntent_s != 1) { ob.hoverIntent_t = setTimeout( function(){compare(ev,ob);} , cfg.interval );}

			// else e.type == "mouseleave"
			} else {
				// unbind expensive mousemove event
				$(ob).unbind("mousemove",track);
				// if hoverIntent state is true, then call the mouseOut function after the specified delay
				if (ob.hoverIntent_s == 1) { ob.hoverIntent_t = setTimeout( function(){delay(ev,ob);} , cfg.timeout );}
			}
		};

		// bind the function to the two event listeners
		//return this.bind('mouseenter',handleHover).bind('mouseleave',handleHover);
    return this.live('hover', handleHover);
	};
})(jQuery);