@steve31415/request-button 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/modal.d.ts +24 -0
- package/dist/modal.d.ts.map +1 -0
- package/dist/modal.js +110 -0
- package/dist/modal.js.map +1 -0
- package/dist/route.d.ts +24 -0
- package/dist/route.d.ts.map +1 -0
- package/dist/route.js +110 -0
- package/dist/route.js.map +1 -0
- package/dist/styles.d.ts +6 -0
- package/dist/styles.d.ts.map +1 -0
- package/dist/styles.js +64 -0
- package/dist/styles.js.map +1 -0
- package/package.json +43 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAC7C,YAAY,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC"}
|
package/dist/modal.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal logger interface for dependency injection.
|
|
3
|
+
* Consumers call setRequestLogger() with their own logger instance.
|
|
4
|
+
*/
|
|
5
|
+
interface RequestLogger {
|
|
6
|
+
info(msg: string, data?: Record<string, unknown>): void;
|
|
7
|
+
error(msg: string, data?: Record<string, unknown>): void;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Set the logger used by the request modal. Optional — if not called,
|
|
11
|
+
* the modal simply won't log.
|
|
12
|
+
*/
|
|
13
|
+
export declare function setRequestLogger(logger: RequestLogger): void;
|
|
14
|
+
/**
|
|
15
|
+
* Open the request modal. Injects styles on first call. Does nothing
|
|
16
|
+
* if the modal is already open.
|
|
17
|
+
*/
|
|
18
|
+
export declare function openRequestModal(): void;
|
|
19
|
+
/**
|
|
20
|
+
* Close the request modal, removing it and the backdrop from the DOM.
|
|
21
|
+
*/
|
|
22
|
+
export declare function closeRequestModal(): void;
|
|
23
|
+
export {};
|
|
24
|
+
//# sourceMappingURL=modal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modal.d.ts","sourceRoot":"","sources":["../src/modal.ts"],"names":[],"mappings":"AAYA;;;GAGG;AACH,UAAU,aAAa;IACrB,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACxD,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;CAC1D;AAID;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAE5D;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAqCvC;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAIxC"}
|
package/dist/modal.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// Request modal: overlay for submitting feature requests / bug reports to the todo app.
|
|
2
|
+
import { injectStyles } from "./styles";
|
|
3
|
+
// Element IDs
|
|
4
|
+
const MODAL_ID = "request-modal";
|
|
5
|
+
const BACKDROP_ID = "request-backdrop";
|
|
6
|
+
const TEXTAREA_ID = "request-text";
|
|
7
|
+
const ERROR_ID = "request-error";
|
|
8
|
+
const SUBMIT_ID = "request-submit";
|
|
9
|
+
const CANCEL_ID = "request-cancel";
|
|
10
|
+
let loggerRef = null;
|
|
11
|
+
/**
|
|
12
|
+
* Set the logger used by the request modal. Optional — if not called,
|
|
13
|
+
* the modal simply won't log.
|
|
14
|
+
*/
|
|
15
|
+
export function setRequestLogger(logger) {
|
|
16
|
+
loggerRef = logger;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Open the request modal. Injects styles on first call. Does nothing
|
|
20
|
+
* if the modal is already open.
|
|
21
|
+
*/
|
|
22
|
+
export function openRequestModal() {
|
|
23
|
+
// Don't open if already open
|
|
24
|
+
if (document.getElementById(MODAL_ID))
|
|
25
|
+
return;
|
|
26
|
+
injectStyles();
|
|
27
|
+
// Backdrop
|
|
28
|
+
const backdrop = document.createElement("div");
|
|
29
|
+
backdrop.id = BACKDROP_ID;
|
|
30
|
+
backdrop.className = "request-backdrop";
|
|
31
|
+
backdrop.onclick = closeRequestModal;
|
|
32
|
+
document.body.appendChild(backdrop);
|
|
33
|
+
// Modal
|
|
34
|
+
const modal = document.createElement("div");
|
|
35
|
+
modal.id = MODAL_ID;
|
|
36
|
+
modal.className = "request-modal";
|
|
37
|
+
modal.innerHTML = `
|
|
38
|
+
<h4>Submit a Request</h4>
|
|
39
|
+
<p>Enter a feature request or bug report:</p>
|
|
40
|
+
<textarea id="${TEXTAREA_ID}" rows="6" placeholder="Describe what you'd like..."></textarea>
|
|
41
|
+
<div id="${ERROR_ID}" class="request-error"></div>
|
|
42
|
+
<div class="request-actions">
|
|
43
|
+
<button id="${SUBMIT_ID}" class="contrast">Submit</button>
|
|
44
|
+
<button id="${CANCEL_ID}" class="outline secondary">Cancel</button>
|
|
45
|
+
</div>
|
|
46
|
+
`;
|
|
47
|
+
document.body.appendChild(modal);
|
|
48
|
+
// Focus the textarea
|
|
49
|
+
const textarea = document.getElementById(TEXTAREA_ID);
|
|
50
|
+
textarea?.focus();
|
|
51
|
+
// Wire handlers
|
|
52
|
+
document.getElementById(CANCEL_ID).onclick = closeRequestModal;
|
|
53
|
+
document.getElementById(SUBMIT_ID).onclick = handleSubmit;
|
|
54
|
+
document.addEventListener("keydown", handleKeydown);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Close the request modal, removing it and the backdrop from the DOM.
|
|
58
|
+
*/
|
|
59
|
+
export function closeRequestModal() {
|
|
60
|
+
document.getElementById(MODAL_ID)?.remove();
|
|
61
|
+
document.getElementById(BACKDROP_ID)?.remove();
|
|
62
|
+
document.removeEventListener("keydown", handleKeydown);
|
|
63
|
+
}
|
|
64
|
+
function handleKeydown(e) {
|
|
65
|
+
if (e.key === "Escape") {
|
|
66
|
+
closeRequestModal();
|
|
67
|
+
}
|
|
68
|
+
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
|
|
69
|
+
e.preventDefault();
|
|
70
|
+
handleSubmit();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async function handleSubmit() {
|
|
74
|
+
const submitBtn = document.getElementById(SUBMIT_ID);
|
|
75
|
+
if (submitBtn?.disabled)
|
|
76
|
+
return; // already submitting
|
|
77
|
+
const textarea = document.getElementById(TEXTAREA_ID);
|
|
78
|
+
const errorDiv = document.getElementById(ERROR_ID);
|
|
79
|
+
const text = textarea?.value?.trim();
|
|
80
|
+
if (!text) {
|
|
81
|
+
errorDiv.textContent = "Please enter some text.";
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
// Disable UI during submission
|
|
85
|
+
submitBtn.disabled = true;
|
|
86
|
+
submitBtn.textContent = "Submitting...";
|
|
87
|
+
errorDiv.textContent = "";
|
|
88
|
+
try {
|
|
89
|
+
loggerRef?.info("Submitting request", { textLength: text.length });
|
|
90
|
+
const res = await fetch("/api/request", {
|
|
91
|
+
method: "POST",
|
|
92
|
+
headers: { "Content-Type": "application/json" },
|
|
93
|
+
body: JSON.stringify({ text, url: window.location.href }),
|
|
94
|
+
});
|
|
95
|
+
if (!res.ok) {
|
|
96
|
+
const data = await res.json().catch(() => ({ error: "Request failed" }));
|
|
97
|
+
throw new Error(data.error || `HTTP ${res.status}`);
|
|
98
|
+
}
|
|
99
|
+
loggerRef?.info("Request submitted successfully");
|
|
100
|
+
closeRequestModal();
|
|
101
|
+
}
|
|
102
|
+
catch (err) {
|
|
103
|
+
const message = err instanceof Error ? err.message : "Request failed";
|
|
104
|
+
loggerRef?.error("Request submission failed", { error: message });
|
|
105
|
+
errorDiv.textContent = message;
|
|
106
|
+
submitBtn.disabled = false;
|
|
107
|
+
submitBtn.textContent = "Submit";
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=modal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"modal.js","sourceRoot":"","sources":["../src/modal.ts"],"names":[],"mappings":"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"}
|
package/dist/route.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Context } from "hono";
|
|
2
|
+
/**
|
|
3
|
+
* Options for creating a request route handler.
|
|
4
|
+
*/
|
|
5
|
+
export interface RequestRouteOptions {
|
|
6
|
+
/** Application name — used to find/create the `apps/{appName}` list in Todo */
|
|
7
|
+
appName: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Factory function that returns a Hono handler for POST /api/request.
|
|
11
|
+
*
|
|
12
|
+
* The handler forwards feature requests / bug reports to the Todo app via
|
|
13
|
+
* a Cloudflare service binding (`c.env.TODO`).
|
|
14
|
+
*/
|
|
15
|
+
export declare function createRequestRoute(options: RequestRouteOptions): (c: Context) => Promise<(Response & import("hono").TypedResponse<{
|
|
16
|
+
error: string;
|
|
17
|
+
}, 503, "json">) | (Response & import("hono").TypedResponse<{
|
|
18
|
+
error: string;
|
|
19
|
+
}, 400, "json">) | (Response & import("hono").TypedResponse<{
|
|
20
|
+
error: string;
|
|
21
|
+
}, 502, "json">) | (Response & import("hono").TypedResponse<{
|
|
22
|
+
ok: true;
|
|
23
|
+
}, import("hono/utils/http-status").ContentfulStatusCode, "json">)>;
|
|
24
|
+
//# sourceMappingURL=route.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../src/route.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAGpC;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,+EAA+E;IAC/E,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB,IAI/C,GAAG,OAAO;;;;;;;;oEAsHzB"}
|
package/dist/route.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { getLogger } from "@steve31415/log-logger-ts";
|
|
2
|
+
/**
|
|
3
|
+
* Factory function that returns a Hono handler for POST /api/request.
|
|
4
|
+
*
|
|
5
|
+
* The handler forwards feature requests / bug reports to the Todo app via
|
|
6
|
+
* a Cloudflare service binding (`c.env.TODO`).
|
|
7
|
+
*/
|
|
8
|
+
export function createRequestRoute(options) {
|
|
9
|
+
const { appName } = options;
|
|
10
|
+
const listName = `apps/${appName}`;
|
|
11
|
+
return async (c) => {
|
|
12
|
+
const logger = getLogger(c);
|
|
13
|
+
const env = c.env;
|
|
14
|
+
if (!env.TODO) {
|
|
15
|
+
logger.error("TODO service binding not configured");
|
|
16
|
+
return c.json({ error: "Request submission not available" }, 503);
|
|
17
|
+
}
|
|
18
|
+
let body;
|
|
19
|
+
try {
|
|
20
|
+
body = await c.req.json();
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
logger.warn("Invalid JSON body");
|
|
24
|
+
return c.json({ error: "Invalid JSON body" }, 400);
|
|
25
|
+
}
|
|
26
|
+
const { text, url: pageUrl } = body;
|
|
27
|
+
if (!text || typeof text !== "string" || !text.trim()) {
|
|
28
|
+
logger.warn("Validation failed: text is required");
|
|
29
|
+
return c.json({ error: "text is required" }, 400);
|
|
30
|
+
}
|
|
31
|
+
logger.info("Submitting request to todo", {
|
|
32
|
+
pageUrl,
|
|
33
|
+
textLength: text.length,
|
|
34
|
+
});
|
|
35
|
+
const cookie = c.req.header("Cookie") || "";
|
|
36
|
+
// Find the apps/{appName} list
|
|
37
|
+
const startList = Date.now();
|
|
38
|
+
const listsRes = await env.TODO.fetch("https://todo/api/lists", {
|
|
39
|
+
headers: { Cookie: cookie },
|
|
40
|
+
});
|
|
41
|
+
logger.info("Todo GET /api/lists", {
|
|
42
|
+
status: listsRes.status,
|
|
43
|
+
elapsedMs: Date.now() - startList,
|
|
44
|
+
});
|
|
45
|
+
if (!listsRes.ok) {
|
|
46
|
+
logger.error("Failed to fetch todo lists", { status: listsRes.status });
|
|
47
|
+
return c.json({ error: "Failed to reach todo app" }, 502);
|
|
48
|
+
}
|
|
49
|
+
const lists = (await listsRes.json());
|
|
50
|
+
let listId = lists.find((l) => l.name === listName)?.id;
|
|
51
|
+
// Create the list if it doesn't exist
|
|
52
|
+
if (!listId) {
|
|
53
|
+
const startCreate = Date.now();
|
|
54
|
+
const createRes = await env.TODO.fetch("https://todo/api/lists", {
|
|
55
|
+
method: "POST",
|
|
56
|
+
headers: { Cookie: cookie, "Content-Type": "application/json" },
|
|
57
|
+
body: JSON.stringify({ name: listName }),
|
|
58
|
+
});
|
|
59
|
+
logger.info("Todo POST /api/lists", {
|
|
60
|
+
status: createRes.status,
|
|
61
|
+
elapsedMs: Date.now() - startCreate,
|
|
62
|
+
});
|
|
63
|
+
if (!createRes.ok) {
|
|
64
|
+
logger.error("Failed to create todo list", {
|
|
65
|
+
status: createRes.status,
|
|
66
|
+
});
|
|
67
|
+
return c.json({ error: "Failed to create todo list" }, 502);
|
|
68
|
+
}
|
|
69
|
+
const created = (await createRes.json());
|
|
70
|
+
listId = created.id;
|
|
71
|
+
}
|
|
72
|
+
// Format timestamp in Pacific time
|
|
73
|
+
const timestamp = new Date().toLocaleString("en-US", {
|
|
74
|
+
timeZone: "America/Los_Angeles",
|
|
75
|
+
year: "numeric",
|
|
76
|
+
month: "2-digit",
|
|
77
|
+
day: "2-digit",
|
|
78
|
+
hour: "numeric",
|
|
79
|
+
minute: "2-digit",
|
|
80
|
+
hour12: true,
|
|
81
|
+
}) + " PT";
|
|
82
|
+
const formattedText = [
|
|
83
|
+
`Feature request submitted from ${appName} app`,
|
|
84
|
+
`Timestamp: ${timestamp}`,
|
|
85
|
+
`Page: ${pageUrl || "(unknown)"}`,
|
|
86
|
+
"",
|
|
87
|
+
"---",
|
|
88
|
+
"",
|
|
89
|
+
text.trim(),
|
|
90
|
+
].join("\n");
|
|
91
|
+
// Create the todo item
|
|
92
|
+
const startItem = Date.now();
|
|
93
|
+
const itemRes = await env.TODO.fetch(`https://todo/api/lists/${listId}/items`, {
|
|
94
|
+
method: "POST",
|
|
95
|
+
headers: { Cookie: cookie, "Content-Type": "application/json" },
|
|
96
|
+
body: JSON.stringify({ text: formattedText }),
|
|
97
|
+
});
|
|
98
|
+
logger.info("Todo POST /api/lists/:id/items", {
|
|
99
|
+
status: itemRes.status,
|
|
100
|
+
elapsedMs: Date.now() - startItem,
|
|
101
|
+
});
|
|
102
|
+
if (!itemRes.ok) {
|
|
103
|
+
logger.error("Failed to create todo item", { status: itemRes.status });
|
|
104
|
+
return c.json({ error: "Failed to create request item" }, 502);
|
|
105
|
+
}
|
|
106
|
+
logger.info("Request submitted successfully", { listId });
|
|
107
|
+
return c.json({ ok: true });
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=route.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route.js","sourceRoot":"","sources":["../src/route.ts"],"names":[],"mappings":"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"}
|
package/dist/styles.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"styles.d.ts","sourceRoot":"","sources":["../src/styles.ts"],"names":[],"mappings":"AAqDA;;;GAGG;AACH,wBAAgB,YAAY,IAAI,IAAI,CAOnC"}
|
package/dist/styles.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const STYLES_ID = "request-button-styles";
|
|
2
|
+
const CSS = `
|
|
3
|
+
.request-backdrop {
|
|
4
|
+
position: fixed;
|
|
5
|
+
inset: 0;
|
|
6
|
+
background: rgba(0, 0, 0, 0.3);
|
|
7
|
+
z-index: 9998;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.request-modal {
|
|
11
|
+
position: fixed;
|
|
12
|
+
top: 50%;
|
|
13
|
+
left: 50%;
|
|
14
|
+
transform: translate(-50%, -50%);
|
|
15
|
+
background: var(--pico-background-color);
|
|
16
|
+
border: 1px solid var(--pico-muted-border-color);
|
|
17
|
+
border-radius: 4px;
|
|
18
|
+
padding: 1.5rem;
|
|
19
|
+
z-index: 9999;
|
|
20
|
+
width: min(500px, 90vw);
|
|
21
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.request-modal h4 {
|
|
25
|
+
margin: 0 0 0.5rem 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.request-modal textarea {
|
|
29
|
+
width: 100%;
|
|
30
|
+
resize: vertical;
|
|
31
|
+
margin-bottom: 0.5rem;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.request-error {
|
|
35
|
+
color: var(--pico-del-color);
|
|
36
|
+
font-size: 0.85rem;
|
|
37
|
+
min-height: 1.2em;
|
|
38
|
+
margin-bottom: 0.5rem;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.request-actions {
|
|
42
|
+
display: flex;
|
|
43
|
+
gap: 0.5rem;
|
|
44
|
+
justify-content: flex-end;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.request-actions button {
|
|
48
|
+
width: auto;
|
|
49
|
+
margin: 0;
|
|
50
|
+
}
|
|
51
|
+
`;
|
|
52
|
+
/**
|
|
53
|
+
* Inject the request modal styles into <head>. Idempotent — skips if the
|
|
54
|
+
* style tag already exists.
|
|
55
|
+
*/
|
|
56
|
+
export function injectStyles() {
|
|
57
|
+
if (document.getElementById(STYLES_ID))
|
|
58
|
+
return;
|
|
59
|
+
const style = document.createElement("style");
|
|
60
|
+
style.id = STYLES_ID;
|
|
61
|
+
style.textContent = CSS;
|
|
62
|
+
document.head.appendChild(style);
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=styles.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"styles.js","sourceRoot":"","sources":["../src/styles.ts"],"names":[],"mappings":"AAAA,MAAM,SAAS,GAAG,uBAAuB,CAAC;AAE1C,MAAM,GAAG,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiDX,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,YAAY;IAC1B,IAAI,QAAQ,CAAC,cAAc,CAAC,SAAS,CAAC;QAAE,OAAO;IAE/C,MAAM,KAAK,GAAG,QAAQ,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;IAC9C,KAAK,CAAC,EAAE,GAAG,SAAS,CAAC;IACrB,KAAK,CAAC,WAAW,GAAG,GAAG,CAAC;IACxB,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;AACnC,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@steve31415/request-button",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Shared Request button for Plasticine Way apps — submit feature requests and bug reports to the Todo app",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"module": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./modal": {
|
|
15
|
+
"types": "./dist/modal.d.ts",
|
|
16
|
+
"import": "./dist/modal.js"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"files": ["dist", "README.md"],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsc",
|
|
22
|
+
"prepublishOnly": "npm run build",
|
|
23
|
+
"test": "vitest run",
|
|
24
|
+
"test:dom": "vitest run --config vitest.config.dom.ts",
|
|
25
|
+
"test:all": "vitest run && vitest run --config vitest.config.dom.ts"
|
|
26
|
+
},
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "https://github.com/plasticine-apps/request-button.git"
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"hono": "^4.0.0",
|
|
34
|
+
"@steve31415/log-logger-ts": "^1.0.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"hono": "^4.11.9",
|
|
38
|
+
"@steve31415/log-logger-ts": "^1.1.4",
|
|
39
|
+
"typescript": "^5.0.0",
|
|
40
|
+
"vitest": "^2.1.9",
|
|
41
|
+
"happy-dom": "^17.4.4"
|
|
42
|
+
}
|
|
43
|
+
}
|