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; |