snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
This commit is contained in:
87
apps/meteor/app/markdown/lib/parser/original/code.js
Normal file
87
apps/meteor/app/markdown/lib/parser/original/code.js
Normal file
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* code() is a named function that will parse `inline code` and ```codeblock``` syntaxes
|
||||
* @param {Object} message - The message object
|
||||
*/
|
||||
import { unescapeHTML } from '@rocket.chat/tools';
|
||||
|
||||
import { addAsToken } from './token';
|
||||
import hljs, { register } from '../../hljs';
|
||||
|
||||
const inlinecode = (message) => {
|
||||
// Support `text`
|
||||
message.html = message.html.replace(/\`([^`\r\n]+)\`([<_*~]|\B|\b|$)/gm, (match, p1, p2) =>
|
||||
addAsToken(
|
||||
message,
|
||||
`<span class=\"copyonly\">\`</span><span><code class=\"code-colors inline\">${p1}</code></span><span class=\"copyonly\">\`</span>${p2}`,
|
||||
'inlinecode',
|
||||
{ noHtml: match },
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
const codeblocks = (message) => {
|
||||
// Count occurencies of ```
|
||||
const count = (message.html.match(/```/gm) || []).length;
|
||||
|
||||
if (count) {
|
||||
// Check if we need to add a final ```
|
||||
if (count % 2 > 0) {
|
||||
message.html = `${message.html}\n\`\`\``;
|
||||
message.msg = `${message.msg}\n\`\`\``;
|
||||
}
|
||||
|
||||
// Separate text in code blocks and non code blocks
|
||||
const msgParts = message.html.split(/(^.*)(```(?:[a-zA-Z]+)?(?:(?:.|\r|\n)*?)```)(.*\n?)$/gm);
|
||||
|
||||
for (let index = 0; index < msgParts.length; index++) {
|
||||
// Verify if this part is code
|
||||
const part = msgParts[index];
|
||||
const codeMatch = part.match(/^```[\r\n]*(.*[\r\n\ ]?)[\r\n]*([\s\S]*?)```+?$/);
|
||||
|
||||
if (codeMatch) {
|
||||
// Process highlight if this part is code
|
||||
const singleLine = codeMatch[0].indexOf('\n') === -1;
|
||||
const lang = !singleLine && Array.from(hljs.listLanguages()).includes(codeMatch[1].trim()) ? codeMatch[1].trim() : '';
|
||||
const emptyLanguage = lang === '' ? unescapeHTML(codeMatch[1] + codeMatch[2]) : unescapeHTML(codeMatch[2]);
|
||||
const code = singleLine ? unescapeHTML(codeMatch[1]) : emptyLanguage;
|
||||
|
||||
const result = (() => {
|
||||
if (lang) {
|
||||
try {
|
||||
register(lang);
|
||||
return hljs.highlight(lang, code);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
return hljs.highlightAuto(lang + code);
|
||||
})();
|
||||
const token = addAsToken(
|
||||
message,
|
||||
`<pre><code class='code-colors hljs ${result.language}'><span class='copyonly'>\`\`\`<br></span>${result.value}<span class='copyonly'><br>\`\`\`</span></code></pre>`,
|
||||
'code',
|
||||
{
|
||||
noHtml: codeMatch[0],
|
||||
highlight: true,
|
||||
},
|
||||
);
|
||||
|
||||
msgParts[index] = token;
|
||||
} else {
|
||||
msgParts[index] = part;
|
||||
}
|
||||
}
|
||||
|
||||
// Re-mount message
|
||||
message.html = msgParts.join('');
|
||||
}
|
||||
};
|
||||
|
||||
export const code = (message) => {
|
||||
if (message.html?.trim()) {
|
||||
codeblocks(message);
|
||||
inlinecode(message);
|
||||
}
|
||||
|
||||
return message;
|
||||
};
|
||||
194
apps/meteor/app/markdown/lib/parser/original/markdown.js
Normal file
194
apps/meteor/app/markdown/lib/parser/original/markdown.js
Normal file
@@ -0,0 +1,194 @@
|
||||
import { addAsToken, isToken, validateAllowedTokens } from './token';
|
||||
import { getMarkdownLinkRegexes } from '../linkRegexes';
|
||||
|
||||
const validateUrl = (url, message) => {
|
||||
// Don't render markdown inside links
|
||||
if (message?.tokens?.some((token) => url.includes(token.token))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Valid urls don't contain whitespaces
|
||||
if (/\s/.test(url.trim())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
new URL(url);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const endsWithWhitespace = (text) => text.substring(text.length - 1).match(/\s/);
|
||||
|
||||
const getParseableMarkersCount = (start, end) => {
|
||||
const usableMarkers = start.length > 1 ? 2 : 1;
|
||||
return end.length - usableMarkers >= 0 ? usableMarkers : 1;
|
||||
};
|
||||
|
||||
const getTextWrapper = (marker, tagName) => (textPrepend, wrappedText, textAppend) =>
|
||||
`${textPrepend}<span class="copyonly">${marker}</span><${tagName}>${wrappedText}</${tagName}><span class="copyonly">${marker}</span>${textAppend}`;
|
||||
|
||||
const getRegexReplacer = (replaceFunction, getRegex) => (marker, tagName) => {
|
||||
const wrapper = getTextWrapper(marker, tagName);
|
||||
// The regex only depends on the marker, so build it once when the parser is
|
||||
// created instead of on every message. Safe with /g because
|
||||
// String.prototype.replace resets lastIndex on each call.
|
||||
const regex = getRegex(marker);
|
||||
return (msg) => msg.replace(regex, (...args) => replaceFunction(wrapper, ...args));
|
||||
};
|
||||
|
||||
const getParserWithCustomMarker = getRegexReplacer(
|
||||
(wrapper, match, p1, p2, p3) => {
|
||||
if (endsWithWhitespace(p2)) {
|
||||
return match;
|
||||
}
|
||||
const finalMarkerCount = getParseableMarkersCount(p1, p3);
|
||||
return wrapper(p1.substring(finalMarkerCount), p2, p3.substring(finalMarkerCount));
|
||||
},
|
||||
(marker) => new RegExp(`(\\${marker}+(?!\\s))([^\\${marker}\\r\\n]+)(\\${marker}+)`, 'gm'),
|
||||
);
|
||||
|
||||
const parseBold = getParserWithCustomMarker('*', 'strong');
|
||||
|
||||
const parseStrike = getParserWithCustomMarker('~', 'strike');
|
||||
|
||||
const parseItalic = getRegexReplacer(
|
||||
(wrapper, match, p1, p2, p3, p4, p5) => {
|
||||
if (p1 || p5 || endsWithWhitespace(p3)) {
|
||||
return match;
|
||||
}
|
||||
|
||||
const finalMarkerCount = getParseableMarkersCount(p2, p4);
|
||||
return wrapper(p2.substring(finalMarkerCount), p3, p4.substring(finalMarkerCount));
|
||||
},
|
||||
() => new RegExp('([^\\r\\n\\s~*_]){0,1}(\\_+(?!\\s))([^\\_\\r\\n]+)(\\_+)([^\\r\\n\\s]){0,1}', 'gm'),
|
||||
)('_', 'em');
|
||||
|
||||
const parseNotEscaped = (message, { supportSchemesForLink, headers, rootUrl }) => {
|
||||
let msg = message.html;
|
||||
if (!message.tokens) {
|
||||
message.tokens = [];
|
||||
}
|
||||
|
||||
const schemes = (supportSchemesForLink || '').split(',').join('|');
|
||||
const linkRegexes = getMarkdownLinkRegexes(schemes);
|
||||
|
||||
if (headers) {
|
||||
// Support # Text for h1
|
||||
msg = msg.replace(
|
||||
/^# (([\S\w\d-_\/\*\.,\\][ \u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]?)+)/gm,
|
||||
'<h1>$1</h1>',
|
||||
);
|
||||
|
||||
// Support # Text for h2
|
||||
msg = msg.replace(
|
||||
/^## (([\S\w\d-_\/\*\.,\\][ \u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]?)+)/gm,
|
||||
'<h2>$1</h2>',
|
||||
);
|
||||
|
||||
// Support # Text for h3
|
||||
msg = msg.replace(
|
||||
/^### (([\S\w\d-_\/\*\.,\\][ \u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]?)+)/gm,
|
||||
'<h3>$1</h3>',
|
||||
);
|
||||
|
||||
// Support # Text for h4
|
||||
msg = msg.replace(
|
||||
/^#### (([\S\w\d-_\/\*\.,\\][ \u00a0\u1680\u180e\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]?)+)/gm,
|
||||
'<h4>$1</h4>',
|
||||
);
|
||||
}
|
||||
|
||||
// Support *text* to make bold
|
||||
msg = parseBold(msg);
|
||||
|
||||
// Support _text_ to make italics
|
||||
msg = parseItalic(msg);
|
||||
|
||||
// // Support ~text~ to strike through text
|
||||
msg = parseStrike(msg);
|
||||
|
||||
// Support for block quote
|
||||
// >>>
|
||||
// Text
|
||||
// <<<
|
||||
msg = msg.replace(
|
||||
/(?:>){3}\n+([\s\S]*?)\n+(?:<){3}/g,
|
||||
'<blockquote class="background-transparent-darker-before"><span class="copyonly">>>></span>$1<span class="copyonly"><<<</span></blockquote>',
|
||||
);
|
||||
|
||||
// Support >Text for quote
|
||||
msg = msg.replace(
|
||||
/^>(.*)$/gm,
|
||||
'<blockquote class="background-transparent-darker-before"><span class="copyonly">></span>$1</blockquote>',
|
||||
);
|
||||
|
||||
// Remove white-space around blockquote (prevent <br>). Because blockquote is block element.
|
||||
msg = msg.replace(
|
||||
/\s*<blockquote class="background-transparent-darker-before">/gm,
|
||||
'<blockquote class="background-transparent-darker-before">',
|
||||
);
|
||||
msg = msg.replace(/<\/blockquote>\s*/gm, '</blockquote>');
|
||||
|
||||
// Remove new-line between blockquotes.
|
||||
msg = msg.replace(/<\/blockquote>\n<blockquote/gm, '</blockquote><blockquote');
|
||||
|
||||
// Support 
|
||||
msg = msg.replace(linkRegexes.image, (match, title, url) => {
|
||||
if (!validateUrl(url, message)) {
|
||||
return match;
|
||||
}
|
||||
if (isToken(title) && !validateAllowedTokens(message, title, ['bold', 'italic', 'strike'])) {
|
||||
return match;
|
||||
}
|
||||
url = encodeURI(url);
|
||||
|
||||
const target = url.indexOf(rootUrl) === 0 ? '' : '_blank';
|
||||
return addAsToken(
|
||||
message,
|
||||
`<a data-title="${url}" href="${url}" title="${title}" target="${target}" rel="noopener noreferrer"><div class="inline-image" style="background-image: url(${url});"></div></a>`,
|
||||
'link',
|
||||
);
|
||||
});
|
||||
|
||||
// Support [Text](http://link)
|
||||
msg = msg.replace(linkRegexes.link, (match, title, url) => {
|
||||
if (!validateUrl(url, message)) {
|
||||
return match;
|
||||
}
|
||||
if (isToken(title) && !validateAllowedTokens(message, title, ['bold', 'italic', 'strike'])) {
|
||||
return match;
|
||||
}
|
||||
const target = url.indexOf(rootUrl) === 0 ? '' : '_blank';
|
||||
title = title.replace(/&/g, '&');
|
||||
|
||||
const escapedUrl = encodeURI(url);
|
||||
|
||||
return addAsToken(
|
||||
message,
|
||||
`<a data-title="${escapedUrl}" href="${escapedUrl}" target="${target}" rel="noopener noreferrer">${title}</a>`,
|
||||
'link',
|
||||
);
|
||||
});
|
||||
|
||||
// Support <http://link|Text>
|
||||
msg = msg.replace(linkRegexes.pipedLink, (match, url, title) => {
|
||||
if (!validateUrl(url, message)) {
|
||||
return match;
|
||||
}
|
||||
if (isToken(title) && !validateAllowedTokens(message, title, ['bold', 'italic', 'strike'])) {
|
||||
return match;
|
||||
}
|
||||
url = encodeURI(url);
|
||||
const target = url.indexOf(rootUrl) === 0 ? '' : '_blank';
|
||||
return addAsToken(message, `<a data-title="${url}" href="${url}" target="${target}" rel="noopener noreferrer">${title}</a>`, 'link');
|
||||
});
|
||||
return msg;
|
||||
};
|
||||
|
||||
export const markdown = (message, options) => {
|
||||
message.html = parseNotEscaped(message, options);
|
||||
return message;
|
||||
};
|
||||
25
apps/meteor/app/markdown/lib/parser/original/original.js
Normal file
25
apps/meteor/app/markdown/lib/parser/original/original.js
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Markdown is a named function that will parse markdown syntax
|
||||
* @param {Object} message - The message object
|
||||
*/
|
||||
import { code } from './code.js';
|
||||
import { markdown } from './markdown.js';
|
||||
|
||||
export const original = (
|
||||
message,
|
||||
options = {
|
||||
supportSchemesForLink: 'http,https',
|
||||
headers: true,
|
||||
},
|
||||
) => {
|
||||
// Parse markdown code
|
||||
message = code(message);
|
||||
|
||||
// Parse markdown
|
||||
message = markdown(message, options);
|
||||
|
||||
// Replace linebreak to br
|
||||
message.html = message.html.replace(/\n/gm, '<br>');
|
||||
|
||||
return message;
|
||||
};
|
||||
38
apps/meteor/app/markdown/lib/parser/original/token.ts
Normal file
38
apps/meteor/app/markdown/lib/parser/original/token.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Markdown is a named function that will parse markdown syntax
|
||||
* @param {String} msg - The message html
|
||||
*/
|
||||
import type { TokenType, TokenExtra } from '@rocket.chat/core-typings';
|
||||
import { Random } from '@rocket.chat/random';
|
||||
|
||||
type MessageTokens = {
|
||||
tokens?: {
|
||||
token: string;
|
||||
type: TokenType;
|
||||
text: string;
|
||||
extra?: TokenExtra;
|
||||
}[];
|
||||
};
|
||||
|
||||
export const addAsToken = (message: MessageTokens, html: string, type: TokenType, extra?: TokenExtra): string => {
|
||||
if (!message.tokens) {
|
||||
message.tokens = [];
|
||||
}
|
||||
const token = `=!=${Random.id()}=!=`;
|
||||
message.tokens.push({
|
||||
token,
|
||||
type,
|
||||
text: html,
|
||||
...(extra && { ...extra }),
|
||||
});
|
||||
|
||||
return token;
|
||||
};
|
||||
|
||||
export const isToken = (msg: string): boolean => /=!=[.a-z0-9]{17}=!=/gim.test(msg.trim());
|
||||
|
||||
export const validateAllowedTokens = (message: MessageTokens, id: string, desiredTokens: TokenType[]): boolean => {
|
||||
const tokens: string[] = id.match(/=!=[.a-z0-9]{17}=!=/gim) || [];
|
||||
const tokensFound = message.tokens?.filter(({ token }) => tokens.includes(token)) || [];
|
||||
return tokensFound.length === 0 || tokensFound.every((token) => token.type && desiredTokens.includes(token.type));
|
||||
};
|
||||
Reference in New Issue
Block a user