folder restructuring, lifecycle stuf, upgrade to htmx 2
This commit is contained in:
parent
d84c8b5552
commit
f50ded8589
28 changed files with 889 additions and 97 deletions
3
assets/css/input.css
Normal file
3
assets/css/input.css
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
14
assets/css/tailwind.config.js
Normal file
14
assets/css/tailwind.config.js
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
const {join} = require("node:path");
|
||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
const root = join(__dirname, "../../");
|
||||||
|
const content = join(root, "**/*.go");
|
||||||
|
|
||||||
|
console.log(content)
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
content: [content],
|
||||||
|
theme: {
|
||||||
|
extend: {},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
};
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
import * as htmx from "htmx.org";
|
import htmx from "htmx.org";
|
||||||
|
|
||||||
htmx.defineExtension("debug", {
|
htmx.defineExtension("debug", {
|
||||||
|
// @ts-ignore
|
||||||
onEvent: function (name, evt) {
|
onEvent: function (name, evt) {
|
||||||
if (console.debug) {
|
if (console.debug) {
|
||||||
console.debug(name, evt.target, evt);
|
console.debug(name);
|
||||||
} else if (console) {
|
} else if (console) {
|
||||||
console.log("DEBUG:", name, evt.target, evt);
|
console.log("DEBUG:", name);
|
||||||
} else {
|
} else {
|
||||||
// noop
|
// noop
|
||||||
}
|
}
|
||||||
21
assets/js/extensions/mutation-error.ts
Normal file
21
assets/js/extensions/mutation-error.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import htmx from "htmx.org";
|
||||||
|
|
||||||
|
htmx.defineExtension("mutation-error", {
|
||||||
|
// @ts-ignore
|
||||||
|
onEvent: (name, evt) => {
|
||||||
|
if (!(evt instanceof CustomEvent)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (name === "htmx:afterRequest") {
|
||||||
|
if (!evt.detail || !evt.detail.xhr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const status = evt.detail.xhr.status;
|
||||||
|
if (status >= 400) {
|
||||||
|
htmx.findAll("[hx-on\\:\\:mutation-error]").forEach((element) => {
|
||||||
|
htmx.trigger(element, "htmx:mutation-error", { status });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import * as htmx from "htmx.org";
|
import htmx from "htmx.org";
|
||||||
|
|
||||||
function dependsOn(pathSpec: any, url: string) {
|
function dependsOn(pathSpec: any, url: string) {
|
||||||
if (pathSpec === "ignore") {
|
if (pathSpec === "ignore") {
|
||||||
|
|
@ -33,7 +33,11 @@ function refreshPath(path: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
htmx.defineExtension("path-deps", {
|
htmx.defineExtension("path-deps", {
|
||||||
|
// @ts-ignore
|
||||||
onEvent: function (name, evt) {
|
onEvent: function (name, evt) {
|
||||||
|
if (!(evt instanceof CustomEvent)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (name === "htmx:beforeOnLoad") {
|
if (name === "htmx:beforeOnLoad") {
|
||||||
const config = evt.detail.requestConfig;
|
const config = evt.detail.requestConfig;
|
||||||
// mutating call
|
// mutating call
|
||||||
136
assets/js/extensions/response-targets.ts
Normal file
136
assets/js/extensions/response-targets.ts
Normal file
|
|
@ -0,0 +1,136 @@
|
||||||
|
import htmx from "htmx.org";
|
||||||
|
const config: any = htmx.config;
|
||||||
|
|
||||||
|
/** @type {import("../htmx").HtmxInternalApi} */
|
||||||
|
let api: any;
|
||||||
|
|
||||||
|
const attrPrefix = "hx-target-";
|
||||||
|
|
||||||
|
// IE11 doesn't support string.startsWith
|
||||||
|
function startsWith(str: string, prefix: string) {
|
||||||
|
return str.substring(0, prefix.length) === prefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {HTMLElement} elt
|
||||||
|
* @param respCodeNumber
|
||||||
|
* @returns {HTMLElement | null}
|
||||||
|
*/
|
||||||
|
function getRespCodeTarget(elt: Element, respCodeNumber: number) {
|
||||||
|
if (!elt || !respCodeNumber) return null;
|
||||||
|
|
||||||
|
const respCode = respCodeNumber.toString();
|
||||||
|
|
||||||
|
// '*' is the original syntax, as the obvious character for a wildcard.
|
||||||
|
// The 'x' alternative was added for maximum compatibility with HTML
|
||||||
|
// templating engines, due to ambiguity around which characters are
|
||||||
|
// supported in HTML attributes.
|
||||||
|
//
|
||||||
|
// Start with the most specific possible attribute and generalize from
|
||||||
|
// there.
|
||||||
|
const attrPossibilities = [
|
||||||
|
respCode,
|
||||||
|
|
||||||
|
respCode.substr(0, 2) + "*",
|
||||||
|
respCode.substr(0, 2) + "x",
|
||||||
|
|
||||||
|
respCode.substr(0, 1) + "*",
|
||||||
|
respCode.substr(0, 1) + "x",
|
||||||
|
respCode.substr(0, 1) + "**",
|
||||||
|
respCode.substr(0, 1) + "xx",
|
||||||
|
|
||||||
|
"*",
|
||||||
|
"x",
|
||||||
|
"***",
|
||||||
|
"xxx",
|
||||||
|
];
|
||||||
|
if (startsWith(respCode, "4") || startsWith(respCode, "5")) {
|
||||||
|
attrPossibilities.push("error");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < attrPossibilities.length; i++) {
|
||||||
|
const attr = attrPrefix + attrPossibilities[i];
|
||||||
|
const attrValue = api.getClosestAttributeValue(elt, attr);
|
||||||
|
if (attrValue) {
|
||||||
|
if (attrValue === "this") {
|
||||||
|
return api.findThisElement(elt, attr);
|
||||||
|
} else {
|
||||||
|
return api.querySelectorExt(elt, attrValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {Event} evt */
|
||||||
|
function handleErrorFlag(evt: CustomEvent) {
|
||||||
|
if (evt.detail.isError) {
|
||||||
|
if (config.responseTargetUnsetsError) {
|
||||||
|
evt.detail.isError = false;
|
||||||
|
}
|
||||||
|
} else if (config.responseTargetSetsError) {
|
||||||
|
evt.detail.isError = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
htmx.defineExtension("response-targets", {
|
||||||
|
// @ts-ignore
|
||||||
|
init: (apiRef) => {
|
||||||
|
api = apiRef;
|
||||||
|
|
||||||
|
if (config.responseTargetUnsetsError === undefined) {
|
||||||
|
config.responseTargetUnsetsError = true;
|
||||||
|
}
|
||||||
|
if (config.responseTargetSetsError === undefined) {
|
||||||
|
config.responseTargetSetsError = false;
|
||||||
|
}
|
||||||
|
if (config.responseTargetPrefersExisting === undefined) {
|
||||||
|
config.responseTargetPrefersExisting = false;
|
||||||
|
}
|
||||||
|
if (config.responseTargetPrefersRetargetHeader === undefined) {
|
||||||
|
config.responseTargetPrefersRetargetHeader = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
onEvent: (name, evt) => {
|
||||||
|
if (!(evt instanceof CustomEvent)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
name === "htmx:beforeSwap" &&
|
||||||
|
evt.detail.xhr &&
|
||||||
|
evt.detail.xhr.status !== 200
|
||||||
|
) {
|
||||||
|
if (evt.detail.target) {
|
||||||
|
if (config.responseTargetPrefersExisting) {
|
||||||
|
evt.detail.shouldSwap = true;
|
||||||
|
handleErrorFlag(evt);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
config.responseTargetPrefersRetargetHeader &&
|
||||||
|
evt.detail.xhr.getAllResponseHeaders().match(/HX-Retarget:/i)
|
||||||
|
) {
|
||||||
|
evt.detail.shouldSwap = true;
|
||||||
|
handleErrorFlag(evt);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!evt.detail.requestConfig) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const target = getRespCodeTarget(
|
||||||
|
evt.detail.requestConfig.elt,
|
||||||
|
evt.detail.xhr.status,
|
||||||
|
);
|
||||||
|
if (target) {
|
||||||
|
handleErrorFlag(evt);
|
||||||
|
evt.detail.shouldSwap = true;
|
||||||
|
evt.detail.target = target;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
43
assets/js/extensions/trigger-children.ts
Normal file
43
assets/js/extensions/trigger-children.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
import htmx, { HtmxSettleInfo, HtmxSwapStyle } from "htmx.org";
|
||||||
|
|
||||||
|
htmx.defineExtension("trigger-children", {
|
||||||
|
onEvent: (name, evt: Event | CustomEvent) => {
|
||||||
|
if (!(evt instanceof CustomEvent)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const target = evt.detail.target as HTMLElement;
|
||||||
|
if (target && target.children) {
|
||||||
|
Array.from(target.children).forEach((e) => {
|
||||||
|
htmx.trigger(e, name, null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
init: function (api: any): void {},
|
||||||
|
transformResponse: function (
|
||||||
|
text: string,
|
||||||
|
xhr: XMLHttpRequest,
|
||||||
|
elt: Element,
|
||||||
|
): string {
|
||||||
|
return text;
|
||||||
|
},
|
||||||
|
isInlineSwap: function (swapStyle: HtmxSwapStyle): boolean {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
handleSwap: function (
|
||||||
|
swapStyle: HtmxSwapStyle,
|
||||||
|
target: Node,
|
||||||
|
fragment: Node,
|
||||||
|
settleInfo: HtmxSettleInfo,
|
||||||
|
): boolean | Node[] {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
encodeParameters: function (
|
||||||
|
xhr: XMLHttpRequest,
|
||||||
|
parameters: FormData,
|
||||||
|
elt: Node,
|
||||||
|
) {},
|
||||||
|
getSelectors: function (): string[] | null {
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -1,18 +1,9 @@
|
||||||
import * as htmx from "htmx.org";
|
import htmx from "htmx.org";
|
||||||
import "./extensions/pathdeps";
|
import "./extensions/pathdeps";
|
||||||
import "./extensions/trigger-children";
|
import "./extensions/trigger-children";
|
||||||
import "./extensions/debug";
|
import "./extensions/debug";
|
||||||
|
import "./extensions/response-targets";
|
||||||
declare module "htmx.org" {
|
import "./extensions/mutation-error";
|
||||||
// Example: Adding type definitions for an exported function
|
|
||||||
export function swap(
|
|
||||||
target: Element,
|
|
||||||
content: string,
|
|
||||||
spec?: {
|
|
||||||
swapStyle?: "innerHTML" | "outerHTML";
|
|
||||||
},
|
|
||||||
): any;
|
|
||||||
}
|
|
||||||
|
|
||||||
function watchUrl(callback: (oldUrl: string, newUrl: string) => void) {
|
function watchUrl(callback: (oldUrl: string, newUrl: string) => void) {
|
||||||
let lastUrl = window.location.href;
|
let lastUrl = window.location.href;
|
||||||
|
|
@ -38,7 +29,11 @@ function onUrlChange(newUrl: string) {
|
||||||
}
|
}
|
||||||
const split = triggers.split(", ");
|
const split = triggers.split(", ");
|
||||||
if (split.find((s) => s === "url")) {
|
if (split.find((s) => s === "url")) {
|
||||||
htmx.swap(element, "url");
|
htmx.swap(element, "url", {
|
||||||
|
swapStyle: "outerHTML",
|
||||||
|
swapDelay: 0,
|
||||||
|
settleDelay: 0,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
for (let [key, values] of url.searchParams) {
|
for (let [key, values] of url.searchParams) {
|
||||||
let eventName = "qs:" + key;
|
let eventName = "qs:" + key;
|
||||||
|
|
@ -60,6 +55,8 @@ function onUrlChange(newUrl: string) {
|
||||||
if (value) {
|
if (value) {
|
||||||
htmx.swap(el, el.getAttribute(name) ?? "", {
|
htmx.swap(el, el.getAttribute(name) ?? "", {
|
||||||
swapStyle: "innerHTML",
|
swapStyle: "innerHTML",
|
||||||
|
swapDelay: 0,
|
||||||
|
settleDelay: 0,
|
||||||
});
|
});
|
||||||
hasMatch = true;
|
hasMatch = true;
|
||||||
break;
|
break;
|
||||||
|
|
@ -72,7 +69,7 @@ function onUrlChange(newUrl: string) {
|
||||||
htmx.swap(
|
htmx.swap(
|
||||||
el,
|
el,
|
||||||
el.getAttribute("hx-match-qp-mapping:" + defaultKey) ?? "",
|
el.getAttribute("hx-match-qp-mapping:" + defaultKey) ?? "",
|
||||||
{ swapStyle: "innerHTML" },
|
{ swapStyle: "innerHTML", swapDelay: 0, settleDelay: 0 },
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
425
js/package-lock.json → assets/js/package-lock.json
generated
425
js/package-lock.json → assets/js/package-lock.json
generated
|
|
@ -9,16 +9,29 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"htmx.org": "^1.9.12"
|
"htmx.org": "~2.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@swc/core": "^1.7.26",
|
"@swc/core": "^1.7.26",
|
||||||
"@types/node": "^22.5.4",
|
"@types/node": "^22.5.4",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
|
"tailwindcss": "^3.4.11",
|
||||||
"tsup": "^8.2.4",
|
"tsup": "^8.2.4",
|
||||||
"typescript": "^5.6.2"
|
"typescript": "^5.6.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@alloc/quick-lru": {
|
||||||
|
"version": "5.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
|
||||||
|
"integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@esbuild/aix-ppc64": {
|
"node_modules/@esbuild/aix-ppc64": {
|
||||||
"version": "0.23.1",
|
"version": "0.23.1",
|
||||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz",
|
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz",
|
||||||
|
|
@ -992,6 +1005,12 @@
|
||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/arg": {
|
||||||
|
"version": "5.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
|
||||||
|
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/array-union": {
|
"node_modules/array-union": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
|
||||||
|
|
@ -1064,6 +1083,15 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/camelcase-css": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/chokidar": {
|
"node_modules/chokidar": {
|
||||||
"version": "3.6.0",
|
"version": "3.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||||
|
|
@ -1138,6 +1166,18 @@
|
||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cssesc": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"cssesc": "bin/cssesc"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.3.7",
|
"version": "4.3.7",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
|
||||||
|
|
@ -1155,6 +1195,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/didyoumean": {
|
||||||
|
"version": "1.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
|
||||||
|
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/dir-glob": {
|
"node_modules/dir-glob": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
|
||||||
|
|
@ -1167,6 +1213,12 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dlv": {
|
||||||
|
"version": "1.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
|
||||||
|
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/eastasianwidth": {
|
"node_modules/eastasianwidth": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||||
|
|
@ -1320,6 +1372,15 @@
|
||||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/function-bind": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/get-stream": {
|
"node_modules/get-stream": {
|
||||||
"version": "6.0.1",
|
"version": "6.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
|
||||||
|
|
@ -1384,10 +1445,22 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/hasown": {
|
||||||
|
"version": "2.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||||
|
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"function-bind": "^1.1.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/htmx.org": {
|
"node_modules/htmx.org": {
|
||||||
"version": "1.9.12",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-1.9.12.tgz",
|
"resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-2.0.2.tgz",
|
||||||
"integrity": "sha512-VZAohXyF7xPGS52IM8d1T1283y+X4D+Owf3qY1NZ9RuBypyu9l8cGsxUMAG5fEAb/DhT7rDoJ9Hpu5/HxFD3cw=="
|
"integrity": "sha512-eUPIpQaWKKstX393XNCRCMJTrqPzikh36Y9RceqsUZLTtlFjFaVDgwZLUsrFk8J2uzZxkkfiy0TE359j2eN6hA=="
|
||||||
},
|
},
|
||||||
"node_modules/human-signals": {
|
"node_modules/human-signals": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
|
|
@ -1419,6 +1492,21 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-core-module": {
|
||||||
|
"version": "2.15.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz",
|
||||||
|
"integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"hasown": "^2.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/is-extglob": {
|
"node_modules/is-extglob": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||||
|
|
@ -1491,6 +1579,15 @@
|
||||||
"@pkgjs/parseargs": "^0.11.0"
|
"@pkgjs/parseargs": "^0.11.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jiti": {
|
||||||
|
"version": "1.21.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz",
|
||||||
|
"integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"jiti": "bin/jiti.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/joycon": {
|
"node_modules/joycon": {
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz",
|
||||||
|
|
@ -1617,6 +1714,24 @@
|
||||||
"thenify-all": "^1.0.0"
|
"thenify-all": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/nanoid": {
|
||||||
|
"version": "3.3.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
|
||||||
|
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ai"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"bin": {
|
||||||
|
"nanoid": "bin/nanoid.cjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/normalize-path": {
|
"node_modules/normalize-path": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||||
|
|
@ -1647,6 +1762,15 @@
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/object-hash": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/onetime": {
|
"node_modules/onetime": {
|
||||||
"version": "5.1.2",
|
"version": "5.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
|
||||||
|
|
@ -1677,6 +1801,12 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/path-parse": {
|
||||||
|
"version": "1.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||||
|
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/path-scurry": {
|
"node_modules/path-scurry": {
|
||||||
"version": "1.11.1",
|
"version": "1.11.1",
|
||||||
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
|
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
|
||||||
|
|
@ -1720,6 +1850,15 @@
|
||||||
"url": "https://github.com/sponsors/jonschlinkert"
|
"url": "https://github.com/sponsors/jonschlinkert"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/pify": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/pirates": {
|
"node_modules/pirates": {
|
||||||
"version": "4.0.6",
|
"version": "4.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
|
||||||
|
|
@ -1729,6 +1868,70 @@
|
||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/postcss": {
|
||||||
|
"version": "8.4.45",
|
||||||
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.45.tgz",
|
||||||
|
"integrity": "sha512-7KTLTdzdZZYscUc65XmjFiB73vBhBfbPztCYdUNvlaso9PrzjzcmjqBPR0lNGkcVlcO4BjiO5rK/qNz+XAen1Q==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/postcss/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "tidelift",
|
||||||
|
"url": "https://tidelift.com/funding/github/npm/postcss"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ai"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"nanoid": "^3.3.7",
|
||||||
|
"picocolors": "^1.0.1",
|
||||||
|
"source-map-js": "^1.2.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^10 || ^12 || >=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postcss-import": {
|
||||||
|
"version": "15.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
|
||||||
|
"integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"postcss-value-parser": "^4.0.0",
|
||||||
|
"read-cache": "^1.0.0",
|
||||||
|
"resolve": "^1.1.7"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"postcss": "^8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postcss-js": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"camelcase-css": "^2.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^12 || ^14 || >= 16"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/postcss/"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"postcss": "^8.4.21"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/postcss-load-config": {
|
"node_modules/postcss-load-config": {
|
||||||
"version": "6.0.1",
|
"version": "6.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz",
|
||||||
|
|
@ -1771,6 +1974,50 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/postcss-nested": {
|
||||||
|
"version": "6.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
|
||||||
|
"integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/postcss/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ai"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"postcss-selector-parser": "^6.1.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"postcss": "^8.2.14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postcss-selector-parser": {
|
||||||
|
"version": "6.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
|
||||||
|
"integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"cssesc": "^3.0.0",
|
||||||
|
"util-deprecate": "^1.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/postcss-value-parser": {
|
||||||
|
"version": "4.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||||
|
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/prettier": {
|
"node_modules/prettier": {
|
||||||
"version": "3.3.3",
|
"version": "3.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz",
|
||||||
|
|
@ -1815,6 +2062,15 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"node_modules/read-cache": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"pify": "^2.3.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/readdirp": {
|
"node_modules/readdirp": {
|
||||||
"version": "3.6.0",
|
"version": "3.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||||
|
|
@ -1827,6 +2083,23 @@
|
||||||
"node": ">=8.10.0"
|
"node": ">=8.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/resolve": {
|
||||||
|
"version": "1.22.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
||||||
|
"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"is-core-module": "^2.13.0",
|
||||||
|
"path-parse": "^1.0.7",
|
||||||
|
"supports-preserve-symlinks-flag": "^1.0.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"resolve": "bin/resolve"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/resolve-from": {
|
"node_modules/resolve-from": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
|
||||||
|
|
@ -1952,6 +2225,15 @@
|
||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/source-map-js": {
|
||||||
|
"version": "1.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||||
|
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/string-width": {
|
"node_modules/string-width": {
|
||||||
"version": "5.1.2",
|
"version": "5.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
|
||||||
|
|
@ -2079,6 +2361,123 @@
|
||||||
"node": ">=16 || 14 >=14.17"
|
"node": ">=16 || 14 >=14.17"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/supports-preserve-symlinks-flag": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.4"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tailwindcss": {
|
||||||
|
"version": "3.4.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.11.tgz",
|
||||||
|
"integrity": "sha512-qhEuBcLemjSJk5ajccN9xJFtM/h0AVCPaA6C92jNP+M2J8kX+eMJHI7R2HFKUvvAsMpcfLILMCFYSeDwpMmlUg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@alloc/quick-lru": "^5.2.0",
|
||||||
|
"arg": "^5.0.2",
|
||||||
|
"chokidar": "^3.5.3",
|
||||||
|
"didyoumean": "^1.2.2",
|
||||||
|
"dlv": "^1.1.3",
|
||||||
|
"fast-glob": "^3.3.0",
|
||||||
|
"glob-parent": "^6.0.2",
|
||||||
|
"is-glob": "^4.0.3",
|
||||||
|
"jiti": "^1.21.0",
|
||||||
|
"lilconfig": "^2.1.0",
|
||||||
|
"micromatch": "^4.0.5",
|
||||||
|
"normalize-path": "^3.0.0",
|
||||||
|
"object-hash": "^3.0.0",
|
||||||
|
"picocolors": "^1.0.0",
|
||||||
|
"postcss": "^8.4.23",
|
||||||
|
"postcss-import": "^15.1.0",
|
||||||
|
"postcss-js": "^4.0.1",
|
||||||
|
"postcss-load-config": "^4.0.1",
|
||||||
|
"postcss-nested": "^6.0.1",
|
||||||
|
"postcss-selector-parser": "^6.0.11",
|
||||||
|
"resolve": "^1.22.2",
|
||||||
|
"sucrase": "^3.32.0"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"tailwind": "lib/cli.js",
|
||||||
|
"tailwindcss": "lib/cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tailwindcss/node_modules/glob-parent": {
|
||||||
|
"version": "6.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
||||||
|
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"is-glob": "^4.0.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.13.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tailwindcss/node_modules/lilconfig": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tailwindcss/node_modules/postcss-load-config": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==",
|
||||||
|
"dev": true,
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/postcss/"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "github",
|
||||||
|
"url": "https://github.com/sponsors/ai"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"lilconfig": "^3.0.0",
|
||||||
|
"yaml": "^2.3.4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"postcss": ">=8.0.9",
|
||||||
|
"ts-node": ">=9.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"postcss": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"ts-node": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/tailwindcss/node_modules/postcss-load-config/node_modules/lilconfig": {
|
||||||
|
"version": "3.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz",
|
||||||
|
"integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/antonk52"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/thenify": {
|
"node_modules/thenify": {
|
||||||
"version": "3.3.1",
|
"version": "3.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
|
||||||
|
|
@ -2206,6 +2605,12 @@
|
||||||
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
|
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/util-deprecate": {
|
||||||
|
"version": "1.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
|
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/webidl-conversions": {
|
"node_modules/webidl-conversions": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
|
||||||
|
|
@ -2328,6 +2733,18 @@
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"node_modules/yaml": {
|
||||||
|
"version": "2.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.1.tgz",
|
||||||
|
"integrity": "sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"yaml": "bin.mjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5,19 +5,21 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"watch": "tsup ./mhtml.ts --watch --config ./tsup.config.ts",
|
"watch": "tsup ./mhtml.ts --watch --config ./tsup.config.ts",
|
||||||
"build": "tsup ./mhtml.ts --minify --config ./tsup.config.ts",
|
"build": "tsup ./mhtml.ts --minify --config ./tsup.config.ts",
|
||||||
|
"tailwind:watch": "npx tailwindcss -i ./input.css -o ./output.css --watch",
|
||||||
"pretty": "prettier --write ."
|
"pretty": "prettier --write ."
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"description": "",
|
"description": "",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"htmx.org": "^1.9.12"
|
"htmx.org": "~2.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@swc/core": "^1.7.26",
|
"@swc/core": "^1.7.26",
|
||||||
"@types/node": "^22.5.4",
|
"@types/node": "^22.5.4",
|
||||||
|
"prettier": "^3.3.3",
|
||||||
|
"tailwindcss": "^3.4.11",
|
||||||
"tsup": "^8.2.4",
|
"tsup": "^8.2.4",
|
||||||
"typescript": "^5.6.2",
|
"typescript": "^5.6.2"
|
||||||
"prettier": "^3.3.3"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -10,6 +10,6 @@
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"forceConsistentCasingInFileNames": true
|
"forceConsistentCasingInFileNames": true
|
||||||
},
|
},
|
||||||
"include": ["./*.ts"],
|
"include": ["./*.ts", "./*.js"],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules"]
|
||||||
}
|
}
|
||||||
|
|
@ -3,19 +3,21 @@ import { defineConfig } from "tsup";
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
format: ["esm"],
|
format: ["esm"],
|
||||||
entry: ["./src/mhtml.ts"],
|
entry: ["./src/mhtml.ts"],
|
||||||
outDir: "./dist",
|
outDir: "./../dist",
|
||||||
dts: false,
|
dts: false,
|
||||||
shims: true,
|
shims: true,
|
||||||
skipNodeModulesBundle: true,
|
skipNodeModulesBundle: true,
|
||||||
clean: true,
|
clean: false,
|
||||||
target: "es6",
|
target: "esnext",
|
||||||
|
treeshake: false,
|
||||||
|
sourcemap: true,
|
||||||
platform: "browser",
|
platform: "browser",
|
||||||
outExtension: () => {
|
outExtension: () => {
|
||||||
return {
|
return {
|
||||||
js: ".js",
|
js: ".js",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
minify: true,
|
minify: false,
|
||||||
bundle: true,
|
bundle: true,
|
||||||
// https://github.com/egoist/tsup/issues/619
|
// https://github.com/egoist/tsup/issues/619
|
||||||
noExternal: [/(.*)/],
|
noExternal: [/(.*)/],
|
||||||
49
features/patient/patient-service.go
Normal file
49
features/patient/patient-service.go
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
package patient
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"mhtml/database"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Patient struct {
|
||||||
|
Name string
|
||||||
|
ReasonForVisit string
|
||||||
|
AppointmentDate time.Time
|
||||||
|
LocationName string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
ctx *fiber.Ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewService(ctx *fiber.Ctx) *Service {
|
||||||
|
return &Service{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreatePatientRequest struct {
|
||||||
|
Name string
|
||||||
|
ReasonForVisit string
|
||||||
|
LocationName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Service) Create(request CreatePatientRequest) error {
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
database.HSet("patients", uuid.New().String(), Patient{
|
||||||
|
Name: request.Name,
|
||||||
|
ReasonForVisit: request.ReasonForVisit,
|
||||||
|
AppointmentDate: time.Now(),
|
||||||
|
LocationName: "New York",
|
||||||
|
})
|
||||||
|
return errors.New("error creating patient")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Service) List() ([]*Patient, error) {
|
||||||
|
patients, err := database.HList[Patient]("patients")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return patients, nil
|
||||||
|
}
|
||||||
|
|
@ -51,12 +51,8 @@ func NewPartial(root Renderable) *Partial {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetFunctionName(i interface{}) string {
|
|
||||||
return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetPartialPath(partial func(ctx *fiber.Ctx) *Partial) string {
|
func GetPartialPath(partial func(ctx *fiber.Ctx) *Partial) string {
|
||||||
return GetFunctionName(partial)
|
return runtime.FuncForPC(reflect.ValueOf(partial).Pointer()).Name()
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetPartialPathWithQs(partial func(ctx *fiber.Ctx) *Partial, qs string) string {
|
func GetPartialPathWithQs(partial func(ctx *fiber.Ctx) *Partial, qs string) string {
|
||||||
|
|
|
||||||
91
h/lifecycle.go
Normal file
91
h/lifecycle.go
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
package h
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
var HxBeforeRequest = "hx-on::before-request"
|
||||||
|
var HxAfterRequest = "hx-on::after-request"
|
||||||
|
var HxOnMutationError = "hx-on::mutation-error"
|
||||||
|
|
||||||
|
type LifeCycle struct {
|
||||||
|
beforeRequest []JsCommand
|
||||||
|
afterRequest []JsCommand
|
||||||
|
onMutationError []JsCommand
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLifeCycle() *LifeCycle {
|
||||||
|
return &LifeCycle{
|
||||||
|
beforeRequest: []JsCommand{},
|
||||||
|
afterRequest: []JsCommand{},
|
||||||
|
onMutationError: []JsCommand{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LifeCycle) BeforeRequest(cmd ...JsCommand) *LifeCycle {
|
||||||
|
l.beforeRequest = append(l.beforeRequest, cmd...)
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LifeCycle) AfterRequest(cmd ...JsCommand) *LifeCycle {
|
||||||
|
l.afterRequest = append(l.afterRequest, cmd...)
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LifeCycle) OnMutationError(cmd ...JsCommand) *LifeCycle {
|
||||||
|
l.onMutationError = append(l.onMutationError, cmd...)
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LifeCycle) Render() *Node {
|
||||||
|
beforeRequest := ""
|
||||||
|
afterReqest := ""
|
||||||
|
onMutationError := ""
|
||||||
|
for _, command := range l.beforeRequest {
|
||||||
|
beforeRequest += fmt.Sprintf("%s;", command.Command)
|
||||||
|
}
|
||||||
|
for _, command := range l.afterRequest {
|
||||||
|
afterReqest += fmt.Sprintf("%s;", command.Command)
|
||||||
|
}
|
||||||
|
for _, command := range l.onMutationError {
|
||||||
|
onMutationError += fmt.Sprintf("%s;", command.Command)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Children(
|
||||||
|
If(beforeRequest != "", Attribute(HxBeforeRequest, beforeRequest)),
|
||||||
|
If(afterReqest != "", Attribute(HxAfterRequest, afterReqest)),
|
||||||
|
If(onMutationError != "", Attribute(HxOnMutationError, onMutationError)),
|
||||||
|
).Render()
|
||||||
|
}
|
||||||
|
|
||||||
|
type JsCommand struct {
|
||||||
|
Command string
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetText(text string) JsCommand {
|
||||||
|
return JsCommand{Command: fmt.Sprintf("this.innerText = '%s'", text)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddAttribute(name, value string) JsCommand {
|
||||||
|
return JsCommand{Command: fmt.Sprintf("this.setAttribute('%s', '%s')", name, value)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func RemoveAttribute(name string) JsCommand {
|
||||||
|
return JsCommand{Command: fmt.Sprintf("this.removeAttribute('%s')", name)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddClass(class string) JsCommand {
|
||||||
|
return JsCommand{Command: fmt.Sprintf("this.classList.add('%s')", class)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func RemoveClass(class string) JsCommand {
|
||||||
|
return JsCommand{Command: fmt.Sprintf("this.classList.remove('%s')", class)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Alert(text string) JsCommand {
|
||||||
|
return JsCommand{Command: fmt.Sprintf("alert('%s')", text)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func EvalJs(js string) JsCommand {
|
||||||
|
return JsCommand{Command: js}
|
||||||
|
}
|
||||||
|
|
@ -41,7 +41,13 @@ func (page Builder) renderNode(node *Node) {
|
||||||
|
|
||||||
flatChildren := make([]Renderable, 0)
|
flatChildren := make([]Renderable, 0)
|
||||||
for _, child := range node.children {
|
for _, child := range node.children {
|
||||||
|
|
||||||
|
if child == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
c := child.Render()
|
c := child.Render()
|
||||||
|
|
||||||
flatChildren = append(flatChildren, child)
|
flatChildren = append(flatChildren, child)
|
||||||
if c.tag == FlagChildrenList {
|
if c.tag == FlagChildrenList {
|
||||||
for _, gc := range c.children {
|
for _, gc := range c.children {
|
||||||
|
|
|
||||||
7
h/tag.go
7
h/tag.go
|
|
@ -276,6 +276,9 @@ func PushQsHeader(ctx *fiber.Ctx, key string, value string) *Headers {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHeaders(headers ...string) *Headers {
|
func NewHeaders(headers ...string) *Headers {
|
||||||
|
if len(headers)%2 != 0 {
|
||||||
|
return &Headers{}
|
||||||
|
}
|
||||||
m := make(Headers)
|
m := make(Headers)
|
||||||
for i := 0; i < len(headers); i++ {
|
for i := 0; i < len(headers); i++ {
|
||||||
m[headers[i]] = headers[i+1]
|
m[headers[i]] = headers[i+1]
|
||||||
|
|
@ -372,6 +375,10 @@ func BeforeRequestSetAttribute(key string, value string) Renderable {
|
||||||
return Attribute("hx-on::before-request", `this.setAttribute('`+key+`', '`+value+`')`)
|
return Attribute("hx-on::before-request", `this.setAttribute('`+key+`', '`+value+`')`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func OnMutationErrorSetText(text string) Renderable {
|
||||||
|
return Attribute("hx-on::mutation-error", `this.innerText = '`+text+`'`)
|
||||||
|
}
|
||||||
|
|
||||||
func BeforeRequestSetText(text string) Renderable {
|
func BeforeRequestSetText(text string) Renderable {
|
||||||
return Attribute("hx-on::before-request", `this.innerText = '`+text+`'`)
|
return Attribute("hx-on::before-request", `this.innerText = '`+text+`'`)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
import * as htmx from "htmx.org";
|
|
||||||
import { HtmxEvent } from "htmx.org";
|
|
||||||
|
|
||||||
htmx.defineExtension("trigger-children", {
|
|
||||||
onEvent: (name: HtmxEvent, evt: CustomEvent) => {
|
|
||||||
const target = evt.detail.target as HTMLElement;
|
|
||||||
if (target && target.children) {
|
|
||||||
Array.from(target.children).forEach((e) => {
|
|
||||||
htmx.trigger(e, name, null);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
13
justfile
13
justfile
|
|
@ -9,4 +9,15 @@ watch-gen:
|
||||||
go run ./tooling/watch.go --command 'go run ./tooling/astgen'
|
go run ./tooling/watch.go --command 'go run ./tooling/astgen'
|
||||||
|
|
||||||
watch-js:
|
watch-js:
|
||||||
cd js && npm run build && npm run watch
|
cd assets/js && npm run build && npm run watch
|
||||||
|
|
||||||
|
setup-tailwind-cli:
|
||||||
|
cd assets/css && curl -sLO https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-macos-arm64
|
||||||
|
cd assets/css && chmod +x tailwindcss-macos-arm64
|
||||||
|
cd assets/css && mv tailwindcss-macos-arm64 tailwindcss
|
||||||
|
|
||||||
|
watch-css:
|
||||||
|
cd assets/css && ./tailwindcss -i input.css -o ./../dist/main.css --watch
|
||||||
|
|
||||||
|
build-css:
|
||||||
|
cd assets/css && ./tailwindcss -i input.css -o ./../dist/main.css --minify
|
||||||
18
k6.js
18
k6.js
|
|
@ -1,18 +0,0 @@
|
||||||
import http from 'k6/http';
|
|
||||||
import { sleep } from 'k6';
|
|
||||||
|
|
||||||
export let options = {
|
|
||||||
stages: [
|
|
||||||
{ duration: '1m', target: 100 }, // Ramp-up to 100 RPS over 1 minute
|
|
||||||
{ duration: '10m', target: 100 }, // Stay at 100 RPS for 10 minutes
|
|
||||||
{ duration: '1m', target: 0 }, // Ramp-down to 0 RPS
|
|
||||||
],
|
|
||||||
thresholds: {
|
|
||||||
http_req_duration: ['p(95)<500'], // 95% of requests should be below 500ms
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function () {
|
|
||||||
http.get('http://localhost:3000/patients');
|
|
||||||
sleep(1 / 100); // Make 100 requests per second
|
|
||||||
}
|
|
||||||
3
main.go
3
main.go
|
|
@ -12,7 +12,8 @@ import (
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
f := fiber.New()
|
f := fiber.New()
|
||||||
f.Static("/js", "./js")
|
|
||||||
|
f.Static("/public", "./assets/dist")
|
||||||
|
|
||||||
f.Use(func(ctx *fiber.Ctx) error {
|
f.Use(func(ctx *fiber.Ctx) error {
|
||||||
if ctx.Cookies("mhtml-session") != "" {
|
if ctx.Cookies("mhtml-session") != "" {
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import (
|
||||||
|
|
||||||
func RootPage(children ...h.Renderable) h.Renderable {
|
func RootPage(children ...h.Renderable) h.Renderable {
|
||||||
return h.Html(
|
return h.Html(
|
||||||
h.HxExtension("path-deps"),
|
h.HxExtension("path-deps, response-targets, mutation-error"),
|
||||||
h.Head(
|
h.Head(
|
||||||
h.Script("https://cdn.tailwindcss.com"),
|
h.Script("https://cdn.tailwindcss.com"),
|
||||||
h.Script("/js/dist/mhtml.js"),
|
h.Script("/js/dist/mhtml.js"),
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,15 @@ package pages
|
||||||
import (
|
import (
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
"mhtml/h"
|
"mhtml/h"
|
||||||
"mhtml/pages/base"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func IndexPage(c *fiber.Ctx) *h.Page {
|
func IndexPage(c *fiber.Ctx) *h.Page {
|
||||||
return h.NewPage(base.RootPage(h.P("this is cool")))
|
return h.NewPage(h.Html(
|
||||||
|
h.HxExtension("path-deps, response-targets, mutation-error"),
|
||||||
|
h.Head(
|
||||||
|
h.Script("https://cdn.tailwindcss.com"),
|
||||||
|
h.Script("/js/dist/mhtml.js"),
|
||||||
|
),
|
||||||
|
h.Body(),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,9 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
"mhtml/h"
|
"mhtml/h"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test(ctx *fiber.Ctx) *h.Page {
|
func Test(ctx *fiber.Ctx) *h.Page {
|
||||||
time.Sleep(time.Second * 1)
|
|
||||||
text := fmt.Sprintf("News ID: %s", ctx.Params("id"))
|
text := fmt.Sprintf("News ID: %s", ctx.Params("id"))
|
||||||
return h.NewPage(
|
return h.NewPage(
|
||||||
h.Div(h.Text(text)),
|
h.Div(h.Text(text)),
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,9 @@ package patient
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
"github.com/google/uuid"
|
"mhtml/features/patient"
|
||||||
"mhtml/database"
|
|
||||||
"mhtml/h"
|
"mhtml/h"
|
||||||
"mhtml/partials/sheet"
|
"mhtml/partials/sheet"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Create(ctx *fiber.Ctx) *h.Partial {
|
func Create(ctx *fiber.Ctx) *h.Partial {
|
||||||
|
|
@ -14,13 +12,22 @@ func Create(ctx *fiber.Ctx) *h.Partial {
|
||||||
reason := ctx.FormValue("reason-for-visit")
|
reason := ctx.FormValue("reason-for-visit")
|
||||||
location := ctx.FormValue("location-name")
|
location := ctx.FormValue("location-name")
|
||||||
|
|
||||||
database.HSet("patients", uuid.New().String(), Patient{
|
err := patient.NewService(ctx).Create(patient.CreatePatientRequest{
|
||||||
Name: name,
|
Name: name,
|
||||||
ReasonForVisit: reason,
|
ReasonForVisit: reason,
|
||||||
AppointmentDate: time.Now(),
|
|
||||||
LocationName: location,
|
LocationName: location,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ctx.Status(500)
|
||||||
|
return h.NewPartialWithHeaders(h.NewHeaders(""),
|
||||||
|
h.Div(
|
||||||
|
h.Text("Error creating patient"),
|
||||||
|
h.Class("text-red-500"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
headers := h.CombineHeaders(h.PushQsHeader(ctx, "adding", ""), &map[string]string{
|
headers := h.CombineHeaders(h.PushQsHeader(ctx, "adding", ""), &map[string]string{
|
||||||
"HX-Trigger": "patient-added",
|
"HX-Trigger": "patient-added",
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -2,23 +2,15 @@ package patient
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
"mhtml/database"
|
"mhtml/features/patient"
|
||||||
"mhtml/h"
|
"mhtml/h"
|
||||||
"mhtml/partials/sheet"
|
"mhtml/partials/sheet"
|
||||||
"mhtml/ui"
|
"mhtml/ui"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Patient struct {
|
|
||||||
Name string
|
|
||||||
ReasonForVisit string
|
|
||||||
AppointmentDate time.Time
|
|
||||||
LocationName string
|
|
||||||
}
|
|
||||||
|
|
||||||
func List(ctx *fiber.Ctx) *h.Partial {
|
func List(ctx *fiber.Ctx) *h.Partial {
|
||||||
patients, err := database.HList[Patient]("patients")
|
patients, err := patient.NewService(ctx).List()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return h.NewPartial(h.Div(
|
return h.NewPartial(h.Div(
|
||||||
|
|
@ -35,7 +27,6 @@ func List(ctx *fiber.Ctx) *h.Partial {
|
||||||
}
|
}
|
||||||
|
|
||||||
return h.NewPartial(h.Div(
|
return h.NewPartial(h.Div(
|
||||||
h.HxExtension("debug"),
|
|
||||||
h.Class("mt-8"),
|
h.Class("mt-8"),
|
||||||
h.Id("patient-list"),
|
h.Id("patient-list"),
|
||||||
h.List(patients, Row),
|
h.List(patients, Row),
|
||||||
|
|
@ -92,7 +83,8 @@ func ValidateForm(ctx *fiber.Ctx) *h.Partial {
|
||||||
|
|
||||||
func addPatientForm() h.Renderable {
|
func addPatientForm() h.Renderable {
|
||||||
return h.Form(
|
return h.Form(
|
||||||
h.TriggerChildren(),
|
h.HxExtension("debug, trigger-children"),
|
||||||
|
h.Attribute("hx-target-5*", "#submit-error"),
|
||||||
h.Post(h.GetPartialPath(Create)),
|
h.Post(h.GetPartialPath(Create)),
|
||||||
h.Class("flex flex-col gap-2"),
|
h.Class("flex flex-col gap-2"),
|
||||||
ui.Input(ui.InputProps{
|
ui.Input(ui.InputProps{
|
||||||
|
|
@ -124,10 +116,14 @@ func addPatientForm() h.Renderable {
|
||||||
Class: "rounded p-2",
|
Class: "rounded p-2",
|
||||||
Type: "submit",
|
Type: "submit",
|
||||||
}),
|
}),
|
||||||
|
h.Div(
|
||||||
|
h.Id("submit-error"),
|
||||||
|
h.Class("text-red-500"),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Row(patient *Patient, index int) h.Renderable {
|
func Row(patient *patient.Patient, index int) h.Renderable {
|
||||||
return h.Div(
|
return h.Div(
|
||||||
h.Class("flex flex-col gap-2 rounded p-4", h.Ternary(index%2 == 0, "bg-red-100", "")),
|
h.Class("flex flex-col gap-2 rounded p-4", h.Ternary(index%2 == 0, "bg-red-100", "")),
|
||||||
h.Pf("Name: %s", patient.Name),
|
h.Pf("Name: %s", patient.Name),
|
||||||
|
|
|
||||||
21
ui/button.go
21
ui/button.go
|
|
@ -29,6 +29,23 @@ func Button(props ButtonProps) h.Renderable {
|
||||||
|
|
||||||
text := h.Text(props.Text)
|
text := h.Text(props.Text)
|
||||||
|
|
||||||
|
lifecycle := h.NewLifeCycle().
|
||||||
|
BeforeRequest(
|
||||||
|
h.AddAttribute("disabled", "true"),
|
||||||
|
h.SetText("Loading..."),
|
||||||
|
h.AddClass("bg-gray-400"),
|
||||||
|
).
|
||||||
|
AfterRequest(
|
||||||
|
h.RemoveAttribute("disabled"),
|
||||||
|
h.RemoveClass("bg-gray-400"),
|
||||||
|
h.SetText(props.Text),
|
||||||
|
).
|
||||||
|
OnMutationError(
|
||||||
|
h.SetText("failed"),
|
||||||
|
h.AddClass("bg-red-400"),
|
||||||
|
h.RemoveAttribute("disabled"),
|
||||||
|
)
|
||||||
|
|
||||||
button := h.Button(
|
button := h.Button(
|
||||||
h.If(props.Id != "", h.Id(props.Id)),
|
h.If(props.Id != "", h.Id(props.Id)),
|
||||||
h.If(props.Children != nil, h.Children(props.Children...)),
|
h.If(props.Children != nil, h.Children(props.Children...)),
|
||||||
|
|
@ -36,10 +53,8 @@ func Button(props ButtonProps) h.Renderable {
|
||||||
h.Class("flex gap-1 items-center border p-4 rounded cursor-hover", props.Class),
|
h.Class("flex gap-1 items-center border p-4 rounded cursor-hover", props.Class),
|
||||||
h.If(props.Get != "", h.Get(props.Get)),
|
h.If(props.Get != "", h.Get(props.Get)),
|
||||||
h.If(props.Target != "", h.Target(props.Target)),
|
h.If(props.Target != "", h.Target(props.Target)),
|
||||||
//h.Attribute("hx-indicator", "#spinner"),
|
|
||||||
h.IfElse(props.Type != "", h.Type(props.Type), h.Type("button")),
|
h.IfElse(props.Type != "", h.Type(props.Type), h.Type("button")),
|
||||||
h.BeforeRequestSetText("Loading..."),
|
lifecycle,
|
||||||
h.AfterRequestSetText(props.Text),
|
|
||||||
text,
|
text,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue