diff --git a/.gitignore b/.gitignore index d95bdfc..c3376e7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ -.direnv -.vite -Session.vim +node_modules +.env +uno.css +*_templ.go +*_templ.txt diff --git a/ai.txt b/assets/ai.txt similarity index 100% rename from ai.txt rename to assets/ai.txt diff --git a/assets/assets.go b/assets/assets.go new file mode 100644 index 0000000..aa5397e --- /dev/null +++ b/assets/assets.go @@ -0,0 +1,6 @@ +package assets + +import "embed" + +//go:embed brand css fonts img ai.txt robots.txt +var ASSETS embed.FS diff --git a/assets/brand/code-light-asset.svg b/assets/brand/code-light-asset.svg new file mode 100644 index 0000000..9fd5f18 --- /dev/null +++ b/assets/brand/code-light-asset.svg @@ -0,0 +1,288 @@ + + + + diff --git a/assets/brand/creators-light-asset.svg b/assets/brand/creators-light-asset.svg new file mode 100644 index 0000000..5a29835 --- /dev/null +++ b/assets/brand/creators-light-asset.svg @@ -0,0 +1,335 @@ + + + + diff --git a/static/light-asset.svg b/assets/brand/light-asset.svg similarity index 100% rename from static/light-asset.svg rename to assets/brand/light-asset.svg diff --git a/assets/css/index-page.css b/assets/css/index-page.css new file mode 100644 index 0000000..c555d84 --- /dev/null +++ b/assets/css/index-page.css @@ -0,0 +1,8 @@ +#logo svg { + width: 100%; + height: 100%; + + backdrop-filter: blur(5px); + + /* * { fill: #0ff !important; } */ +} diff --git a/assets/css/style.css b/assets/css/style.css new file mode 100644 index 0000000..bf07c2e --- /dev/null +++ b/assets/css/style.css @@ -0,0 +1,36 @@ +@font-face { + font-family: "Cal Sans"; + font-style: normal; + src: url("/assets/fonts/CalSans.woff2") format("woff2"), + url("/assets/fonts/CalSans.woff") format("woff"), + url("/assets/fonts/CalSans.ttf") format("truetype"), +} +@font-face { + font-family: "Karla"; + font-style: normal; + src: url("/assets/fonts/KarlaVF.woff2") format("woff2"), + url("/assets/fonts/KarlaVF.ttf") format("truetype"), + url("/assets/fonts/KarlaMedium.otf") format("opentype"); +} +@font-face { + font-family: "Karla"; + font-style: italic; + src: url("/assets/fonts/KarlaItalicVF.woff2") format("woff2"), + url("/assets/fonts/KarlaItalicVF.ttf") format("truetype"), + url("/assets/fonts/KarlaItalicMedium.otf") format("opentype"); +} + +:root * { + --black: #000000; + --white: #ffffff; + + --cyan: #00ffff; + --purple: #ff00ff; + + --background-00: var(--black); + --foreground-00: var(--white); + --accent-00: var(--white); + + --cal-sans: "Cal Sans", 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif; + --sans: "Karla", 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif; +} diff --git a/assets/fonts/CalSans.ttf b/assets/fonts/CalSans.ttf new file mode 100644 index 0000000..4a2950a Binary files /dev/null and b/assets/fonts/CalSans.ttf differ diff --git a/assets/fonts/CalSans.woff b/assets/fonts/CalSans.woff new file mode 100644 index 0000000..da45991 Binary files /dev/null and b/assets/fonts/CalSans.woff differ diff --git a/static/cal-sans.woff2 b/assets/fonts/CalSans.woff2 similarity index 100% rename from static/cal-sans.woff2 rename to assets/fonts/CalSans.woff2 diff --git a/assets/fonts/KarlaItalicMedium.otf b/assets/fonts/KarlaItalicMedium.otf new file mode 100644 index 0000000..c413a26 Binary files /dev/null and b/assets/fonts/KarlaItalicMedium.otf differ diff --git a/assets/fonts/KarlaItalicVF.ttf b/assets/fonts/KarlaItalicVF.ttf new file mode 100644 index 0000000..7d36b81 Binary files /dev/null and b/assets/fonts/KarlaItalicVF.ttf differ diff --git a/assets/fonts/KarlaItalicVF.woff2 b/assets/fonts/KarlaItalicVF.woff2 new file mode 100644 index 0000000..3efe57f Binary files /dev/null and b/assets/fonts/KarlaItalicVF.woff2 differ diff --git a/assets/fonts/KarlaMedium.otf b/assets/fonts/KarlaMedium.otf new file mode 100644 index 0000000..625457c Binary files /dev/null and b/assets/fonts/KarlaMedium.otf differ diff --git a/assets/fonts/KarlaVF.ttf b/assets/fonts/KarlaVF.ttf new file mode 100644 index 0000000..939d29e Binary files /dev/null and b/assets/fonts/KarlaVF.ttf differ diff --git a/assets/fonts/KarlaVF.woff2 b/assets/fonts/KarlaVF.woff2 new file mode 100644 index 0000000..5620a0f Binary files /dev/null and b/assets/fonts/KarlaVF.woff2 differ diff --git a/assets/img/cat-1.png b/assets/img/cat-1.png new file mode 100644 index 0000000..659fac9 Binary files /dev/null and b/assets/img/cat-1.png differ diff --git a/assets/img/cat-2.png b/assets/img/cat-2.png new file mode 100644 index 0000000..80602f5 Binary files /dev/null and b/assets/img/cat-2.png differ diff --git a/assets/img/cat-3.png b/assets/img/cat-3.png new file mode 100644 index 0000000..21cc8f6 Binary files /dev/null and b/assets/img/cat-3.png differ diff --git a/assets/lib/htmx.d.ts b/assets/lib/htmx.d.ts new file mode 100644 index 0000000..0fc5cab --- /dev/null +++ b/assets/lib/htmx.d.ts @@ -0,0 +1,239 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck + +/** + * @copyright Big Sky Software 2024 + * @license 0BSD + * @copyright Big Sky Software 2024 + * @author Big Sky Software + * + * This source code is copied from HTMX's GitHub repository, located at + * https://github.com/bigskysoftware/htmx/blob/master/dist/htmx.esm.js. + * + * This source code and the original are licensed under the Zero-Clause BSD license, + * which a copy is available in the original [GitHub](https://github.com/bigskysoftware/htmx/blob/master/LICENSE) + * and here below: + * + * Zero-Clause BSD + * ============= + * + * Permission to use, copy, modify, and/or distribute this software for + * any purpose with or without fee is hereby granted. + * + * THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE + * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY + * DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* eslint-disable */ + +export default htmx; +export type HttpVerb = "get" | "head" | "post" | "put" | "delete" | "connect" | "options" | "trace" | "patch"; +export type SwapOptions = { + select?: string; + selectOOB?: string; + eventInfo?: any; + anchor?: string; + contextElement?: Element; + afterSwapCallback?: swapCallback; + afterSettleCallback?: swapCallback; +}; +export type swapCallback = () => any; +export type HtmxSwapStyle = "innerHTML" | "outerHTML" | "beforebegin" | "afterbegin" | "beforeend" | "afterend" | "delete" | "none" | string; +export type HtmxSwapSpecification = { + swapStyle: HtmxSwapStyle; + swapDelay: number; + settleDelay: number; + transition?: boolean; + ignoreTitle?: boolean; + head?: string; + scroll?: "top" | "bottom"; + scrollTarget?: string; + show?: string; + showTarget?: string; + focusScroll?: boolean; +}; +export type ConditionalFunction = ((this: Node, evt: Event) => boolean) & { + source: string; +}; +export type HtmxTriggerSpecification = { + trigger: string; + pollInterval?: number; + eventFilter?: ConditionalFunction; + changed?: boolean; + once?: boolean; + consume?: boolean; + delay?: number; + from?: string; + target?: string; + throttle?: number; + queue?: string; + root?: string; + threshold?: string; +}; +export type HtmxElementValidationError = { + elt: Element; + message: string; + validity: ValidityState; +}; +export type HtmxHeaderSpecification = Record; +export type HtmxAjaxHelperContext = { + source?: Element | string; + event?: Event; + handler?: HtmxAjaxHandler; + target?: Element | string; + swap?: HtmxSwapStyle; + values?: any | FormData; + headers?: Record; + select?: string; +}; +export type HtmxRequestConfig = { + boosted: boolean; + useUrlParams: boolean; + formData: FormData; + /** + * formData proxy + */ + parameters: any; + unfilteredFormData: FormData; + /** + * unfilteredFormData proxy + */ + unfilteredParameters: any; + headers: HtmxHeaderSpecification; + target: Element; + verb: HttpVerb; + errors: HtmxElementValidationError[]; + withCredentials: boolean; + timeout: number; + path: string; + triggeringEvent: Event; +}; +export type HtmxResponseInfo = { + xhr: XMLHttpRequest; + target: Element; + requestConfig: HtmxRequestConfig; + etc: HtmxAjaxEtc; + boosted: boolean; + select: string; + pathInfo: { + requestPath: string; + finalRequestPath: string; + responsePath: string | null; + anchor: string; + }; + failed?: boolean; + successful?: boolean; + keepIndicators?: boolean; +}; +export type HtmxAjaxEtc = { + returnPromise?: boolean; + handler?: HtmxAjaxHandler; + select?: string; + targetOverride?: Element; + swapOverride?: HtmxSwapStyle; + headers?: Record; + values?: any | FormData; + credentials?: boolean; + timeout?: number; +}; +export type HtmxResponseHandlingConfig = { + code?: string; + swap: boolean; + error?: boolean; + ignoreTitle?: boolean; + select?: string; + target?: string; + swapOverride?: string; + event?: string; +}; +export type HtmxBeforeSwapDetails = HtmxResponseInfo & { + shouldSwap: boolean; + serverResponse: any; + isError: boolean; + ignoreTitle: boolean; + selectOverride: string; + swapOverride: string; +}; +export type HtmxAjaxHandler = (elt: Element, responseInfo: HtmxResponseInfo) => any; +export type HtmxSettleTask = (() => void); +export type HtmxSettleInfo = { + tasks: HtmxSettleTask[]; + elts: Element[]; + title?: string; +}; +export type HtmxExtension = { + init: (api: any) => void; + onEvent: (name: string, event: Event | CustomEvent) => boolean; + transformResponse: (text: string, xhr: XMLHttpRequest, elt: Element) => string; + isInlineSwap: (swapStyle: HtmxSwapStyle) => boolean; + handleSwap: (swapStyle: HtmxSwapStyle, target: Node, fragment: Node, settleInfo: HtmxSettleInfo) => boolean | Node[]; + encodeParameters: (xhr: XMLHttpRequest, parameters: FormData, elt: Node) => any | string | null; + getSelectors: () => string[] | null; +}; +declare namespace htmx { + let onLoad: (callback: (elt: Node) => void) => EventListener; + let process: (elt: Element | string) => void; + let on: (arg1: EventTarget | string, arg2: string | EventListener, arg3?: EventListener | any | boolean, arg4?: any | boolean) => EventListener; + let off: (arg1: EventTarget | string, arg2: string | EventListener, arg3?: EventListener) => EventListener; + let trigger: (elt: EventTarget | string, eventName: string, detail?: any | undefined) => boolean; + let ajax: (verb: HttpVerb, path: string, context: Element | string | HtmxAjaxHelperContext) => Promise; + let find: (eltOrSelector: ParentNode | string, selector?: string) => Element | null; + let findAll: (eltOrSelector: ParentNode | string, selector?: string) => NodeListOf; + let closest: (elt: Element | string, selector: string) => Element | null; + function values(elt: Element, type: HttpVerb): any; + let remove: (elt: Node, delay?: number) => void; + let addClass: (elt: Element | string, clazz: string, delay?: number) => void; + let removeClass: (node: Node | string, clazz: string, delay?: number) => void; + let toggleClass: (elt: Element | string, clazz: string) => void; + let takeClass: (elt: Node | string, clazz: string) => void; + let swap: (target: string | Element, content: string, swapSpec: HtmxSwapSpecification, swapOptions?: SwapOptions) => void; + let defineExtension: (name: string, extension: HtmxExtension) => void; + let removeExtension: (name: string) => void; + let logAll: () => void; + let logNone: () => void; + let logger: any; + namespace config { + let historyEnabled: boolean; + let historyCacheSize: number; + let refreshOnHistoryMiss: boolean; + let defaultSwapStyle: HtmxSwapStyle; + let defaultSwapDelay: number; + let defaultSettleDelay: number; + let includeIndicatorStyles: boolean; + let indicatorClass: string; + let requestClass: string; + let addedClass: string; + let settlingClass: string; + let swappingClass: string; + let allowEval: boolean; + let allowScriptTags: boolean; + let inlineScriptNonce: string; + let inlineStyleNonce: string; + let attributesToSettle: string[]; + let withCredentials: boolean; + let timeout: number; + let wsReconnectDelay: "full-jitter" | ((retryCount: number) => number); + let wsBinaryType: BinaryType; + let disableSelector: string; + let scrollBehavior: "auto" | "instant" | "smooth"; + let defaultFocusScroll: boolean; + let getCacheBusterParam: boolean; + let globalViewTransitions: boolean; + let methodsThatUseUrlParams: (HttpVerb)[]; + let selfRequestsOnly: boolean; + let ignoreTitle: boolean; + let scrollIntoViewOnBoost: boolean; + let triggerSpecsCache: any | null; + let disableInheritance: boolean; + let responseHandling: HtmxResponseHandlingConfig[]; + let allowNestedOobSwaps: boolean; + } + let parseInterval: (str: string) => number | undefined; + let _: (str: string) => any; + let version: string; +} diff --git a/assets/lib/htmx.js b/assets/lib/htmx.js new file mode 100644 index 0000000..1742fa7 --- /dev/null +++ b/assets/lib/htmx.js @@ -0,0 +1,5409 @@ +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-nocheck + +/** + * @copyright Big Sky Software 2024 + * @license 0BSD + * @author Big Sky Software + * + * This source code is copied from HTMX's GitHub repository, located at + * https://github.com/bigskysoftware/htmx/blob/master/dist/htmx.esm.js. + * + * This source code and the original are licensed under the Zero-Clause BSD license, + * which a copy is available in the original [GitHub](https://github.com/bigskysoftware/htmx/blob/master/LICENSE) + * and here below: + * + * Zero-Clause BSD + * ============= + * + * Permission to use, copy, modify, and/or distribute this software for + * any purpose with or without fee is hereby granted. + * + * THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE + * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY + * DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT + * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * @author Big Sky Software + */ + +/* eslint-disable */ + +var htmx = (function () { + 'use strict'; + + // Public API + const htmx = { + /** @type {typeof internalEval} */ + _: null, + /** @type {typeof addClassToElement} */ + addClass: null, + /** @type {typeof ajaxHelper} */ + ajax: null, + /** @type {typeof closest} */ + closest: null, + /** + * A property holding the configuration htmx uses at runtime. + * + * Note that using a [meta tag](https://htmx.org/docs/#config) is the preferred mechanism for setting these properties. + * @see https://htmx.org/api/#config + */ + config: { + /** + * The class to temporarily place on elements that htmx has added to the DOM. + * @type string + * @default 'htmx-added' + */ + addedClass: 'htmx-added', + /** + * Allows the use of eval-like functionality in htmx, to enable **hx-vars**, trigger conditions & script tag evaluation. Can be set to **false** for CSP compatibility. + * @type boolean + * @default true + */ + allowEval: true, + /** + * Whether to process OOB swaps on elements that are nested within the main response element. + * @type boolean + * @default true + */ + allowNestedOobSwaps: true, + /** + * If set to false, disables the interpretation of script tags. + * @type boolean + * @default true + */ + allowScriptTags: true, + /** + * The attributes to settle during the settling phase. + * @type string[] + * @default ['class', 'style', 'width', 'height'] + */ + attributesToSettle: ['class', 'style', 'width', 'height'], + /** + * If the focused element should be scrolled into view. + * @type boolean + * @default false + */ + defaultFocusScroll: false, + /** + * The default delay between completing the content swap and settling attributes. + * @type number + * @default 20 + */ + defaultSettleDelay: 20, + /** + * The default delay between receiving a response from the server and doing the swap. + * @type number + * @default 0 + */ + defaultSwapDelay: 0, + /** + * The default swap style to use if **[hx-swap](https://htmx.org/attributes/hx-swap)** is omitted. + * @type HtmxSwapStyle + * @default 'innerHTML' + */ + defaultSwapStyle: 'innerHTML', + /** @type boolean */ + disableInheritance: false, + /** + * @type string + * @default '[hx-disable], [data-hx-disable]' + */ + disableSelector: '[hx-disable], [data-hx-disable]', + /** + * If set to true htmx will include a cache-busting parameter in GET requests to avoid caching partial responses by the browser. + * @type boolean + * @default false + */ + getCacheBusterParam: false, + /** + * If set to true, htmx will use the View Transition API when swapping in new content. + * @type boolean + * @default false + */ + globalViewTransitions: false, + /** + * The number of pages to keep in **localStorage** for history support. + * @type number + * @default 10 + */ + historyCacheSize: 10, + /** + * Whether to use history. + * @type boolean + * @default true + */ + historyEnabled: true, + /** + * If set to true htmx will not update the title of the document when a title tag is found in new content. + * @type boolean + * @default false + */ + ignoreTitle: false, + /** + * If true, htmx will inject a small amount of CSS into the page to make indicators invisible unless the **htmx-indicator** class is present. + * @type boolean + * @default true + */ + includeIndicatorStyles: true, + /** + * The class to place on indicators when a request is in flight. + * @type string + * @default 'htmx-indicator' + */ + indicatorClass: 'htmx-indicator', + /** + * If set, the nonce will be added to inline scripts. + * @type string + * @default '' + */ + inlineScriptNonce: '', + /** + * If set, the nonce will be added to inline styles. + * @type string + * @default '' + */ + inlineStyleNonce: '', + /** + * Htmx will format requests with these methods by encoding their parameters in the URL, not the request body. + * @type {(HttpVerb)[]} + * @default ['get', 'delete'] + */ + methodsThatUseUrlParams: ['get', 'delete'], + /** + * @type boolean + * @default false + */ + refreshOnHistoryMiss: false, + /** + * The class to place on triggering elements when a request is in flight. + * @type string + * @default 'htmx-request' + */ + requestClass: 'htmx-request', + /** @type HtmxResponseHandlingConfig[] */ + responseHandling: [ + { code: '204', swap: false }, + { code: '[23]..', swap: true }, + { code: '[45]..', error: true, swap: false }, + ], + /** + * @type {'auto' | 'instant' | 'smooth'} + * @default 'instant' + */ + scrollBehavior: 'instant', + /** + * Whether the target of a boosted element is scrolled into the viewport. + * @type boolean + * @default true + */ + scrollIntoViewOnBoost: true, + /** + * If set to true, disables htmx-based requests to non-origin hosts. + * @type boolean + * @default false + */ + selfRequestsOnly: true, + /** + * The class to place on target elements when htmx is in the settling phase. + * @type string + * @default 'htmx-settling' + */ + settlingClass: 'htmx-settling', + /** + * The class to place on target elements when htmx is in the swapping phase. + * @type string + * @default 'htmx-swapping' + */ + swappingClass: 'htmx-swapping', + /** + * @type number + * @default 0 + */ + timeout: 0, + /** + * The cache to store evaluated trigger specifications into. + * You may define a simple object to use a never-clearing cache, or implement your own system using a [proxy object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Proxy). + * @type {object | null} + * @default null + */ + triggerSpecsCache: null, + /** + * Allow cross-site Access-Control requests using credentials such as cookies, authorization headers or TLS client certificates. + * @type boolean + * @default false + */ + withCredentials: false, + /** + * The type of binary data being received over the WebSocket connection. + * @type BinaryType + * @default 'blob' + */ + wsBinaryType: 'blob', + /** + * The default implementation of **getWebSocketReconnectDelay** for reconnecting after unexpected connection loss by the event code **Abnormal Closure**, **Service Restart** or **Try Again Later**. + * @type {'full-jitter' | ((retryCount:number) => number)} + * @default "full-jitter" + */ + wsReconnectDelay: 'full-jitter', + }, + /* Extension entrypoints */ + /** @type {typeof defineExtension} */ + defineExtension: null, + /* DOM querying helpers */ + /** @type {typeof find} */ + find: null, + /** @type {typeof findAll} */ + findAll: null, + /* Debugging */ + /** @type {typeof logAll} */ + logAll: null, + /* Debugging */ + /** + * The logger htmx uses to log with. + * @see https://htmx.org/api/#logger + */ + logger: null, + /** @type {typeof logNone} */ + logNone: null, + /** @type {typeof removeEventListenerImpl} */ + off: null, + /** @type {typeof addEventListenerImpl} */ + on: null, + // Tsc madness here, assigning the functions directly results in an invalid TypeScript output, but reassigning is fine + /* Event processing */ + /** @type {typeof onLoadHelper} */ + onLoad: null, + /** @type {typeof parseInterval} */ + parseInterval: null, + /** @type {typeof processNode} */ + process: null, + /* DOM manipulation helpers */ + /** @type {typeof removeElement} */ + remove: null, + /** @type {typeof removeClassFromElement} */ + removeClass: null, + /** @type {typeof removeExtension} */ + removeExtension: null, + /** @type {typeof swap} */ + swap: null, + /** @type {typeof takeClassForElement} */ + takeClass: null, + /** @type {typeof toggleClassOnElement} */ + toggleClass: null, + /** @type {typeof triggerEvent} */ + trigger: null, + /** + * Returns the input values that would resolve for a given element via the htmx value resolution mechanism. + * @param {Element} elt - The element to resolve values on. + * @param {HttpVerb} type - The request type (e.g. **get** or **post**) non-GET's will include the enclosing form of the element. Defaults to **post**. + * @returns {object} + * @see https://htmx.org/api/#values + */ + values: function (elt, type) { + const inputValues = getInputValues(elt, type || 'post'); + return inputValues.values; + }, + version: '2.0.3', + }; + // Tsc madness part 2 + htmx.onLoad = onLoadHelper; + htmx.process = processNode; + htmx.on = addEventListenerImpl; + htmx.off = removeEventListenerImpl; + htmx.trigger = triggerEvent; + htmx.ajax = ajaxHelper; + htmx.find = find; + htmx.findAll = findAll; + htmx.closest = closest; + htmx.remove = removeElement; + htmx.addClass = addClassToElement; + htmx.removeClass = removeClassFromElement; + htmx.toggleClass = toggleClassOnElement; + htmx.takeClass = takeClassForElement; + htmx.swap = swap; + htmx.defineExtension = defineExtension; + htmx.removeExtension = removeExtension; + htmx.logAll = logAll; + htmx.logNone = logNone; + htmx.parseInterval = parseInterval; + htmx._ = internalEval; + + const internalAPI = { + addTriggerHandler, + bodyContains, + canAccessLocalStorage, + filterValues, + findThisElement, + getAttributeValue, + getClosestAttributeValue, + getClosestMatch, + getExpressionVars, + getHeaders, + getInputValues, + getInternalData, + getSwapSpecification, + getTarget, + getTriggerSpecs, + hasAttribute, + makeFragment, + makeSettleInfo, + mergeObjects, + oobSwap, + querySelectorExt, + settleImmediately, + shouldCancel, + swap, + triggerErrorEvent, + triggerEvent, + withExtensions, + }; + + const VERBS = ['get', 'post', 'put', 'delete', 'patch']; + const VERB_SELECTOR = VERBS.map(function (verb) { + return '[hx-' + verb + '], [data-hx-' + verb + ']'; + }).join(', '); + + // = =================================================================== + // Utilities + // = =================================================================== + + /** + * Parses an interval string consistent with the way htmx does. Useful for plugins that have timing-related attributes. + * + * Caution: Accepts an int followed by either **s** or **ms**. All other values use **parseFloat**. + * @param {string} str - Timing string. + * @returns {number|undefined} + * @see https://htmx.org/api/#parseInterval + */ + function parseInterval(str) { + if (str == undefined) { + return undefined; + } + + let interval = NaN; + if (str.slice(-2) == 'ms') { + interval = parseFloat(str.slice(0, -2)); + } + else if (str.slice(-1) == 's') { + interval = parseFloat(str.slice(0, -1)) * 1000; + } + else if (str.slice(-1) == 'm') { + interval = parseFloat(str.slice(0, -1)) * 1000 * 60; + } + else { + interval = parseFloat(str); + } + return isNaN(interval) ? undefined : interval; + } + + /** + * @param {Node} elt + * @param {string} name + * @returns {(string | null)} + */ + function getRawAttribute(elt, name) { + return elt instanceof Element && elt.getAttribute(name); + } + + /** + * @param {Element} elt + * @param {string} qualifiedName + * @returns {boolean} + */ + /** + * Resolve with both hx and data-hx prefixes. + * @param elt + * @param qualifiedName + */ + function hasAttribute(elt, qualifiedName) { + return !!elt.hasAttribute && (elt.hasAttribute(qualifiedName) + || elt.hasAttribute('data-' + qualifiedName)); + } + + /** + * @param {Node} elt + * @param {string} qualifiedName + * @returns {(string | null)} + */ + function getAttributeValue(elt, qualifiedName) { + return getRawAttribute(elt, qualifiedName) || getRawAttribute(elt, 'data-' + qualifiedName); + } + + /** + * @param {Node} elt + * @returns {Node | null} + */ + function parentElt(elt) { + const parent = elt.parentElement; + if (!parent && elt.parentNode instanceof ShadowRoot) return elt.parentNode; + return parent; + } + + /** + * @returns {Document} + */ + function getDocument() { + return document; + } + + /** + * @param {Node} elt + * @param {boolean} global + * @returns {Node|Document} + */ + function getRootNode(elt, global) { + return elt.getRootNode ? elt.getRootNode({ composed: global }) : getDocument(); + } + + /** + * @param {Node} elt + * @param {(e:Node) => boolean} condition + * @returns {Node | null} + */ + function getClosestMatch(elt, condition) { + while (elt && !condition(elt)) { + elt = parentElt(elt); + } + + return elt || null; + } + + /** + * @param {Element} initialElement + * @param {Element} ancestor + * @param {string} attributeName + * @returns {string|null} + */ + function getAttributeValueWithDisinheritance(initialElement, ancestor, attributeName) { + const attributeValue = getAttributeValue(ancestor, attributeName); + const disinherit = getAttributeValue(ancestor, 'hx-disinherit'); + var inherit = getAttributeValue(ancestor, 'hx-inherit'); + if (initialElement !== ancestor) { + if (htmx.config.disableInheritance) { + if (inherit && (inherit === '*' || inherit.split(' ').indexOf(attributeName) >= 0)) { + return attributeValue; + } + else { + return null; + } + } + if (disinherit && (disinherit === '*' || disinherit.split(' ').indexOf(attributeName) >= 0)) { + return 'unset'; + } + } + return attributeValue; + } + + /** + * @param {Element} elt + * @param {string} attributeName + * @returns {string | null} + */ + function getClosestAttributeValue(elt, attributeName) { + let closestAttr = null; + getClosestMatch(elt, function (e) { + return !!(closestAttr = getAttributeValueWithDisinheritance(elt, asElement(e), attributeName)); + }); + if (closestAttr !== 'unset') { + return closestAttr; + } + } + + /** + * @param {Node} elt + * @param {string} selector + * @returns {boolean} + */ + function matches(elt, selector) { + // @ts-ignore: non-standard properties for browser compatibility + // noinspection JSUnresolvedVariable + const matchesFunction = elt instanceof Element && (elt.matches || elt.matchesSelector || elt.msMatchesSelector || elt.mozMatchesSelector || elt.webkitMatchesSelector || elt.oMatchesSelector); + return !!matchesFunction && matchesFunction.call(elt, selector); + } + + /** + * @param {string} str + * @returns {string} + */ + function getStartTag(str) { + const tagMatcher = /<([a-z][^\0\t\n\f\r />]*)/i; + const match = tagMatcher.exec(str); + if (match) { + return match[1].toLowerCase(); + } + else { + return ''; + } + } + + /** + * @param {string} resp + * @returns {Document} + */ + function parseHTML(resp) { + const parser = new DOMParser(); + return parser.parseFromString(resp, 'text/html'); + } + + /** + * @param {DocumentFragment} fragment + * @param {Node} elt + */ + function takeChildrenFor(fragment, elt) { + while (elt.childNodes.length > 0) { + fragment.append(elt.childNodes[0]); + } + } + + /** + * @param {HTMLScriptElement} script + * @returns {HTMLScriptElement} + */ + function duplicateScript(script) { + const newScript = getDocument().createElement('script'); + forEach(script.attributes, function (attr) { + newScript.setAttribute(attr.name, attr.value); + }); + newScript.textContent = script.textContent; + newScript.async = false; + if (htmx.config.inlineScriptNonce) { + newScript.nonce = htmx.config.inlineScriptNonce; + } + return newScript; + } + + /** + * @param {HTMLScriptElement} script + * @returns {boolean} + */ + function isJavaScriptScriptNode(script) { + return script.matches('script') && (script.type === 'text/javascript' || script.type === 'module' || script.type === ''); + } + + /** + * We have to make new copies of script tags that we are going to insert because + * SOME browsers (not saying who, but it involves an element and an animal) don't + * execute scripts created in