Render IRC colors and formatting, closes #46
This commit is contained in:
parent
ed432881ef
commit
0c902f8ac8
File diff suppressed because one or more lines are too long
@ -8,6 +8,7 @@ import {
|
||||
isChannel,
|
||||
formatDate
|
||||
} from 'utils';
|
||||
import colorify from 'utils/colorify';
|
||||
import createReducer from 'utils/createReducer';
|
||||
import { getApp } from './app';
|
||||
import { getSelectedTab } from './tab';
|
||||
@ -224,7 +225,8 @@ function initMessage(message, tab, state) {
|
||||
6 * charWidth,
|
||||
windowWidth
|
||||
);
|
||||
message.content = linkify(message.content);
|
||||
|
||||
message.content = colorify(linkify(message.content));
|
||||
|
||||
return message;
|
||||
}
|
||||
|
356
client/js/utils/colorify.js
Normal file
356
client/js/utils/colorify.js
Normal file
@ -0,0 +1,356 @@
|
||||
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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user