MediaWiki:Gadget-gsnews.js

Revision as of 19:36, 9 August 2021 by Banri (talk | contribs) (Created page with "//<nowiki> global jQuery, mediaWiki, mw, gs, gswiki, moment, ga: 'use strict'; ;(function($, mw, gs){ var portletLink, $popup, $arrow, $content, $list, news_it...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
//<nowiki>
/*global jQuery, mediaWiki, mw, gs, gswiki, moment, ga */
'use strict';

;(function($, mw, gs){
	var portletLink,
		$popup,
		$arrow,
		$content,
		$list,
		news_items,
		isopen = false,
		url = '',
		lastUrl = '',
		page = 1,
		lastKey = 'gsw-gsnews-lastcheck',
		activityKey = 'gsw-gsnews-last',
		standaloneClass = 'gsw-gsnews-page',
		currentSkin = mw.config.get('skin');

	// Site icon overrides (hosted locally)
	var srcicons = {
		twitter: { regex:/twitter\./i , icon:'<img src="/images/thumb/1/1c/Twitter_news_icon.svg/240px-Twitter_news_icon.svg.png?597fa" alt="Twitter logo">' },
		reddit: { regex:/reddit\./i , icon:'<img src="/images/thumb/6/68/Reddit_news_icon.svg/240px-Reddit_news_icon.svg.png?597fa" alt="Reddit logo">' },
		gswiki: { regex:/gem\.wiki/i , icon:'<img src="/images/2/21/gsw_news_icon.png?3591e" alt="Gemini Station wiki logo">' },
		discord: { regex:/discordapp\./i , icon:'<img src="/images/thumb/b/b2/Discord_news_icon.svg/210px-Discord_news_icon.svg.png?6438d" alt="Discord logo">' },
	};
	// Allow images from
	var hasimg = {
		twitter: true,
		reddit: true,
		gswiki: true,
		discord: false,
		unknown: true
	};
	
	function hasLocalStorage () {
		// because gsw-util for some reason doesn't work here
	    try {
	      localStorage.setItem('test', 'test')
	      localStorage.removeItem('test')
	      return true
	    } catch (e) {
	      return false
	    }
	}

	function init() {
		$content = $('<div>').addClass('gsw-rsnews-content');
		$list = $('<ul>').addClass('news-list');
		
		if (currentSkin !== 'minerva') { // AKA we're not on mobile
			portletLink = mw.util.addPortletLink(
				'p-personal',
				'',
				'',
				'pt-rsnews',
				'Gemini Staion News',
				null,
				$('#pt-userpage, #pt-anonuserpage')
			);
		
			$(portletLink).find('a').addClass('oo-ui-icon-feedback').click(function(e) {
				e.preventDefault();
				mw.log('Open/Close RS news');
	
				if (isopen) {
					$popup.hide();
					isopen = false;
				} else {
					// Google analytics tracker
					if (typeof ga === 'function') {
						ga('gtag_UA_126479006_1.send', 'event', 'Gadget-rsnews', 'Open', 'Normal');
					}
					
					handleInitialLoad(false);
				}
			});
	
			$arrow = $('<div>').addClass('gsw-rsnews-arrow');
			$popup = $('<div>').addClass('gsw-rsnews').css({
				'max-width': '500px'
			}).append(
				$arrow,
				$('<h2>').html('Latest Gemini Station News'),
				$('<div>').addClass('gsw-rsnews-help').html('Curated by trusted community members. <a href="/w/Help:Gadget-rsnews" target="_blank">Learn more</a>. View this feed <a href="/w/GSWiki:News" target="_blank">on a page</a>.'),
				$content
			);
	
			$('body').append($popup);
			$popup.hide();
	
			// Reposition popup on window resize
			$(window).resize(resizeEvent);
	
			// Close popup when anywhere else is clicked
			$(document).click(function(e) {
				if (isopen && !$(e.target).closest('.gsw-rsnews').length) {
					$popup.hide();
					isopen = false;
				}
			});
	
			// Check for new news items periodically (updates counter)
			checkNew();
			// setInterval(checkNew, 300000); lets not cause 12million hits a day.
		}
		
		// If we're on the standalone page, we should show the feed there
		if ($('.' + standaloneClass).length) {
			$('.' + standaloneClass).addClass('gsw-rsnews').html($content);
			
			// change the click handler on the portlet link because
			// nobody needs to use this and it might cause bugs
			if (portletLink !== undefined) {
				$(portletLink).find('a').off('click').click(function(e) {
					e.preventDefault();
					mw.notify( 'You are on the standalone page for the feed already. You do not need to open this popup.', { tag: 'rsnews' } );
				})	
			}

			handleInitialLoad(true);
		}
	}
	
	function handleInitialLoad (standalone) {
		mw.loader.using(['moment'], function () {
			$.ajax({
				dataType: 'json',
				url: url,
				headers: { 'Cache-Control': 'max-age=0' },
				error: function (jqXHR, status, error) {
					openError(jqXHR, status, error, standalone);
				},
				success: function (newsjson) {
					mw.log('Success getting news items');
					mw.log(newsjson);
					if ( !(newsjson.data && newsjson.data[0]) ) {
						openError({}, 'Missing news items', 'returned json contained no data, or has an unrecognised structure');
						return;
					}
			
					news_items = null; // items should be cleared when opening the popup
					page = 1;
					$content.empty();
					$list.empty();
					addItems(newsjson, true);
					$content.append($list);
					updateLastActivity(newsjson);
					
					if (!standalone) {
						openPopup();
					}
				}
			});
		});
	}

	/**
	 * Open popup, add news items
	 * @return {undefined}
	 */
	function openPopup(newsjson, status, jqXHR) {
		isopen = true;
		resizeEvent();
		$popup.show();
	}
	
	function setLoading () {
		mw.log('Setting RSNews as loading');
		$('.gsw-rsnews-content').addClass('gsw-social-loading');
	}
	
	function setLoadingDone () {
		mw.log('Setting RSNews as loading done');
		$('.gsw-rsnews-content').removeClass('gsw-social-loading');
	}

	/**
	 * Add items to list
	 * @return {undefined}
	 */
	function addItems(newsjson, checkActivity) {
		// Google analytics tracker
		function trackClick( e ) {
			if (typeof ga === 'function') {
				ga('gtag_UA_126479006_1.send', 'event', 'Gadget-rsnews', 'Open link', e.data.type);
			}
		}
		
		if (checkActivity) {
			doActivityCheck(newsjson)
		}

		var newestdate = moment(0);
		newsjson.data.forEach(function (item) {
			mw.log(item);
			var dateAdded = moment(item.dateAdded);

			if (!news_items || dateAdded.isAfter(news_items[0].date, 'seconds') || dateAdded.isBefore(news_items[news_items.length-1].date, 'seconds') ) {
				var ltype = 'unknown',
					$icon,$time,$img,$item,
					excerpt = item.excerpt || '';

				// Check link type
				for (const t in srcicons) {
					if (srcicons[t].regex.test(item.url)) {
						ltype = t;
						$icon = $(srcicons[t].icon);
						break;
					}
				}
				mw.log(item.url);
				mw.log(ltype);

				// Icon image
				if (!$icon && item.icon) {
					$icon = $('<img>').attr({
						src: item.icon,
						alt: 'website icon'
					});
				}

				// Post date/time
				if (item.dateAdded) {
					var pdate = moment(item.dateAdded);
					$time = $('<time>').addClass('news-date').attr({
						'datetime': pdate.format(),
						'title': pdate.local().format('lll')
					}).text( pdate.calendar(null, { sameDay: function(){return '['+this.fromNow()+']';} }) );
				} else {
					$time = $('<span>').addClass('news-date').attr({ title:'Unknown date-time' }).text('');
				}

				// Limit excerpt length, add ...
				if (excerpt.length > 300) {
					excerpt = excerpt.slice(0,300) + '<b> ...</b>';
				}
				
				excerpt = excerpt.replace(/(https?:\/\/(?:.+\.)?t\.co\/[a-zA-Z0-9_]+)/g, '') // remove t.co links
				excerpt = excerpt.replace(/\s+/g, ' ').trim(); // remove extra whitespace and trim

				// Item jquery element
				$item = $('<li>').append(
					$('<a>').addClass('news-item').attr({
						title: item.title,
						href: item.url,
						target: '_blank'
					}).append(
						$icon.addClass('news-icon'),
						$('<span>').addClass('news-title').text(item.title),
						$time,
						$('<br>'),
						$('<span>').addClass('news-snippet').html(excerpt)
					)
				);

				// Post image
				if (hasimg[ltype] && item.image) {
					$item.find('.news-snippet').prepend(
						$('<img>').addClass('news-image').attr({ src: item.image, alt: 'article image' })
					);
				}

				if ( !news_items ) {
					// No news items
					news_items = [ {
						id: item.id,
						type: ltype,
						date: dateAdded,
						$element: $item
					} ];
				} else if ( dateAdded.isAfter(news_items[0].date, 'seconds') ) {
					// Newer than last post
					news_items.unshift({
						id: item.id,
						date: dateAdded,
						$element: $item
					});
				} else {
					// Older than last post
					news_items.push({
						id: item.id,
						date: dateAdded,
						$element: $item
					});
				}
			}
		});
		mw.log(news_items);

		// Generate visible list
		$list.empty();
		news_items.forEach(function (item) {
			item.$element.find('a.news-item').click({type:item.type, id:item.id}, trackClick);
			$list.append( item.$element );
		});
		
		// Refresh and show more buttons
		if (newsjson.pagination && newsjson.pagination.has_more) {
			$list.append($('<li>').addClass('news-refresh news-more').append(
				$('<button>')
					.addClass('news-more-button')
					.attr({ type:'button' })
					.text('Show more')
					.click(function () {
						page ++;
						$.ajax({
							dataType: 'json',
							url: url + '?page=' + page,
							headers: { 'Cache-Control': 'max-age=0' },
							error: openError,
							success: showMore
						});
					})
			));
		}
		
		setLoadingDone();
		
		// Update latest check time
		if (hasLocalStorage()) {
			try {
				localStorage.setItem(lastKey, moment().format());
			} catch (err) {
				console.warn('Error saving last check to localStorage');
			}
		}
	}

	/**
	 * Open popup, show error
	 * @return {undefined}
	 */
	function openError (jqXHR, status, error, standalone) {
		console.warn('Error loading news:\n' + status + ': ' + error);
		
		$content.empty();
		$content.append(
			$('<p>')
				.addClass('news-error')
				.html('Error loading news:<br>Status: ' + status + '<br>Error msg: ' + error)
		);		
		
		if (!standalone) {
			isopen = true;
			resizeEvent();
			$popup.show();
		}
	}

	/**
	 * Show more news items
	 * @return {undefined}
	 */
	function showMore(newsjson, status, jqXHR) {
		$content.find('li.news-refresh').remove();
		
		setLoading();

		mw.log('Success getting more news items');
		mw.log(newsjson);
		if ( !(newsjson.data && newsjson.data[0]) ) {
			console.warn('No news items: returned json contained no data, or has an unrecognised structure');
			setLoadingDone();
			return;
		}
		

		addItems(newsjson, false);
	}

	function doActivityCheck(newsjson) {
		var lastItem;
		
		try {
			lastItem = localStorage.getItem(activityKey);
		} catch (err) {
			console.warn('Error loading last item from localStorage');
		}
		
		var itemId = null
		
		if (typeof newsjson === 'object') {
			itemId = newsjson.id
		} else {
			itemId = newsjson.data[0].id
		}
		
		if (itemId !== parseInt(lastItem)) {
			$(portletLink).find('a.oo-ui-icon-feedback')
				.addClass('gsw-rsnews-hasunread')
		} else {
			$(portletLink).find('a.oo-ui-icon-feedback')
				.removeClass('gsw-rsnews-hasunread')
		}
	}
	
	function updateLastActivity(newsjson) {
		// save new id
		try {
			localStorage.setItem(activityKey, newsjson.data[0].id);
		} catch (err) {
			console.warn('Error saving last activity to localStorage');
		}
	}

	/**
	 * Checks for new items
	 * @return {undefined}
	 */
	function checkNew() {
		var last = '0';
		if (hasLocalStorage()) {
			try {
				last = localStorage.getItem(lastKey);
			} catch (err) {
				console.warn('Error loading last check from localStorage');
				last = '0';
			}
		}
		
		var checkRead = function (newsjson, status, jqXHR) {
			doActivityCheck(newsjson);
		};

		$.ajax({
			dataType: 'json',
			url: lastUrl,
			headers: { 'Cache-Control': 'max-age=0' },
			error: console.warn,
			success: checkRead
		});
	}

	/**
	 * Move popup to correct position
	 * @return {undefined}
	 */
	function resizeEvent() {
		if (!isopen) {
			return;
		}

		var offset = $(portletLink).offset(),
			height = $(portletLink).outerHeight(),
			width = $(portletLink).outerWidth(),
			pwidth = $popup.outerWidth(),
			wheight = $(window).height(),
			ptop = offset.top + height + 15;

		$popup.css({
			top: ptop + 'px',
			left: (offset.left + width/2 - pwidth/2) + 'px',
			'max-height': (wheight - ptop - 7) + 'px'
		});
		$arrow.css({
			left: (pwidth/2 - 10) + 'px'
		});
	}
	
	mw.loader.using(['ext.gadget.gsw-util', 'mediawiki.util'], function () {
		$(init);
	});
}(jQuery, mediaWiki, gswiki));
//</nowiki>