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.
// DiscussionsRC
// RC-like feed for Discussions
// @author Noreplyz
// @version 0.3
// Work in progress!
// <nowiki>
;(function($, mw) {
'use strict';
// Loads or checks for DiscussionsRC
if (window.discRC) {
return;
}
if (mw.config.get('wgPageName') === mw.config.get('wgFormattedNamespaces')[-1] + ':DiscussionsRC') {
$('h1.page-header__title').text('DiscussionsRC');
document.title = 'DiscussionsRC | ' + mw.config.get('wgSiteName') + ' | Fandom';
$('#WikiaMainContent, #content').empty().append($('<div>', {id: 'discrc'}));
}
if (!$('#discrc').length) {
return;
}
var discRC = {};
discRC.wikis = [];
discRC.entries = [];
var dRCAPI = {};
dRCAPI.msg;
// API request for wiki variables
dRCAPI.getWikiVariables = function(apipath, timeout) {
if (!apipath.match(/\.(fandom|wikia)\.(com|org)/g)) {
apipath = '//' + apipath + '.wikia.com/api.php';
}
return $.ajax({
url: apipath,
type: 'GET',
format: 'json',
dataType: 'jsonp',
crossDomain: 'true',
timeout: timeout ? timeout : 5000,
xhrFields: {
withCredentials: true
},
data: {
action: 'query',
meta: 'siteinfo',
siprop: ['general', 'variables'].join('|'),
format:'json'
}
});
};
// Returns API request for list of posts from Discussions
dRCAPI.getPosts = function(baseUrl, limit, page, reported, viewableOnly) {
return $.ajax({
url: baseUrl + '/wikia.php?controller=DiscussionPost&method=getPosts',
type: 'GET',
format: 'json',
crossDomain: 'true',
xhrFields: {
withCredentials: true
},
data: {
limit: limit,
page: page,
responseGroup: 'small',
reported: reported,
containerType: 'FORUM',
viewableOnly: viewableOnly
}
});
};
// Returns API request for list of posts in a thread from Discussions
dRCAPI.getThread = function(baseUrl, threadId, page, limit, viewableOnly) {
return $.ajax({
url: baseUrl + '/wikia.php?controller=DiscussionThread&method=getThread&threadId=' + threadId,
type: 'GET',
format: 'json',
crossDomain: 'true',
xhrFields: {
withCredentials: true
},
data: {
limit: limit,
page: page,
responseGroup: 'full',
sortDirection: 'descending',
sortKey: 'creation_date',
viewableOnly: viewableOnly
}
}).then(function(thread) {
// NB: As of when this hunk was introduced, `sortDirection=ascending` appears to be
// interpreted as `sortDirection=descending` when `sortKey=creation_date`. Here,
// we're matching the official client's approach of requesting with a descending
// sort order, then reversing the replies client-side.
if (Object.prototype.hasOwnProperty.call(thread._embedded, 'doc:posts')) {
thread._embedded['doc:posts'].reverse();
}
return thread;
});
};
// Mustache templates
discRC.templates = {
// Individual RC entry
rcEntry: '<li class="drc-entry">' +
'{{^singleWiki}}{{wiki.wiki}} . . {{/singleWiki}}' +
'{{#title}}<span class="drc-new" title="{{newPostTooltip}}"><svg class="wds-icon wds-icon-tiny"><use xlink:href="#wds-icons-{{newPostIcon}}-tiny"></use></svg></span>{{/title}}{{^title}}<span class="drc-reply" title="' + dRCAPI.msg('new-reply').escape() + '">R</span>{{/title}} . . ' +
'(<span class="drc-view" data-city="{{wiki.id}}" data-thread="{{threadId}}"{{^title}} data-reply="{{id}}"{{/title}}><a>view</a></span>) . . ' +
'<span class="drc-title">{{#title}}<a href="{{wiki.baseUrl}}/f/p/{{threadId}}">{{title}}{{/title}}{{^title}}<a href="{{wiki.baseUrl}}/f/p/{{threadId}}/r/{{id}}">{{threadtitle}}{{/title}}</a></span> ' +
'on <span class="drc-forumname"><a href="{{wiki.baseUrl}}/f?catId={{forumId}}">{{forumName}}</a></span>; ' +
'<span class="drc-timestamp">{{creationDate.dateString}}</span> . . ' +
'<span class="drc-creator"><a href="{{wiki.baseUrl}}/wiki/User:{{createdBy.name}}">{{createdBy.name}}</a></span> '+
'<span class="mw-usertoollinks">(<a href="{{wiki.baseUrl}}/wiki/User talk:{{createdBy.name}}">wall</a> | <a href="{{wiki.baseUrl}}/wiki/Special:Contributions/{{createdBy.name}}">contribs</a> | <a href="{{wiki.baseUrl}}/f/u/{{createdBy.id}}">posts</a> | <a href="{{wiki.baseUrl}}/wiki/Special:Block/{{createdBy.name}}">block</a>)</span> . . ' +
'<span class="drc-snippet">({{truncatedContent}})</span>' +
'</li>',
mainPost: '<div class="drc-post-header"><h2>{{post.title}}</h2><div class="drc-post-hlinks">' +
'<span class="drc-post-category">in <a href="{{wiki.baseUrl}}/f?catId={{post.forumId}}">{{post.forumName}}</a></span> | ' +
'<span class="drc-post-dlink"><a href="{{wiki.baseUrl}}/f/p/{{post.id}}">' + dRCAPI.msg('view-post-in-discussions-link').escape() + '</a></span></div></div>' +
'<div class="drc-post{{#isHighlighted}} drc-post-highlighted{{/isHighlighted}}">' +
'<div class="drc-post-avatar"><img src="{{post.createdBy.avatarUrl}}/zoom-crop/width/40/height/40" srcset="{{post.createdBy.avatarUrl}}/zoom-crop/width/40/height/40, {{post.createdBy.avatarUrl}}/zoom-crop/width/80/height/80 2x"/></div><div class="drc-post-author">{{post.createdBy.name}}' +
' • <a href="{{wiki.baseUrl}}/wiki/Special:Contributions/{{post.createdBy.name}}">contribs</a> • <a href="{{wiki.baseUrl}}/f/u/{{post.createdBy.id}}">posts</a></div>' +
'<div class="drc-post-date"><a href="{{wiki.baseUrl}}/f/p/{{post.id}}">{{date}}</a></div>' +
'<div class="drc-post-content">{{post.rawContent}}</div>' +
'</div>',
postReply: '<div class="drc-post-reply{{#isHighlighted}} drc-post-highlighted{{/isHighlighted}}">' +
'<div class="drc-post-avatar"><img src="{{post.createdBy.avatarUrl}}/zoom-crop/width/40/height/40" srcset="{{post.createdBy.avatarUrl}}/zoom-crop/width/40/height/40, {{post.createdBy.avatarUrl}}/zoom-crop/width/80/height/80 2x"/></div><div class="drc-post-author">{{post.createdBy.name}}' +
' • <a href="{{wiki.baseUrl}}/wiki/Special:Contributions/{{post.createdBy.name}}">contribs</a> • <a href="{{wiki.baseUrl}}/f/u/{{post.createdBy.id}}">posts</a></div>' +
'<div class="drc-post-date"><a href="{{wiki.baseUrl}}/f/p/{{post.threadId}}/r/{{post.id}}">{{date}}</a></div>' +
'<div class="drc-post-content">{{post.rawContent}}</div>' +
'</div>'
};
// Render a template using Mustache
discRC.render = function(template, args) {
return $(
Mustache.render(template, args)
);
};
/**
* Truncate a string over a given length and add ellipsis if necessary
* @param {string} str - string to be truncated
* @param {integer} limit - max length of the string before truncating
* @return {string} truncated string
*/
discRC.truncate = function(str, limit) {
return (str.length < limit) ? str : str.substring(0, limit).replace(/.$/gi, '...');
};
// Pad numbers if 0-9 with an extra 0
discRC.timePad = function(n) {
return (n < 10) ? '0' + n : n;
};
// Loads posts and places them into discRC.entries
discRC.generatePosts = function(wikis, page) {
var promises = [];
$.each(wikis, function(i, wiki) {
var promise = dRCAPI.getPosts(wiki.baseUrl, 100, page, false, true).done(function(data) {
if (data._embedded['doc:posts'].length > 0) {
$.each(data._embedded['doc:posts'], function(i, post) {
var postDate = new Date(post.creationDate.epochSecond * 1000);
post.creationDate.dateString = discRC.timePad(postDate.getHours()) + ':' + discRC.timePad(postDate.getMinutes());
post.truncatedContent = discRC.truncate(post.rawContent, 50);
if (post.title === null) {
post.threadtitle = post._embedded.thread[0].title;
} else {
switch (post.funnel) {
case 'LINK':
post.newPostIcon = 'link';
post.newPostTooltip = dRCAPI.msg('new-link-post').plain();
break;
case 'IMAGE':
post.newPostIcon = 'image';
post.newPostTooltip = dRCAPI.msg('new-image-post').plain();
break;
case 'POLL':
post.newPostIcon = 'poll';
post.newPostTooltip = dRCAPI.msg('new-poll-post').plain();
break;
default:
console.warn('Unexpected funnel type "%s" for post %s', post.funnel, post.threadId);
/* fall through */
case 'TEXT':
post.newPostIcon = 'text';
post.newPostTooltip = dRCAPI.msg('new-text-post').plain();
break;
}
}
post.wiki = wiki;
post.singleWiki = wikis.length === 1;
discRC.entries.push({
time: postDate,
entry: discRC.render(discRC.templates.rcEntry, post)
});
});
} else {
$('#discrc').prepend(dRCAPI.msg('unavailableNoPosts', encodeURIComponent(wiki.wiki)).plain());
}
}).fail(function() {
$('#discrc').prepend(dRCAPI.msg('unavailable', encodeURIComponent(wiki.wiki)).plain());
});
promises.push(promise);
});
$.when.apply(null, promises).then(function() {
discRC.showAllPosts();
});
};
// Shows all posts that have been stored in discRC.entries
discRC.showAllPosts = function() {
$('#discrc').append('<ul id="discrc-list"></ul>');
discRC.entries.sort(function(a, b) {
return a.time > b.time ? -1 : 1;
});
var currDate = new Date(1900, 1, 1);
$.each(discRC.entries, function(i, e) {
if (currDate.getDate() !== e.time.getDate() ||
currDate.getMonth() !== e.time.getMonth() ||
currDate.getFullYear() !== e.time.getFullYear()) {
$('#discrc-list').append('<h2>' + e.time.getDate() + ' ' + mw.config.get('wgMonthNames')[e.time.getMonth() + 1] + ' ' + e.time.getFullYear() + '</h2>');
}
$('#discrc-list').append(e.entry);
currDate = e.time;
});
};
// Click events
// @pre data-city is always a valid city ID
discRC.events = function() {
$('#discrc').on('click', '.drc-view', function() {
var cityId = $(this).attr('data-city');
var wiki = discRC.wikis.filter(function(wiki) {
return wiki.id.toString() === cityId.toString();
})[0];
console.log(wiki);
var threadId = $(this).attr('data-thread');
var replyId = $(this).attr('data-reply');
dRCAPI.getThread(wiki.baseUrl, threadId, 0, 100, false).then(function(thread) {
console.log('got threads');
// Format the date
var date = new Date(thread.creationDate.epochSecond * 1000);
date = discRC.timePad(date.getHours()) + ':' + discRC.timePad(date.getMinutes()) + ', ' + date.getDate() + ' ' + mw.config.get('wgMonthNames')[date.getMonth() + 1] + ' ' + date.getFullYear();
// Fix avatar
if (!thread.createdBy.avatarUrl) {
thread.createdBy.avatarUrl = 'https://vignette.wikia.nocookie.net/messaging/images/1/19/Avatar.jpg/revision/latest';
}
// Place into box
$('#drc-view-post-content').empty().append(
discRC.render(
discRC.templates.mainPost, {
post: thread,
wiki: wiki,
date: date,
isHighlighted: (replyId === undefined)
}
)
);
window.dev.modal.modals['drc-view-post']._modal.updateSize();
if (!Object.prototype.hasOwnProperty.call(thread._embedded, 'doc:posts')) {
return;
}
// For each post, put into box as well
$.each(thread._embedded['doc:posts'], function(i, post) {
// Format date
var date = new Date(post.creationDate.epochSecond * 1000);
date = discRC.timePad(date.getHours()) + ':' + discRC.timePad(date.getMinutes()) + ', ' + date.getDate() + ' ' + mw.config.get('wgMonthNames')[date.getMonth() + 1] + ' ' + date.getFullYear();
// Fix avatar
if (!post.createdBy.avatarUrl) {
post.createdBy.avatarUrl = 'https://vignette.wikia.nocookie.net/messaging/images/1/19/Avatar.jpg/revision/latest';
}
$('#drc-view-post-content').append(
discRC.render(discRC.templates.postReply, {
post: post,
wiki: wiki,
date: date,
isHighlighted: (post.id === replyId)
})
);
window.dev.modal.modals['drc-view-post']._modal.updateSize();
});
var highlightedPost = document.querySelector('#drc-view-post-content .drc-post-highlighted');
if (highlightedPost !== null) {
highlightedPost.scrollIntoView({block: 'center'});
}
}, function(err) {
$('#drc-view-post-content').empty().append(
'Post not found'
);
});
if (!discRC.modal) {
discRC.modal = new window.dev.modal.Modal({
title: 'DiscussionsRC',
content: '<div id="drc-view-post-content"></div>',
id: 'drc-view-post',
width: 600,
size: 'large',
buttons: [{text: 'Close', label: 'Close', flags: ['safe', 'close'] }],
callback: function(wrapper) {
wrapper.find('button.close.wikia-chiclet-button > img').replaceWith('<svg class="wds-icon"><use xlink:href="#wds-icons-close"></use></svg>');
}
});
discRC.modal.create();
discRC.modal.show();
console.log('showed');
} else {
discRC.modal.show();
}
});
};
// Loads wiki variables, checks if wikis have Discussions and
// starts generating posts for each wiki
discRC.init = function() {
$('#discrc').empty();
var loadWikiList = $('#discrc').data('wiki');
var wikiLoadPromises = [];
if (loadWikiList) {
$.each(loadWikiList.split(','), function(i, wiki) {
var promise = dRCAPI.getWikiVariables(wiki).done(function(data) {
discRC.wikis.push({
id: $.grep(data.query.variables, function(e) {return e.id === 'wgCityId';})[0]['*'],
baseUrl: data.query.general.server + data.query.general.scriptpath,
wiki: data.query.general.wikiid,
name: data.query.general.sitename
});
}).fail(function() {
$('#discrc').prepend(dRCAPI.msg('error', encodeURIComponent(wiki)).plain() + '<br />');
});
wikiLoadPromises.push(promise);
});
} else {
discRC.wikis.push({
id: mw.config.get('wgCityId'),
baseUrl: mw.config.get('wgServer') + mw.config.get('wgScriptPath'),
wiki: mw.config.get('wgDBname'),
name: mw.config.get('wgSiteName')
});
}
importArticles({
type: 'style',
articles: [
'u:dev:MediaWiki:DiscussionsRC.css',
]
});
(function() {
// To support theme designer and personal customizations, we'll define:
// - `background-color` for `.drc-post-header` from `.modalWrapper` in <https://github.com/Wikia/app/blob/release-866.001/skins/shared/modal.scss#L14>; and
// - `background-color` for `.drc-post-highlighted` from `.SpeechBubble .speech-bubble-message.current` in <https://github.com/Wikia/app/blob/release-866.001/skins/oasis/css/core/SpeechBubble.scss#L103>.
//
// Note that whilst some of the above properties are trivially based on
// values that we can access via `sassParams`, we'll elect to observe
// these properties directly for consistency.
//
// In most cases, the stylesheets upon which we depend _should_ be loaded
// by the time we reach here.
// Generate the following tree:
// ```haml
// .sandbox(style="display: none;")
// .modalWrapper
// .SpeechBubble
// .speech-bubble-message.current
// ```
var modalWrapper = document.createElement('div');
modalWrapper.className = 'modalWrapper';
var speechBubbleMessage = document.createElement('div');
speechBubbleMessage.className = 'speech-bubble-message current';
var speechBubble = document.createElement('div');
speechBubble.className = 'SpeechBubble';
speechBubble.appendChild(speechBubbleMessage);
var sandbox = document.createElement('div');
sandbox.style.display = 'none';
sandbox.appendChild(modalWrapper);
sandbox.appendChild(speechBubble);
// Insert into DOM, observe the properties in which we're interested, then clean up.
document.body.appendChild(sandbox);
var headerBackgroundColor = getComputedStyle(modalWrapper).backgroundColor;
var highlightBackgroundColor = getComputedStyle(speechBubbleMessage).backgroundColor;
document.body.removeChild(sandbox);
// Generate and insert our stylesheet into the DOM.
var generatedStyles = document.createElement('style');
generatedStyles.appendChild(document.createTextNode(
'.drc-post-header{background-color:' + headerBackgroundColor + ';}' +
'.drc-post-highlighted{background-color:' + highlightBackgroundColor + ';}'
));
document.head.appendChild(generatedStyles);
}());
$.get(mw.config.get('wgExtensionAssetsPath') + '/wikia/DesignSystem/node_modules/design-system/dist/svg/sprite.svg').done(function(svgDocument) {
$('body').prepend(svgDocument.rootElement);
});
$.when.apply(null, wikiLoadPromises).always(function() {
discRC.generatePosts(discRC.wikis, 0);
discRC.events();
});
};
importArticle({
type: 'script',
article: [
'u:dev:MediaWiki:Modal.js',
'u:dev:MediaWiki:I18n-js/code.js'
]
});
mw.hook('dev.i18n').add(function (i18n) {
i18n.loadMessages('DiscussionsRC').done(function (i18no) {
dRCAPI.msg = i18no.msg;
mw.hook('dev.modal').add(function() {
mw.loader.using('mediawiki.template.mustache', function() {
discRC.init();
});
});
});
});
window.discRC = discRC;
})(jQuery, mediaWiki);
// </nowiki>