[FIX] mail: linkify message content
closes odoo/odoo#125307 X-original-commit: 406ff4727e361588bded08c6719f932a8b800b35 Signed-off-by: Sébastien Theys (seb) <seb@odoo.com> Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
@@ -8,6 +8,25 @@ import { url } from "@web/core/utils/urls";
|
||||
const urlRegexp =
|
||||
/\b(?:https?:\/\/\d{1,3}(?:\.\d{1,3}){3}|(?:https?:\/\/|(?:www\.))[-a-z0-9@:%._+~#=\u00C0-\u024F\u1E00-\u1EFF]{2,256}\.[a-z]{2,13})\b(?:[-a-z0-9@:%_+~#?&[\]^|{}`\\'$//=\u00C0-\u024F\u1E00-\u1EFF]|,(?!$| )|\.(?!$| |\.)|;(?!$| ))*/gi;
|
||||
|
||||
/**
|
||||
* Escape < > & as html entities
|
||||
*
|
||||
* @param {string}
|
||||
* @return {string}
|
||||
*/
|
||||
const _escapeEntities = (function () {
|
||||
const map = { "&": "&", "<": "<", ">": ">" };
|
||||
const escaper = function (match) {
|
||||
return map[match];
|
||||
};
|
||||
const testRegexp = RegExp("(?:&|<|>)");
|
||||
const replaceRegexp = RegExp("(?:&|<|>)", "g");
|
||||
return function (string) {
|
||||
string = string == null ? "" : "" + string;
|
||||
return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string;
|
||||
};
|
||||
})();
|
||||
|
||||
/**
|
||||
* @param rawBody {string}
|
||||
* @param validRecords {Object}
|
||||
@@ -40,7 +59,7 @@ export async function prettifyMessageContent(rawBody, validRecords = []) {
|
||||
* @param {function} transformFunction
|
||||
* @returns {string}
|
||||
*/
|
||||
function parseAndTransform(htmlString, transformFunction) {
|
||||
export function parseAndTransform(htmlString, transformFunction) {
|
||||
const openToken = "OPEN" + Date.now();
|
||||
const string = htmlString.replace(/</g, openToken);
|
||||
let children;
|
||||
@@ -80,29 +99,25 @@ function _parseAndTransform(nodes, transformFunction) {
|
||||
|
||||
/**
|
||||
* @param {string} text
|
||||
* @param {Object} [attrs={}]
|
||||
* @return {string} linkified text
|
||||
*/
|
||||
function linkify(text, attrs) {
|
||||
attrs = attrs || {};
|
||||
if (attrs.target === undefined) {
|
||||
attrs.target = "_blank";
|
||||
function linkify(text) {
|
||||
let curIndex = 0;
|
||||
let result = "";
|
||||
let match;
|
||||
while ((match = urlRegexp.exec(text)) !== null) {
|
||||
result += _escapeEntities(text.slice(curIndex, match.index));
|
||||
const url = match[0];
|
||||
const href = encodeURI(!/^https?:\/\//i.test(url) ? "http://" + url : url);
|
||||
result += `<a target="_blank" rel="noreferrer noopener" href="${href}">${_escapeEntities(
|
||||
url
|
||||
)}</a>`;
|
||||
curIndex = match.index + match[0].length;
|
||||
}
|
||||
if (attrs.target === "_blank") {
|
||||
attrs.rel = "noreferrer noopener";
|
||||
}
|
||||
attrs = Object.keys(attrs)
|
||||
.map(function (key) {
|
||||
return key + '="' + escape(attrs[key]) + '"';
|
||||
})
|
||||
.join(" ");
|
||||
return text.replace(urlRegexp, function (url) {
|
||||
var href = !/^https?:\/\//i.test(url) ? "http://" + url : url;
|
||||
return "<a " + attrs + ' href="' + href + '">' + url + "</a>";
|
||||
});
|
||||
return result + _escapeEntities(text.slice(curIndex));
|
||||
}
|
||||
|
||||
function addLink(node, transformChildren) {
|
||||
export function addLink(node, transformChildren) {
|
||||
if (node.nodeType === 3) {
|
||||
// text node
|
||||
const linkified = linkify(node.data);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* @odoo-module */
|
||||
|
||||
import * as utils from "@mail/js/utils";
|
||||
import { addLink, parseAndTransform } from "@mail/utils/common/format";
|
||||
import { start, startServer } from "@mail/../tests/helpers/test_utils";
|
||||
|
||||
QUnit.module("Mail utils");
|
||||
@@ -21,7 +21,7 @@ QUnit.test("add_link utility function", function (assert) {
|
||||
};
|
||||
|
||||
for (const [content, willLinkify] of Object.entries(testInputs)) {
|
||||
const output = utils.parseAndTransform(content, utils.addLink);
|
||||
const output = parseAndTransform(content, addLink);
|
||||
if (willLinkify) {
|
||||
assert.strictEqual(output.indexOf("<a "), 0);
|
||||
assert.strictEqual(output.indexOf("</a>"), output.length - 4);
|
||||
@@ -55,14 +55,14 @@ QUnit.test("addLink: utility function and special entities", function (assert) {
|
||||
};
|
||||
|
||||
for (const [content, result] of Object.entries(testInputs)) {
|
||||
const output = utils.parseAndTransform(content, utils.addLink);
|
||||
const output = parseAndTransform(content, addLink);
|
||||
assert.strictEqual(output, result);
|
||||
}
|
||||
});
|
||||
|
||||
QUnit.test("addLink: linkify inside text node (1 occurrence)", function (assert) {
|
||||
const content = "<p>some text https://somelink.com</p>";
|
||||
const linkified = utils.parseAndTransform(content, utils.addLink);
|
||||
const linkified = parseAndTransform(content, addLink);
|
||||
assert.ok(linkified.startsWith("<p>some text <a"));
|
||||
assert.ok(linkified.endsWith("</a></p>"));
|
||||
|
||||
@@ -83,7 +83,7 @@ QUnit.test("addLink: linkify inside text node (2 occurrences)", function (assert
|
||||
// stringified representation, we continue deeper assertion with query
|
||||
// selectors.
|
||||
const content = "<p>some text https://somelink.com and again https://somelink2.com ...</p>";
|
||||
const linkified = utils.parseAndTransform(content, utils.addLink);
|
||||
const linkified = parseAndTransform(content, addLink);
|
||||
const fragment = document.createDocumentFragment();
|
||||
const div = document.createElement("div");
|
||||
fragment.appendChild(div);
|
||||
|
||||
Reference in New Issue
Block a user