Quick install for HTML-based projects:
npm install --save-dev
webpack \
clean-webpack-plugin \
css-minimizer-webpack-plugin \
html-webpack-plugin \
html-loader
//@see https://github.com/microsoft/TypeScript/issues/23182#issuecomment-379091887 | |
type swithcNever<T, A, B> = [T] extends [never] ? A : B; | |
//@see https://stackoverflow.com/a/63568058/11407695 | |
type Flatten<T extends any[]> = T extends [infer U, ...infer R] | |
? U extends any[] | |
? [...Flatten<U>, ...Flatten<R>] | |
: [U, ...Flatten<R>] | |
: []; |
type Indices<A> = Exclude<keyof A, keyof any[]>; | |
type valueAtIndexToNever<T extends any[], I extends number> = { | |
[ P in keyof T ] : P extends Indices<T> ? | |
P extends `\${I}` ? never : T[P] : | |
T[P] | |
} | |
type test1 = valueAtIndexToNever<[1,2,3],1>; //[1, never, 3]; |
export const remClasses = ( | |
{ classList }: { classList: DOMTokenList }, | |
...classes: string[] | |
) => classList.remove(...classes); | |
export const addClasses = ( | |
{ classList }: { classList: DOMTokenList }, | |
...classes: string[] | |
) => classList.add(...classes); |
/** | |
* @summary extracts values from select | |
* @param {HTMLSelectElement} sel | |
* @return {string[]} | |
*/ | |
const getSelectVals = ({ options }) => | |
Array.from(options).map(({ value }) => value); | |
/** | |
* @summary checks if select has a value |
import { BaseEncodingOptions } from "fs"; | |
import { appendFile, mkdir } from "fs/promises"; | |
import { parse, sep } from "path"; | |
const recursiveAppend = async ( | |
path: string, | |
encoding: BaseEncodingOptions["encoding"] = "utf-8" | |
) => { | |
const parts = path.split(sep); |
Quick install for HTML-based projects:
npm install --save-dev
webpack \
clean-webpack-plugin \
css-minimizer-webpack-plugin \
html-webpack-plugin \
html-loader
/** | |
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/TextMetrics} | |
* @summary measure text width | |
* @param {CanvasRenderingContext2D} ctx drawing context | |
* @returns {number} | |
*/ | |
const measureWidth = (ctx, text) => { | |
const measurement = ctx.measureText(text); | |
const getFolderByName = (name: string, create = false) => { | |
const iter = DriveApp.getFoldersByName(name); | |
return iter.hasNext() | |
? iter.next() | |
: create | |
? DriveApp.createFolder(name) | |
: null; | |
}; |
/** | |
* @summary converts unicode literal \u{<code point>} into hex byte sequence \x<byte>... | |
*/ | |
const fromUnicodeToHexBytes = (unicode: string) => { | |
//make bytes unsigned -128 +127 -> +256 or & 0xff -> 0 256 | |
return Utilities.newBlob(unicode) | |
.getBytes() | |
.map((bt) => `\\x${(bt & 0xff).toString(16)}`) | |
.join(""); | |
}; |
const withInterval = async ({ | |
timeouts = [], | |
delay = 0, | |
interval = 4, | |
callback, | |
times = 1, | |
stopIf = () => false | |
}) => { | |
if (!times) { | |
return; |