authorgravatar for git@paperclover.netclover caruso <git@paperclover.net> 2025-06-22 14:38:36-07:00
committergravatar for git@paperclover.netclover caruso <git@paperclover.net> 2025-10-14 02:40:45-07:00
loge258365f5bba27170707594f3548c6a9753532a5
tree3542a526d12afc339b137f2dd1fc80f6b86678c1
parent579f44c68f4fd91813fbb5adcac82a4736b6c12d
signature Commit is signed but in an unrecognized format.

file viewer work


19 files changed, 343 insertions(+), 88 deletions(-)

framework/backend/entry-node.ts+3-1
...@@ -3,7 +3,9 @@ import "#debug";...@@ -3,7 +3,9 @@ import "#debug";
3 3
4const protocol = "http"; 4const protocol = "http";
5 5
6const server = serve(app, ({ address, port }) => { 6const server = serve({
7 fetch: app.fetch,
8}, ({ address, port }) => {
7 if (address === "::") address = "::1"; 9 if (address === "::") address = "::1";
8 console.info(url.format({ 10 console.info(url.format({
9 protocol, 11 protocol,
framework/engine/jsx-runtime.ts+13-5
...@@ -17,13 +17,21 @@ export function jsxDEV(...@@ -17,13 +17,21 @@ export function jsxDEV(
17 _key: string, 17 _key: string,
18 // Unused with the clover engine 18 // Unused with the clover engine
19 _isStaticChildren: boolean, 19 _isStaticChildren: boolean,
20 // Unused with the clover engine 20 source: engine.SrcLoc,
21 _source: unknown,
22): engine.Element { 21): engine.Element {
22 const { fileName, lineNumber, columnNumber } = source;
23
24 // Assert the component type is valid to render.
23 if (typeof type !== "function" && typeof type !== "string") { 25 if (typeof type !== "function" && typeof type !== "string") {
24 throw new Error("Invalid component type: " + engine.inspect(type)); 26 throw new Error(
27 `Invalid component type at ${fileName}:${lineNumber}:${columnNumber}: ` +
28 engine.inspect(type) +
29 ". Clover SSR element must be a function or string",
30 );
25 } 31 }
26 return [engine.kElement, type, props]; 32
33 // Construct an `ssr.Element`
34 return [engine.kElement, type, props, "", source];
27} 35}
28 36
29// jsxs 37// jsxs
...@@ -37,7 +45,7 @@ declare global {...@@ -37,7 +45,7 @@ declare global {
37 interface ElementChildrenAttribute { 45 interface ElementChildrenAttribute {
38 children: Node; 46 children: Node;
39 } 47 }
40 type Element = engine.Node; 48 type Element = engine.Element;
41 type ElementType = keyof IntrinsicElements | engine.Component; 49 type ElementType = keyof IntrinsicElements | engine.Component;
42 type ElementClass = ReturnType<engine.Component>; 50 type ElementClass = ReturnType<engine.Component>;
43 } 51 }
framework/engine/marko-runtime.ts+4-3
...@@ -123,14 +123,15 @@ export function escapeXML(input: unknown) {...@@ -123,14 +123,15 @@ export function escapeXML(input: unknown) {
123 // The rationale of this check is that the default toString method 123 // The rationale of this check is that the default toString method
124 // creating `[object Object]` is universally useless to any end user. 124 // creating `[object Object]` is universally useless to any end user.
125 if ( 125 if (
126 input == null ||
126 (typeof input === "object" && input && 127 (typeof input === "object" && input &&
127 // only block this if it's the default `toString` 128 // only block this if it's the default `toString`
128 input.toString === Object.prototype.toString) 129 input.toString === Object.prototype.toString)
129 ) { 130 ) {
130 throw new Error( 131 throw new Error(
131 `Unexpected object in template placeholder: '` + 132 `Unexpected value in template placeholder: '` +
132 engine.inspect({ name: "clover" }) + "'. " + 133 engine.inspect(input) + "'. " +
133 `To emit a literal '[object Object]', use \${String(value)}`, 134 `To emit a literal '${input}', use \${String(value)}`,
134 ); 135 );
135 } 136 }
136 return marko.escapeXML(input); 137 return marko.escapeXML(input);
framework/engine/ssr.ts+17-3
...@@ -79,6 +79,8 @@ export type Element = [...@@ -79,6 +79,8 @@ export type Element = [
79 tag: typeof kElement, 79 tag: typeof kElement,
80 type: string | Component, 80 type: string | Component,
81 props: Record<string, unknown>, 81 props: Record<string, unknown>,
82 _?: "",
83 source?: SrcLoc,
82]; 84];
83export type DirectHtml = [tag: typeof kDirectHtml, html: ResolvedNode]; 85export type DirectHtml = [tag: typeof kDirectHtml, html: ResolvedNode];
84/** 86/**
...@@ -88,6 +90,12 @@ export type DirectHtml = [tag: typeof kDirectHtml, html: ResolvedNode];...@@ -88,6 +90,12 @@ export type DirectHtml = [tag: typeof kDirectHtml, html: ResolvedNode];
88export type Component = ( 90export type Component = (
89 props: Record<any, any>, 91 props: Record<any, any>,
90) => Exclude<Node, undefined>; 92) => Exclude<Node, undefined>;
93/** Emitted by JSX runtime */
94export interface SrcLoc {
95 fileName: string;
96 lineNumber: number;
97 columnNumber: number;
98}
91 99
92/** 100/**
93 * Resolution narrows the type 'Node' into 'ResolvedNode'. Async trees are 101 * Resolution narrows the type 'Node' into 'ResolvedNode'. Async trees are
...@@ -130,9 +138,15 @@ export function resolveNode(r: Render, node: unknown): ResolvedNode {...@@ -130,9 +138,15 @@ export function resolveNode(r: Render, node: unknown): ResolvedNode {
130 const { 1: tag, 2: props } = node; 138 const { 1: tag, 2: props } = node;
131 if (typeof tag === "function") { 139 if (typeof tag === "function") {
132 currentRender = r; 140 currentRender = r;
133 const result = tag(props); 141 try {
134 currentRender = null; 142 return resolveNode(r, tag(props));
135 return resolveNode(r, result); 143 } catch (e) {
144 const { 4: src } = node;
145 if (e && typeof e === "object") {
146 }
147 } finally {
148 currentRender = null;
149 }
136 } 150 }
137 if (typeof tag !== "string") throw new Error("Unexpected " + typeof type); 151 if (typeof tag !== "string") throw new Error("Unexpected " + typeof type);
138 const children = props?.children; 152 const children = props?.children;
framework/hot.ts+1
...@@ -145,6 +145,7 @@ function loadEsbuildCode(...@@ -145,6 +145,7 @@ function loadEsbuildCode(
145 target: "esnext",145 target: "esnext",
146 jsx: "automatic",146 jsx: "automatic",
147 jsxImportSource: "#ssr",147 jsxImportSource: "#ssr",
148 jsxDev: true,
148 sourcefile: filepath,149 sourcefile: filepath,
149 }).code;150 }).code;
150 return module._compile(src, filepath, "commonjs");151 return module._compile(src, filepath, "commonjs");
src/backend.ts-1
...@@ -11,7 +11,6 @@ app.use(admin.middleware);...@@ -11,7 +11,6 @@ app.use(admin.middleware);
11 11
12app.route("", require("./q+a/backend.ts").app); 12app.route("", require("./q+a/backend.ts").app);
13app.route("", require("./file-viewer/backend.tsx").app); 13app.route("", require("./file-viewer/backend.tsx").app);
14// app.route("", require("./friends/backend.tsx").app);
15 14
16app.use(assets.middleware); 15app.use(assets.middleware);
17 16
src/file-viewer/backend.tsx+13-3
...@@ -42,9 +42,15 @@ app.post("/file/cotyledon", async (c) => {...@@ -42,9 +42,15 @@ app.post("/file/cotyledon", async (c) => {
42});42});
4343
44app.get("/file/*", async (c, next) => {44app.get("/file/*", async (c, next) => {
45 if (c.req.header("User-Agent")?.toLowerCase()?.includes("discordbot")) {45 const ua = c.req.header("User-Agent")?.toLowerCase() ?? "";
46 const lofi = ua.includes("msie") || ua.includes("rv:") || false;
47
48 // Discord ignores 'robots.txt' which violates the license agreement.
49 if (ua.includes("discordbot")) {
46 return next();50 return next();
47 }51 }
52 console.log(ua, lofi);
53
48 let rawFilePath = c.req.path.slice(5) || "/";54 let rawFilePath = c.req.path.slice(5) || "/";
49 if (rawFilePath.endsWith("$partial")) {55 if (rawFilePath.endsWith("$partial")) {
50 return getPartialPage(c, rawFilePath.slice(0, -"$partial".length));56 return getPartialPage(c, rawFilePath.slice(0, -"$partial".length));
...@@ -88,7 +94,7 @@ app.get("/file/*", async (c, next) => {...@@ -88,7 +94,7 @@ app.get("/file/*", async (c, next) => {
88 } satisfies APIDirectoryList;94 } satisfies APIDirectoryList;
89 return c.json(json);95 return c.json(json);
90 }96 }
91 c.res = await renderView(c, "file-viewer/clofi", {97 c.res = await renderView(c, `file-viewer/${lofi ? "lofi" : "clofi"}`, {
92 file,98 file,
93 hasCotyledonCookie,99 hasCotyledonCookie,
94 });100 });
...@@ -100,7 +106,11 @@ app.get("/file/*", async (c, next) => {...@@ -100,7 +106,11 @@ app.get("/file/*", async (c, next) => {
100 if (c.req.query("dl") !== undefined) {106 if (c.req.query("dl") !== undefined) {
101 viewMode = "download";107 viewMode = "download";
102 }108 }
103 if (viewMode == undefined && c.req.header("Accept")?.includes("text/html")) {109 if (
110 viewMode == undefined &&
111 c.req.header("Accept")?.includes("text/html") &&
112 !lofi
113 ) {
104 prefetchFile(file.path);114 prefetchFile(file.path);
105 c.res = await renderView(c, "file-viewer/clofi", {115 c.res = await renderView(c, "file-viewer/clofi", {
106 file,116 file,
src/file-viewer/format.ts+31-9
...@@ -9,15 +9,15 @@ export function formatSize(bytes: number) {...@@ -9,15 +9,15 @@ export function formatSize(bytes: number) {
9 return `${(bytes / 1024 / 1024 / 1024).toFixed(1)} GB`;9 return `${(bytes / 1024 / 1024 / 1024).toFixed(1)} GB`;
10}10}
1111
12export function formatDate(date: Date) {12// export function formatDateDefined(date: Date) {
13 // YYYY-MM-DD, format in PST timezone13// // YYYY-MM-DD, format in PST timezone
14 return date.toLocaleDateString("sv", { timeZone: "America/Los_Angeles" });14// return date.toLocaleDateString("sv", { timeZone: "America/Los_Angeles" });
15}15// }
1616//
17export function formatShortDate(date: Date) {17// export function formatShortDate(date: Date) {
18 // YY-MM-DD, format in PST timezone18// // YY-MM-DD, format in PST timezone
19 return formatDate(date).slice(2);19// return formatDate(date).slice(2);
20}20// }
2121
22export function formatDuration(seconds: number) {22export function formatDuration(seconds: number) {
23 const minutes = Math.floor(seconds / 60);23 const minutes = Math.floor(seconds / 60);
...@@ -255,5 +255,27 @@ export function highlightHashComments(text: string) {...@@ -255,5 +255,27 @@ export function highlightHashComments(text: string) {
255 .join("\n");255 .join("\n");
256}256}
257257
258const unknownDate = new Date("1970-01-03");
259const unknownDateWithKnownYear = new Date("1970-02-20");
260
261export function formatDate(dateTime: Date) {
262 return dateTime < unknownDateWithKnownYear
263 ? (
264 dateTime < unknownDate
265 ? (
266 "??.??.??"
267 )
268 : `xx.xx.${21 + Math.floor(dateTime.getTime() / 86400000)}`
269 )
270 : (
271 `${(dateTime.getMonth() + 1).toString().padStart(2, "0")}.${
272 dateTime
273 .getDate()
274 .toString()
275 .padStart(2, "0")
276 }.${dateTime.getFullYear().toString().slice(2)}`
277 );
278}
279
258import type { MediaFile } from "@/file-viewer/models/MediaFile.ts";280import type { MediaFile } from "@/file-viewer/models/MediaFile.ts";
259import { escapeHtml } from "#ssr";281import { escapeHtml } from "#ssr";
src/file-viewer/pages/file.404.tsx created+34
...@@ -0,0 +1,34 @@
1import { MediaFile } from "../models/MediaFile.ts";
2import { MediaPanel } from "../views/clofi.tsx";
3import { addScript } from "#sitegen";
4
5export const theme = {
6 bg: "#312652",
7 fg: "#f0f0ff",
8 primary: "#fabe32",
9};
10
11export const meta = { title: "file not found" };
12
13export default function CotyledonPage() {
14 addScript("../scripts/canvas_cotyledon.client.ts");
15 return (
16 <div class="files ctld ctld-sb">
17 <MediaPanel
18 file={MediaFile.getByPath("/")!}
19 isLast={false}
20 activeFilename={null}
21 hasCotyledonCookie={false}
22 />
23 <div class="panel last">
24 <div className="header"></div>
25 <div className="content file-view notfound">
26 <p>this file does not exist ...</p>
27 <p>
28 <a href="/file">return</a>
29 </p>
30 </div>
31 </div>
32 </div>
33 );
34}
src/file-viewer/pages/file.cotyledon_speedbump.tsx+1-1
...@@ -1,7 +1,7 @@...@@ -1,7 +1,7 @@
1import { MediaFile } from "../models/MediaFile.ts";1import { MediaFile } from "../models/MediaFile.ts";
2import { Speedbump } from "../cotyledon.tsx";
3import { MediaPanel } from "../views/clofi.tsx";2import { MediaPanel } from "../views/clofi.tsx";
4import { addScript } from "#sitegen";3import { addScript } from "#sitegen";
4import { Speedbump } from "../cotyledon.tsx";
55
6export const theme = {6export const theme = {
7 bg: "#312652",7 bg: "#312652",
src/file-viewer/sort.ts created+58
...@@ -0,0 +1,58 @@
1export function splitRootDirFiles(dir: MediaFile, hasCotyledonCookie: boolean) {
2 const children = dir.getPublicChildren();
3 let readme: MediaFile | null = null;
4
5 const groups = {
6 // years 2025 and onwards
7 years: [] as MediaFile[],
8 // named categories
9 categories: [] as MediaFile[],
10 // years 2017 to 2024
11 cotyledon: [] as MediaFile[],
12 };
13 const colorMap: Record<string, string> = {
14 years: "#a2ff91",
15 categories: "#9c91ff",
16 cotyledon: "#ff91ca",
17 };
18 for (const child of children) {
19 const basename = child.basename;
20 if (basename === "readme.txt") {
21 readme = child;
22 continue;
23 }
24
25 const year = basename.match(/^(\d{4})/);
26 if (year) {
27 const n = parseInt(year[1]);
28 if (n >= 2025) {
29 groups.years.push(child);
30 } else {
31 groups.cotyledon.push(child);
32 }
33 } else {
34 groups.categories.push(child);
35 }
36 }
37
38 let sections = [];
39 for (const [key, files] of Object.entries(groups)) {
40 if (key === "cotyledon" && !hasCotyledonCookie) {
41 continue;
42 }
43 if (key === "years" || key === "cotyledon") {
44 files.sort((a, b) => {
45 return b.basename.localeCompare(a.basename);
46 });
47 } else {
48 files.sort((a, b) => {
49 return a.basename.localeCompare(b.basename);
50 });
51 }
52 sections.push({ key, titleColor: colorMap[key], files });
53 }
54
55 return { readme, sections };
56}
57
58import { MediaFile } from "./models/MediaFile.ts";
src/file-viewer/views/canvas.astro deleted-12
...@@ -1,12 +0,0 @@
1---
2import { useInlineScript } from "../framework/page-resources.ts";
3
4const { script } = Astro.props;
5useInlineScript('canvas_' + script as any);
6useInlineScript('canvas_demo');
7---
8<canvas id="canvas"
9 data-canvas={script}
10 data-standalone="true"
11 style="position: fixed; top: 0; left: 0; width: 100vw; height: 100vh;"
12></canvas>
src/file-viewer/views/canvas.marko created+17
...@@ -0,0 +1,17 @@
1export interface Input {
2 script: string;
3}
4export const meta = { title: "canvas" };
5
6<const/{ script } = input />
7
8<canvas id="canvas"
9 data-canvas=script
10 data-standalone="true"
11 style="position: fixed; top: 0; left: 0; width: 100vw; height: 100vh;"
12></canvas>
13<AddScript="file-viewer/scripts/canvas_" + script + ".client.ts" />
14
15client import "./canvas.client.ts";
16
17import { addScript as AddScript } from '#sitegen';
src/file-viewer/views/clofi.css+9
...@@ -687,6 +687,15 @@ h3 {...@@ -687,6 +687,15 @@ h3 {
687 transform: translateY(0);687 transform: translateY(0);
688 }688 }
689}689}
690.notfound {
691 p {
692 text-align: center;
693 }
694 a {
695 text-decoration: underline;
696 }
697}
698
690/* MOBILE */699/* MOBILE */
691@media (max-width: 1000px) {700@media (max-width: 1000px) {
692 html, body {701 html, body {
src/file-viewer/views/clofi.tsx+5-49
...@@ -406,7 +406,7 @@ function ListItem({...@@ -406,7 +406,7 @@ function ListItem({
406 noDate?: boolean;406 noDate?: boolean;
407}) {407}) {
408 const dateTime = file.date;408 const dateTime = file.date;
409 let shortDate = dateTime < unknownDateWithKnownYear409 const shortDate = dateTime < unknownDateWithKnownYear
410 ? (410 ? (
411 dateTime < unknownDate411 dateTime < unknownDate
412 ? (412 ? (
...@@ -565,40 +565,7 @@ function RootDirView({...@@ -565,40 +565,7 @@ function RootDirView({
565 hasCotyledonCookie: boolean;565 hasCotyledonCookie: boolean;
566}) {566}) {
567 const children = dir.getPublicChildren();567 const children = dir.getPublicChildren();
568 let readme: MediaFile | null = null;568 const { readme, sections } = sort.splitRootDirFiles(dir, hasCotyledonCookie);
569
570 const groups = {
571 // years 2025 and onwards
572 years: [] as MediaFile[],
573 // named categories
574 categories: [] as MediaFile[],
575 // years 2017 to 2024
576 cotyledon: [] as MediaFile[],
577 };
578 const colorMap = {
579 years: "#a2ff91",
580 categories: "#9c91ff",
581 cotyledon: "#ff91ca",
582 };
583 for (const child of children) {
584 const basename = child.basename;
585 if (basename === readmeFile) {
586 readme = child;
587 continue;
588 }
589
590 const year = basename.match(/^(\d{4})/);
591 if (year) {
592 const n = parseInt(year[1]);
593 if (n >= 2025) {
594 groups.years.push(child);
595 } else {
596 groups.cotyledon.push(child);
597 }
598 } else {
599 groups.categories.push(child);
600 }
601 }
602569
603 if (readme && isLast) activeFilename ||= readmeFile;570 if (readme && isLast) activeFilename ||= readmeFile;
604571
...@@ -613,22 +580,10 @@ function RootDirView({...@@ -613,22 +580,10 @@ function RootDirView({
613 />580 />
614 </ul>581 </ul>
615 )}582 )}
616 {Object.entries(groups).map(([key, files]) => {583 {sections.map(({ key, titleColor, files }) => {
617 if (key === "cotyledon" && !hasCotyledonCookie) {
618 return null;
619 }
620 if (key === "years" || key === "cotyledon") {
621 files.sort((a, b) => {
622 return b.basename.localeCompare(a.basename);
623 });
624 } else {
625 files.sort((a, b) => {
626 return a.basename.localeCompare(b.basename);
627 });
628 }
629 return (584 return (
630 <div class="group" data-group={key}>585 <div class="group" data-group={key}>
631 <h3 style={`color:${colorMap[key as keyof typeof colorMap]};`}>586 <h3 style={{ color: titleColor }}>
632 {key}587 {key}
633 </h3>588 </h3>
634 <ul>589 <ul>
...@@ -1001,3 +956,4 @@ import {...@@ -1001,3 +956,4 @@ import {
1001} from "@/file-viewer/format.ts";956} from "@/file-viewer/format.ts";
1002import { ForEveryone } from "@/file-viewer/cotyledon.tsx";957import { ForEveryone } from "@/file-viewer/cotyledon.tsx";
1003import * as mime from "#sitegen/mime";958import * as mime from "#sitegen/mime";
959import * as sort from "../sort.ts";
src/file-viewer/views/lofi.css created+43
...@@ -0,0 +1,43 @@
1body {
2 margin: 0;
3 padding: 0;
4}
5#lofi {
6 padding: 32px;
7}
8h1 {
9 margin-top: 0;
10 font-size: 3em;
11 color: var(--primary);
12 font-family: monospace;
13}
14ul, li {
15 margin: 0;
16 padding: 0;
17 list-style-type: none;
18}
19ul {
20 padding-right: 4em;
21}
22li a {
23 display: block;
24 color: white;
25 line-height: 2em;
26 padding: 0 1em;
27 border-radius: 4px;
28}
29li a:hover {
30 background-color: rgba(255,255,255,0.2);
31 font-weight: bold;
32 text-decoration: none!important;
33}
34.dir a {
35 color: #99eeFF
36}
37.ext {
38 opacity: 0.5;
39}
40.meta {
41 margin-left: 1em;
42 opacity: 0.75;
43}
src/file-viewer/views/lofi.marko created+71
...@@ -0,0 +1,71 @@
1import "./lofi.css";
2export interface Input {
3 file: MediaFile;
4 hasCotyledonCookie: boolean;
5}
6export { meta, theme } from "./clofi.tsx";
7
8<const/{ file: dir, hasCotyledonCookie } = input />
9<const/{ path: fullPath, dirname, basename, kind } = dir />
10<const/isRoot = fullPath == '/'>
11
12<div#lofi>
13
14<define/ListItem|{ value: file }|>
15 <const/dir=file.kind === MediaFileKind.directory/>
16 <const/meta=(
17 dir
18 ? formatSize(file.size)
19 : (file.duration ?? 0) > 0
20 ? formatDuration(file.duration!)
21 : null
22 )/>
23 <li class={ dir }>
24 <a href=`/file${escapeUri(file.path)}` >
25 <code>${formatDate(file.date)}</>${" "}
26 ${file.basenameWithoutExt}<span class="ext">${file.extension}</>${dir ? '/' : ''}
27 <if=meta><span class='meta'>(${meta})</></>
28 </a>
29 </li>
30</define>
31
32<h1>
33 <if=isRoot>clo's files</>
34 <else>${fullPath}</>
35</h1>
36
37<if=isRoot>
38 <const/{ readme, sections } = sort.splitRootDirFiles(dir, hasCotyledonCookie) />
39 <if=readme><ul><ListItem=readme/></ul></>
40 <for|{key, files, titleColor }| of=sections>
41 <h2 style={color: titleColor }>${key}</>
42 <ul>
43 <for|item| of=files><ListItem=item /></>
44 </ul>
45 </>
46 <if=!hasCotyledonCookie>
47 <br><br><br><br><br><br>
48 <p style={ opacity: 0.3 }>
49 would you like to
50 <a
51 href="/file/cotyledon"
52 style={
53 color: 'white',
54 'text-decoration': 'underline',
55 }
56 >dive deeper?</a>
57 </p>
58 </>
59</><else>
60 <ul>
61 <li><a href=`/file${escapeUri(path.posix.dirname(fullPath))}`>[up one...]</a></li>
62 <for|item| of=dir.getChildren()> <ListItem=item /> </>
63 </ul>
64</>
65
66</div>
67
68import * as path from "node:path";
69import { escapeUri, formatDuration, formatSize, formatDate } from "@/file-viewer/format.ts";
70import { MediaFileKind } from "@/file-viewer/models/MediaFile.ts";
71import * as sort from "@/file-viewer/sort.ts";
src/file-viewer/views/lofideon.marko created+22
...@@ -0,0 +1,22 @@
1export interface Input {
2 stage: number
3}
4export const meta = { title: 'C O T Y L E D O N' };
5export const theme = {
6 bg: '#ff00ff',
7 fg: '#000000',
8};
9
10<h1>co<br>ty<br>le<br>don</h1>
11
12<if=input.stage==0>
13
14<p>
15this place is sacred, but dangerous. i have to keep visitors to an absolute minimum; you'll get dust on all the artifacts.
16</p><p>
17by entering our museum, you agree not to use your camera. flash off isn't enough; the bits and bytes are alergic even to a camera's sensor
18</p><p>
19<sub>(in english: please do not store downloads after you're done viewing them)</sub>
20</p>
21
22</>
tsconfig.json+1-1
...@@ -3,7 +3,7 @@...@@ -3,7 +3,7 @@
3 "allowImportingTsExtensions": true,3 "allowImportingTsExtensions": true,
4 "baseUrl": ".",4 "baseUrl": ".",
5 "incremental": true,5 "incremental": true,
6 "jsx": "react-jsx",6 "jsx": "react-jsxdev",
7 "jsxImportSource": "#ssr",7 "jsxImportSource": "#ssr",
8 "lib": ["dom", "esnext", "esnext.iterator"],8 "lib": ["dom", "esnext", "esnext.iterator"],
9 "module": "nodenext",9 "module": "nodenext",