diff --git a/client/css/style.css b/client/css/style.css index 9cf83736..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; } diff --git a/client/js/keybinds.js b/client/js/keybinds.js index 0353ef43..eb9f51a4 100644 --- a/client/js/keybinds.js +++ b/client/js/keybinds.js @@ -73,6 +73,7 @@ const colorsHotkeys = { 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 b0b702e6..642b4e52 100644 --- a/client/js/libs/handlebars/ircmessageparser/parseStyle.js +++ b/client/js/libs/handlebars/ircmessageparser/parseStyle.js @@ -9,6 +9,7 @@ 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. @@ -23,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`, `strikethrough`), and `start`/`end` cursors. +// `underline`, `strikethrough`, `monospace`), and `start`/`end` cursors. function parseStyle(text) { const result = []; let start = 0; @@ -31,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, strikethrough; + let colorCodes, bold, textColor, bgColor, hexColor, hexBgColor, reverse, italic, underline, strikethrough, monospace; const resetStyle = () => { bold = false; @@ -43,6 +44,7 @@ function parseStyle(text) { italic = false; underline = false; strikethrough = false; + monospace = false; }; resetStyle(); @@ -71,6 +73,7 @@ function parseStyle(text) { italic, underline, strikethrough, + monospace, text: processedText, start: fragmentStart, end: fragmentStart + processedText.length, @@ -164,6 +167,11 @@ function parseStyle(text) { emitFragment(); strikethrough = !strikethrough; break; + + case MONOSPACE: + emitFragment(); + monospace = !monospace; + break; } // Evaluate the next character at the next iteration @@ -176,7 +184,7 @@ function parseStyle(text) { return result; } -const properties = ["bold", "textColor", "bgColor", "hexColor", "hexBgColor", "italic", "underline", "reverse", "strikethrough"]; +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 fe286337..71bb3017 100644 --- a/client/js/libs/handlebars/parse.js +++ b/client/js/libs/handlebars/parse.js @@ -31,6 +31,9 @@ function createFragment(fragment) { 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 ddf47ad6..35ef3ede 100644 --- a/client/views/windows/help.tpl +++ b/client/views/windows/help.tpl @@ -74,6 +74,15 @@ +
+
+ Ctrl + M +
+
+

Mark all text typed after this shortcut as monospaced.

+
+
+
Ctrl + O diff --git a/test/client/js/libs/handlebars/ircmessageparser/parseStyle.js b/test/client/js/libs/handlebars/ircmessageparser/parseStyle.js index 8f3fb6b1..99f14a80 100644 --- a/test/client/js/libs/handlebars/ircmessageparser/parseStyle.js +++ b/test/client/js/libs/handlebars/ircmessageparser/parseStyle.js @@ -16,6 +16,7 @@ describe("parseStyle", () => { italic: false, underline: false, strikethrough: false, + monospace: false, text: "textwithcontrolcodes", start: 0, @@ -39,6 +40,7 @@ describe("parseStyle", () => { italic: false, underline: false, strikethrough: false, + monospace: false, text: "bold", start: 0, @@ -62,6 +64,7 @@ describe("parseStyle", () => { italic: false, underline: false, strikethrough: true, + monospace: false, text: "strikethrough text", start: 0, @@ -73,6 +76,192 @@ describe("parseStyle", () => { 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 = [{ @@ -85,6 +274,7 @@ describe("parseStyle", () => { italic: true, underline: false, strikethrough: false, + monospace: false, text: "italic formatting ", start: 0, @@ -99,6 +289,7 @@ describe("parseStyle", () => { italic: true, underline: false, strikethrough: true, + monospace: false, text: "with strikethrough", start: 18, @@ -113,6 +304,7 @@ describe("parseStyle", () => { italic: false, underline: false, strikethrough: true, + monospace: false, text: " no italic ", start: 36, @@ -127,6 +319,7 @@ describe("parseStyle", () => { italic: false, underline: false, strikethrough: false, + monospace: false, text: " and vanilla", start: 47, @@ -150,6 +343,7 @@ describe("parseStyle", () => { italic: false, underline: false, strikethrough: false, + monospace: false, text: "text with color ", start: 0, @@ -164,6 +358,7 @@ describe("parseStyle", () => { italic: false, underline: false, strikethrough: true, + monospace: false, text: "and strikethrough", start: 16, @@ -187,6 +382,7 @@ describe("parseStyle", () => { italic: true, underline: false, strikethrough: true, + monospace: false, text: "string with multiple unclosed formats", start: 0, @@ -210,6 +406,7 @@ describe("parseStyle", () => { italic: false, underline: false, strikethrough: false, + monospace: false, text: "toggling ", start: 0, @@ -224,6 +421,7 @@ describe("parseStyle", () => { italic: false, underline: false, strikethrough: true, + monospace: false, text: "on and ", start: 9, @@ -238,6 +436,7 @@ describe("parseStyle", () => { italic: false, underline: false, strikethrough: false, + monospace: false, text: "off and ", start: 16, @@ -252,6 +451,7 @@ describe("parseStyle", () => { italic: false, underline: false, strikethrough: true, + monospace: false, text: "on again", start: 24, @@ -275,6 +475,7 @@ describe("parseStyle", () => { italic: false, underline: false, strikethrough: false, + monospace: false, text: "yellowText", start: 0, @@ -298,6 +499,7 @@ describe("parseStyle", () => { italic: false, underline: false, strikethrough: false, + monospace: false, text: "yellowBG redText", start: 0, @@ -321,6 +523,7 @@ describe("parseStyle", () => { italic: true, underline: false, strikethrough: false, + monospace: false, text: "italic", start: 0, @@ -344,6 +547,7 @@ describe("parseStyle", () => { italic: false, underline: false, strikethrough: false, + monospace: false, text: "test ", start: 0, @@ -358,6 +562,7 @@ describe("parseStyle", () => { italic: false, underline: false, strikethrough: false, + monospace: false, text: "nice ", start: 5, @@ -372,6 +577,7 @@ describe("parseStyle", () => { italic: false, underline: false, strikethrough: false, + monospace: false, text: "RES006 ", start: 10, @@ -386,6 +592,7 @@ describe("parseStyle", () => { italic: false, underline: false, strikethrough: false, + monospace: false, text: "colored", start: 17, @@ -400,6 +607,7 @@ describe("parseStyle", () => { italic: false, underline: false, strikethrough: false, + monospace: false, text: " background", start: 24, @@ -414,6 +622,7 @@ describe("parseStyle", () => { italic: false, underline: false, strikethrough: false, + monospace: false, text: "?", start: 35, @@ -437,6 +646,7 @@ describe("parseStyle", () => { italic: false, underline: false, strikethrough: false, + monospace: false, text: "bold", start: 0, @@ -451,6 +661,7 @@ describe("parseStyle", () => { italic: false, underline: false, strikethrough: false, + monospace: false, text: "yellow", start: 4, @@ -465,6 +676,7 @@ describe("parseStyle", () => { italic: false, underline: false, strikethrough: false, + monospace: false, text: "nonBold", start: 10, @@ -479,6 +691,7 @@ describe("parseStyle", () => { italic: false, underline: false, strikethrough: false, + monospace: false, text: "default", start: 17, @@ -502,6 +715,7 @@ describe("parseStyle", () => { italic: false, underline: false, strikethrough: false, + monospace: false, text: "bold", start: 0, @@ -516,6 +730,7 @@ describe("parseStyle", () => { italic: false, underline: false, strikethrough: false, + monospace: false, text: " ", start: 4, @@ -530,6 +745,7 @@ describe("parseStyle", () => { italic: false, underline: false, strikethrough: false, + monospace: false, text: "bold", start: 5, @@ -542,7 +758,7 @@ describe("parseStyle", () => { }); it("should reset all styles", () => { - const input = "\x1e\x02\x034\x16\x1d\x1ffull\x0fnone"; + const input = "\x11\x1e\x02\x034\x16\x1d\x1ffull\x0fnone"; const expected = [{ bold: true, textColor: 4, @@ -553,6 +769,7 @@ describe("parseStyle", () => { italic: true, underline: true, strikethrough: true, + monospace: true, text: "full", start: 0, @@ -567,6 +784,7 @@ describe("parseStyle", () => { italic: false, underline: false, strikethrough: false, + monospace: false, text: "none", start: 4, @@ -590,6 +808,7 @@ describe("parseStyle", () => { italic: false, underline: false, strikethrough: false, + monospace: false, text: "a", start: 0, @@ -615,6 +834,7 @@ describe("parseStyle", () => { 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 cbec4339..bb9e0efe 100644 --- a/test/client/js/libs/handlebars/parse.js +++ b/test/client/js/libs/handlebars/parse.js @@ -239,6 +239,10 @@ describe("parse Handlebars helper", () => { name: "strikethrough", input: "\x1estrikethrough", expected: 'strikethrough', + }, { + name: "monospace", + input: "\x11monospace", + expected: 'monospace', }, { name: "resets", input: "\x02bold\x038yellow\x02nonBold\x03default",