/**
* Namespace for comm-related functionalities.
*
* @memberof IITC
* @namespace comm
*/
/**
* @type {chat.ChannelDescription[]}
* @memberof IITC.comm
*/
var _channels = [
{
id: 'all',
name: 'All',
localBounds: true,
inputPrompt: 'broadcast:',
inputClass: 'public',
request: requestChannel,
render: renderChannel,
sendMessage: sendChatMessage,
},
{
id: 'faction',
name: 'Faction',
localBounds: true,
inputPrompt: 'tell faction:',
inputClass: 'faction',
request: requestChannel,
render: renderChannel,
sendMessage: sendChatMessage,
},
{
id: 'alerts',
name: 'Alerts',
inputPrompt: 'tell Jarvis:',
inputClass: 'alerts',
request: requestChannel,
render: renderChannel,
sendMessage: function () {
alert("Jarvis: A strange game. The only winning move is not to play. How about a nice game of chess?\n(You can't comm to the 'alerts' channel!)");
},
},
];
/**
* Holds data related to each intel channel.
*
* @type {Object}
* @memberof IITC.comm
*/
var _channelsData = {};
/**
* Initialize the channel data.
*
* @function IITC.comm._initChannelData
* @private
* @param {chat.ChannelDescription} id - The channel id.
*/
function _initChannelData(id) {
// preserve channel object
if (!_channelsData[id]) _channelsData[id] = {};
_channelsData[id].data = {};
_channelsData[id].guids = [];
_channelsData[id].oldestTimestamp = -1;
delete _channelsData[id].oldestGUID;
_channelsData[id].newestTimestamp = -1;
delete _channelsData[id].newestGUID;
}
/**
* Template of portal link in comm.
* @type {String}
* @memberof IITC.comm
*/
let portalTemplate =
'<a onclick="window.selectPortalByLatLng({{ lat }}, {{ lng }});return false" title="{{ title }}" href="{{ url }}" class="help">{{ portal_name }}</a>';
/**
* Template for time cell.
* @type {String}
* @memberof IITC.comm
*/
let timeCellTemplate = '<td><time class="{{ class_names }}" title="{{ time_title }}" data-timestamp="{{ unixtime }}">{{ time }}</time></td>';
/**
* Template for player's nickname cell.
* @type {String}
* @memberof IITC.comm
*/
let nickCellTemplate = '<td><span class="invisep"><</span><mark class="{{ class_names }}">{{ nick }}</mark><span class="invisep">></span></td>';
/**
* Template for chat message text cell.
* @type {String}
* @memberof IITC.comm
*/
let msgCellTemplate = '<td class="{{ class_names }}">{{ msg }}</td>';
/**
* Template for message row, includes cells for time, player nickname and message text.
* @type {String}
* @memberof IITC.comm
*/
let msgRowTemplate = '<tr data-guid="{{ guid }}" class="{{ class_names }}">{{ time_cell }}{{ nick_cell }}{{ msg_cell }}</tr>';
/**
* Template for message divider.
* @type {String}
* @memberof IITC.comm
*/
let dividerTemplate = '<tr class="divider"><td><hr></td><td>{{ text }}</td><td><hr></td></tr>';
/**
* Returns the coordinates for the message to be sent, default is the center of the map.
*
* @function IITC.comm.getLatLngForSendingMessage
* @returns {L.LatLng}
*/
function getLatLngForSendingMessage() {
return map.getCenter();
}
/**
* Updates the oldest and newest message timestamps and GUIDs in the chat storage.
*
* @function IITC.comm._updateOldNewHash
* @private
* @param {Object} newData - The new chat data received.
* @param {Object} storageHash - The chat storage object.
* @param {boolean} isOlderMsgs - Whether the new data contains older messages.
* @param {boolean} isAscendingOrder - Whether the new data is in ascending order.
*/
function _updateOldNewHash(newData, storageHash, isOlderMsgs, isAscendingOrder) {
// track oldest + newest timestamps/GUID
if (newData.result.length > 0) {
var first = {
guid: newData.result[0][0],
time: newData.result[0][1],
};
var last = {
guid: newData.result[newData.result.length - 1][0],
time: newData.result[newData.result.length - 1][1],
};
if (isAscendingOrder) {
var temp = first;
first = last;
last = temp;
}
if (storageHash.oldestTimestamp === -1 || storageHash.oldestTimestamp >= last.time) {
if (isOlderMsgs || storageHash.oldestTimestamp !== last.time) {
storageHash.oldestTimestamp = last.time;
storageHash.oldestGUID = last.guid;
}
}
if (storageHash.newestTimestamp === -1 || storageHash.newestTimestamp <= first.time) {
if (!isOlderMsgs || storageHash.newestTimestamp !== first.time) {
storageHash.newestTimestamp = first.time;
storageHash.newestGUID = first.guid;
}
}
}
}
/**
* Parses comm message data into a more convenient format.
*
* @function IITC.comm.parseMsgData
* @param {Object} data - The raw comm message data.
* @returns {Object} The parsed comm message data.
*/
function parseMsgData(data) {
var categories = data[2].plext.categories;
var isPublic = (categories & 1) === 1;
var isSecure = (categories & 2) === 2;
var msgAlert = (categories & 4) === 4;
var msgToPlayer = msgAlert && (isPublic || isSecure);
var time = data[1];
var team = window.teamStringToId(data[2].plext.team);
var auto = data[2].plext.plextType !== 'PLAYER_GENERATED';
var systemNarrowcast = data[2].plext.plextType === 'SYSTEM_NARROWCAST';
var markup = data[2].plext.markup;
var player = {
name: '',
team: team,
};
markup.forEach(function (ent) {
switch (ent[0]) {
case 'SENDER': // user generated messages
player.name = ent[1].plain.replace(/: $/, ''); // cut “: ” at end
break;
case 'PLAYER': // automatically generated messages
player.name = ent[1].plain;
player.team = window.teamStringToId(ent[1].team);
break;
default:
break;
}
});
return {
guid: data[0],
time: time,
public: isPublic,
secure: isSecure,
alert: msgAlert,
msgToPlayer: msgToPlayer,
type: data[2].plext.plextType,
narrowcast: systemNarrowcast,
auto: auto,
team: team,
player: player,
markup: markup,
};
}
/**
* Writes new chat data to the chat storage and manages the order of messages.
*
* @function IITC.comm._writeDataToHash
* @private
* @param {Object} newData - The new chat data received.
* @param {Object} storageHash - The chat storage object.
* @param {boolean} isOlderMsgs - Whether the new data contains older messages.
* @param {boolean} isAscendingOrder - Whether the new data is in ascending order.
*/
function _writeDataToHash(newData, storageHash, isOlderMsgs, isAscendingOrder) {
_updateOldNewHash(newData, storageHash, isOlderMsgs, isAscendingOrder);
newData.result.forEach(function (json) {
// avoid duplicates
if (json[0] in storageHash.data) {
return true;
}
var parsedData = IITC.comm.parseMsgData(json);
// format: timestamp, autogenerated, HTML message, nick, additional data (parsed, plugin specific data...)
storageHash.data[parsedData.guid] = [parsedData.time, parsedData.auto, IITC.comm.renderMsgRow(parsedData), parsedData.player.name, parsedData];
if (isAscendingOrder) {
storageHash.guids.push(parsedData.guid);
} else {
storageHash.guids.unshift(parsedData.guid);
}
});
}
/**
* Posts a chat message to intel comm context.
*
* @function IITC.comm.sendChatMessage
* @param {string} tab intel tab name (either all or faction)
* @param {string} msg message to be sent
*/
function sendChatMessage(tab, msg) {
if (tab !== 'all' && tab !== 'faction') return;
const latlng = IITC.comm.getLatLngForSendingMessage();
var data = {
message: msg,
latE6: Math.round(latlng.lat * 1e6),
lngE6: Math.round(latlng.lng * 1e6),
tab: tab,
};
var errMsg = 'Your message could not be delivered. You can copy&' + 'paste it here and try again if you want:\n\n' + msg;
window.postAjax(
'sendPlext',
data,
function (response) {
if (response.error) alert(errMsg);
window.startRefreshTimeout(0.1 * 1000); // only comm uses the refresh timer stuff, so a perfect way of forcing an early refresh after a send message
},
function () {
alert(errMsg);
}
);
}
var _oldBBox = null;
/**
* Generates post data for chat requests.
*
* @function IITC.comm._genPostData
* @private
* @param {string} channel - The chat channel.
* @param {boolean} getOlderMsgs - Flag to determine if older messages are being requested.
* @param args=undefined - Used for backward compatibility when calling a function with three arguments.
* @returns {Object} The generated post data.
*/
function _genPostData(channel, getOlderMsgs, ...args) {
if (typeof channel !== 'string') {
throw new Error('API changed: isFaction flag now a channel string - all, faction, alerts');
}
if (args.length === 1) {
getOlderMsgs = args[0];
}
var b = window.clampLatLngBounds(map.getBounds());
// set a current bounding box if none set so far
if (!_oldBBox) _oldBBox = b;
// to avoid unnecessary comm refreshes, a small difference compared to the previous bounding box
// is not considered different
var CHAT_BOUNDINGBOX_SAME_FACTOR = 0.1;
// if the old and new box contain each other, after expanding by the factor, don't reset comm
if (!(b.pad(CHAT_BOUNDINGBOX_SAME_FACTOR).contains(_oldBBox) && _oldBBox.pad(CHAT_BOUNDINGBOX_SAME_FACTOR).contains(b))) {
log.log('Bounding Box changed, comm will be cleared (old: ' + _oldBBox.toBBoxString() + '; new: ' + b.toBBoxString() + ')');
// need to reset these flags now because clearing will only occur
// after the request is finished – i.e. there would be one almost
// useless request.
_channels.forEach(function (entry) {
if (entry.localBounds) {
_initChannelData(entry.id);
$('#chat' + entry.id).data('needsClearing', true);
}
});
_oldBBox = b;
}
if (!_channelsData[channel]) _initChannelData(channel);
var storageHash = _channelsData[channel];
var ne = b.getNorthEast();
var sw = b.getSouthWest();
var data = {
minLatE6: Math.round(sw.lat * 1e6),
minLngE6: Math.round(sw.lng * 1e6),
maxLatE6: Math.round(ne.lat * 1e6),
maxLngE6: Math.round(ne.lng * 1e6),
minTimestampMs: -1,
maxTimestampMs: -1,
tab: channel,
};
if (getOlderMsgs) {
// ask for older comm when scrolling up
data = $.extend(data, {
maxTimestampMs: storageHash.oldestTimestamp,
plextContinuationGuid: storageHash.oldestGUID,
});
} else {
// ask for newer comm
var min = storageHash.newestTimestamp;
// the initial request will have both timestamp values set to -1,
// thus we receive the newest 50. After that, we will only receive
// messages with a timestamp greater or equal to min above.
// After resuming from idle, there might be more new messages than
// desiredNumItems. So on the first request, we are not really up to
// date. We will eventually catch up, as long as there are less new
// messages than 50 per each refresh cycle.
// A proper solution would be to query until no more new results are
// returned.
// Currently this edge case is not handled. Let’s see if this is a
// problem in crowded areas.
$.extend(data, {
minTimestampMs: min,
plextContinuationGuid: storageHash.newestGUID,
});
// when requesting with an actual minimum timestamp, request oldest rather than newest first.
// this matches the stock intel site, and ensures no gaps when continuing after an extended idle period
if (min > -1) $.extend(data, { ascendingTimestampOrder: true });
}
return data;
}
var _requestRunning = {};
/**
* Requests chat messages.
*
* @function IITC.comm.requestChannel
* @param {string} channel - Comm Intel channel (all/faction/alerts)
* @param {boolean} getOlderMsgs - Flag to determine if older messages are being requested.
* @param {boolean} [isRetry=false] - Flag to indicate if this is a retry attempt.
*/
function requestChannel(channel, getOlderMsgs, isRetry) {
if (_requestRunning[channel] && !isRetry) return;
if (window.isIdle()) return window.renderUpdateStatus();
_requestRunning[channel] = true;
$("#chatcontrols a[data-channel='" + channel + "']").addClass('loading');
var d = _genPostData(channel, getOlderMsgs);
window.postAjax(
'getPlexts',
d,
function (data) {
_handleChannel(channel, data, getOlderMsgs, d.ascendingTimestampOrder);
},
isRetry
? function () {
_requestRunning[channel] = false;
}
: function (_, textStatus) {
if (textStatus === 'abort') _requestRunning[channel] = false;
else requestChannel(channel, getOlderMsgs, true);
}
);
}
/**
* Handles faction chat response.
*
* @function IITC.comm._handleChannel
* @private
* @param {string} channel - Comm Intel channel (all/faction/alerts)
* @param {Object} data - Response data from server.
* @param {boolean} olderMsgs - Indicates if older messages were requested.
* @param {boolean} ascendingTimestampOrder - Indicates if messages are in ascending timestamp order.
*/
function _handleChannel(channel, data, olderMsgs, ascendingTimestampOrder) {
_requestRunning[channel] = false;
$("#chatcontrols a[data-channel='" + channel + "']").removeClass('loading');
if (!data || !data.result) {
window.failedRequestCount++;
return log.warn(channel + ' comm error. Waiting for next auto-refresh.');
}
if (!data.result.length && !$('#chat' + channel).data('needsClearing')) {
// no new data and current data in comm._faction.data is already rendered
return;
}
$('#chat' + channel).data('needsClearing', null);
if (!_channelsData[channel]) _initChannelData(channel);
var old = _channelsData[channel].oldestGUID;
_writeDataToHash(data, _channelsData[channel], olderMsgs, ascendingTimestampOrder);
var oldMsgsWereAdded = old !== _channelsData[channel].oldestGUID;
var hook = channel + 'ChatDataAvailable';
// backward compability
if (channel === 'all') hook = 'publicChatDataAvailable';
window.runHooks(hook, { raw: data, result: data.result, processed: _channelsData[channel].data });
// generic hook
window.runHooks('commDataAvailable', { channel: channel, raw: data, result: data.result, processed: _channelsData[channel].data });
renderChannel(channel, oldMsgsWereAdded);
}
/**
* Renders intel chat.
*
* @function IITC.comm.renderChannel
* @param {string} channel - Comm Intel channel (all/faction/alerts)
* @param {boolean} oldMsgsWereAdded - Indicates if old messages were added in the current rendering.
*/
function renderChannel(channel, oldMsgsWereAdded) {
if (!_channelsData[channel]) _initChannelData(channel);
IITC.comm.renderData(_channelsData[channel].data, 'chat' + channel, oldMsgsWereAdded, _channelsData[channel].guids);
}
//
// Rendering primitive for markup, chat cells (td) and chat row (tr)
//
/**
* Renders text for the chat, converting plain text to HTML and adding links.
*
* @function IITC.comm.renderText
* @param {Object} text - An object containing the plain text to render.
* @returns {string} The rendered HTML string.
*/
function renderText(text) {
let content;
if (text.team) {
let teamId = window.teamStringToId(text.team);
if (teamId === window.TEAM_NONE) teamId = window.TEAM_MAC;
const spanClass = window.TEAM_TO_CSS[teamId];
content = $('<div>').append($('<span>', { class: spanClass, text: text.plain }));
} else {
content = $('<div>').text(text.plain);
}
return content.html().autoLink();
}
/**
* List of transformations for portal names used in chat.
* Each transformation function takes the portal markup object and returns a transformed name.
* If a transformation does not apply, the original name is returned.
*
* @const IITC.comm.portalNameTransformations
* @example
* // Adding a transformation that appends the portal location to its name
* portalNameTransformations.push((markup) => {
* const latlng = `${markup.latE6 / 1E6},${markup.lngE6 / 1E6}`; // Convert E6 format to decimal
* return `[${latlng}] ${markup.name}`;
* });
*/
const portalNameTransformations = [
// Transformation for 'US Post Office'
(markup) => {
if (markup.name === 'US Post Office') {
const address = markup.address.split(',');
return 'USPS: ' + address[0];
}
return markup.name;
},
];
/**
* Overrides portal names used repeatedly in chat, such as 'US Post Office', with more specific names.
* Applies a series of transformations to the portal name based on the portal markup.
*
* @function IITC.comm.getChatPortalName
* @param {Object} markup - An object containing portal markup, including the name and address.
* @returns {string} The processed portal name.
*/
function getChatPortalName(markup) {
// Use reduce to apply each transformation to the data
const transformedData = portalNameTransformations.reduce((initialMarkup, transform) => {
const updatedName = transform(initialMarkup);
return { ...initialMarkup, name: updatedName };
}, markup);
return transformedData.name;
}
/**
* Renders a portal link for use in the chat.
*
* @function IITC.comm.renderPortal
* @param {Object} portal - The portal data.
* @returns {string} HTML string of the portal link.
*/
function renderPortal(portal) {
const lat = portal.latE6 / 1e6;
const lng = portal.lngE6 / 1e6;
const permalink = window.makePermalink([lat, lng]);
const portalName = IITC.comm.getChatPortalName(portal);
return IITC.comm.portalTemplate
.replace('{{ lat }}', lat.toString())
.replace('{{ lng }}', lng.toString())
.replace('{{ title }}', portal.address)
.replace('{{ url }}', permalink)
.replace('{{ portal_name }}', portalName);
}
/**
* Renders a faction entity for use in the chat.
*
* @function IITC.comm.renderFactionEnt
* @param {Object} faction - The faction data.
* @returns {string} HTML string representing the faction.
*/
function renderFactionEnt(faction) {
var teamId = window.teamStringToId(faction.team);
var name = window.TEAM_NAMES[teamId];
var spanClass = window.TEAM_TO_CSS[teamId];
return $('<div>').html($('<span>').attr('class', spanClass).text(name)).html();
}
/**
* Renders a player's nickname in chat.
*
* @function IITC.comm.renderPlayer
* @param {Object} player - The player object containing nickname and team.
* @param {boolean} at - Whether to prepend '@' to the nickname.
* @param {boolean} sender - Whether the player is the sender of a message.
* @returns {string} The HTML string representing the player's nickname in chat.
*/
function renderPlayer(player, at, sender) {
var name = player.plain;
if (sender) {
name = player.plain.replace(/: $/, '');
} else if (at) {
name = player.plain.replace(/^@/, '');
}
var thisToPlayer = name === window.PLAYER.nickname;
var spanClass = 'nickname ' + (thisToPlayer ? 'pl_nudge_me' : player.team + ' pl_nudge_player');
return $('<div>')
.html(
$('<span>')
.attr('class', spanClass)
.text((at ? '@' : '') + name)
)
.html();
}
/**
* Renders a chat message entity based on its type.
*
* @function IITC.comm.renderMarkupEntity
* @param {Array} ent - The entity array, where the first element is the type and the second element is the data.
* @returns {string} The HTML string representing the chat message entity.
*/
function renderMarkupEntity(ent) {
switch (ent[0]) {
case 'TEXT':
return IITC.comm.renderText(ent[1]);
case 'PORTAL':
return IITC.comm.renderPortal(ent[1]);
case 'FACTION':
return IITC.comm.renderFactionEnt(ent[1]);
case 'SENDER':
return IITC.comm.renderPlayer(ent[1], false, true);
case 'PLAYER':
return IITC.comm.renderPlayer(ent[1]);
case 'AT_PLAYER':
return IITC.comm.renderPlayer(ent[1], true);
default:
}
return $('<div>')
.text(ent[0] + ':<' + ent[1].plain + '>')
.html();
}
/**
* Renders the markup of a chat message, converting special entities like player names, portals, etc., into HTML.
*
* @function IITC.comm.renderMarkup
* @param {Array} markup - The markup array of a chat message.
* @returns {string} The HTML string representing the complete rendered chat message.
*/
function renderMarkup(markup) {
var msg = '';
markup.forEach(function (ent, ind) {
switch (ent[0]) {
case 'SENDER':
case 'SECURE':
// skip as already handled
break;
case 'PLAYER': // automatically generated messages
if (ind > 0) msg += IITC.comm.renderMarkupEntity(ent); // don’t repeat nick directly
break;
default:
// add other enitities whatever the type
msg += IITC.comm.renderMarkupEntity(ent);
break;
}
});
return msg;
}
/**
* List of transformations to be applied to the message data.
* Each transformation function takes the full message data object and returns the transformed markup.
* The default transformations aim to convert the message markup into an older, more straightforward format,
* facilitating easier understanding and backward compatibility with plugins expecting the older message format.
*
* @const IITC.comm.messageTransformFunctions
* @example
* // Adding a new transformation function to the array
* // This new function adds a "new" prefix to the player's plain text if the player is from the RESISTANCE team
* messageTransformFunctions.push((data) => {
* const markup = data.markup;
* if (markup.length > 2 && markup[0][0] === 'PLAYER' && markup[0][1].team === 'RESISTANCE') {
* markup[1][1].plain = 'new ' + markup[1][1].plain;
* }
* return markup;
* });
*/
const messageTransformFunctions = [
// Collapse <faction> + "Link"/"Field".
(data) => {
const markup = data.markup;
if (
markup.length > 4 &&
markup[3][0] === 'FACTION' &&
markup[4][0] === 'TEXT' &&
(markup[4][1].plain === ' Link ' || markup[4][1].plain === ' Control Field @')
) {
markup[4][1].team = markup[3][1].team;
markup.splice(3, 1);
}
return markup;
},
// Skip "Agent <player>" at the beginning
(data) => {
const markup = data.markup;
if (markup.length > 1 && markup[0][0] === 'TEXT' && markup[0][1].plain === 'Agent ' && markup[1][0] === 'PLAYER') {
markup.splice(0, 2);
}
return markup;
},
// Skip "<faction> agent <player>" at the beginning
(data) => {
const markup = data.markup;
if (markup.length > 2 && markup[0][0] === 'FACTION' && markup[1][0] === 'TEXT' && markup[1][1].plain === ' agent ' && markup[2][0] === 'PLAYER') {
markup.splice(0, 3);
}
return markup;
},
];
/**
* Applies transformations to the markup array based on the transformations defined in
* the {@link IITC.comm.messageTransformFunctions} array.
* Assumes all transformations return a new markup array.
* May be used to build an entirely new markup to be rendered without altering the original one.
*
* @function IITC.comm.transformMessage
* @param {Object} data - The data for the message, including time, player, and message content.
* @returns {Object} The transformed markup array.
*/
const transformMessage = (data) => {
const initialData = JSON.parse(JSON.stringify(data));
// Use reduce to apply each transformation to the data
const transformedData = messageTransformFunctions.reduce((data, transform) => {
const updatedMarkup = transform(data);
return { ...data, markup: updatedMarkup };
}, initialData);
return transformedData.markup;
};
/**
* Renders a cell in the chat table to display the time a message was sent.
* Formats the time and adds it to a <time> HTML element with a tooltip showing the full date and time.
*
* @function IITC.comm.renderTimeCell
* @param {number} unixtime - The timestamp of the message.
* @param {string} classNames - Additional class names to be added to the time cell.
* @returns {string} The HTML string representing a table cell with the formatted time.
*/
function renderTimeCell(unixtime, classNames) {
const time = window.unixTimeToHHmm(unixtime);
const datetime = window.unixTimeToDateTimeString(unixtime, true);
// add <small> tags around the milliseconds
const datetime_title = (datetime.slice(0, 19) + '<small class="milliseconds">' + datetime.slice(19) + '</small>')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"');
return IITC.comm.timeCellTemplate
.replace('{{ class_names }}', classNames)
.replace('{{ datetime }}', datetime)
.replace('{{ time_title }}', datetime_title)
.replace('{{ unixtime }}', unixtime.toString())
.replace('{{ time }}', time);
}
/**
* Renders a cell in the chat table for a player's nickname.
* Wraps the nickname in <mark> HTML element for highlighting.
*
* @function IITC.comm.renderNickCell
* @param {string} nick - The nickname of the player.
* @param {string} classNames - Additional class names to be added to the nickname cell.
* @returns {string} The HTML string representing a table cell with the player's nickname.
*/
function renderNickCell(nick, classNames) {
return IITC.comm.nickCellTemplate.replace('{{ class_names }}', classNames).replace('{{ nick }}', nick);
}
/**
* Renders a cell in the chat table for a chat message.
* The message is inserted as inner HTML of the table cell.
*
* @function IITC.comm.renderMsgCell
* @param {string} msg - The chat message to be displayed.
* @param {string} classNames - Additional class names to be added to the message cell.
* @returns {string} The HTML string representing a table cell with the chat message.
*/
function renderMsgCell(msg, classNames) {
return IITC.comm.msgCellTemplate.replace('{{ class_names }}', classNames).replace('{{ msg }}', msg);
}
/**
* Renders a row for a chat message including time, nickname, and message cells.
*
* @function IITC.comm.renderMsgRow
* @param {Object} data - The data for the message, including time, player, and message content.
* @returns {string} The HTML string representing a row in the chat table.
*/
function renderMsgRow(data) {
var timeClass = data.msgToPlayer ? 'pl_nudge_date' : '';
var timeCell = IITC.comm.renderTimeCell(data.time, timeClass);
var nickClasses = ['nickname'];
if (window.TEAM_TO_CSS[data.player.team]) {
nickClasses.push(window.TEAM_TO_CSS[data.player.team]);
}
// highlight things said/done by the player in a unique colour
// (similar to @player mentions from others in the chat text itself)
if (data.player.name === window.PLAYER.nickname) {
nickClasses.push('pl_nudge_me');
}
var nickCell = IITC.comm.renderNickCell(data.player.name, nickClasses.join(' '));
const markup = IITC.comm.transformMessage(data);
var msg = IITC.comm.renderMarkup(markup);
var msgClass = data.narrowcast ? 'system_narrowcast' : '';
var msgCell = IITC.comm.renderMsgCell(msg, msgClass);
var className = '';
if (!data.auto && data.public) {
className = 'public';
} else if (!data.auto && data.secure) {
className = 'faction';
}
return IITC.comm.msgRowTemplate
.replace('{{ class_names }}', className)
.replace('{{ guid }}', data.guid)
.replace('{{ time_cell }}', timeCell)
.replace('{{ nick_cell }}', nickCell)
.replace('{{ msg_cell }}', msgCell);
}
/**
* Renders a divider row in the chat table.
*
* @function IITC.comm.renderDivider
* @param {string} text - Text to display within the divider row.
* @returns {string} The HTML string representing a divider row in the chat table.
*/
function renderDivider(text) {
return IITC.comm.dividerTemplate.replace('{{ text }}', text);
}
/**
* Renders data from the data-hash to the element defined by the given ID.
*
* @function IITC.comm.renderData
* @param {Object} data - Chat data to be rendered.
* @param {string} element - ID of the DOM element to render the chat into.
* @param {boolean} likelyWereOldMsgs - Flag indicating if older messages are likely to have been added.
* @param {Array} sortedGuids - Sorted array of GUIDs representing the order of messages.
*/
function renderData(data, element, likelyWereOldMsgs, sortedGuids) {
var elm = $('#' + element);
if (elm.is(':hidden')) {
return;
}
// if sortedGuids is not specified (legacy), sort old to new
// (disregarding server order)
var vals = sortedGuids;
if (vals === undefined) {
vals = $.map(data, function (v, k) {
return [[v[0], k]];
});
vals = vals.sort(function (a, b) {
return a[0] - b[0];
});
vals = vals.map(function (v) {
return v[1];
});
}
// render to string with date separators inserted
var msgs = '';
var prevTime = null;
vals.forEach(function (guid) {
var msg = data[guid];
if (IITC.comm.declarativeMessageFilter.filterMessage(msg[4])) return;
var nextTime = new Date(msg[0]).toLocaleDateString();
if (prevTime && prevTime !== nextTime) {
msgs += IITC.comm.renderDivider(nextTime);
}
msgs += msg[2];
prevTime = nextTime;
});
var firstRender = elm.is(':empty');
var scrollBefore = window.scrollBottom(elm);
elm.html('<table>' + msgs + '</table>');
if (firstRender) {
elm.data('needsScrollTop', 99999999);
} else {
chat.keepScrollPosition(elm, scrollBefore, likelyWereOldMsgs);
}
if (elm.data('needsScrollTop')) {
elm.data('ignoreNextScroll', true);
elm.scrollTop(elm.data('needsScrollTop'));
elm.data('needsScrollTop', null);
}
}
for (const channel of _channels) {
_initChannelData(channel.id);
}
IITC.comm = {
channels: _channels,
sendChatMessage,
parseMsgData,
getLatLngForSendingMessage,
// List of transformations
portalNameTransformations,
messageTransformFunctions,
// Render primitive, may be override
renderMsgRow,
renderDivider,
renderTimeCell,
renderNickCell,
renderMsgCell,
renderMarkup,
transformMessage,
renderMarkupEntity,
renderPlayer,
renderFactionEnt,
renderPortal,
renderText,
getChatPortalName,
// templates
portalTemplate,
timeCellTemplate,
nickCellTemplate,
msgCellTemplate,
msgRowTemplate,
dividerTemplate,
// exposed API for legacy
requestChannel,
renderChannel,
renderData,
_channelsData,
_genPostData,
_updateOldNewHash,
_writeDataToHash,
};
/* global log, map, chat, IITC */