mirror of
https://github.com/logos-messaging/examples.waku.org.git
synced 2026-01-05 22:33:08 +00:00
Merge 3c7795e121a0d305040401d5b4e6fdd7231648b5 into fca33640692a0c1cae431585782aa799042751a7
This commit is contained in:
commit
eac385859f
35
examples/flush-notes/.gitignore
vendored
Normal file
35
examples/flush-notes/.gitignore
vendored
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# next.js
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env*.local
|
||||||
|
|
||||||
|
# vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
|
next-env.d.ts
|
||||||
16
examples/flush-notes/README.md
Normal file
16
examples/flush-notes/README.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
## Waku dependencies
|
||||||
|
- @waku/interfaces
|
||||||
|
- @waku/message-encryption
|
||||||
|
- @waku/sdk
|
||||||
|
- @waku/utils
|
||||||
|
|
||||||
|
## Description
|
||||||
|
Exchange encrypted or plain notes by link.
|
||||||
|
This example shows how symmetric encryption can be used to encrypt only part of Waku message.
|
||||||
|
|
||||||
|
## How to run
|
||||||
|
```bash
|
||||||
|
npm start
|
||||||
|
# or
|
||||||
|
yarn start
|
||||||
|
```
|
||||||
14
examples/flush-notes/next.config.js
Normal file
14
examples/flush-notes/next.config.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
const nextConfig = {
|
||||||
|
output: "export",
|
||||||
|
async rewrites() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
source: "/view/:path*",
|
||||||
|
destination: "/view",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = nextConfig;
|
||||||
6902
examples/flush-notes/package-lock.json
generated
Normal file
6902
examples/flush-notes/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
30
examples/flush-notes/package.json
Normal file
30
examples/flush-notes/package.json
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"name": "flush-notes",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"build": "next build",
|
||||||
|
"start": "next dev",
|
||||||
|
"lint": "next lint"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@waku/interfaces": "0.0.21-7eb3375.0",
|
||||||
|
"@waku/message-encryption": "0.0.24-7eb3375.0",
|
||||||
|
"@waku/sdk": "0.0.22-7eb3375.0",
|
||||||
|
"@waku/utils": "0.0.14-7eb3375.0",
|
||||||
|
"next": "14.0.2",
|
||||||
|
"react": "^18",
|
||||||
|
"react-dom": "^18",
|
||||||
|
"react-markdown": "^9.0.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^20",
|
||||||
|
"@types/react": "^18",
|
||||||
|
"@types/react-dom": "^18",
|
||||||
|
"autoprefixer": "^10.0.1",
|
||||||
|
"eslint": "^8",
|
||||||
|
"eslint-config-next": "14.0.2",
|
||||||
|
"postcss": "^8",
|
||||||
|
"typescript": "^5"
|
||||||
|
}
|
||||||
|
}
|
||||||
5
examples/flush-notes/postcss.config.js
Normal file
5
examples/flush-notes/postcss.config.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
BIN
examples/flush-notes/src/app/favicon.ico
Normal file
BIN
examples/flush-notes/src/app/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
39
examples/flush-notes/src/app/globals.css
Normal file
39
examples/flush-notes/src/app/globals.css
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-block {
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-info {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.create-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.to-encrypt {
|
||||||
|
font-size: 1rem;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.save-note {
|
||||||
|
width: 100px;
|
||||||
|
height: 50px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.note-value {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
min-height: 500px;
|
||||||
|
}
|
||||||
23
examples/flush-notes/src/app/layout.tsx
Normal file
23
examples/flush-notes/src/app/layout.tsx
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { Inter } from "next/font/google";
|
||||||
|
import { WakuProvider } from "@/components/WakuProvider";
|
||||||
|
import "./globals.css";
|
||||||
|
|
||||||
|
const inter = Inter({ subsets: ["latin"] });
|
||||||
|
|
||||||
|
export default function RootLayout({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<html lang="en">
|
||||||
|
<title>Share notes</title>
|
||||||
|
<body className={inter.className}>
|
||||||
|
<WakuProvider>{children}</WakuProvider>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
57
examples/flush-notes/src/app/page.tsx
Normal file
57
examples/flush-notes/src/app/page.tsx
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import { notes } from "@/services/notes";
|
||||||
|
|
||||||
|
export default function Create() {
|
||||||
|
const router = useRouter();
|
||||||
|
const { note, onNoteChange } = useEditNote();
|
||||||
|
|
||||||
|
const onSave = async () => {
|
||||||
|
if (!note) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { id, key } = await notes.createNote(note);
|
||||||
|
const passwordParam = `?key=${key}`;
|
||||||
|
|
||||||
|
router.push(`/view/${id}${passwordParam}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to create a note:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<p className="note-info">
|
||||||
|
Your record will be stored for couple of days. Markdown is supported.
|
||||||
|
</p>
|
||||||
|
<div className="create-header">
|
||||||
|
<div></div>
|
||||||
|
<button onClick={onSave} className="save-note">
|
||||||
|
Save note
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<textarea
|
||||||
|
className="note-value"
|
||||||
|
value={note}
|
||||||
|
onChange={onNoteChange}
|
||||||
|
></textarea>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const useEditNote = () => {
|
||||||
|
const [state, setState] = React.useState<string>("");
|
||||||
|
|
||||||
|
const onNoteChange = (event: React.FormEvent<HTMLTextAreaElement>) => {
|
||||||
|
setState(event?.currentTarget?.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
note: state,
|
||||||
|
onNoteChange,
|
||||||
|
};
|
||||||
|
};
|
||||||
31
examples/flush-notes/src/app/view/page.tsx
Normal file
31
examples/flush-notes/src/app/view/page.tsx
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import Markdown from "react-markdown";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import { useNoteURL } from "@/hooks/useNoteURL";
|
||||||
|
import { notes } from "@/services/notes";
|
||||||
|
import { Loading } from "@/components/Loading";
|
||||||
|
|
||||||
|
const View = () => {
|
||||||
|
const router = useRouter();
|
||||||
|
const { id, key } = useNoteURL();
|
||||||
|
const [note, setNote] = React.useState<string>("");
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!id) {
|
||||||
|
router.replace("/404");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
notes.readNote(id, key).then((note) => setNote(note || ""));
|
||||||
|
}, [id, key, setNote]);
|
||||||
|
|
||||||
|
if (!note) {
|
||||||
|
return <Loading />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Markdown>{note}</Markdown>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default View;
|
||||||
7
examples/flush-notes/src/components/Loading.tsx
Normal file
7
examples/flush-notes/src/components/Loading.tsx
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export const Loading = () => {
|
||||||
|
return (
|
||||||
|
<div className="loading-block">
|
||||||
|
<p>Loading...</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
65
examples/flush-notes/src/components/WakuProvider.tsx
Normal file
65
examples/flush-notes/src/components/WakuProvider.tsx
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import { waku, Waku, WakuEvents } from "@/services/waku";
|
||||||
|
import { WakuStatus } from "@/const";
|
||||||
|
import { Loading } from "@/components/Loading";
|
||||||
|
|
||||||
|
type WakuContextProps = {
|
||||||
|
status: WakuStatus;
|
||||||
|
waku?: Waku;
|
||||||
|
};
|
||||||
|
|
||||||
|
const WakuContext = React.createContext<WakuContextProps>({
|
||||||
|
status: WakuStatus.Initializing,
|
||||||
|
waku: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
type WakuProviderProps = {
|
||||||
|
children: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useWaku = () => {
|
||||||
|
const { status, waku } = React.useContext(WakuContext);
|
||||||
|
|
||||||
|
return { status, waku };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WakuProvider = (props: WakuProviderProps) => {
|
||||||
|
const wakuRef = React.useRef<Waku>();
|
||||||
|
const [status, setStatus] = React.useState<WakuStatus>(
|
||||||
|
WakuStatus.Initializing
|
||||||
|
);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (wakuRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const statusListener = (event: CustomEvent) => {
|
||||||
|
setStatus(event.detail);
|
||||||
|
};
|
||||||
|
waku.addEventListener(WakuEvents.Status, statusListener);
|
||||||
|
|
||||||
|
waku.init().then(() => {
|
||||||
|
wakuRef.current = waku;
|
||||||
|
});
|
||||||
|
return () => {
|
||||||
|
waku.removeEventListener(WakuEvents.Status, statusListener);
|
||||||
|
};
|
||||||
|
}, [wakuRef, setStatus]);
|
||||||
|
|
||||||
|
if (status === WakuStatus.Failed) {
|
||||||
|
return <>{status}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status !== WakuStatus.Connected) {
|
||||||
|
return <Loading />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<WakuContext.Provider value={{ status, waku: wakuRef.current }}>
|
||||||
|
{props.children}
|
||||||
|
</WakuContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
8
examples/flush-notes/src/const.ts
Normal file
8
examples/flush-notes/src/const.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export const CONTENT_TOPIC = "/flush-notes/1/note/proto";
|
||||||
|
|
||||||
|
export enum WakuStatus {
|
||||||
|
Initializing = "Initializing...",
|
||||||
|
WaitingForPeers = "Waiting for peers...",
|
||||||
|
Connected = "Connected",
|
||||||
|
Failed = "Failed to initialize(see logs)",
|
||||||
|
}
|
||||||
15
examples/flush-notes/src/hooks/useNoteURL.ts
Normal file
15
examples/flush-notes/src/hooks/useNoteURL.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
"use client";
|
||||||
|
import { usePathname, useSearchParams } from "next/navigation";
|
||||||
|
|
||||||
|
export const useNoteURL = (): { id: string; key: string } => {
|
||||||
|
const pathname = usePathname();
|
||||||
|
const params = useSearchParams();
|
||||||
|
|
||||||
|
const segments = pathname.split("/");
|
||||||
|
const viewIndex = segments.indexOf("view");
|
||||||
|
const key = params.get("key");
|
||||||
|
|
||||||
|
const id = segments[viewIndex + 1];
|
||||||
|
|
||||||
|
return { key, id };
|
||||||
|
};
|
||||||
91
examples/flush-notes/src/services/notes.ts
Normal file
91
examples/flush-notes/src/services/notes.ts
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { waku } from "@/services/waku";
|
||||||
|
import { CONTENT_TOPIC } from "@/const";
|
||||||
|
import {
|
||||||
|
createEncoder,
|
||||||
|
createDecoder,
|
||||||
|
DecodedMessage,
|
||||||
|
generateSymmetricKey,
|
||||||
|
} from "@waku/message-encryption/symmetric";
|
||||||
|
import { Unsubscribe, utf8ToBytes, bytesToUtf8 } from "@waku/sdk";
|
||||||
|
import { bytesToHex, hexToBytes } from "@waku/utils/bytes";
|
||||||
|
|
||||||
|
const UUID_V4_STR_LEN = 8 + 1 + 4 + 1 + 4 + 1 + 4 + 1 + 12; // 8-4-4-4-12 format
|
||||||
|
|
||||||
|
type Note = {
|
||||||
|
id: string;
|
||||||
|
content: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type NoteResult = {
|
||||||
|
id: string;
|
||||||
|
key: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class Notes {
|
||||||
|
private messages: DecodedMessage[] = [];
|
||||||
|
private subscription: undefined | Unsubscribe;
|
||||||
|
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
public async createNote(content: string): Promise<NoteResult> {
|
||||||
|
const symKey = generateSymmetricKey();
|
||||||
|
|
||||||
|
const encoder = createEncoder({ contentTopic: CONTENT_TOPIC, symKey });
|
||||||
|
const id = self.crypto.randomUUID();
|
||||||
|
|
||||||
|
if (id.length !== UUID_V4_STR_LEN) {
|
||||||
|
throw "Unexpected uuid length";
|
||||||
|
}
|
||||||
|
|
||||||
|
await waku.send(encoder, {
|
||||||
|
payload: utf8ToBytes(id + content),
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
key: bytesToHex(symKey),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async readNote(id: string, key: string): Promise<string | undefined> {
|
||||||
|
await this.initMessages(hexToBytes(key));
|
||||||
|
|
||||||
|
const message = this.messages
|
||||||
|
.map((m) => {
|
||||||
|
try {
|
||||||
|
const str = bytesToUtf8(m.payload);
|
||||||
|
|
||||||
|
const id = str.substring(0, UUID_V4_STR_LEN);
|
||||||
|
const content = str.substring(UUID_V4_STR_LEN);
|
||||||
|
|
||||||
|
return { id, content } as Note;
|
||||||
|
} catch (error) {
|
||||||
|
console.log("Failed to read message:", error);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.find((v) => {
|
||||||
|
if (v?.id === id) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return message?.content;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async initMessages(key: Uint8Array) {
|
||||||
|
if (this.subscription) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const decoder = createDecoder(CONTENT_TOPIC, key);
|
||||||
|
|
||||||
|
this.messages = await waku.getHistory(decoder);
|
||||||
|
this.subscription = await waku.subscribe(decoder, (message) => {
|
||||||
|
this.messages.push(message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const notes = new Notes();
|
||||||
105
examples/flush-notes/src/services/waku.ts
Normal file
105
examples/flush-notes/src/services/waku.ts
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { WakuStatus } from "@/const";
|
||||||
|
import {
|
||||||
|
IDecoder,
|
||||||
|
IEncoder,
|
||||||
|
IMessage,
|
||||||
|
LightNode,
|
||||||
|
createLightNode,
|
||||||
|
waitForRemotePeer,
|
||||||
|
IDecodedMessage,
|
||||||
|
} from "@waku/sdk";
|
||||||
|
|
||||||
|
type EventListener = (event: CustomEvent) => void;
|
||||||
|
|
||||||
|
export enum WakuEvents {
|
||||||
|
Status = "status",
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Waku {
|
||||||
|
private node: undefined | LightNode;
|
||||||
|
|
||||||
|
private emitter = new EventTarget();
|
||||||
|
private initialized: boolean = false;
|
||||||
|
private initializing: boolean = false;
|
||||||
|
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
public async init(): Promise<void> {
|
||||||
|
if (this.initialized || this.initializing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.initializing = true;
|
||||||
|
try {
|
||||||
|
this.emitStatusEvent(WakuStatus.Initializing);
|
||||||
|
const node = await createLightNode({ defaultBootstrap: true });
|
||||||
|
await node.start();
|
||||||
|
this.emitStatusEvent(WakuStatus.WaitingForPeers);
|
||||||
|
await waitForRemotePeer(node);
|
||||||
|
this.node = node;
|
||||||
|
this.initialized = true;
|
||||||
|
this.emitStatusEvent(WakuStatus.Connected);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to initialize Waku node:", error);
|
||||||
|
this.emitStatusEvent(WakuStatus.Failed);
|
||||||
|
}
|
||||||
|
this.initializing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public addEventListener(event: WakuEvents, fn: EventListener) {
|
||||||
|
return this.emitter.addEventListener(event, fn as any);
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeEventListener(event: WakuEvents, fn: EventListener) {
|
||||||
|
return this.emitter.removeEventListener(event, fn as any);
|
||||||
|
}
|
||||||
|
|
||||||
|
public send(encoder: IEncoder, message: IMessage) {
|
||||||
|
this.ensureWakuInitialized();
|
||||||
|
return this.node?.lightPush.send(encoder, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getHistory(
|
||||||
|
decoder: IDecoder<IDecodedMessage>
|
||||||
|
): Promise<IDecodedMessage[]> {
|
||||||
|
this.ensureWakuInitialized();
|
||||||
|
|
||||||
|
let messages: IDecodedMessage[] = [];
|
||||||
|
for await (const promises of this.node!.store.queryGenerator([decoder])) {
|
||||||
|
const messagesRaw = await Promise.all(promises);
|
||||||
|
const filteredMessages = messagesRaw.filter(
|
||||||
|
(v): v is IDecodedMessage => !!v
|
||||||
|
);
|
||||||
|
|
||||||
|
messages = [...messages, ...filteredMessages];
|
||||||
|
}
|
||||||
|
|
||||||
|
return messages;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async subscribe(
|
||||||
|
decoder: IDecoder<IDecodedMessage>,
|
||||||
|
fn: (m: IDecodedMessage) => void
|
||||||
|
) {
|
||||||
|
this.ensureWakuInitialized();
|
||||||
|
return this.node!.filter.subscribe(decoder, fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
private emitStatusEvent(payload: string) {
|
||||||
|
this.emitter.dispatchEvent(
|
||||||
|
new CustomEvent(WakuEvents.Status, { detail: payload })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ensureWakuInitialized() {
|
||||||
|
if (!waku.initialized) {
|
||||||
|
const message = "Waku is not initialized.";
|
||||||
|
console.log(message);
|
||||||
|
throw Error(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const waku = new Waku();
|
||||||
27
examples/flush-notes/tsconfig.json
Normal file
27
examples/flush-notes/tsconfig.json
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es5",
|
||||||
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"incremental": true,
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "next"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user