Files
pyscript/pyscriptjs/src/utils.ts
2023-05-06 08:20:08 +02:00

111 lines
3.3 KiB
TypeScript

import { $$ } from 'basic-devtools';
import { _createAlertBanner } from './exceptions';
export function escape(str: string): string {
return str.replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
export function htmlDecode(input: string): string | null {
const doc = new DOMParser().parseFromString(ltrim(escape(input)), 'text/html');
return doc.documentElement.textContent;
}
export function ltrim(code: string): string {
const lines = code.split('\n');
if (lines.length == 0) return code;
const lengths = lines
.filter(line => line.trim().length != 0)
.map(line => {
return line.match(/^\s*/)?.pop()?.length;
});
const k = Math.min(...lengths);
return k != 0 ? lines.map(line => line.substring(k)).join('\n') : code;
}
let _uniqueIdCounter = 0;
export function ensureUniqueId(el: HTMLElement) {
if (el.id === '') el.id = `py-internal-${_uniqueIdCounter++}`;
}
export function showWarning(msg: string, messageType: 'text' | 'html' = 'text'): void {
_createAlertBanner(msg, 'warning', messageType);
}
export function readTextFromPath(path: string) {
const request = new XMLHttpRequest();
request.open('GET', path, false);
request.send();
const returnValue = request.responseText;
return returnValue;
}
export function inJest(): boolean {
return typeof process === 'object' && process.env.JEST_WORKER_ID !== undefined;
}
export function joinPaths(parts: string[], separator = '/') {
const res = parts
.map(function (part) {
return part.trim().replace(/(^[/]*|[/]*$)/g, '');
})
.filter(p => p !== '' && p !== '.')
.join(separator || '/');
if (parts[0].startsWith('/')) {
return '/' + res;
}
return res;
}
export function createDeprecationWarning(msg: string, elementName: string): void {
createSingularWarning(msg, elementName);
}
/** Adds a warning banner with content {msg} at the top of the page if
* and only if no banner containing the {sentinelText} already exists.
* If sentinelText is null, the full text of {msg} is used instead
*
* @param msg {string} The full text content of the warning banner to be displayed
* @param sentinelText {string} [null] The text to match against existing warning banners.
* If null, the full text of 'msg' is used instead.
*/
export function createSingularWarning(msg: string, sentinelText?: string): void {
const banners = $$('.alert-banner, .py-warning', document);
let bannerCount = 0;
for (const banner of banners) {
if (banner.innerHTML.includes(sentinelText || msg)) {
bannerCount++;
}
}
if (bannerCount == 0) {
_createAlertBanner(msg, 'warning');
}
}
/**
* @returns A new asynchronous lock
* @private
*/
export function createLock(): () => Promise<() => void> {
// This is a promise that is resolved when the lock is open, not resolved when lock is held.
let _lock = Promise.resolve();
/**
* Acquire the async lock
* @returns A zero argument function that releases the lock.
* @private
*/
async function acquireLock() {
const old_lock = _lock;
let releaseLock: () => void;
_lock = new Promise(resolve => (releaseLock = resolve));
await old_lock;
return releaseLock;
}
return acquireLock;
}