@@ -9,6 +9,8 @@ import {
|
|||||||
test('createElementFromHTML', () => {
|
test('createElementFromHTML', () => {
|
||||||
expect(createElementFromHTML('<a>foo<span>bar</span></a>').outerHTML).toEqual('<a>foo<span>bar</span></a>');
|
expect(createElementFromHTML('<a>foo<span>bar</span></a>').outerHTML).toEqual('<a>foo<span>bar</span></a>');
|
||||||
expect(createElementFromHTML('<tr data-x="1"><td>foo</td></tr>').outerHTML).toEqual('<tr data-x="1"><td>foo</td></tr>');
|
expect(createElementFromHTML('<tr data-x="1"><td>foo</td></tr>').outerHTML).toEqual('<tr data-x="1"><td>foo</td></tr>');
|
||||||
|
expect(createElementFromHTML('<TR data-x="1"><td>foo</td></TR>').outerHTML).toEqual('<tr data-x="1"><td>foo</td></tr>');
|
||||||
|
expect(createElementFromHTML('<trx></trx>').outerHTML).toEqual('<trx></trx>');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('createElementFromAttrs', () => {
|
test('createElementFromAttrs', () => {
|
||||||
|
|||||||
@@ -267,9 +267,14 @@ export function isElemVisible(el: HTMLElement): boolean {
|
|||||||
|
|
||||||
export function createElementFromHTML<T extends Element>(htmlString: string): T {
|
export function createElementFromHTML<T extends Element>(htmlString: string): T {
|
||||||
htmlString = htmlString.trim();
|
htmlString = htmlString.trim();
|
||||||
|
const isLetter = (code: number) => (code >= 65 && code <= 90) || (code >= 97 && code <= 122);
|
||||||
|
const startsWithTag = (s: string, tag: string) => {
|
||||||
|
return s.startsWith('<') &&
|
||||||
|
s.substring(1, 1 + tag.length).toLowerCase() === tag.toLowerCase() &&
|
||||||
|
!isLetter(s[1 + tag.length].charCodeAt(0));
|
||||||
|
};
|
||||||
// There is no way to create some elements without a proper parent, jQuery's approach: https://github.com/jquery/jquery/blob/main/src/manipulation/wrapMap.js
|
// There is no way to create some elements without a proper parent, jQuery's approach: https://github.com/jquery/jquery/blob/main/src/manipulation/wrapMap.js
|
||||||
// eslint-disable-next-line github/unescaped-html-literal
|
if (startsWithTag(htmlString, 'tr')) {
|
||||||
if (htmlString.startsWith('<tr')) {
|
|
||||||
const container = document.createElement('table');
|
const container = document.createElement('table');
|
||||||
container.innerHTML = htmlString;
|
container.innerHTML = htmlString;
|
||||||
return container.querySelector<T>('tr')!;
|
return container.querySelector<T>('tr')!;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import {html, htmlRaw} from './html.ts';
|
||||||
|
|
||||||
export function urlQueryEscape(s: string) {
|
export function urlQueryEscape(s: string) {
|
||||||
// See "TestQueryEscape" in backend
|
// See "TestQueryEscape" in backend
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent#encoding_for_rfc3986
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent#encoding_for_rfc3986
|
||||||
@@ -35,9 +37,9 @@ const urlLinkifyPattern = /(<([-\w]+)[^>]*>)|(<\/([-\w]+)[^>]*>)|(https?:\/\/[^\
|
|||||||
const trailingPunctPattern = /[.,;:!?]+$/;
|
const trailingPunctPattern = /[.,;:!?]+$/;
|
||||||
|
|
||||||
// Convert URLs to clickable links in HTML, preserving existing HTML tags
|
// Convert URLs to clickable links in HTML, preserving existing HTML tags
|
||||||
export function linkifyURLs(html: string): string {
|
export function linkifyURLs(htmlString: string): string {
|
||||||
let inAnchor = false;
|
let inAnchor = false;
|
||||||
return html.replace(urlLinkifyPattern, (match, _openTagFull, openTag, _closeTagFull, closeTag, url) => {
|
return htmlString.replace(urlLinkifyPattern, (match, _openTagFull, openTag, _closeTagFull, closeTag, url) => {
|
||||||
// skip URLs inside existing <a> tags
|
// skip URLs inside existing <a> tags
|
||||||
if (openTag === 'a') {
|
if (openTag === 'a') {
|
||||||
inAnchor = true;
|
inAnchor = true;
|
||||||
@@ -54,6 +56,6 @@ export function linkifyURLs(html: string): string {
|
|||||||
const cleanUrl = trailingPunct ? url.slice(0, -trailingPunct[0].length) : url;
|
const cleanUrl = trailingPunct ? url.slice(0, -trailingPunct[0].length) : url;
|
||||||
const trailing = trailingPunct ? trailingPunct[0] : '';
|
const trailing = trailingPunct ? trailingPunct[0] : '';
|
||||||
// safe because regexp only matches valid URLs (no quotes or angle brackets)
|
// safe because regexp only matches valid URLs (no quotes or angle brackets)
|
||||||
return `<a href="${cleanUrl}" target="_blank">${cleanUrl}</a>${trailing}`; // eslint-disable-line github/unescaped-html-literal
|
return html`<a href="${htmlRaw(cleanUrl)}" target="_blank">${htmlRaw(cleanUrl)}</a>${htmlRaw(trailing)}`;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user