diff --git a/client/css/style.css b/client/css/style.css index 6ab8c4d3..9cf83736 100644 --- a/client/css/style.css +++ b/client/css/style.css @@ -1947,6 +1947,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..0353ef43 100644 --- a/client/js/keybinds.js +++ b/client/js/keybinds.js @@ -72,6 +72,7 @@ const colorsHotkeys = { u: "\x1F", i: "\x1D", o: "\x0F", + s: "\x1e", }; 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..b0b702e6 100644 --- a/client/js/libs/handlebars/ircmessageparser/parseStyle.js +++ b/client/js/libs/handlebars/ircmessageparser/parseStyle.js @@ -8,6 +8,7 @@ const RESET = "\x0f"; const REVERSE = "\x16"; const ITALIC = "\x1d"; const UNDERLINE = "\x1f"; +const STRIKETHROUGH = "\x1e"; // 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 +23,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`), and `start`/`end` cursors. function parseStyle(text) { const result = []; let start = 0; @@ -30,7 +31,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; const resetStyle = () => { bold = false; @@ -41,6 +42,7 @@ function parseStyle(text) { reverse = false; italic = false; underline = false; + strikethrough = false; }; resetStyle(); @@ -68,6 +70,7 @@ function parseStyle(text) { reverse, italic, underline, + strikethrough, text: processedText, start: fragmentStart, end: fragmentStart + processedText.length, @@ -156,6 +159,11 @@ function parseStyle(text) { emitFragment(); underline = !underline; break; + + case STRIKETHROUGH: + emitFragment(); + strikethrough = !strikethrough; + break; } // Evaluate the next character at the next iteration @@ -168,7 +176,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"]; function prepare(text) { return parseStyle(text) diff --git a/client/js/libs/handlebars/parse.js b/client/js/libs/handlebars/parse.js index ec0a7dc6..fe286337 100644 --- a/client/js/libs/handlebars/parse.js +++ b/client/js/libs/handlebars/parse.js @@ -28,6 +28,9 @@ function createFragment(fragment) { if (fragment.underline) { classes.push("irc-underline"); } + if (fragment.strikethrough) { + classes.push("irc-strikethrough"); + } 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..ddf47ad6 100644 --- a/client/views/windows/help.tpl +++ b/client/views/windows/help.tpl @@ -65,6 +65,15 @@ +
+
+ Ctrl + S +
+
+

Mark all text typed after this shortcut as struck through.

+
+
+
Ctrl + O diff --git a/test/client/js/libs/handlebars/ircmessageparser/parseStyle.js b/test/client/js/libs/handlebars/ircmessageparser/parseStyle.js index 6d3e225d..8f3fb6b1 100644 --- a/test/client/js/libs/handlebars/ircmessageparser/parseStyle.js +++ b/test/client/js/libs/handlebars/ircmessageparser/parseStyle.js @@ -15,6 +15,7 @@ describe("parseStyle", () => { reverse: false, italic: false, underline: false, + strikethrough: false, text: "textwithcontrolcodes", start: 0, @@ -37,6 +38,7 @@ describe("parseStyle", () => { reverse: false, italic: false, underline: false, + strikethrough: false, text: "bold", start: 0, @@ -48,6 +50,219 @@ 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, + text: "strikethrough text", + + start: 0, + end: 18, + }]; + + 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, + 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, + 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, + 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, + 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, + 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, + 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, + 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, + text: "toggling ", + + start: 0, + end: 9, + }, { + bold: false, + textColor: undefined, + bgColor: undefined, + hexColor: undefined, + hexBgColor: undefined, + reverse: false, + italic: false, + underline: false, + strikethrough: 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, + 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, + 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 +274,7 @@ describe("parseStyle", () => { reverse: false, italic: false, underline: false, + strikethrough: false, text: "yellowText", start: 0, @@ -81,6 +297,7 @@ describe("parseStyle", () => { reverse: false, italic: false, underline: false, + strikethrough: false, text: "yellowBG redText", start: 0, @@ -103,6 +320,7 @@ describe("parseStyle", () => { reverse: false, italic: true, underline: false, + strikethrough: false, text: "italic", start: 0, @@ -125,6 +343,7 @@ describe("parseStyle", () => { reverse: false, italic: false, underline: false, + strikethrough: false, text: "test ", start: 0, @@ -138,6 +357,7 @@ describe("parseStyle", () => { reverse: false, italic: false, underline: false, + strikethrough: false, text: "nice ", start: 5, @@ -151,6 +371,7 @@ describe("parseStyle", () => { reverse: false, italic: false, underline: false, + strikethrough: false, text: "RES006 ", start: 10, @@ -164,6 +385,7 @@ describe("parseStyle", () => { reverse: false, italic: false, underline: false, + strikethrough: false, text: "colored", start: 17, @@ -177,6 +399,7 @@ describe("parseStyle", () => { reverse: false, italic: false, underline: false, + strikethrough: false, text: " background", start: 24, @@ -190,6 +413,7 @@ describe("parseStyle", () => { reverse: false, italic: false, underline: false, + strikethrough: false, text: "?", start: 35, @@ -212,6 +436,7 @@ describe("parseStyle", () => { reverse: false, italic: false, underline: false, + strikethrough: false, text: "bold", start: 0, @@ -225,6 +450,7 @@ describe("parseStyle", () => { reverse: false, italic: false, underline: false, + strikethrough: false, text: "yellow", start: 4, @@ -238,6 +464,7 @@ describe("parseStyle", () => { reverse: false, italic: false, underline: false, + strikethrough: false, text: "nonBold", start: 10, @@ -251,6 +478,7 @@ describe("parseStyle", () => { reverse: false, italic: false, underline: false, + strikethrough: false, text: "default", start: 17, @@ -273,6 +501,7 @@ describe("parseStyle", () => { reverse: false, italic: false, underline: false, + strikethrough: false, text: "bold", start: 0, @@ -286,6 +515,7 @@ describe("parseStyle", () => { reverse: false, italic: false, underline: false, + strikethrough: false, text: " ", start: 4, @@ -299,6 +529,7 @@ describe("parseStyle", () => { reverse: false, italic: false, underline: false, + strikethrough: false, text: "bold", start: 5, @@ -311,7 +542,7 @@ describe("parseStyle", () => { }); it("should reset all styles", () => { - const input = "\x02\x034\x16\x1d\x1ffull\x0fnone"; + const input = "\x1e\x02\x034\x16\x1d\x1ffull\x0fnone"; const expected = [{ bold: true, textColor: 4, @@ -321,6 +552,7 @@ describe("parseStyle", () => { reverse: true, italic: true, underline: true, + strikethrough: true, text: "full", start: 0, @@ -334,6 +566,7 @@ describe("parseStyle", () => { reverse: false, italic: false, underline: false, + strikethrough: false, text: "none", start: 4, @@ -356,6 +589,7 @@ describe("parseStyle", () => { reverse: false, italic: false, underline: false, + strikethrough: false, text: "a", start: 0, @@ -380,6 +614,7 @@ describe("parseStyle", () => { reverse: false, italic: false, underline: false, + strikethrough: 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..cbec4339 100644 --- a/test/client/js/libs/handlebars/parse.js +++ b/test/client/js/libs/handlebars/parse.js @@ -235,6 +235,10 @@ describe("parse Handlebars helper", () => { name: "underline", input: "\x1funderline", expected: 'underline', + }, { + name: "strikethrough", + input: "\x1estrikethrough", + expected: 'strikethrough', }, { name: "resets", input: "\x02bold\x038yellow\x02nonBold\x03default",