core/src/types/message-format.ts

300 lines
7.0 KiB
TypeScript

/* 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: '<code>', end: '</code>'},
action: {start: '<i>', end: '</i>'},
bold: {start: '<b>', end: '</b>'},
italics: {start: '<i>', end: '</i>'},
emphasis: {start: '<em>', end: '</em>'},
underline: {start: '<u>', end: '</u>'},
multicode: {start: '<pre><code>', end: '</code></pre>'},
};
constructor() {
super(true, true);
}
public color(color: string, msg: string): string {
return `<span style="color:${color};">${msg}</span>`;
}
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;
}
}