300 lines
7.0 KiB
TypeScript
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;
|
|
}
|
|
}
|