From 81304fe9d8fe68c8aa51d5ddb0b96a9d98162219 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Fri, 3 Jun 2022 20:42:39 -0600 Subject: [PATCH] Chore: migrate katex to ts (#25501) --- .../app/katex/client/{index.js => index.ts} | 111 +++++++++++++----- .../app/katex/server/{index.js => index.ts} | 0 apps/meteor/client/components/Katex.tsx | 2 +- .../client/startup/renderMessage/katex.ts | 2 +- apps/meteor/package.json | 1 + yarn.lock | 8 ++ 6 files changed, 94 insertions(+), 30 deletions(-) rename apps/meteor/app/katex/client/{index.js => index.ts} (55%) rename apps/meteor/app/katex/server/{index.js => index.ts} (100%) diff --git a/apps/meteor/app/katex/client/index.js b/apps/meteor/app/katex/client/index.ts similarity index 55% rename from apps/meteor/app/katex/client/index.js rename to apps/meteor/app/katex/client/index.ts index 139b420af26b..a2cee08f78e4 100644 --- a/apps/meteor/app/katex/client/index.js +++ b/apps/meteor/app/katex/client/index.ts @@ -1,52 +1,71 @@ import { Random } from 'meteor/random'; -import katex from 'katex'; +import KatexPackage from 'katex'; import { unescapeHTML, escapeHTML } from '@rocket.chat/string-helpers'; - import 'katex/dist/katex.min.css'; import './style.css'; +import { IMessage } from '@rocket.chat/core-typings'; class Boundary { - length() { + start: number; + + end: number; + + length(): number { return this.end - this.start; } - extract(str) { + extract(str: string): string { return str.substr(this.start, this.length()); } } +type Delimiter = { + opener: string; + closer: string; + displayMode: boolean; + enabled: () => boolean; +}; + +type OpeningDelimiter = { options: Delimiter; pos: number }; + +type LatexBoundary = { outer: Boundary; inner: Boundary }; + class Katex { - constructor(katex, { dollarSyntax, parenthesisSyntax }) { + katex: KatexPackage; + + delimitersMap: Delimiter[]; + + constructor(katex: KatexPackage, { dollarSyntax, parenthesisSyntax }: { dollarSyntax: boolean; parenthesisSyntax: boolean }) { this.katex = katex; this.delimitersMap = [ { opener: '\\[', closer: '\\]', displayMode: true, - enabled: () => parenthesisSyntax, + enabled: (): boolean => parenthesisSyntax, }, { opener: '\\(', closer: '\\)', displayMode: false, - enabled: () => parenthesisSyntax, + enabled: (): boolean => parenthesisSyntax, }, { opener: '$$', closer: '$$', displayMode: true, - enabled: () => dollarSyntax, + enabled: (): boolean => dollarSyntax, }, { opener: '$', closer: '$', displayMode: false, - enabled: () => dollarSyntax, + enabled: (): boolean => dollarSyntax, }, ]; } - findOpeningDelimiter(str, start) { + findOpeningDelimiter(str: string, start: number): OpeningDelimiter | null { const matches = this.delimitersMap .filter((options) => options.enabled()) .map((options) => ({ @@ -70,7 +89,7 @@ class Katex { return match; } - getLatexBoundaries(str, { options: { closer }, pos }) { + getLatexBoundaries(str: string, { options: { closer }, pos }: OpeningDelimiter): LatexBoundary | null { const closerIndex = str.substr(pos + closer.length).indexOf(closer); if (closerIndex < 0) { return null; @@ -92,15 +111,17 @@ class Katex { } // Searches for the first latex block in the given string - findLatex(str) { + findLatex(str: string): (LatexBoundary & { options: Delimiter }) | null { let start = 0; let openingDelimiterMatch; while ((openingDelimiterMatch = this.findOpeningDelimiter(str, start++)) != null) { const match = this.getLatexBoundaries(str, openingDelimiterMatch); - if (match && match.inner.extract(str).trim().length) { - match.options = openingDelimiterMatch.options; - return match; + if (match?.inner.extract(str).trim().length) { + return { + ...match, + options: openingDelimiterMatch.options, + }; } } @@ -109,7 +130,7 @@ class Katex { // Breaks a message to what comes before, after and to the content of a // matched latex block - extractLatex(str, match) { + extractLatex(str: string, match: LatexBoundary): { before: string; latex: string; after: string } { const before = str.substr(0, match.outer.start); const after = str.substr(match.outer.end); let latex = match.inner.extract(str); @@ -123,9 +144,9 @@ class Katex { // Takes a latex math string and the desired display mode and renders it // to HTML using the KaTeX library - renderLatex = (latex, displayMode) => { + renderLatex = (latex: string, displayMode: Delimiter['displayMode']): string => { try { - return this.katex.renderToString(latex, { + return KatexPackage.renderToString(latex, { displayMode, macros: { '\\href': '\\@secondoftwo', @@ -137,11 +158,15 @@ class Katex { }; // Takes a string and renders all latex blocks inside it - render(str, renderFunction) { + render(str: string, renderFunction: (latex: string, displayMode: Delimiter['displayMode']) => string): string { let result = ''; while (this.findLatex(str) != null) { // Find the first latex block in the string const match = this.findLatex(str); + if (!match) { + continue; + } + const parts = this.extractLatex(str, match); // Add to the reuslt what comes before the latex block as well as @@ -155,7 +180,11 @@ class Katex { return result; } - renderMessage = (message) => { + public renderMessage(message: string): string; + + public renderMessage(message: IMessage): IMessage; + + public renderMessage(message: string | IMessage): string | IMessage { if (typeof message === 'string') { return this.render(message, this.renderLatex); } @@ -170,7 +199,7 @@ class Katex { message.html = this.render(message.html, (latex, displayMode) => { const token = `=!=${Random.id()}=!=`; - message.tokens.push({ + message.tokens?.push({ token, text: this.renderLatex(latex, displayMode), }); @@ -178,14 +207,40 @@ class Katex { }); return message; - }; + } } -export const createKatexMessageRendering = (options) => { - const instance = new Katex(katex, options); - return (message) => instance.renderMessage(message); -}; +export function createKatexMessageRendering( + options: { + dollarSyntax: boolean; + parenthesisSyntax: boolean; + }, + _isMessage: true, +): (message: IMessage) => IMessage; +export function createKatexMessageRendering( + options: { + dollarSyntax: boolean; + parenthesisSyntax: boolean; + }, + _isMessage: false, +): (message: string) => string; +export function createKatexMessageRendering( + options: { + dollarSyntax: boolean; + parenthesisSyntax: boolean; + }, + _isMessage: true | false, +): ((message: string) => string) | ((message: IMessage) => IMessage) { + const instance = new Katex(KatexPackage, options); + if (_isMessage) { + return (message: IMessage): IMessage => instance.renderMessage(message); + } + return (message: string): string => instance.renderMessage(message); +} -export const getKatexHtml = (text, katex) => { - return createKatexMessageRendering({ dollarSyntax: katex.dollarSyntaxEnabled, parenthesisSyntax: katex.parenthesisSyntaxEnabled })(text); +export const getKatexHtml = (text: string, katex: { dollarSyntaxEnabled: boolean; parenthesisSyntaxEnabled: boolean }): string => { + return createKatexMessageRendering( + { dollarSyntax: katex.dollarSyntaxEnabled, parenthesisSyntax: katex.parenthesisSyntaxEnabled }, + false, + )(text); }; diff --git a/apps/meteor/app/katex/server/index.js b/apps/meteor/app/katex/server/index.ts similarity index 100% rename from apps/meteor/app/katex/server/index.js rename to apps/meteor/app/katex/server/index.ts diff --git a/apps/meteor/client/components/Katex.tsx b/apps/meteor/client/components/Katex.tsx index 6a66a1157191..a7cb3f279f4b 100644 --- a/apps/meteor/client/components/Katex.tsx +++ b/apps/meteor/client/components/Katex.tsx @@ -11,7 +11,7 @@ type KatexProps = { }; const Katex = ({ text, options }: KatexProps): ReactElement => ( - + ); export default memo(Katex); diff --git a/apps/meteor/client/startup/renderMessage/katex.ts b/apps/meteor/client/startup/renderMessage/katex.ts index 005181c9b92e..68b76006c117 100644 --- a/apps/meteor/client/startup/renderMessage/katex.ts +++ b/apps/meteor/client/startup/renderMessage/katex.ts @@ -19,7 +19,7 @@ Meteor.startup(() => { }; import('../../../app/katex/client').then(({ createKatexMessageRendering }) => { - const renderMessage = createKatexMessageRendering(options); + const renderMessage = createKatexMessageRendering(options, true); callbacks.remove('renderMessage', 'katex'); callbacks.add('renderMessage', renderMessage, callbacks.priority.HIGH + 1, 'katex'); }); diff --git a/apps/meteor/package.json b/apps/meteor/package.json index 39ddbbd81a67..976b71cf6f2d 100644 --- a/apps/meteor/package.json +++ b/apps/meteor/package.json @@ -207,6 +207,7 @@ "@slack/client": "^4.12.0", "@slack/rtm-api": "^6.0.0", "@types/cookie": "^0.5.1", + "@types/katex": "^0.14.0", "@types/lodash": "^4.14.182", "@types/lodash.debounce": "^4.0.6", "@types/object-path": "^0.11.1", diff --git a/yarn.lock b/yarn.lock index c3570d34a32f..b8f3140d3d2a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4799,6 +4799,7 @@ __metadata: "@types/jsdom": ^16.2.12 "@types/jsdom-global": ^3.0.2 "@types/jsrsasign": ^9.0.3 + "@types/katex": ^0.14.0 "@types/ldapjs": ^2.2.1 "@types/less": ^3.0.2 "@types/lodash": ^4.14.182 @@ -7277,6 +7278,13 @@ __metadata: languageName: node linkType: hard +"@types/katex@npm:^0.14.0": + version: 0.14.0 + resolution: "@types/katex@npm:0.14.0" + checksum: 330e0d0337ba48c87f5b793965fbad673653789bf6e50dfe8d726a7b0cbefd37195055e31503aae629814aa79447e4f23a4b87ad1ac565c0d9a9d9978836f39b + languageName: node + linkType: hard + "@types/keyv@npm:^3.1.1": version: 3.1.4 resolution: "@types/keyv@npm:3.1.4"