BetterFetLife

See website

Du musst eine Erweiterung wie Tampermonkey, Greasemonkey oder Violentmonkey installieren, um dieses Skript zu installieren.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

Sie müssten eine Skript Manager Erweiterung installieren damit sie dieses Skript installieren können

(Ich habe schon ein Skript Manager, Lass mich es installieren!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name        BetterFetLife
// @namespace   com.fetlife.better
// @include     https://fetlife.com/*
// @version     1.4
// @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
	$(document).hoverIntent({
		selector: 'a[href^="/users/"], a[href^="https://fetlife.com/users/"]',
		over: 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')	
				}
			});
			
		},
		
		out: 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);
		}
	});
	$(document).on('mouseover', '#bfl-user', function(e){
		clearTimeout(hideUserPopupTimeout);
	});
	$(document).on('mouseleave', '#bfl-user', 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>											'
	);
	
	
	$(document).hoverIntent({
		selector: 'a[href^="/users/"][href*="/pictures"], a[href^="https://fetlife.com/users/"][href*="/pictures"]',
		over: 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 > 0 ) 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);
	                    }
	                });
	            }
	        });
			
		},
	
		out: 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);
		}
	});
	$(document).on('mouseover', '#bfl-image', function(e){
		clearTimeout(hideImagePopupTimeout);	
		clearTimeout(hideUserPopupTimeout);
	});
	$(document).on('mouseleave', '#bfl-image', 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();
	}
	
	$(document).on('click', '#bfl-image .like-wrap', 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" method except that
 * instead of firing the handlerIn function immediately, hoverIntent checks
 * to see if the user's mouse has slowed down (beneath the sensitivity
 * threshold) before firing the event. The handlerOut function is only
 * called after a matching handlerIn.
 *
 * // basic usage ... just like .hover()
 * .hoverIntent( handlerIn, handlerOut )
 * .hoverIntent( handlerInOut )
 *
 * // basic usage ... with event delegation!
 * .hoverIntent( handlerIn, handlerOut, selector )
 * .hoverIntent( handlerInOut, selector )
 *
 * // using a basic configuration object
 * .hoverIntent( config )
 *
 * @param  handlerIn   function OR configuration object
 * @param  handlerOut  function OR selector for delegation OR undefined
 * @param  selector    selector OR undefined
 * @author Brian Cherne <brian(at)cherne(dot)net>
 */
(function($) {
    $.fn.hoverIntent = function(handlerIn,handlerOut,selector) {

        // default configuration values
        var cfg = {
            interval: 100,
            sensitivity: 6,
            timeout: 0
        };

        if ( typeof handlerIn === "object" ) {
            cfg = $.extend(cfg, handlerIn );
        } else if ($.isFunction(handlerOut)) {
            cfg = $.extend(cfg, { over: handlerIn, out: handlerOut, selector: selector } );
        } else {
            cfg = $.extend(cfg, { over: handlerIn, out: handlerIn, selector: handlerOut } );
        }

        // 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.sqrt( (pX-cX)*(pX-cX) + (pY-cY)*(pY-cY) ) < cfg.sensitivity ) {
                $(ob).off("mousemove.hoverIntent",track);
                // set hoverIntent state to true (so mouseOut can be called)
                ob.hoverIntent_s = true;
                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 = false;
            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 = $.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).on("mousemove.hoverIntent",track);
                // start polling interval (self-calling timeout) to compare mouse coordinates over time
                if (!ob.hoverIntent_s) { ob.hoverIntent_t = setTimeout( function(){compare(ev,ob);} , cfg.interval );}

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

        // listen for mouseenter and mouseleave
        return this.on({'mouseenter.hoverIntent':handleHover,'mouseleave.hoverIntent':handleHover}, cfg.selector);
    };
})(jQuery);