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", {
|
||||
// @ts-ignore
|
||||
onEvent: function (name, evt) {
|
||||
if (console.debug) {
|
||||
console.debug(name, evt.target, evt);
|
||||
console.debug(name);
|
||||
} else if (console) {
|
||||
console.log("DEBUG:", name, evt.target, evt);
|
||||
console.log("DEBUG:", name);
|
||||
} else {
|
||||
// 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) {
|
||||
if (pathSpec === "ignore") {
|
||||
|
|
@ -33,7 +33,11 @@ function refreshPath(path: string) {
|
|||
}
|
||||
|
||||
htmx.defineExtension("path-deps", {
|
||||
// @ts-ignore
|
||||
onEvent: function (name, evt) {
|
||||
if (!(evt instanceof CustomEvent)) {
|
||||
return false;
|
||||
}
|
||||
if (name === "htmx:beforeOnLoad") {
|
||||
const config = evt.detail.requestConfig;
|
||||
// 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/trigger-children";
|
||||
import "./extensions/debug";
|
||||
|
||||
declare module "htmx.org" {
|
||||
// Example: Adding type definitions for an exported function
|
||||
export function swap(
|
||||
target: Element,
|
||||
content: string,
|
||||
spec?: {
|
||||
swapStyle?: "innerHTML" | "outerHTML";
|
||||
},
|
||||
): any;
|
||||
}
|
||||
import "./extensions/response-targets";
|
||||
import "./extensions/mutation-error";
|
||||
|
||||
function watchUrl(callback: (oldUrl: string, newUrl: string) => void) {
|
||||
let lastUrl = window.location.href;
|
||||
|
|
@ -38,7 +29,11 @@ function onUrlChange(newUrl: string) {
|
|||
}
|
||||
const split = triggers.split(", ");
|
||||
if (split.find((s) => s === "url")) {
|
||||
htmx.swap(element, "url");
|
||||
htmx.swap(element, "url", {
|
||||
swapStyle: "outerHTML",
|
||||
swapDelay: 0,
|
||||
settleDelay: 0,
|
||||
});
|
||||
} else {
|
||||
for (let [key, values] of url.searchParams) {
|
||||
let eventName = "qs:" + key;
|
||||
|
|
@ -60,6 +55,8 @@ function onUrlChange(newUrl: string) {
|
|||
if (value) {
|
||||
htmx.swap(el, el.getAttribute(name) ?? "", {
|
||||
swapStyle: "innerHTML",
|
||||
swapDelay: 0,
|
||||
settleDelay: 0,
|
||||
});
|
||||
hasMatch = true;
|
||||
break;
|
||||
|
|
@ -72,7 +69,7 @@ function onUrlChange(newUrl: string) {
|
|||
htmx.swap(
|
||||
el,
|
||||
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",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"htmx.org": "^1.9.12"
|
||||
"htmx.org": "~2.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@swc/core": "^1.7.26",
|
||||
"@types/node": "^22.5.4",
|
||||
"prettier": "^3.3.3",
|
||||
"tailwindcss": "^3.4.11",
|
||||
"tsup": "^8.2.4",
|
||||
"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": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz",
|
||||
|
|
@ -992,6 +1005,12 @@
|
|||
"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": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
|
||||
|
|
@ -1064,6 +1083,15 @@
|
|||
"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": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||
|
|
@ -1138,6 +1166,18 @@
|
|||
"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": {
|
||||
"version": "4.3.7",
|
||||
"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": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
|
||||
|
|
@ -1167,6 +1213,12 @@
|
|||
"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": {
|
||||
"version": "0.2.0",
|
||||
"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_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": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
|
||||
|
|
@ -1384,10 +1445,22 @@
|
|||
"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": {
|
||||
"version": "1.9.12",
|
||||
"resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-1.9.12.tgz",
|
||||
"integrity": "sha512-VZAohXyF7xPGS52IM8d1T1283y+X4D+Owf3qY1NZ9RuBypyu9l8cGsxUMAG5fEAb/DhT7rDoJ9Hpu5/HxFD3cw=="
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-2.0.2.tgz",
|
||||
"integrity": "sha512-eUPIpQaWKKstX393XNCRCMJTrqPzikh36Y9RceqsUZLTtlFjFaVDgwZLUsrFk8J2uzZxkkfiy0TE359j2eN6hA=="
|
||||
},
|
||||
"node_modules/human-signals": {
|
||||
"version": "2.1.0",
|
||||
|
|
@ -1419,6 +1492,21 @@
|
|||
"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": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||
|
|
@ -1491,6 +1579,15 @@
|
|||
"@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": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz",
|
||||
|
|
@ -1617,6 +1714,24 @@
|
|||
"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": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||
|
|
@ -1647,6 +1762,15 @@
|
|||
"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": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
|
||||
|
|
@ -1677,6 +1801,12 @@
|
|||
"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": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
|
||||
|
|
@ -1720,6 +1850,15 @@
|
|||
"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": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
|
||||
|
|
@ -1729,6 +1868,70 @@
|
|||
"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": {
|
||||
"version": "6.0.1",
|
||||
"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": {
|
||||
"version": "3.3.3",
|
||||
"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": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||
|
|
@ -1827,6 +2083,23 @@
|
|||
"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": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
|
||||
|
|
@ -1952,6 +2225,15 @@
|
|||
"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": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
|
||||
|
|
@ -2079,6 +2361,123 @@
|
|||
"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": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
|
||||
|
|
@ -2206,6 +2605,12 @@
|
|||
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
|
||||
"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": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
|
||||
|
|
@ -2328,6 +2733,18 @@
|
|||
"engines": {
|
||||
"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": {
|
||||
"watch": "tsup ./mhtml.ts --watch --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 ."
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"htmx.org": "^1.9.12"
|
||||
"htmx.org": "~2.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@swc/core": "^1.7.26",
|
||||
"@types/node": "^22.5.4",
|
||||
"prettier": "^3.3.3",
|
||||
"tailwindcss": "^3.4.11",
|
||||
"tsup": "^8.2.4",
|
||||
"typescript": "^5.6.2",
|
||||
"prettier": "^3.3.3"
|
||||
"typescript": "^5.6.2"
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,6 @@
|
|||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": ["./*.ts"],
|
||||
"include": ["./*.ts", "./*.js"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
|
@ -3,19 +3,21 @@ import { defineConfig } from "tsup";
|
|||
export default defineConfig({
|
||||
format: ["esm"],
|
||||
entry: ["./src/mhtml.ts"],
|
||||
outDir: "./dist",
|
||||
outDir: "./../dist",
|
||||
dts: false,
|
||||
shims: true,
|
||||
skipNodeModulesBundle: true,
|
||||
clean: true,
|
||||
target: "es6",
|
||||
clean: false,
|
||||
target: "esnext",
|
||||
treeshake: false,
|
||||
sourcemap: true,
|
||||
platform: "browser",
|
||||
outExtension: () => {
|
||||
return {
|
||||
js: ".js",
|
||||
};
|
||||
},
|
||||
minify: true,
|
||||
minify: false,
|
||||
bundle: true,
|
||||
// https://github.com/egoist/tsup/issues/619
|
||||
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 {
|
||||
return GetFunctionName(partial)
|
||||
return runtime.FuncForPC(reflect.ValueOf(partial).Pointer()).Name()
|
||||
}
|
||||
|
||||
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)
|
||||
for _, child := range node.children {
|
||||
|
||||
if child == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
c := child.Render()
|
||||
|
||||
flatChildren = append(flatChildren, child)
|
||||
if c.tag == FlagChildrenList {
|
||||
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 {
|
||||
if len(headers)%2 != 0 {
|
||||
return &Headers{}
|
||||
}
|
||||
m := make(Headers)
|
||||
for i := 0; i < len(headers); i++ {
|
||||
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+`')`)
|
||||
}
|
||||
|
||||
func OnMutationErrorSetText(text string) Renderable {
|
||||
return Attribute("hx-on::mutation-error", `this.innerText = '`+text+`'`)
|
||||
}
|
||||
|
||||
func BeforeRequestSetText(text string) Renderable {
|
||||
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'
|
||||
|
||||
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() {
|
||||
f := fiber.New()
|
||||
f.Static("/js", "./js")
|
||||
|
||||
f.Static("/public", "./assets/dist")
|
||||
|
||||
f.Use(func(ctx *fiber.Ctx) error {
|
||||
if ctx.Cookies("mhtml-session") != "" {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import (
|
|||
|
||||
func RootPage(children ...h.Renderable) h.Renderable {
|
||||
return h.Html(
|
||||
h.HxExtension("path-deps"),
|
||||
h.HxExtension("path-deps, response-targets, mutation-error"),
|
||||
h.Head(
|
||||
h.Script("https://cdn.tailwindcss.com"),
|
||||
h.Script("/js/dist/mhtml.js"),
|
||||
|
|
|
|||
|
|
@ -3,9 +3,15 @@ package pages
|
|||
import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"mhtml/h"
|
||||
"mhtml/pages/base"
|
||||
)
|
||||
|
||||
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"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"mhtml/h"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Test(ctx *fiber.Ctx) *h.Page {
|
||||
time.Sleep(time.Second * 1)
|
||||
text := fmt.Sprintf("News ID: %s", ctx.Params("id"))
|
||||
return h.NewPage(
|
||||
h.Div(h.Text(text)),
|
||||
|
|
|
|||
|
|
@ -2,11 +2,9 @@ package patient
|
|||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/google/uuid"
|
||||
"mhtml/database"
|
||||
"mhtml/features/patient"
|
||||
"mhtml/h"
|
||||
"mhtml/partials/sheet"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Create(ctx *fiber.Ctx) *h.Partial {
|
||||
|
|
@ -14,13 +12,22 @@ func Create(ctx *fiber.Ctx) *h.Partial {
|
|||
reason := ctx.FormValue("reason-for-visit")
|
||||
location := ctx.FormValue("location-name")
|
||||
|
||||
database.HSet("patients", uuid.New().String(), Patient{
|
||||
Name: name,
|
||||
ReasonForVisit: reason,
|
||||
AppointmentDate: time.Now(),
|
||||
LocationName: location,
|
||||
err := patient.NewService(ctx).Create(patient.CreatePatientRequest{
|
||||
Name: name,
|
||||
ReasonForVisit: reason,
|
||||
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{
|
||||
"HX-Trigger": "patient-added",
|
||||
})
|
||||
|
|
|
|||
|
|
@ -2,23 +2,15 @@ package patient
|
|||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"mhtml/database"
|
||||
"mhtml/features/patient"
|
||||
"mhtml/h"
|
||||
"mhtml/partials/sheet"
|
||||
"mhtml/ui"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Patient struct {
|
||||
Name string
|
||||
ReasonForVisit string
|
||||
AppointmentDate time.Time
|
||||
LocationName string
|
||||
}
|
||||
|
||||
func List(ctx *fiber.Ctx) *h.Partial {
|
||||
patients, err := database.HList[Patient]("patients")
|
||||
patients, err := patient.NewService(ctx).List()
|
||||
|
||||
if err != nil {
|
||||
return h.NewPartial(h.Div(
|
||||
|
|
@ -35,7 +27,6 @@ func List(ctx *fiber.Ctx) *h.Partial {
|
|||
}
|
||||
|
||||
return h.NewPartial(h.Div(
|
||||
h.HxExtension("debug"),
|
||||
h.Class("mt-8"),
|
||||
h.Id("patient-list"),
|
||||
h.List(patients, Row),
|
||||
|
|
@ -92,7 +83,8 @@ func ValidateForm(ctx *fiber.Ctx) *h.Partial {
|
|||
|
||||
func addPatientForm() h.Renderable {
|
||||
return h.Form(
|
||||
h.TriggerChildren(),
|
||||
h.HxExtension("debug, trigger-children"),
|
||||
h.Attribute("hx-target-5*", "#submit-error"),
|
||||
h.Post(h.GetPartialPath(Create)),
|
||||
h.Class("flex flex-col gap-2"),
|
||||
ui.Input(ui.InputProps{
|
||||
|
|
@ -124,10 +116,14 @@ func addPatientForm() h.Renderable {
|
|||
Class: "rounded p-2",
|
||||
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(
|
||||
h.Class("flex flex-col gap-2 rounded p-4", h.Ternary(index%2 == 0, "bg-red-100", "")),
|
||||
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)
|
||||
|
||||
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(
|
||||
h.If(props.Id != "", h.Id(props.Id)),
|
||||
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.If(props.Get != "", h.Get(props.Get)),
|
||||
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.BeforeRequestSetText("Loading..."),
|
||||
h.AfterRequestSetText(props.Text),
|
||||
lifecycle,
|
||||
text,
|
||||
)
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue