/* Recommended usage 16-bit colors: black darkblue green red brown purple gold yellow limegreen cyan lightblue blue pink darkgray gray white or any hex value prepended with # Formats: action code multicode (multiline code) bold italics emphasis strike underline */ import { thousandsSeparator, timeSince, toHHMMSS } from '../common'; // Color Utilities export function colorDistance(s: string, t: string): number { if (!s.length || !t.length) { return 0; } return colorDistance(s.slice(2), t.slice(2)) + Math.abs(parseInt(s.slice(0, 2), 16) - parseInt(t.slice(0, 2), 16)); } export function approximateB16Color(cl: string): string { const arr = []; let closest = 'white'; for (const i in b16Colors) { arr.push(b16Colors[i].substring(1)); } cl = cl.substring(1); arr.sort((a, b) => { return colorDistance(a, cl) - colorDistance(b, cl); }); for (const i in b16Colors) { if (b16Colors[i] === '#' + arr[0]) { closest = i; } } return closest; } export function b16toHex(name: string): string { if (!b16Colors[name]) { return '#000000'; } return b16Colors[name]; } interface IMethod { start: string; end: string; } declare type Keyed = {[key: string]: string}; declare type Method = {[key: string]: IMethod}; const b16Colors: Keyed = { black: '#000000', darkblue: '#00007f', green: '#009300', red: '#ff0000', brown: '#7f0000', purple: '#9c009c', gold: '#fc7f00', yellow: '#ffff00', limegreen: '#00fc00', cyan: '#00ffff', lightblue: '#0000fc', blue: '#009393', pink: '#ff00ff', darkgray: '#7f7f7f', gray: '#d2d2d2', white: '#ffffff' }; /** * Base class for message formatting */ export class Formatter { public colors: Keyed = {}; public formatting: Method = {}; public colorEscape = ''; constructor( public supportFormatting = false, public supportColors = false ) {} public color(color: string, msg: string): string { return msg; } public format(method: string, msg: string): string { if (!this.supportFormatting) { return msg; } if (!this.formatting[method]) { return msg; } if (this.formatting[method].start && this.formatting[method].end) { return this.formatting[method].start + msg + this.formatting[method].end; } else { return this.formatting[method] + msg + this.formatting[method]; } } public strip(msg: string): string { return msg; } // Object compositor. // This default function turns objects into plain text without any formatting. // Override this in your protocol for the desired effect. /* [ ['element type', 'text', { param: value }], ... ] Element types: field - A field type Parameters: * label - Label for this field. If an array, the first item is considered an "icon" and wont have ':' appended to it. * type - The type of the field. title - A title field description - Descriptive field metric - A Number value. Requires integer, will be transformed into xxx,xxx,xxx time - Time value. Requires UNIX timestamp. timesince - Time since value. Requires UNIX timestamp, will be transformed into x seconds/minutes/hours/days ago duration - Duration value. Requires integer, will be transformed into HH:MM:SS content - Full message body. * color - The color of the field. Not always supported. bold/b/strong - Bold text i/italic - Italic text color - A colored text. Not always supported. url - An URL. Parameters: * label - Label for this URL image - An Image. */ public compose(objs: any): any { const str = []; for (const i in objs) { const elem = objs[i]; const elemType = elem[0]; let elemValue = elem[1]; const elemParams = elem[2]; if (!elemValue) { continue; } // Special types if (elemParams && elemParams.type) { switch (elemParams.type) { case 'time': elemValue = new Date(elemValue).toString(); break; case 'metric': elemValue = thousandsSeparator(elemValue); break; case 'timesince': elemValue = timeSince(elemValue); break; case 'duration': elemValue = toHHMMSS(elemValue); break; } } if (elemParams && elemParams.label) { let label = elemParams.label; // If the label param is an array, choose the last element // The last element is generally the text version, as opposed to // the first element being an icon. if (typeof label === 'object') { label = elemParams.label[elemParams.label.length - 1]; } str.push(elemParams.label + ': ' + elemValue); } else { str.push(elemValue); } } // May return an object, but your protocol must support it. return str.join(' '); } } /** * Built-in formatter for HTML-enabled protocols */ export class HTMLFormatter extends Formatter { public formatting: Method = { code: {start: '', end: ''}, action: {start: '', end: ''}, bold: {start: '', end: ''}, italics: {start: '', end: ''}, emphasis: {start: '', end: ''}, underline: {start: '', end: ''}, multicode: {start: '
', end: '
'}, }; constructor() { super(true, true); } public color(color: string, msg: string): string { return `${msg}`; } public format(method: string, msg: string): string { if (!this.formatting[method]) { return msg; } if (this.formatting[method].start && this.formatting[method].end) { return this.formatting[method].start + msg + this.formatting[method].end; } else { return this.formatting[method] + msg + this.formatting[method]; } } public strip(msg: string): string { return msg.replace(/<\/?[^>]+(>|$)/g, ''); } } /** * Built-in formatter for Markdown-enabled protocols */ export class MarkdownFormatter extends Formatter { public formatting: Method = { code: {start: '`', end: '`'}, bold: {start: '**', end: '**'}, action: {start: '*', end: '*'}, italics: {start: '*', end: '*'}, emphasis: {start: '*', end: '*'}, underline: {start: '__', end: '__'}, multicode: {start: '```', end: '```'}, }; constructor() { super(true, false); } public color(color: string, msg: string): string { return msg; } public format(method: string, msg: string): string { if (!this.formatting[method]) { return msg; } if (this.formatting[method].start && this.formatting[method].end) { return this.formatting[method].start + msg + this.formatting[method].end; } else { return this.formatting[method] + msg + this.formatting[method]; } } public strip(msg: string): string { return msg; } }