357 lines
6.7 KiB
JavaScript
357 lines
6.7 KiB
JavaScript
|
import React from 'react';
|
||
|
|
||
|
const formatChars = {
|
||
|
bold: 0x02,
|
||
|
italic: 0x1d,
|
||
|
underline: 0x1f,
|
||
|
strikethrough: 0x1e,
|
||
|
color: 0x03,
|
||
|
reverseColor: 0x16,
|
||
|
reset: 0x0f
|
||
|
};
|
||
|
|
||
|
const colors = {
|
||
|
0: 'white',
|
||
|
1: 'black',
|
||
|
2: 'blue',
|
||
|
3: 'green',
|
||
|
4: 'red',
|
||
|
5: 'brown',
|
||
|
6: 'magenta',
|
||
|
7: 'orange',
|
||
|
8: 'yellow',
|
||
|
9: 'lightgreen',
|
||
|
10: 'cyan',
|
||
|
11: 'lightcyan',
|
||
|
12: 'lightblue',
|
||
|
13: 'pink',
|
||
|
14: 'gray',
|
||
|
15: 'lightgray',
|
||
|
16: '#470000',
|
||
|
17: '#472100',
|
||
|
18: '#474700',
|
||
|
19: '#324700',
|
||
|
20: '#004700',
|
||
|
21: '#00472c',
|
||
|
22: '#004747',
|
||
|
23: '#002747',
|
||
|
24: '#000047',
|
||
|
25: '#2e0047',
|
||
|
26: '#470047',
|
||
|
27: '#47002a',
|
||
|
28: '#740000',
|
||
|
29: '#743a00',
|
||
|
30: '#747400',
|
||
|
31: '#517400',
|
||
|
32: '#007400',
|
||
|
33: '#007449',
|
||
|
34: '#007474',
|
||
|
35: '#004074',
|
||
|
36: '#000074',
|
||
|
37: '#4b0074',
|
||
|
38: '#740074',
|
||
|
39: '#740045',
|
||
|
40: '#b50000',
|
||
|
41: '#b56300',
|
||
|
42: '#b5b500',
|
||
|
43: '#7db500',
|
||
|
44: '#00b500',
|
||
|
45: '#00b571',
|
||
|
46: '#00b5b5',
|
||
|
47: '#0063b5',
|
||
|
48: '#0000b5',
|
||
|
49: '#7500b5',
|
||
|
50: '#b500b5',
|
||
|
51: '#b5006b',
|
||
|
52: '#ff0000',
|
||
|
53: '#ff8c00',
|
||
|
54: '#ffff00',
|
||
|
55: '#b2ff00',
|
||
|
56: '#00ff00',
|
||
|
57: '#00ffa0',
|
||
|
58: '#00ffff',
|
||
|
59: '#008cff',
|
||
|
60: '#0000ff',
|
||
|
61: '#a500ff',
|
||
|
62: '#ff00ff',
|
||
|
63: '#ff0098',
|
||
|
64: '#ff5959',
|
||
|
65: '#ffb459',
|
||
|
66: '#ffff71',
|
||
|
67: '#cfff60',
|
||
|
68: '#6fff6f',
|
||
|
69: '#65ffc9',
|
||
|
70: '#6dffff',
|
||
|
71: '#59b4ff',
|
||
|
72: '#5959ff',
|
||
|
73: '#c459ff',
|
||
|
74: '#ff66ff',
|
||
|
75: '#ff59bc',
|
||
|
76: '#ff9c9c',
|
||
|
77: '#ffd39c',
|
||
|
78: '#ffff9c',
|
||
|
79: '#e2ff9c',
|
||
|
80: '#9cff9c',
|
||
|
81: '#9cffdb',
|
||
|
82: '#9cffff',
|
||
|
83: '#9cd3ff',
|
||
|
84: '#9c9cff',
|
||
|
85: '#dc9cff',
|
||
|
86: '#ff9cff',
|
||
|
87: '#ff94d3',
|
||
|
88: '#000000',
|
||
|
89: '#131313',
|
||
|
90: '#282828',
|
||
|
91: '#363636',
|
||
|
92: '#4d4d4d',
|
||
|
93: '#656565',
|
||
|
94: '#818181',
|
||
|
95: '#9f9f9f',
|
||
|
96: '#bcbcbc',
|
||
|
97: '#e2e2e2',
|
||
|
98: '#ffffff'
|
||
|
};
|
||
|
|
||
|
function tokenize(str) {
|
||
|
const tokens = [];
|
||
|
|
||
|
let colorBuffer = '';
|
||
|
let color = false;
|
||
|
let background = false;
|
||
|
let colorToken;
|
||
|
|
||
|
let start = 0;
|
||
|
let end = 0;
|
||
|
|
||
|
const pushText = () => {
|
||
|
if (end > start) {
|
||
|
tokens.push({
|
||
|
type: 'text',
|
||
|
content: str.slice(start, end)
|
||
|
});
|
||
|
start = end;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
const pushToken = token => {
|
||
|
pushText();
|
||
|
tokens.push(token);
|
||
|
};
|
||
|
|
||
|
for (let i = 0; i < str.length; i++) {
|
||
|
const charCode = str.charCodeAt(i);
|
||
|
|
||
|
if (color) {
|
||
|
if (charCode >= 48 && charCode <= 57 && colorBuffer.length < 2) {
|
||
|
colorBuffer += str[i];
|
||
|
} else if (charCode === 44 && !background) {
|
||
|
colorToken.color = colors[parseInt(colorBuffer, 10)];
|
||
|
colorBuffer = '';
|
||
|
background = true;
|
||
|
} else {
|
||
|
if (background) {
|
||
|
if (colorBuffer.length > 0) {
|
||
|
colorToken.background = colors[parseInt(colorBuffer, 10)];
|
||
|
} else {
|
||
|
// Trailing comma
|
||
|
start--;
|
||
|
}
|
||
|
} else {
|
||
|
colorToken.color = colors[parseInt(colorBuffer, 10)];
|
||
|
}
|
||
|
|
||
|
start--;
|
||
|
colorBuffer = '';
|
||
|
color = false;
|
||
|
tokens.push(colorToken);
|
||
|
}
|
||
|
} else {
|
||
|
switch (charCode) {
|
||
|
case formatChars.bold:
|
||
|
pushToken({
|
||
|
type: 'bold'
|
||
|
});
|
||
|
break;
|
||
|
|
||
|
case formatChars.italic:
|
||
|
pushToken({
|
||
|
type: 'italic'
|
||
|
});
|
||
|
break;
|
||
|
|
||
|
case formatChars.underline:
|
||
|
pushToken({
|
||
|
type: 'underline'
|
||
|
});
|
||
|
break;
|
||
|
|
||
|
case formatChars.strikethrough:
|
||
|
pushToken({
|
||
|
type: 'strikethrough'
|
||
|
});
|
||
|
break;
|
||
|
|
||
|
case formatChars.color:
|
||
|
pushText();
|
||
|
|
||
|
colorToken = {
|
||
|
type: 'color'
|
||
|
};
|
||
|
color = true;
|
||
|
background = false;
|
||
|
break;
|
||
|
|
||
|
case formatChars.reverseColor:
|
||
|
pushToken({
|
||
|
type: 'reverse'
|
||
|
});
|
||
|
break;
|
||
|
|
||
|
case formatChars.reset:
|
||
|
pushToken({
|
||
|
type: 'reset'
|
||
|
});
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
start--;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
start++;
|
||
|
end++;
|
||
|
}
|
||
|
|
||
|
if (start === 0) {
|
||
|
return str;
|
||
|
}
|
||
|
|
||
|
pushText();
|
||
|
|
||
|
return tokens;
|
||
|
}
|
||
|
|
||
|
function colorifyString(str, state = {}) {
|
||
|
const tokens = tokenize(str);
|
||
|
|
||
|
if (typeof tokens === 'string') {
|
||
|
return [tokens, state];
|
||
|
}
|
||
|
|
||
|
const result = [];
|
||
|
let style = state.style || {};
|
||
|
let reverse = state.reverse || false;
|
||
|
|
||
|
const toggle = (prop, value, multiple) => {
|
||
|
if (style[prop]) {
|
||
|
if (multiple) {
|
||
|
const props = style[prop].split(' ');
|
||
|
const i = props.indexOf(value);
|
||
|
if (i !== -1) {
|
||
|
props.splice(i, 1);
|
||
|
} else {
|
||
|
props.push(value);
|
||
|
}
|
||
|
style[prop] = props.join(' ');
|
||
|
} else {
|
||
|
delete style[prop];
|
||
|
}
|
||
|
} else {
|
||
|
style[prop] = value;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
for (let i = 0; i < tokens.length; i++) {
|
||
|
const token = tokens[i];
|
||
|
|
||
|
switch (token.type) {
|
||
|
case 'bold':
|
||
|
toggle('fontWeight', 700);
|
||
|
break;
|
||
|
|
||
|
case 'italic':
|
||
|
toggle('fontStyle', 'italic');
|
||
|
break;
|
||
|
|
||
|
case 'underline':
|
||
|
toggle('textDecoration', 'underline', true);
|
||
|
break;
|
||
|
|
||
|
case 'strikethrough':
|
||
|
toggle('textDecoration', 'line-through', true);
|
||
|
break;
|
||
|
|
||
|
case 'color':
|
||
|
if (!token.color) {
|
||
|
delete style.color;
|
||
|
delete style.background;
|
||
|
} else if (reverse) {
|
||
|
style.color = token.background;
|
||
|
style.background = token.color;
|
||
|
} else {
|
||
|
style.color = token.color;
|
||
|
style.background = token.background;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 'reverse':
|
||
|
reverse = !reverse;
|
||
|
if (style.color) {
|
||
|
const bg = style.background;
|
||
|
style.background = style.color;
|
||
|
style.color = bg;
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
case 'reset':
|
||
|
style = {};
|
||
|
break;
|
||
|
|
||
|
case 'text':
|
||
|
if (Object.keys(style).length > 0) {
|
||
|
result.push(<span style={style}>{token.content}</span>);
|
||
|
style = { ...style };
|
||
|
} else {
|
||
|
result.push(token.content);
|
||
|
}
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return [result, { style, reverse }];
|
||
|
}
|
||
|
|
||
|
export default function colorify(input) {
|
||
|
if (typeof input === 'string') {
|
||
|
const [colored] = colorifyString(input);
|
||
|
return colored;
|
||
|
}
|
||
|
|
||
|
if (Array.isArray(input)) {
|
||
|
const result = [];
|
||
|
let state;
|
||
|
|
||
|
for (let i = 0; i < input.length; i++) {
|
||
|
if (typeof input[i] === 'string') {
|
||
|
const [colored, nextState] = colorifyString(input[i], state);
|
||
|
|
||
|
if (typeof colored === 'string') {
|
||
|
result.push(colored);
|
||
|
} else {
|
||
|
result.push(...colored);
|
||
|
}
|
||
|
|
||
|
state = nextState;
|
||
|
} else {
|
||
|
result.push(input[i]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
return input;
|
||
|
}
|