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.
/**
* @name DiscussionsActivity
* @author Manuel de la Fuente (https://manuelfte.com)
* @author NoWayThisUsernameIsAlreadyOwnedBySomeone (https://dev.fandom.com/User:NoWayThisUsernameIsAlreadyOwnedBySomeone)
* - Based on DiscussionsFeed (https://dev.fandom.com/wiki/DiscussionsFeed)
* by Flightmare (https://elderscrolls.fandom.com/wiki/User:Flightmare)
* @version 0.10.3
* @license CC-BY-SA-3.0
* @description Creates a special page for latest Discussions messages
*/
/*
* Features curremtly missing from pre-UCP version:
* - embeds
* - deleted, reported, locked status
* - auto refresh
* - show/hide deleted posts if user has permissions (&viewableOnly=false)
*
* Potential ideas for the future:
* - Filter by containerType
* - Filter to show only reported posts
* - Pop up a image lightbox on click, but preserve "open in new tab" via middle-mouse/ctrl+click
* - List extermal links for the post (because those are probably the most interesting)
* - Clean up localization keys to be more consistent (duplicate them temporarily)
* - Incremental updates for auto-refresh instead of getting all posts again
* --> "&since=" + new Date(posts[0].creationData.epochSecond * 1000 + 1).toISOString();
* - "load more" button at the bottom
* - Fetch page titles from mediawiki instead of custom locale?
* - Option to filter between threads and comments
*/
(function(windows, mw) {
"use strict";
if (window.DiscussionsActivityLoaded) return;
window.DiscussionsActivityLoaded = true;
console.log('DiscussionsActivity v0.10.3');
const mwConfig = mw.config.get([
"wgArticlePath",
"wgCanonicalSpecialPageName",
"wgEnableDiscussions",
"wgNamespaceNumber",
"wgPageName",
"wgScriptPath",
"wgServerName",
"wgTitle",
"wgUserGroups",
]);
if (!mwConfig.wgEnableDiscussions) return;
const otherActivityPages = {
Recentchanges: true,
SocialActivity: true,
Newimages: true,
Reports: true, /* https://community.fandom.com/wiki/Help:Interactive_Maps#Special:Reports */
};
const isDiscussionsActivityPage = mwConfig.wgNamespaceNumber === -1 && mwConfig.wgTitle === "DiscussionsActivity";
const isOtherActivityPage = otherActivityPages[mwConfig.wgCanonicalSpecialPageName] === true;
if (!isDiscussionsActivityPage && !isOtherActivityPage) return;
var i18n; /* assigned later when translations have come in */
const perf = {};
perf.started = performance.now();
/*
* As of August 2021 the discussions API request is way slower than normal MediaWiki API requests.
* It will be slower than getting uncached translations, even though those are two sequential calls.
* So we need to make sure to give it a head start.
*/
var messagePromise;
if (isDiscussionsActivityPage) {
messagePromise = new Promise(function(resolve, reject) {
const parameters = "?controller=DiscussionPost&method=getPosts&limit=100";
callApi(parameters, resolve);
});
}
window.importArticles({
type: 'script',
article: 'u:dev:MediaWiki:I18n-js/code.js'
}, {
type: 'style',
article: 'u:dev:MediaWiki:DiscussionsActivity.css'
});
mw.hook('dev.i18n').add(function(lib) {
perf.i18nLoaded = performance.now();
lib.loadMessages('DiscussionsActivity', { cacheVersion: 1 }).done(function(lang) {
i18n = lang.msg;
perf.i18nMessagesLoaded = performance.now();
if (isOtherActivityPage) addDiscussionsActivityLink();
if (isDiscussionsActivityPage) renderDiscussions();
});
});
function callApi(parameters, callback) {
const url = mwConfig.wgScriptPath + "/wikia.php" + parameters;
const request = new XMLHttpRequest();
request.open("GET", url);
request.setRequestHeader('Accept', 'application/hal+json');
request.withCredentials = true;
request.timeout = 10000;
request.onloadend = function() {
callback(request);
};
request.send();
}
function addDiscussionsActivityLink() {
const activityLinkTabs = document.getElementsByClassName("activity-tabs");
if (activityLinkTabs.length === 0) return;
const lists = activityLinkTabs[0].getElementsByTagName("ul");
if (lists.length === 0) return;
const tab = createActivityTab("discussions_activity", "Special:DiscussionsActivity", false);
lists[0].appendChild(tab);
}
function renderDiscussions() {
const contentElement = document.getElementById("content");
contentElement.textContent = "";
perf.pageCleared = performance.now();
createPageHeader(contentElement);
perf.headerCreated = performance.now();
messagePromise.then(function(request) {
switch (request.status) {
case 200:
perf.apiResponded = performance.now();
const postsData = JSON.parse(request.responseText)._embedded["doc:posts"];
perf.jsonParsed = performance.now();
const pageNamePromise = queryCommentPageNames(postsData);
perf.pageNamesQueryStarted = performance.now();
displayPosts(postsData, contentElement, pageNamePromise);
perf.postsRendered = performance.now();
addCommentLinks(pageNamePromise);
break;
case 0:
console.log("DiscussionsActivity cannot connect to server");
break;
default:
console.log("DiscussionsActivity encountered an error of HTTP " + request.status);
break;
}
});
}
function createPageHeader(contentElement) {
changeTitle();
const headerFragment = document.createDocumentFragment();
headerFragment.appendChild(createPageInfo());
headerFragment.appendChild(createActivityTabList());
contentElement.appendChild(headerFragment);
contentElement.appendChild(createSvgSymobls());
}
function changeTitle() {
const titleText = i18n("document_title_new").plain();
var pageTitle = document.getElementById("firstHeading");
pageTitle.innerText = titleText;
const oldDocTitle = document.title;
const index = oldDocTitle.indexOf(" |");
if (index >= 0) document.title = titleText + oldDocTitle.substring(index);
}
function createPageInfo() {
const container = document.createElement("div");
container.className = "rda-activity-summary";
const message = document.createElement("div");
message.innerText = i18n("discussions-activity-summary").plain();
container.appendChild(message);
const help = document.createElement("div");
container.appendChild(help);
const helpLink = document.createElement("a");
helpLink.className = "wds-button wds-is-text";
helpLink.href = "https://dev.fandom.com/DiscussionsActivity";
help.appendChild(helpLink);
helpLink.appendChild(createSvgUsage("#wds-icons-question-small", "wds-icon wds-icon-small"));
const helpText = document.createElement("span");
helpText.innerText = "Help";
helpLink.appendChild(helpText);
return container;
}
function createActivityTabList() {
const activityTabs = document.createElement("div");
activityTabs.className = "wds-tabs__wrapper rda-activity-tabs with-bottom-border";
const tabList = document.createElement("ul");
tabList.classList.add("wds-tabs");
activityTabs.appendChild(tabList);
function canSeeMapReports() {
const moderateInteractiveMapGroups = {
"content-moderator": true,
"global-edit-reviewer": true,
"soap": true,
"staff": true,
"sysop": true,
"wiki-specialist": true,
};
const groups = mwConfig.wgUserGroups;
const groupCount = groups.length;
for (var i = 0; i < groupCount; ++i) {
if (moderateInteractiveMapGroups[groups[i]] === true) return true;
}
return false;
}
const makeTab = createActivityTab;
tabList.appendChild(makeTab("link-text-recent-changes", "Special:RecentChanges", false));
tabList.appendChild(makeTab("link-text-social-activity", "Special:SocialActivity", false));
tabList.appendChild(makeTab("link-text-new-files", "Special:NewFiles", false));
if (canSeeMapReports()) {
tabList.appendChild(makeTab("link-text-reports", "Special:Reports", false));
}
tabList.appendChild(makeTab("discussions_activity", mwConfig.wgPageName, true));
return activityTabs;
}
function createActivityTab(i18nKey, link, isCurrent) {
const name = i18n(i18nKey).plain();
const tab = document.createElement("li");
tab.className = "wds-tabs__tab";
if (isCurrent) tab.classList.add("wds-is-current");
const tabLabel = document.createElement("div");
tabLabel.className = "wds-tabs__tab-label";
tab.appendChild(tabLabel);
tabLabel.appendChild(createArticleTextLink(link, name));
return tab;
}
function queryCommentPageNames(posts) {
var forumIdsToRequest = {};
var postCount = posts.length;
for (var i = 0; i < postCount; ++i) {
const post = posts[i];
if (post._embedded.thread[0].containerType == "ARTICLE_COMMENT") {
forumIdsToRequest[post.forumId] = true;
}
}
const ids = Object.keys(forumIdsToRequest).join(",");
const parameters = "?controller=FeedsAndPosts&method=getArticleNamesAndUsernames&stablePageIds=" + ids;
const promise = new Promise(function(resolve, reject) {
callApi(parameters, resolve);
});
return promise;
}
function displayPosts(posts, parent) {
const fragment = document.createDocumentFragment();
const list = document.createElement("ul");
list.className = "rda-feed";
fragment.appendChild(list);
const postCount = posts.length;
for (var i = 0; i < postCount; ++i) {
const post = posts[i];
const thread = post._embedded.thread[0];
const attachments = post._embedded.attachments[0];
const listEntry = document.createElement("li");
listEntry.classList.add("rda-list-entry");
const entry = document.createElement("div");
entry.classList.add("rda-entry");
const mainData = document.createElement("div");
mainData.classList.add("rda-main-data");
const contentTable = document.createElement("table");
contentTable.classList.add("rda-datatable");
const textContent = createTextRepresentation(post, 250);
if (textContent) {
contentTable.appendChild(createTableRow(i18n("post-info-content").plain(), textContent));
}
if (attachments.contentImages.length > 0) {
const imageList = createImageList(attachments.contentImages);
contentTable.appendChild(createTableRow(i18n("post-info-images").plain(), imageList));
}
if (!post.isReply && thread.tags.length > 0) {
const tagList = createTagList(thread.tags);
contentTable.appendChild(createTableRow(i18n("post-info-tags").plain(), tagList));
}
mainData.appendChild(createTitleLine(post, thread));
mainData.appendChild(createAuthorLine(post));
mainData.appendChild(contentTable);
entry.appendChild(createInfoIcons(post, thread));
entry.appendChild(mainData);
listEntry.appendChild(entry);
list.appendChild(listEntry);
}
parent.appendChild(fragment);
}
function createTableRow(headerText, content) {
const tr = document.createElement("tr");
const th = document.createElement("th");
const td = document.createElement("td");
th.innerText = headerText + ":";
td.innterText = content;
tr.appendChild(th);
tr.appendChild(td);
if (typeof(content) === "string") {
td.innerText = content;
} else {
td.appendChild(content);
}
return tr;
}
function createInfoIcons(post, thread) {
const container = document.createElement("div");
container.classList.add("rda-info-icons");
var containerClass;
var containerTitle;
switch (thread.containerType) {
case "FORUM":
containerClass = "rda-container-forum";
containerTitle = i18n("container-name-forum").plain();
break;
case "WALL":
containerClass = "rda-container-wall";
containerTitle = i18n("container-name-wall").plain();
break;
case "ARTICLE_COMMENT":
containerClass = "rda-container-article";
containerTitle = i18n("container-name-article").plain();
break;
}
var postTypeClass;
var postTypeTitle;
if (post.isReply) {
postTypeClass = "rda-post-type-reply";
postTypeTitle = i18n("post-type-reply").plain();
} else if (post.poll) {
postTypeClass = "rda-post-type-poll";
postTypeTitle = i18n("post-type-poll").plain();
} else {
postTypeClass = "rda-post-type-text";
postTypeTitle = i18n("post-type-text").plain();
}
function createIcon(className, title) {
const icon = document.createElement("div");
icon.classList.add(className);
icon.title = title;
icon.appendChild(document.createElement("div")); /* wrapper for CSS */
return icon;
}
container.appendChild(createIcon(containerClass, containerTitle));
container.appendChild(createIcon(postTypeClass, postTypeTitle));
return container;
}
function createTitleLine(post, thread) {
const replyText = i18n("post-type-reply").plain();
var reply;
var threadTitle;
var container;
switch (thread.containerType) {
case "FORUM":
const forumThreadUrl = "/f/p/" + post.threadId;
threadTitle = createWikiTextLink(forumThreadUrl, thread.title);
container = createWikiTextLink("/f?catId=" + post.forumId, post.forumName);
reply = createWikiTextLink(forumThreadUrl + "/r/" + post.id, replyText);
break;
case "WALL":
const wallNameLength = post.forumName.length - " Message Wall".length;
const wallName = post.forumName.substring(0, wallNameLength);
const wallArticleName = "Message_Wall:" + wallName;
const wallLinkText = i18n("link-title-message-wall", wallName.replace("_", " ")).plain();
const wallThreadUrl = wallArticleName + "?threadId=" + post.threadId;
container = createArticleTextLink(wallArticleName, wallLinkText);
threadTitle = createArticleTextLink(wallThreadUrl, thread.title);
reply = createArticleTextLink(wallThreadUrl + "#" + post.id, replyText);
break;
case "ARTICLE_COMMENT":
const titleText = createTextRepresentation(thread.firstPost, 70, false);
threadTitle = document.createElement("span");
threadTitle.innerText = titleText;
threadTitle.dataset.rdaForumId = post.forumId;
threadTitle.dataset.rdaThreadId = post.threadId;
threadTitle.className = "rda-placeholder";
container = document.createElement("span");
container.innerText = "???";
container.dataset.rdaForumId = post.forumId;
container.className = "rda-placeholder";
reply = document.createElement("span");
reply.dataset.rdaForumId = post.forumId;
reply.dataset.rdaThreadId = post.threadId;
reply.dataset.rdaPostId = post.id;
reply.innerText = replyText;
reply.className = "rda-placeholder";
break;
}
threadTitle.classList.add("rda-title");
const titleLine = document.createElement("div");
if (post.isReply) {
titleLine.appendChild(reply);
titleLine.appendChild(document.createTextNode(" " + i18n("to").plain() + " "));
}
titleLine.appendChild(threadTitle);
titleLine.appendChild(document.createTextNode(" " + i18n("in").plain() + " "));
titleLine.appendChild(container);
return titleLine;
}
function createAuthorLine(post) {
const authorLine = document.createElement("div");
authorLine.classList.add("rda-authorinfo");
/* For some reason IPs always start with a slash, which we need to cut out */
const userName = post.createdBy.name || post.creatorIp.substring(1);
const isLoggedIn = post.creatorId !== "0";
const avatarContainer = document.createElement("div");
avatarContainer.classList.add("wds-avatar");
if (post.createdBy.avatarUrl) {
const image = document.createElement("img");
image.setAttribute("class", "wds-avatar__image rda-avatar");
image.src = createThumbnailUrl(post.createdBy.avatarUrl, 30, 30);
avatarContainer.appendChild(image);
} else {
avatarContainer.appendChild(createSvgUsage("#wds-icons-avatar", "wds-avatar__image rda-avatar"));
}
authorLine.appendChild(avatarContainer);
authorLine.appendChild(document.createTextNode(i18n("created_by").plain() + " "));
const profileActivityPageName = "Special:UserProfileActivity/" + userName;
const profileActivityLink = createArticleTextLink(profileActivityPageName, userName);
authorLine.appendChild(profileActivityLink);
const contribsPageName = "Special:Contributions/" + userName;
const contribsLink = createArticleTextLink(contribsPageName, i18n("contribs").plain());
authorLine.appendChild(document.createTextNode(" ("));
authorLine.appendChild(contribsLink);
if (isLoggedIn) {
const wallLink = createArticleTextLink("User_talk:" + userName, i18n("talk").plain());
authorLine.appendChild(document.createTextNode(" | "));
authorLine.appendChild(wallLink);
const profileLink = createArticleTextLink("User:" + userName, i18n("link-title-profile").plain());
authorLine.appendChild(document.createTextNode(" | "));
authorLine.appendChild(profileLink);
}
authorLine.appendChild(document.createTextNode(")"));
const timeAgo = epochToTimeAgo(post.creationDate.epochSecond);
authorLine.appendChild(document.createTextNode(" " + timeAgo));
return authorLine;
}
function epochToTimeAgo(epoch) {
var elapsed = (Math.floor(new Date().getTime()) - new Date(epoch * 1000)) / 1000;
var factors = [
['seconds', 60],
['minutes', 60],
['hours', 24],
['days', 30],
['months', 12],
['years']
];
for (var i = 0; i < factors.length && elapsed >= factors[i][1]; i++) {
elapsed /= factors[i][1];
}
var unit = factors[i][0];
elapsed = Math.floor(elapsed);
return i18n(unit + '_ago_' + (elapsed > 1 ? 'plural' : 'singular'), elapsed).plain();
}
function createImageList(images) {
const ul = document.createElement("ul");
ul.classList.add("rda-images");
const imageCount = images.length;
for (var i = 0; i < imageCount; ++i) {
const imageData = images[i];
const li = document.createElement("li");
const a = document.createElement("a");
a.href = imageData.url;
const img = document.createElement("img");
img.src = createThumbnailUrl(imageData.url, 200, 120);
li.appendChild(a);
a.appendChild(img);
ul.appendChild(li);
}
return ul;
}
function createTagList(tags) {
const ul = document.createElement("ul");
ul.classList.add("rda-tags");
const tagCount = tags.length;
for (var i = 0; i < tagCount; ++i) {
const tagData = tags[i];
const li = document.createElement("li");
const a = createWikiTextLink("/f/t/" + encodeURIComponent(tagData.articleTitle), tagData.articleTitle);
li.appendChild(a);
ul.appendChild(li);
}
return ul;
}
function addCommentLinks(pageNamesPromise) {
pageNamesPromise.then(function(request) {
switch (request.status) {
case 200:
perf.pageNamesQueryCompleted = performance.now();
const articleNames = JSON.parse(request.responseText).articleNames;
const original = document.getElementsByClassName("rda-feed")[0];
/*
* Clone the whole post list, update the off-screen clone
* and then replace it back as a single operation.
* This seems to perform up to an order of magnitude better
* then doing incremental DOM updates (tested in Firefox and Chrome).
* If there are loss of posts, but no or nearly no article comments,
* doing the direct updates is faster. So ideally we would only do this
* if there is a certain amount of elements needing updating.
* Maybe make the criterion should be something like this:
* articleComments / totalEntries > threashold
* This will require some more testing first to get userful numbers.
*/
const clone = original.cloneNode(true);
const placeholders = clone.getElementsByClassName("rda-placeholder");
const placeholderCount = placeholders.length;
for (var i = 0; i < placeholderCount; ++i) {
const placeholder = placeholders[i];
const articleInfo = articleNames[placeholder.dataset.rdaForumId];
var url = articleInfo.relativeUrl;
if (placeholder.dataset.rdaThreadId) {
url = url + "?commentId=" + placeholder.dataset.rdaThreadId;
if (placeholder.dataset.rdaPostId) {
url = url + "&replyId=" + placeholder.dataset.rdaPostId;
}
}
const originalText = placeholder.innerText;
const newText = originalText === "???" ? articleInfo.title : originalText;
const newElement = createTextLink(url, newText);
newElement.classList = placeholder.classList;
newElement.classList.remove("rda-placeolder");
placeholder.replaceWith(newElement);
}
original.replaceWith(clone);
perf.commentLinksAdded = performance.now();
break;
case 0:
console.log("DiscussionsActivity cannot connect to server");
break;
default:
console.log("DiscussionsActivity encountered an error of HTTP " + request.status);
break;
}
if (new URL(windows.location.href).searchParams.has("rdaDebug")) {
const logPerf = function (text, start, end) {
console.debug(text + ": " + (end - start) + "ms");
};
logPerf("load i18n ", perf.started, perf.i18nLoaded);
logPerf("i18n messages ", perf.i18nLoaded, perf.i18nMessagesLoaded);
logPerf("clear page ", perf.i18nMessagesLoaded, perf.pageCleared);
logPerf("render header ", perf.pageCleared, perf.headerCreated);
logPerf("wait for API ", perf.headerCreated, perf.apiResponded);
logPerf("total API call ", perf.started, perf.apiResponded);
logPerf("parse JSON ", perf.apiResponded, perf.jsonParsed);
logPerf("query page names ", perf.jsonParsed, perf.pageNamesQueryStarted);
logPerf("render posts ", perf.pageNamesQueryStarted, perf.postsRendered);
logPerf("wait for page names", perf.postsRendered, perf.pageNamesQueryCompleted);
logPerf("update page names ", perf.pageNamesQueryCompleted, perf.commentLinksAdded);
logPerf("total ", perf.started, perf.commentLinksAdded);
}
});
}
function createTextRepresentation(post, characterLimit, includeLineBreaks) {
if (includeLineBreaks === undefined) includeLineBreaks = true;
var result;
if (post.jsonModel) {
/*
* As of August 2021 post.rawContent (seemingly incorrectly) strips HTML tags from code blocks.
* Therefor we avoid rawContent entirely and always parse post.jsonModel instead.
*/
result = createTextFromJsonModel(post.jsonModel, characterLimit, includeLineBreaks);
} else if (post.poll) {
result = post.poll.question;
const answers = post.poll.answers;
const answerCount = answers.length;
for (var i = 0; i < answerCount && result.length < characterLimit; ++i) {
result = result + "\n" + answers[i].text;
}
} else if (post.renderedContent) {
/* This is probably migrated from old thread-based technology. */
result = stripHtml(post.renderedContent);
if (!includeLineBreaks) result = result.replace("\n", "");
}
if (!result) return undefined;
const cappedResult = result.length > characterLimit ?
result.substring(0, characterLimit) + "…" :
result;
return cappedResult;
}
function createTextFromJsonModel(jsonModel, characterLimit, includeLineBreaks) {
var result = "";
const json = JSON.parse(jsonModel);
result = parseContentElement(json.content, result, characterLimit, includeLineBreaks);
return result;
}
function parseContentElement(contentElement, result, characterLimit, includeLineBreaks) {
const docElementCount = contentElement.length;
const addLineBreak = function(currentIndex, totalCount) {
if (includeLineBreaks && currentIndex < totalCount - 1) result += "\n";
};
for (var i = 0; i < docElementCount && result.length < characterLimit; ++i) {
const docElement = contentElement[i];
const elementContent = docElement.content;
if (!elementContent) continue;
const subElementCount = elementContent.length;
for (var j = 0; j < subElementCount && result.length < characterLimit; ++j) {
const subElement = elementContent[j];
switch (docElement.type) {
case "paragraph":
case "code_block":
if (subElement.type === "text" && subElement.text) {
result += subElement.text;
}
break;
case "bulletList":
case "orderedList":
if (subElement.type === "listItem" && subElement.content) {
result = parseContentElement(subElement.content, result, characterLimit, includeLineBreaks);
}
addLineBreak(j, subElementCount);
break;
}
}
addLineBreak(i, docElementCount);
}
return result;
}
function stripHtml(html) {
var doc = new DOMParser().parseFromString(html, 'text/html');
return doc.body.innerText || "";
}
function createThumbnailUrl(imageUrl, width, height) {
if (imageUrl.indexOf("/messaging/") !== -1) {
/* This is a pre-UCP default avatar, it needs to be treated like a wiki image */
imageUrl += "/revision/latest";
}
/* see https://github.com/Wikia/vignette#thumbnail-modes */
return imageUrl + "/thumbnail-down/width/" + width + "/height/" + height;
}
function createArticleTextLink(pagename, text) {
return createTextLink(mwConfig.wgArticlePath.replace("$1", pagename), text);
}
function createWikiTextLink(relativeUrl, text) {
return createTextLink(mwConfig.wgScriptPath + relativeUrl, text);
}
function createTextLink(url, text) {
var link = document.createElement("a");
link.href = url;
link.innerText = text;
return link;
}
function createSvgElement(name) {
const svgNamespace = "http://www.w3.org/2000/svg";
const element = document.createElementNS(svgNamespace, name);
return element;
}
function createSvgUsage(href, className) {
const svg = createSvgElement("svg");
svg.setAttribute("class", className);
const use = createSvgElement("use");
use.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", href);
svg.appendChild(use);
return svg;
}
function createSvgSymobls() {
function createSymbol(symbolId, href, size) {
const symbol = createSvgElement("symbol");
symbol.setAttribute("viewBox", "0 0 " + size + " " + size);
symbol.id = symbolId;
const use = createSvgElement("use");
const rootedHref = "/resources-ucp/mw143/dist/svg/" + href;
use.setAttributeNS("http://www.w3.org/1999/xlink", "xlink:href", rootedHref);
symbol.appendChild(use);
return symbol;
}
var svg = createSvgElement("svg");
svg.style = "height: 0px; width: 0px; position: absolute; overflow: hidden;";
svg.setAttribute("aria-hidden", true);
svg.appendChild(createSymbol("wds-icons-avatar", "wds-icons-avatar.svg#user-avatar-a", 24));
svg.appendChild(createSymbol("wds-icons-question-small", "wds-icons-question-small.svg#question-small", 18));
return svg;
}
})(this, mediaWiki);