Advertisement
Guest User

Untitled

a guest
Nov 1st, 2017
82
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 15.92 KB | None | 0 0
  1. import _ from 'lodash';
  2. import * as Colours from './Colours';
  3. import { md5 } from './Md5';
  4. import ThemeManager from 'src/libs/ThemeManager';
  5.  
  6. /**
  7. * Formats a message. Adds bold, underline and colouring
  8. * @param {String} msg The message to format
  9. * @returns {String} The HTML formatted message
  10. */
  11. const colourMatchRegexp = /^\x03(([0-9][0-9]?)(,([0-9][0-9]?))?)/;
  12. export function ircCodesToHtml(input, enableExtras) {
  13. function spanFromOpen() {
  14. let style = '';
  15. let colours;
  16. let classes = [];
  17. let result = '';
  18.  
  19. if (isTagOpen()) {
  20. style += (openTags.bold) ? 'font-weight: bold; ' : '';
  21. style += (openTags.italic) ? 'font-style: italic; ' : '';
  22. style += (openTags.underline) ? 'text-decoration: underline; ' : '';
  23. if (openTags.colour) {
  24. colours = openTags.colour.split(',');
  25. classes.push(colours[0]);
  26. if (colours[1]) {
  27. classes.push(colours[1]);
  28. }
  29. }
  30. result = '<span class="' + classes.join(' ') + '" style="' + style + '">';
  31. }
  32.  
  33. return result;
  34. }
  35. function colourMatch(str) {
  36. return colourMatchRegexp.exec(str);
  37. }
  38. function colourFromNum(num) {
  39. switch (parseInt(num, 10)) {
  40. case 0:
  41. return 'white';
  42. case 1:
  43. return 'black';
  44. case 2:
  45. return 'blue';
  46. case 3:
  47. return 'green';
  48. case 4:
  49. return 'light-red';
  50. case 5:
  51. return 'brown';
  52. case 6:
  53. return 'purple';
  54. case 7:
  55. return 'orange';
  56. case 8:
  57. return 'yellow';
  58. case 9:
  59. return 'light-green';
  60. case 10:
  61. return 'cyan';
  62. case 11:
  63. return 'light-cyan';
  64. case 12:
  65. return 'light-blue';
  66. case 13:
  67. return 'pink';
  68. case 14:
  69. return 'grey';
  70. case 15:
  71. return 'light-grey';
  72. default:
  73. return null;
  74. }
  75. }
  76. function isTagOpen() {
  77. return (openTags.bold || openTags.italic || openTags.underline || openTags.colour);
  78. }
  79. function openTag() {
  80. currentTag = spanFromOpen();
  81. }
  82. function closeTag() {
  83. if (isTagOpen()) {
  84. out += currentTag + '</span>';
  85. }
  86. }
  87. function addContent(content) {
  88. if (isTagOpen()) {
  89. currentTag += content;
  90. } else {
  91. out += content;
  92. }
  93. }
  94. // Invisible characters are still selectable. Ie. when copying text
  95. function addInvisibleContent(content) {
  96. let tag = `<span class="kiwi-formatting-extras-invisible">${content}</span>`;
  97. if (isTagOpen()) {
  98. currentTag += tag;
  99. } else {
  100. out += tag;
  101. }
  102. }
  103.  
  104. let msg = input || '';
  105. let out = '';
  106. let currentTag = '';
  107. let openTags = {
  108. bold: false,
  109. italic: false,
  110. underline: false,
  111. colour: false,
  112. };
  113. let i = 0;
  114. let colours = [];
  115. let match = null;
  116.  
  117. for (i = 0; i < msg.length; i++) {
  118. let char = msg[i];
  119.  
  120. if (enableExtras) {
  121. if (char === '&' && i === 0 && msg.indexOf('&gt; ') === 0) {
  122. // Starting with '> '
  123. out += '<span class="kiwi-formatting-extras-block">' + msg.substr(5);
  124. i = msg.length;
  125. break;
  126. } else if (char === '`') {
  127. let nextQuotePos = msg.indexOf('`', i + 1);
  128. // Only quote if there is a closing quote later in the string
  129. if (nextQuotePos > -1) {
  130. closeTag();
  131.  
  132. out += '<span class="kiwi-formatting-extras-quote">';
  133. addInvisibleContent('`');
  134. out += msg.substring(i + 1, nextQuotePos);
  135. addInvisibleContent('`');
  136. out += '</span>';
  137. i = nextQuotePos;
  138. continue;
  139. }
  140. } else if (char === '*') {
  141. let isBold = msg.substr(i, 2) === '**';
  142. let moreBoldExists = isBold && msg.indexOf('**', i + 2) > -1;
  143. let isItalic = !isBold;
  144. let moreItalicExists = isItalic && msg.indexOf('*', i + 1) > -1;
  145.  
  146. if (isBold && moreBoldExists && !openTags.bold) {
  147. closeTag();
  148. openTags.bold = true;
  149. openTag();
  150. addInvisibleContent('**');
  151. // Skip the next *
  152. i++;
  153. continue;
  154. } else if (isBold && openTags.bold) {
  155. addInvisibleContent('**');
  156. closeTag();
  157. openTags.bold = false;
  158. openTag();
  159. // Skip the next *
  160. i++;
  161. continue;
  162. } else if (isItalic && moreItalicExists && !openTags.italic) {
  163. closeTag();
  164. openTags.italic = true;
  165. openTag();
  166. addInvisibleContent('*');
  167. continue;
  168. } else if (isItalic && openTags.italic) {
  169. addInvisibleContent('*');
  170. closeTag();
  171. openTags.italic = false;
  172. openTag();
  173. continue;
  174. }
  175. } else if (char === '\n') {
  176. addContent('<br>');
  177. continue;
  178. }
  179. }
  180.  
  181. if (char === '\x02') {
  182. closeTag();
  183. openTags.bold = !openTags.bold;
  184. openTag();
  185. continue;
  186. } else if (char === '\x1D') {
  187. closeTag();
  188. openTags.italic = !openTags.italic;
  189. openTag();
  190. continue;
  191. } else if (char === '\x1F') {
  192. closeTag();
  193. openTags.underline = !openTags.underline;
  194. openTag();
  195. continue;
  196. } else if (char === '\x03') {
  197. closeTag();
  198. match = colourMatch(msg.substr(i, 6));
  199. if (match) {
  200. i += match[1].length;
  201. // 2 & 4
  202. colours[0] = 'irc-fg-colour-' + colourFromNum(match[2]);
  203. if (match[4]) {
  204. colours[1] = 'irc-bg-colour-' + colourFromNum(match[4]);
  205. }
  206. openTags.colour = colours.join(',');
  207. } else {
  208. openTags.colour = false;
  209. }
  210. openTag();
  211. continue;
  212. } else if (char === '\x0F') {
  213. closeTag();
  214. openTags.bold = openTags.italic = openTags.underline = openTags.colour = false;
  215. continue;
  216. }
  217.  
  218. addContent(msg[i]);
  219. }
  220.  
  221. closeTag();
  222.  
  223. return out;
  224. }
  225.  
  226. const urlRegex = new RegExp('' +
  227. // Detect either a protocol or 'www.' to start a URL
  228. /(([A-Za-z][A-Za-z0-9-]*:\/\/)|(www\.))/.source +
  229. // The hostname..
  230. /([\w\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF.-]+)/.source +
  231. // The hostname must end in 2-6 alpha characters (the TLD)
  232. /([a-zA-Z]{2,6})/.source +
  233. // Optional port..
  234. /(:[0-9]+)?/.source +
  235. // Optional path..
  236. /(\/[\w\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF!:.?$'()[\]*,;~+=&%@!\-/]*)?/.source +
  237. // Optional fragment
  238. /(#.*)?/.source,
  239. 'ig'
  240. );
  241.  
  242. export function linkifyUrls(input, _opts) {
  243. let opts = _opts || {};
  244. let foundUrls = [];
  245. let result = input.replace(urlRegex, _url => {
  246. let url = _url;
  247. let nice = url;
  248.  
  249. // Don't allow javascript execution
  250. if (url.match(/^javascript:/i)) {
  251. return url;
  252. }
  253.  
  254. // Add the http if no protoocol was found
  255. if (url.match(/^www\./i)) {
  256. url = 'http://' + url;
  257. }
  258.  
  259. // Shorten the displayed URL if it's going to be too long
  260. if (nice.length > 100) {
  261. nice = nice.substr(0, 100) + '...';
  262. }
  263.  
  264. // Make the link clickable
  265. let out = `<a target="_blank" href="${url.replace(/"/g, '%22')}">${_.escape(nice)}</a>`;
  266.  
  267. if (opts.addHandle) {
  268. let cssClass = opts.handleClass || '';
  269. let content = opts.handleContent || '';
  270. out += `<a data-url="${url}" class="${cssClass}">${content}</a>`;
  271. }
  272.  
  273. foundUrls.push(url);
  274. return out;
  275. });
  276.  
  277. return {
  278. urls: foundUrls,
  279. html: result,
  280. };
  281. }
  282.  
  283. export function addEmojis(wordCtx, emojiList, emojiLocation) {
  284. let word = '';
  285. let words = [word];
  286.  
  287. // wordCtx may be an object with extra context about the word
  288. if (typeof wordCtx === 'object') {
  289. word = wordCtx.word;
  290. words = wordCtx.words;
  291. } else {
  292. word = wordCtx;
  293. }
  294.  
  295. // If emojiList.hasOwnProperty exists then use it to check that the word
  296. // is actually part of the object
  297. if (emojiList.hasOwnProperty && !emojiList.hasOwnProperty(word)) {
  298. return word;
  299. }
  300.  
  301. let emoji = emojiList[word];
  302. if (emoji) {
  303. let classes = 'kiwi-messagelist-emoji';
  304. if (_.compact(words).length === 1) {
  305. classes += ' kiwi-messagelist-emoji--single';
  306. }
  307.  
  308. let src = `${emojiLocation}${emoji}.png`;
  309. return `<img class="${classes}" src="${src}" alt="${word}" />`;
  310. }
  311.  
  312. return word;
  313. }
  314.  
  315. export function linkifyChannels(word) {
  316. return word.replace(/(^|\s)([#&][^ .,\007<>]+)$/i, _channel => {
  317. let channelName = _channel.trim();
  318. return `<a class="u-link kiwi-channel" data-channel-name="${_.escape(channelName)}">` +
  319. _.escape(_channel) +
  320. '</a>';
  321. });
  322. }
  323.  
  324. export function linkifyUsers(word, userlist) {
  325. let ret = '';
  326. let nick = '';
  327. let prepend = '';
  328. let append = '';
  329. let punc = ',.!:;-+)]?آ؟\\/<>@';
  330. let validLastChar = punc.indexOf(word[word.length - 1]) > -1;
  331. let normWord = word.toLowerCase();
  332. let hasProp = Object.prototype.hasOwnProperty;
  333.  
  334. // Checking for a nick in order of processing cost
  335. if (hasProp.call(userlist, normWord)) {
  336. nick = word;
  337. } else if (hasProp.call(userlist, normWord.substr(0, normWord.length - 1)) && validLastChar) {
  338. // The last character is usually punctuation of some kind
  339. nick = word.substr(0, word.length - 1);
  340. append = word[word.length - 1];
  341. } else if (hasProp.call(userlist, _.trim(normWord, punc))) {
  342. nick = _.trim(word, punc);
  343. let nickIdx = word.indexOf(nick);
  344. append = word.substr(nickIdx + nick.length);
  345. prepend = word.substr(0, nickIdx);
  346. } else {
  347. return word;
  348. }
  349.  
  350. let escaped = _.escape(nick);
  351. let colour = createNickColour(nick);
  352. ret = `<a class="kiwi-nick" data-nick="${escaped}" style="color:${colour}">${escaped}</a>`;
  353.  
  354. if (prepend) {
  355. ret = _.escape(prepend) + ret;
  356. }
  357. if (append) {
  358. ret += _.escape(append);
  359. }
  360.  
  361. return ret;
  362. }
  363.  
  364. /**
  365. * Convert a nickname string to a colour code
  366. */
  367. let nickColourCache = Object.create(null);
  368. export function createNickColour(nick) {
  369. let nickLower = nick.toLowerCase();
  370.  
  371. if (nickColourCache[nickLower]) {
  372. return nickColourCache[nickLower];
  373. }
  374.  
  375. // The HSL properties are based on this specific colour
  376. let startingColour = '#36809B'; // '#449fc1';
  377.  
  378. let hash = md5(nickLower);
  379. let hueOffset = mapRange(hexVal(hash, 14, 3), 0, 4095, 0, 359);
  380. let satOffset = hexVal(hash, 17);
  381. let baseColour = Colours.rgb2hsl(Colours.hex2rgb(startingColour));
  382. baseColour.h = (((baseColour.h * 360 - hueOffset) + 360) % 360) / 360;
  383.  
  384. if (satOffset % 2 === 0) {
  385. baseColour.s = Math.min(1, ((baseColour.s * 100) + satOffset) / 100);
  386. } else {
  387. baseColour.s = Math.max(0, ((baseColour.s * 100) - satOffset) / 100);
  388. }
  389.  
  390. let themeMngr = ThemeManager.instance();
  391. let brightness = themeMngr.themeVar('nick-brightness');
  392. if (brightness) {
  393. baseColour.l = parseInt(brightness, 10) / 100;
  394. }
  395.  
  396. let rgb = Colours.hsl2rgb(baseColour);
  397. let nickColour = Colours.rgb2hex(rgb);
  398.  
  399. nickColourCache[nickLower] = nickColour;
  400.  
  401. return nickColour;
  402. }
  403.  
  404. /**
  405. * Extract a substring from a hex string and parse it as an integer
  406. * @param {string} hash - Source hex string
  407. * @param {number} index - Start index of substring
  408. * @param {number} [length] - Length of substring. Defaults to 1.
  409. */
  410. export function hexVal(hash, index, len) {
  411. return parseInt(hash.substr(index, len || 1), 16);
  412. }
  413.  
  414. /*
  415. * Re-maps a number from one range to another
  416. * http://processing.org/reference/map_.html
  417. */
  418. export function mapRange(value, vMin, vMax, dMin, dMax) {
  419. let vValue = parseFloat(value);
  420. let vRange = vMax - vMin;
  421. let dRange = dMax - dMin;
  422.  
  423. return (vValue - vMin) * dRange / vRange + dMin;
  424. }
  425.  
  426. /**
  427. * Formats a line of text that will be displayed somewhere
  428. */
  429. const textFormats = {
  430. channel_join: '? %nick (%username) %text',
  431. channel_part: '? %nick (%username) %text',
  432. channel_quit: '? %nick (%username) %text',
  433. channel_kicked: 'â†گ %text',
  434. channel_selfkick: 'أ— %text',
  435. channel_badpassword: 'أ— %text',
  436. channel_topic: 'â“ک %text',
  437. channel_banned: 'أ— %text',
  438. channel_badkey: 'âڑ  %text',
  439. channel_inviteonly: 'âڑ  %channel %text',
  440. channel_alreadyin: 'âڑ  %nick %text',
  441. channel_limitreached: 'âڑ  %channel %text',
  442. channel_invalid_name: 'âڑ  %channel %text',
  443. channel_topic_setby: 'â“ک %text',
  444. channel_has_been_invited: 'â“ک %nick %text',
  445. server_connecting: '%text',
  446. server_connecting_error: '%text',
  447. mode: 'â“ک %nick %text',
  448. selfmode: 'â“ک %nick %text',
  449. nickname_alreadyinuse: 'âڑ  %text',
  450. network_disconnected: 'âڑ  %text',
  451. whois_channels: '%text',
  452. whois_idle_and_signon: '%text',
  453. whois_away: '%text',
  454. whois_server: '%text',
  455. whois_idle: '%text',
  456. whois_notfound: 'â“ک %text',
  457. nick_changed: 'â“ک %text',
  458. applet_notfound: 'âڑ  %text',
  459. encoding_changed: 'â“ک %text',
  460. encoding_invalid: 'âڑ  %text',
  461. settings_saved: 'â“ک %text',
  462. ignore_title: '%text:',
  463. ignore_none: '%text',
  464. ignore_nick: '%text',
  465. ignore_stop_notice: '%text',
  466. ignore_stopped: '%text',
  467. chanop_privs_needed: 'âڑ  %text',
  468. no_such_nick: 'â“ک %nick: %text',
  469. unknown_command: 'â“ک %text',
  470. motd: '%text',
  471. ctcp_response: '[CTCP %nick reply] %message',
  472. ctcp_request: '[CTCP %nick] %message',
  473. privmsg: '%text',
  474. notice: '%text',
  475. action: '* %nick %text',
  476. whois_ident: '%nick [%nick!%ident@%host] * %text',
  477. whois: '%text',
  478. who: '%nick [%nick!%ident@%host] * %realname',
  479. quit: '%text',
  480. rejoin: '%text',
  481. set_setting: 'â“ک %text',
  482. list_aliases: 'â“ک %text',
  483. ignored_pattern: 'â“ک %text',
  484. wallops: '[WALLOPS] %text',
  485. message_nick: '%prefix%nick',
  486. general_error: '%text',
  487. };
  488.  
  489. export function formatText(formatId, params) {
  490. let format = textFormats[formatId];
  491.  
  492. // Expand a user mask into its individual parts (nick, ident, hostname)
  493. if (params.user) {
  494. params.nick = params.user.nick || '';
  495. params.username = params.user.username || '';
  496. params.host = params.user.hostname || '';
  497. params.prefix = params.user.prefix || '';
  498. }
  499.  
  500. // Do the magic. Use the %shorthand syntax to produce output.
  501. let result = format.replace(/%([A-Z]{2,})/ig, (match, key) => {
  502. let ret = '';
  503. if (typeof params[key] !== 'undefined') {
  504. ret = params[key];
  505. }
  506.  
  507. return ret;
  508. });
  509.  
  510. return result;
  511. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement