diff --git a/client/css/style.css b/client/css/style.css
index 6ab8c4d3..1d619b10 100644
--- a/client/css/style.css
+++ b/client/css/style.css
@@ -80,6 +80,7 @@ button {
code,
kbd,
+.irc-monospace,
textarea#user-specified-css-input {
font-family: Consolas, Menlo, Monaco, "Lucida Console", "DejaVu Sans Mono", "Courier New", monospace;
}
@@ -1947,6 +1948,10 @@ part/quit messages where we don't load previews (adds a blank line otherwise) */
text-decoration: underline;
}
+.irc-strikethrough {
+ text-decoration: line-through;
+}
+
.irc-italic {
font-style: italic;
}
diff --git a/client/js/keybinds.js b/client/js/keybinds.js
index 1c269ac3..eb9f51a4 100644
--- a/client/js/keybinds.js
+++ b/client/js/keybinds.js
@@ -72,6 +72,8 @@ const colorsHotkeys = {
u: "\x1F",
i: "\x1D",
o: "\x0F",
+ s: "\x1e",
+ m: "\x11",
};
for (const hotkey in colorsHotkeys) {
diff --git a/client/js/libs/handlebars/ircmessageparser/parseStyle.js b/client/js/libs/handlebars/ircmessageparser/parseStyle.js
index 5a3ca566..642b4e52 100644
--- a/client/js/libs/handlebars/ircmessageparser/parseStyle.js
+++ b/client/js/libs/handlebars/ircmessageparser/parseStyle.js
@@ -8,6 +8,8 @@ const RESET = "\x0f";
const REVERSE = "\x16";
const ITALIC = "\x1d";
const UNDERLINE = "\x1f";
+const STRIKETHROUGH = "\x1e";
+const MONOSPACE = "\x11";
// Color code matcher, with format `XX,YY` where both `XX` and `YY` are
// integers, `XX` is the text color and `YY` is an optional background color.
@@ -22,7 +24,7 @@ const controlCodesRx = /[\u0000-\u001F]/g;
// Converts a given text into an array of objects, each of them representing a
// similarly styled section of the text. Each object carries the `text`, style
// information (`bold`, `textColor`, `bgcolor`, `reverse`, `italic`,
-// `underline`), and `start`/`end` cursors.
+// `underline`, `strikethrough`, `monospace`), and `start`/`end` cursors.
function parseStyle(text) {
const result = [];
let start = 0;
@@ -30,7 +32,7 @@ function parseStyle(text) {
// At any given time, these carry style information since last time a styling
// control code was met.
- let colorCodes, bold, textColor, bgColor, hexColor, hexBgColor, reverse, italic, underline;
+ let colorCodes, bold, textColor, bgColor, hexColor, hexBgColor, reverse, italic, underline, strikethrough, monospace;
const resetStyle = () => {
bold = false;
@@ -41,6 +43,8 @@ function parseStyle(text) {
reverse = false;
italic = false;
underline = false;
+ strikethrough = false;
+ monospace = false;
};
resetStyle();
@@ -68,6 +72,8 @@ function parseStyle(text) {
reverse,
italic,
underline,
+ strikethrough,
+ monospace,
text: processedText,
start: fragmentStart,
end: fragmentStart + processedText.length,
@@ -156,6 +162,16 @@ function parseStyle(text) {
emitFragment();
underline = !underline;
break;
+
+ case STRIKETHROUGH:
+ emitFragment();
+ strikethrough = !strikethrough;
+ break;
+
+ case MONOSPACE:
+ emitFragment();
+ monospace = !monospace;
+ break;
}
// Evaluate the next character at the next iteration
@@ -168,7 +184,7 @@ function parseStyle(text) {
return result;
}
-const properties = ["bold", "textColor", "bgColor", "hexColor", "hexBgColor", "italic", "underline", "reverse"];
+const properties = ["bold", "textColor", "bgColor", "hexColor", "hexBgColor", "italic", "underline", "reverse", "strikethrough", "monospace"];
function prepare(text) {
return parseStyle(text)
diff --git a/client/js/libs/handlebars/parse.js b/client/js/libs/handlebars/parse.js
index ec0a7dc6..71bb3017 100644
--- a/client/js/libs/handlebars/parse.js
+++ b/client/js/libs/handlebars/parse.js
@@ -28,6 +28,12 @@ function createFragment(fragment) {
if (fragment.underline) {
classes.push("irc-underline");
}
+ if (fragment.strikethrough) {
+ classes.push("irc-strikethrough");
+ }
+ if (fragment.monospace) {
+ classes.push("irc-monospace");
+ }
let attributes = classes.length ? ` class="${classes.join(" ")}"` : "";
const escapedText = Handlebars.Utils.escapeExpression(fragment.text);
diff --git a/client/views/windows/help.tpl b/client/views/windows/help.tpl
index 6db8d46d..35ef3ede 100644
--- a/client/views/windows/help.tpl
+++ b/client/views/windows/help.tpl
@@ -65,6 +65,24 @@
+
Ctrl⌘ + O
diff --git a/test/client/js/libs/handlebars/ircmessageparser/parseStyle.js b/test/client/js/libs/handlebars/ircmessageparser/parseStyle.js
index 6d3e225d..99f14a80 100644
--- a/test/client/js/libs/handlebars/ircmessageparser/parseStyle.js
+++ b/test/client/js/libs/handlebars/ircmessageparser/parseStyle.js
@@ -15,6 +15,8 @@ describe("parseStyle", () => {
reverse: false,
italic: false,
underline: false,
+ strikethrough: false,
+ monospace: false,
text: "textwithcontrolcodes",
start: 0,
@@ -37,6 +39,8 @@ describe("parseStyle", () => {
reverse: false,
italic: false,
underline: false,
+ strikethrough: false,
+ monospace: false,
text: "bold",
start: 0,
@@ -48,6 +52,417 @@ describe("parseStyle", () => {
expect(actual).to.deep.equal(expected);
});
+ it("should parse strikethrough", () => {
+ const input = "\x1estrikethrough text\x1e";
+ const expected = [{
+ bold: false,
+ textColor: undefined,
+ bgColor: undefined,
+ hexColor: undefined,
+ hexBgColor: undefined,
+ reverse: false,
+ italic: false,
+ underline: false,
+ strikethrough: true,
+ monospace: false,
+ text: "strikethrough text",
+
+ start: 0,
+ end: 18,
+ }];
+
+ const actual = parseStyle(input);
+
+ expect(actual).to.deep.equal(expected);
+ });
+
+ it("should parse monospace", () => {
+ const input = "\x11monospace text\x1e";
+ const expected = [{
+ bold: false,
+ textColor: undefined,
+ bgColor: undefined,
+ hexColor: undefined,
+ hexBgColor: undefined,
+ reverse: false,
+ italic: false,
+ underline: false,
+ strikethrough: false,
+ monospace: true,
+ text: "monospace text",
+
+ start: 0,
+ end: 14,
+ }];
+
+ const actual = parseStyle(input);
+
+ expect(actual).to.deep.equal(expected);
+ });
+
+ it("should toggle monospace correctly", () => {
+ const input = "toggling \x11on and \x11off and \x11on again\x11";
+ const expected = [{
+ bold: false,
+ textColor: undefined,
+ bgColor: undefined,
+ hexColor: undefined,
+ hexBgColor: undefined,
+ reverse: false,
+ italic: false,
+ underline: false,
+ strikethrough: false,
+ monospace: false,
+ text: "toggling ",
+
+ start: 0,
+ end: 9,
+ }, {
+ bold: false,
+ textColor: undefined,
+ bgColor: undefined,
+ hexColor: undefined,
+ hexBgColor: undefined,
+ reverse: false,
+ italic: false,
+ underline: false,
+ strikethrough: false,
+ monospace: true,
+ text: "on and ",
+
+ start: 9,
+ end: 16,
+ }, {
+ bold: false,
+ textColor: undefined,
+ bgColor: undefined,
+ hexColor: undefined,
+ hexBgColor: undefined,
+ reverse: false,
+ italic: false,
+ underline: false,
+ strikethrough: false,
+ monospace: false,
+ text: "off and ",
+
+ start: 16,
+ end: 24,
+ }, {
+ bold: false,
+ textColor: undefined,
+ bgColor: undefined,
+ hexColor: undefined,
+ hexBgColor: undefined,
+ reverse: false,
+ italic: false,
+ underline: false,
+ strikethrough: false,
+ monospace: true,
+ text: "on again",
+
+ start: 24,
+ end: 32,
+ }];
+
+ const actual = parseStyle(input);
+
+ expect(actual).to.deep.equal(expected);
+ });
+
+ it("should parse monospace and underline", () => {
+ const input = "\x1funderline formatting \x11with monospace\x1f no underline \x11 and vanilla";
+ const expected = [{
+ bold: false,
+ textColor: undefined,
+ bgColor: undefined,
+ hexColor: undefined,
+ hexBgColor: undefined,
+ reverse: false,
+ italic: false,
+ underline: true,
+ strikethrough: false,
+ monospace: false,
+ text: "underline formatting ",
+
+ start: 0,
+ end: 21,
+ }, {
+ bold: false,
+ textColor: undefined,
+ bgColor: undefined,
+ hexColor: undefined,
+ hexBgColor: undefined,
+ reverse: false,
+ italic: false,
+ underline: true,
+ strikethrough: false,
+ monospace: true,
+ text: "with monospace",
+
+ start: 21,
+ end: 35,
+ }, {
+ bold: false,
+ textColor: undefined,
+ bgColor: undefined,
+ hexColor: undefined,
+ hexBgColor: undefined,
+ reverse: false,
+ italic: false,
+ underline: false,
+ strikethrough: false,
+ monospace: true,
+ text: " no underline ",
+
+ start: 35,
+ end: 49,
+ }, {
+ bold: false,
+ textColor: undefined,
+ bgColor: undefined,
+ hexColor: undefined,
+ hexBgColor: undefined,
+ reverse: false,
+ italic: false,
+ underline: false,
+ strikethrough: false,
+ monospace: false,
+ text: " and vanilla",
+
+ start: 49,
+ end: 61,
+ }];
+
+ const actual = parseStyle(input);
+
+ expect(actual).to.deep.equal(expected);
+ });
+
+ it("should parse monospace and text colors", () => {
+ const input = "\x037,9\x11text with color and monospace\x11\x03";
+ const expected = [{
+ bold: false,
+ textColor: 7,
+ bgColor: 9,
+ hexColor: undefined,
+ hexBgColor: undefined,
+ reverse: false,
+ italic: false,
+ underline: false,
+ strikethrough: false,
+ monospace: true,
+ text: "text with color and monospace",
+
+ start: 0,
+ end: 29,
+ }];
+
+ const actual = parseStyle(input);
+
+ expect(actual).to.deep.equal(expected);
+ });
+
+ it("should parse strikethrough and italics", () => {
+ const input = "\x1ditalic formatting \x1ewith strikethrough\x1d no italic \x1e and vanilla";
+ const expected = [{
+ bold: false,
+ textColor: undefined,
+ bgColor: undefined,
+ hexColor: undefined,
+ hexBgColor: undefined,
+ reverse: false,
+ italic: true,
+ underline: false,
+ strikethrough: false,
+ monospace: false,
+ text: "italic formatting ",
+
+ start: 0,
+ end: 18,
+ }, {
+ bold: false,
+ textColor: undefined,
+ bgColor: undefined,
+ hexColor: undefined,
+ hexBgColor: undefined,
+ reverse: false,
+ italic: true,
+ underline: false,
+ strikethrough: true,
+ monospace: false,
+ text: "with strikethrough",
+
+ start: 18,
+ end: 36,
+ }, {
+ bold: false,
+ textColor: undefined,
+ bgColor: undefined,
+ hexColor: undefined,
+ hexBgColor: undefined,
+ reverse: false,
+ italic: false,
+ underline: false,
+ strikethrough: true,
+ monospace: false,
+ text: " no italic ",
+
+ start: 36,
+ end: 47,
+ }, {
+ bold: false,
+ textColor: undefined,
+ bgColor: undefined,
+ hexColor: undefined,
+ hexBgColor: undefined,
+ reverse: false,
+ italic: false,
+ underline: false,
+ strikethrough: false,
+ monospace: false,
+ text: " and vanilla",
+
+ start: 47,
+ end: 59,
+ }];
+
+ const actual = parseStyle(input);
+
+ expect(actual).to.deep.equal(expected);
+ });
+
+ it("should parse strikethrough and text colors", () => {
+ const input = "\x031,2text with color \x1eand strikethrough\x1e\x03";
+ const expected = [{
+ bold: false,
+ textColor: 1,
+ bgColor: 2,
+ hexColor: undefined,
+ hexBgColor: undefined,
+ reverse: false,
+ italic: false,
+ underline: false,
+ strikethrough: false,
+ monospace: false,
+ text: "text with color ",
+
+ start: 0,
+ end: 16,
+ }, {
+ bold: false,
+ textColor: 1,
+ bgColor: 2,
+ hexColor: undefined,
+ hexBgColor: undefined,
+ reverse: false,
+ italic: false,
+ underline: false,
+ strikethrough: true,
+ monospace: false,
+ text: "and strikethrough",
+
+ start: 16,
+ end: 33,
+ }];
+
+ const actual = parseStyle(input);
+
+ expect(actual).to.deep.equal(expected);
+ });
+
+ it("should correctly parse multiple unclosed format tokens", () => {
+ const input = "\x1e\x02\x1d\x033,4string with multiple unclosed formats";
+ const expected = [{
+ bold: true,
+ textColor: 3,
+ bgColor: 4,
+ hexColor: undefined,
+ hexBgColor: undefined,
+ reverse: false,
+ italic: true,
+ underline: false,
+ strikethrough: true,
+ monospace: false,
+ text: "string with multiple unclosed formats",
+
+ start: 0,
+ end: 37,
+ }];
+
+ const actual = parseStyle(input);
+
+ expect(actual).to.deep.equal(expected);
+ });
+
+ it("should toggle strikethrough correctly", () => {
+ const input = "toggling \x1eon and \x1eoff and \x1eon again\x1e";
+ const expected = [{
+ bold: false,
+ textColor: undefined,
+ bgColor: undefined,
+ hexColor: undefined,
+ hexBgColor: undefined,
+ reverse: false,
+ italic: false,
+ underline: false,
+ strikethrough: false,
+ monospace: false,
+ text: "toggling ",
+
+ start: 0,
+ end: 9,
+ }, {
+ bold: false,
+ textColor: undefined,
+ bgColor: undefined,
+ hexColor: undefined,
+ hexBgColor: undefined,
+ reverse: false,
+ italic: false,
+ underline: false,
+ strikethrough: true,
+ monospace: false,
+ text: "on and ",
+
+ start: 9,
+ end: 16,
+ }, {
+ bold: false,
+ textColor: undefined,
+ bgColor: undefined,
+ hexColor: undefined,
+ hexBgColor: undefined,
+ reverse: false,
+ italic: false,
+ underline: false,
+ strikethrough: false,
+ monospace: false,
+ text: "off and ",
+
+ start: 16,
+ end: 24,
+ }, {
+ bold: false,
+ textColor: undefined,
+ bgColor: undefined,
+ hexColor: undefined,
+ hexBgColor: undefined,
+ reverse: false,
+ italic: false,
+ underline: false,
+ strikethrough: true,
+ monospace: false,
+ text: "on again",
+
+ start: 24,
+ end: 32,
+ }];
+
+ const actual = parseStyle(input);
+
+ expect(actual).to.deep.equal(expected);
+ });
+
it("should parse textColor", () => {
const input = "\x038yellowText";
const expected = [{
@@ -59,6 +474,8 @@ describe("parseStyle", () => {
reverse: false,
italic: false,
underline: false,
+ strikethrough: false,
+ monospace: false,
text: "yellowText",
start: 0,
@@ -81,6 +498,8 @@ describe("parseStyle", () => {
reverse: false,
italic: false,
underline: false,
+ strikethrough: false,
+ monospace: false,
text: "yellowBG redText",
start: 0,
@@ -103,6 +522,8 @@ describe("parseStyle", () => {
reverse: false,
italic: true,
underline: false,
+ strikethrough: false,
+ monospace: false,
text: "italic",
start: 0,
@@ -125,6 +546,8 @@ describe("parseStyle", () => {
reverse: false,
italic: false,
underline: false,
+ strikethrough: false,
+ monospace: false,
text: "test ",
start: 0,
@@ -138,6 +561,8 @@ describe("parseStyle", () => {
reverse: false,
italic: false,
underline: false,
+ strikethrough: false,
+ monospace: false,
text: "nice ",
start: 5,
@@ -151,6 +576,8 @@ describe("parseStyle", () => {
reverse: false,
italic: false,
underline: false,
+ strikethrough: false,
+ monospace: false,
text: "RES006 ",
start: 10,
@@ -164,6 +591,8 @@ describe("parseStyle", () => {
reverse: false,
italic: false,
underline: false,
+ strikethrough: false,
+ monospace: false,
text: "colored",
start: 17,
@@ -177,6 +606,8 @@ describe("parseStyle", () => {
reverse: false,
italic: false,
underline: false,
+ strikethrough: false,
+ monospace: false,
text: " background",
start: 24,
@@ -190,6 +621,8 @@ describe("parseStyle", () => {
reverse: false,
italic: false,
underline: false,
+ strikethrough: false,
+ monospace: false,
text: "?",
start: 35,
@@ -212,6 +645,8 @@ describe("parseStyle", () => {
reverse: false,
italic: false,
underline: false,
+ strikethrough: false,
+ monospace: false,
text: "bold",
start: 0,
@@ -225,6 +660,8 @@ describe("parseStyle", () => {
reverse: false,
italic: false,
underline: false,
+ strikethrough: false,
+ monospace: false,
text: "yellow",
start: 4,
@@ -238,6 +675,8 @@ describe("parseStyle", () => {
reverse: false,
italic: false,
underline: false,
+ strikethrough: false,
+ monospace: false,
text: "nonBold",
start: 10,
@@ -251,6 +690,8 @@ describe("parseStyle", () => {
reverse: false,
italic: false,
underline: false,
+ strikethrough: false,
+ monospace: false,
text: "default",
start: 17,
@@ -273,6 +714,8 @@ describe("parseStyle", () => {
reverse: false,
italic: false,
underline: false,
+ strikethrough: false,
+ monospace: false,
text: "bold",
start: 0,
@@ -286,6 +729,8 @@ describe("parseStyle", () => {
reverse: false,
italic: false,
underline: false,
+ strikethrough: false,
+ monospace: false,
text: " ",
start: 4,
@@ -299,6 +744,8 @@ describe("parseStyle", () => {
reverse: false,
italic: false,
underline: false,
+ strikethrough: false,
+ monospace: false,
text: "bold",
start: 5,
@@ -311,7 +758,7 @@ describe("parseStyle", () => {
});
it("should reset all styles", () => {
- const input = "\x02\x034\x16\x1d\x1ffull\x0fnone";
+ const input = "\x11\x1e\x02\x034\x16\x1d\x1ffull\x0fnone";
const expected = [{
bold: true,
textColor: 4,
@@ -321,6 +768,8 @@ describe("parseStyle", () => {
reverse: true,
italic: true,
underline: true,
+ strikethrough: true,
+ monospace: true,
text: "full",
start: 0,
@@ -334,6 +783,8 @@ describe("parseStyle", () => {
reverse: false,
italic: false,
underline: false,
+ strikethrough: false,
+ monospace: false,
text: "none",
start: 4,
@@ -356,6 +807,8 @@ describe("parseStyle", () => {
reverse: false,
italic: false,
underline: false,
+ strikethrough: false,
+ monospace: false,
text: "a",
start: 0,
@@ -380,6 +833,8 @@ describe("parseStyle", () => {
reverse: false,
italic: false,
underline: false,
+ strikethrough: false,
+ monospace: false,
text: rawString,
start: 0,
diff --git a/test/client/js/libs/handlebars/parse.js b/test/client/js/libs/handlebars/parse.js
index 2d26976f..bb9e0efe 100644
--- a/test/client/js/libs/handlebars/parse.js
+++ b/test/client/js/libs/handlebars/parse.js
@@ -235,6 +235,14 @@ describe("parse Handlebars helper", () => {
name: "underline",
input: "\x1funderline",
expected: 'underline',
+ }, {
+ name: "strikethrough",
+ input: "\x1estrikethrough",
+ expected: 'strikethrough',
+ }, {
+ name: "monospace",
+ input: "\x11monospace",
+ expected: 'monospace',
}, {
name: "resets",
input: "\x02bold\x038yellow\x02nonBold\x03default",