commit
2f7ea4a99e
@ -0,0 +1,24 @@ |
||||
# Logs |
||||
logs |
||||
*.log |
||||
npm-debug.log* |
||||
yarn-debug.log* |
||||
yarn-error.log* |
||||
pnpm-debug.log* |
||||
lerna-debug.log* |
||||
|
||||
node_modules |
||||
dist |
||||
dist-ssr |
||||
*.local |
||||
|
||||
# Editor directories and files |
||||
.vscode/* |
||||
!.vscode/extensions.json |
||||
.idea |
||||
.DS_Store |
||||
*.suo |
||||
*.ntvs* |
||||
*.njsproj |
||||
*.sln |
||||
*.sw? |
@ -0,0 +1,10 @@ |
||||
# RBlockNote and NextGraph integration demo |
||||
|
||||
``` |
||||
npm i |
||||
npm run dev |
||||
``` |
||||
|
||||
you need to create a wallet at https://nextgraph.eu |
||||
|
||||
Standalone webapps do not work on Safari (yet). |
@ -0,0 +1,28 @@ |
||||
import js from '@eslint/js' |
||||
import globals from 'globals' |
||||
import reactHooks from 'eslint-plugin-react-hooks' |
||||
import reactRefresh from 'eslint-plugin-react-refresh' |
||||
import tseslint from 'typescript-eslint' |
||||
|
||||
export default tseslint.config( |
||||
{ ignores: ['dist'] }, |
||||
{ |
||||
extends: [js.configs.recommended, ...tseslint.configs.recommended], |
||||
files: ['**/*.{ts,tsx}'], |
||||
languageOptions: { |
||||
ecmaVersion: 2020, |
||||
globals: globals.browser, |
||||
}, |
||||
plugins: { |
||||
'react-hooks': reactHooks, |
||||
'react-refresh': reactRefresh, |
||||
}, |
||||
rules: { |
||||
...reactHooks.configs.recommended.rules, |
||||
'react-refresh/only-export-components': [ |
||||
'warn', |
||||
{ allowConstantExport: true }, |
||||
], |
||||
}, |
||||
}, |
||||
) |
@ -0,0 +1,12 @@ |
||||
<!doctype html> |
||||
<html lang="en"> |
||||
<head> |
||||
<meta charset="UTF-8" /> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
||||
<title>BlockNote + NextGraph</title> |
||||
</head> |
||||
<body> |
||||
<div id="app"></div> |
||||
<script type="module" src="/src/main.tsx"></script> |
||||
</body> |
||||
</html> |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,43 @@ |
||||
{ |
||||
"name": "blocknote", |
||||
"private": true, |
||||
"version": "0.0.0", |
||||
"type": "module", |
||||
"scripts": { |
||||
"dev": "vite", |
||||
"build": "tsc -b && vite build", |
||||
"lint": "eslint .", |
||||
"preview": "vite preview" |
||||
}, |
||||
"dependencies": { |
||||
"@blocknote/core": "^0.31.1", |
||||
"@blocknote/mantine": "^0.31.1", |
||||
"@blocknote/react": "^0.31.1", |
||||
"@heroicons/react": "^2.2.0", |
||||
"lib0": "^0.2.108", |
||||
"nextgraph-react": "^0.1.1-alpha.3", |
||||
"nextgraphweb": "^0.1.1-alpha.4", |
||||
"react": "^19.1.0", |
||||
"react-dom": "^19.1.0", |
||||
"react-router": "^7.6.1", |
||||
"react-router-dom": "^7.6.1", |
||||
"tailwindcss": "^4.1.8", |
||||
"yjs": "^13.6.27" |
||||
}, |
||||
"devDependencies": { |
||||
"@eslint/js": "^9.25.0", |
||||
"@types/react": "^19.1.2", |
||||
"@types/react-dom": "^19.1.2", |
||||
"@vitejs/plugin-react": "^4.4.1", |
||||
"autoprefixer": "^10.4.21", |
||||
"eslint": "^9.25.0", |
||||
"eslint-plugin-react-hooks": "^5.2.0", |
||||
"eslint-plugin-react-refresh": "^0.4.19", |
||||
"globals": "^16.0.0", |
||||
"postcss": "^8.5.3", |
||||
"tailwindcss": "^3.4.17", |
||||
"typescript": "~5.8.3", |
||||
"typescript-eslint": "^8.30.1", |
||||
"vite": "^6.3.5" |
||||
} |
||||
} |
@ -0,0 +1,6 @@ |
||||
export default { |
||||
plugins: { |
||||
tailwindcss: {}, |
||||
autoprefixer: {}, |
||||
}, |
||||
} |
After Width: | Height: | Size: 1.5 KiB |
@ -0,0 +1,53 @@ |
||||
.fullscreen { |
||||
top: 40px; |
||||
position:relative; |
||||
} |
||||
|
||||
.doc { |
||||
height: 100vh; |
||||
} |
||||
|
||||
.centered { |
||||
/*max-width: 1280px;*/ |
||||
margin: 0 auto; |
||||
padding: 0rem; |
||||
text-align: center; |
||||
width: fit-content; |
||||
} |
||||
|
||||
.contact { |
||||
width: 300px; |
||||
height: 300px; |
||||
background-color: #f6f6f6; |
||||
position: relative; |
||||
overflow-wrap: anywhere; |
||||
} |
||||
|
||||
.name { |
||||
padding: 5px; |
||||
height: 35px; |
||||
overflow: hidden; |
||||
background-color: #e0e0e0d0; |
||||
overflow: hidden; |
||||
text-overflow: ellipsis; |
||||
white-space: nowrap; |
||||
} |
||||
|
||||
.email-logo { |
||||
|
||||
} |
||||
|
||||
.email { |
||||
padding: 5px; |
||||
overflow-wrap: anywhere; |
||||
} |
||||
|
||||
input { |
||||
margin: 5px; |
||||
} |
||||
|
||||
.button { |
||||
background-color:rgb(73, 114, 165); |
||||
color:white; |
||||
cursor:pointer; |
||||
} |
@ -0,0 +1,28 @@ |
||||
import React, { FunctionComponent } from 'react'; |
||||
import { Header } from './Header'; |
||||
import { Doc } from './Doc'; |
||||
import { Home } from './Home'; |
||||
import { NextGraphAuthMethod } from './reactMethods'; |
||||
import { BrowserRouter, Routes, Route } from "react-router"; |
||||
|
||||
|
||||
import './App.css' |
||||
import "./styles.css"; |
||||
|
||||
const App: FunctionComponent = () => { |
||||
|
||||
return ( |
||||
<div className="App"> |
||||
<NextGraphAuthMethod> |
||||
<Header /> |
||||
<BrowserRouter> |
||||
<Routes> |
||||
<Route index element={<Home />} /> |
||||
<Route path=":nuri" element={<Doc />} /> |
||||
</Routes> |
||||
</BrowserRouter> |
||||
</NextGraphAuthMethod> |
||||
</div> |
||||
) |
||||
} |
||||
export default App |
@ -0,0 +1,51 @@ |
||||
import "@blocknote/core/fonts/inter.css"; |
||||
import { default as React, FunctionComponent, useMemo } from "react"; |
||||
import { BlockNoteView } from "@blocknote/mantine"; |
||||
import "@blocknote/mantine/style.css"; |
||||
import { useCreateBlockNote } from "@blocknote/react"; |
||||
import * as Y from "yjs"; |
||||
import { NextGraphProvider } from "./provider"; |
||||
import { useNextGraphAuth } from './reactMethods'; |
||||
import { useParams } from "react-router"; |
||||
|
||||
export const Doc: FunctionComponent = () => { |
||||
const { session } = useNextGraphAuth(); |
||||
const params = useParams(); |
||||
|
||||
const [doc, provider] = useMemo(() => { |
||||
const doc = new Y.Doc(); |
||||
const provider = new NextGraphProvider(params.nuri, doc, session); |
||||
return [doc, provider]; |
||||
}, [params, session]); |
||||
|
||||
const editor = useCreateBlockNote({ |
||||
collaboration: { |
||||
// The Yjs Provider responsible for transporting updates:
|
||||
provider, |
||||
// Where to store BlockNote data in the Y.Doc:
|
||||
fragment: doc.getXmlFragment('document-store'), |
||||
// Information (name and color) for this user:
|
||||
user: { |
||||
name: "My Username", |
||||
color: "#ff0000", |
||||
}, |
||||
// When to show user labels on the collaboration cursor. Set by default to
|
||||
// "activity" (show when the cursor moves), but can also be set to "always".
|
||||
showCursorLabels: "activity" |
||||
}, |
||||
}, [params, session]) |
||||
|
||||
// Creates a new editor instance.
|
||||
|
||||
if (!session.sessionId) return <></>; |
||||
|
||||
// Renders the editor instance using a React component.
|
||||
return ( |
||||
<div className="doc"> |
||||
<div className="fullscreen"> |
||||
<BlockNoteView editor={editor} /> |
||||
</div> |
||||
</div> |
||||
); |
||||
|
||||
} |
@ -0,0 +1,44 @@ |
||||
import { FunctionComponent } from "react"; |
||||
import { useNextGraphAuth } from "./reactMethods"; |
||||
|
||||
export const Header: FunctionComponent = () => { |
||||
|
||||
const { session, login, logout } = useNextGraphAuth(); |
||||
|
||||
return ( |
||||
<div> |
||||
|
||||
{session.sessionId ? ( |
||||
// If the session is logged in
|
||||
<div className="p-1 text-white text-center fixed top-0 left-0 right-0" style={{zIndex:1000, height:'36px', backgroundColor:'rgb(73, 114, 165)'}}> |
||||
You are logged in. |
||||
{/* <span className="font-bold clickable" onClick={logout}> Log out</span> */} |
||||
</div> |
||||
) : ( |
||||
// If the session is not logged in
|
||||
<> |
||||
<h1 className="text-2xl text-center mb-10">Welcome to BlockNote + NextGraph demo</h1> |
||||
<div className="text-center text-xl p-1 text-white fixed top-0 left-0 right-0" style={{zIndex:1000, height:'36px', backgroundColor:'rgb(73, 114, 165)'}}> |
||||
Please <span className="font-bold clickable" onClick={login}> Log in</span> |
||||
</div> |
||||
|
||||
<div className="text-center max-w-6xl lg:px-8 mx-auto px-4 text-blue-800"> |
||||
|
||||
<svg onClick={login} |
||||
onKeyUp={login} className="cursor-pointer mt-10 h-16 w-16 mx-auto" data-slot="icon" fill="none" strokeWidth="1.5" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"> |
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M8.25 9V5.25A2.25 2.25 0 0 1 10.5 3h6a2.25 2.25 0 0 1 2.25 2.25v13.5A2.25 2.25 0 0 1 16.5 21h-6a2.25 2.25 0 0 1-2.25-2.25V15M12 9l3 3m0 0-3 3m3-3H2.25"></path> |
||||
</svg> |
||||
|
||||
<button |
||||
onClick={login} |
||||
onKeyUp={login} |
||||
className="select-none ml-0 mt-2 mb-10 text-white bg-blue-800 hover:bg-primary-700/90 focus:ring-4 focus:ring-primary-500/50 rounded-lg text-base p-2 text-center inline-flex items-center dark:focus:ring-primary-700/55" |
||||
> |
||||
Please Log in |
||||
</button> |
||||
</div> |
||||
</> |
||||
)} |
||||
</div> |
||||
); |
||||
}; |
@ -0,0 +1,26 @@ |
||||
import { FunctionComponent } from "react"; |
||||
import { useNextGraphAuth } from "./reactMethods"; |
||||
import { DocumentPlusIcon } from '@heroicons/react/24/outline' |
||||
import { useNavigate } from "react-router-dom"; |
||||
|
||||
export const Home: FunctionComponent = () => { |
||||
const { session } = useNextGraphAuth(); |
||||
const navigate = useNavigate(); |
||||
|
||||
const create = () => { |
||||
session.ng.doc_create(session.sessionId, "YXml","post:blocknote","store").then((nuri) => { |
||||
console.log("new doc created",nuri) |
||||
navigate("/"+nuri); |
||||
}); |
||||
}; |
||||
|
||||
if (!session.sessionId) return <></>; |
||||
|
||||
return <> |
||||
<div className="centered"> |
||||
<div className="flex flex-wrap justify-center gap-5 mt-10 mb-10"> |
||||
<button onClick={create} className="button"><DocumentPlusIcon className="size-7 inline"/> Create Document</button> |
||||
</div> |
||||
</div> |
||||
</>; |
||||
}; |
@ -0,0 +1,36 @@ |
||||
/* |
||||
* Base64URL-ArrayBuffer |
||||
* https://github.com/herrjemand/Base64URL-ArrayBuffer
|
||||
* |
||||
* Copyright (c) 2017 Yuriy Ackermann <ackermann.yuriy@gmail.com> |
||||
* Copyright (c) 2012 Niklas von Hertzen |
||||
* Licensed under the MIT license. |
||||
*
|
||||
*/ |
||||
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; |
||||
|
||||
// Use a lookup table to find the index.
|
||||
var lookup = new Uint8Array(256); |
||||
for (var i = 0; i < chars.length; i++) { |
||||
lookup[chars.charCodeAt(i)] = i; |
||||
} |
||||
|
||||
export const encode = function(arraybuffer) { |
||||
var bytes = new Uint8Array(arraybuffer), |
||||
i, len = bytes.length, base64 = ""; |
||||
|
||||
for (i = 0; i < len; i+=3) { |
||||
base64 += chars[bytes[i] >> 2]; |
||||
base64 += chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)]; |
||||
base64 += chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)]; |
||||
base64 += chars[bytes[i + 2] & 63]; |
||||
} |
||||
|
||||
if ((len % 3) === 2) { |
||||
base64 = base64.substring(0, base64.length - 1); |
||||
} else if (len % 3 === 1) { |
||||
base64 = base64.substring(0, base64.length - 2); |
||||
} |
||||
|
||||
return base64; |
||||
}; |
@ -0,0 +1,3 @@ |
||||
@tailwind base; |
||||
@tailwind components; |
||||
@tailwind utilities; |
@ -0,0 +1,10 @@ |
||||
//import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client' |
||||
import './index.css' |
||||
import App from './App.tsx' |
||||
|
||||
createRoot(document.getElementById('app')!).render( |
||||
// <StrictMode>
|
||||
<App /> |
||||
// </StrictMode>,
|
||||
) |
@ -0,0 +1,93 @@ |
||||
import { ObservableV2 } from 'lib0/observable' |
||||
import { encode } from "./base64url"; |
||||
import * as Y from "yjs"; |
||||
|
||||
const digest_to_string = function(digest) { |
||||
let copy = [...digest.Blake3Digest32]; |
||||
copy.reverse(); |
||||
copy.push(0); |
||||
let buffer = Uint8Array.from(copy); |
||||
return encode(buffer.buffer); |
||||
}; |
||||
|
||||
export class NextGraphProvider extends ObservableV2 { |
||||
/** |
||||
* @param {string} nuri |
||||
* @param {Y.Doc} doc |
||||
* @param {Object} session |
||||
**/ |
||||
constructor (nuri, doc, session) { |
||||
super() |
||||
if (!session.ng) return; |
||||
this.nuri = nuri |
||||
this.doc = doc |
||||
this.session = session |
||||
this.heads = []; |
||||
this.shouldConnect = false |
||||
|
||||
/** |
||||
* Listens to Yjs updates and sends them to NextGraph |
||||
* @param {Uint8Array} update |
||||
* @param {any} origin |
||||
*/ |
||||
this._updateHandler = async (update, origin) => { |
||||
if (!origin.local) { |
||||
try { |
||||
await this.session.ng.discrete_update(this.session.sessionId, update, this.heads, "YXml", this.nuri); |
||||
} catch (e){ |
||||
console.log(e); |
||||
} |
||||
} |
||||
} |
||||
this.doc.on('update', this._updateHandler) |
||||
this.destroy = this.destroy.bind(this) |
||||
this.doc.on('destroy', this.destroy) |
||||
this.connect() |
||||
} |
||||
|
||||
destroy () { |
||||
this.disconnect() |
||||
//this.awareness.off('update', this._awarenessUpdateHandler)
|
||||
this.doc.off('update', this._updateHandler) |
||||
super.destroy() |
||||
} |
||||
|
||||
disconnect () { |
||||
this.shouldConnect = false |
||||
// TODO unsub
|
||||
} |
||||
|
||||
connect () { |
||||
console.log("connecting to doc",this.nuri.substring(0,53)) |
||||
if (this.shouldConnect) return; |
||||
|
||||
this.shouldConnect = true |
||||
this.session.ng.doc_subscribe(this.nuri.substring(0,53), this.session.sessionId,
|
||||
(response) => { |
||||
//console.log("GOT APP RESPONSE", response);
|
||||
if (response.V0.TabInfo) { |
||||
} else if (response.V0.State) { |
||||
if (response.V0.State.discrete) { |
||||
Y.applyUpdate(this.doc, response.V0.State.discrete.YXml, {local:true}) |
||||
} |
||||
for (const head of response.V0.State.heads) { |
||||
let commitId = digest_to_string(head); |
||||
this.heads.push(commitId); |
||||
} |
||||
} else if (response.V0.Patch) { |
||||
if (response.V0.Patch.discrete) { |
||||
Y.applyUpdate(this.doc, response.V0.Patch.discrete.YXml, {local:true}) |
||||
} |
||||
let i = this.heads.length; |
||||
while (i--) { |
||||
if (response.V0.Patch.commit_info.past.includes(this.heads[i])) { |
||||
this.heads.splice(i, 1); |
||||
} |
||||
} |
||||
this.heads.push(response.V0.Patch.commit_id); |
||||
|
||||
} |
||||
} |
||||
); // TODO .then keep unsub
|
||||
} |
||||
} |
@ -0,0 +1,6 @@ |
||||
import { createNextGraphAuthMethod } from "nextgraph-react"; |
||||
|
||||
const methods = createNextGraphAuthMethod(); |
||||
|
||||
export const { NextGraphAuthMethod, useNextGraphAuth } = methods; |
||||
|
@ -0,0 +1,226 @@ |
||||
/* |
||||
// Copyright (c) 2022-2025 Niko Bonnieure, Par le Peuple, NextGraph.org developers |
||||
// All rights reserved. |
||||
// Licensed under the Apache License, Version 2.0 |
||||
// <LICENSE-APACHE2 or http://www.apache.org/licenses/LICENSE-2.0> |
||||
// or the MIT license <LICENSE-MIT or http://opensource.org/licenses/MIT>, |
||||
// at your option. All files in the project carrying such |
||||
// notice may not be copied, modified, or distributed except |
||||
// according to those terms. |
||||
*/ |
||||
|
||||
|
||||
/** To format paths, like Settings > Wallet > Generate Wallet QR */ |
||||
.path { |
||||
font-family: monospace; |
||||
background-color: rgba(73, 114, 165, 0.1); |
||||
} |
||||
/* .splash-loaded { |
||||
display: none; |
||||
} */ |
||||
|
||||
|
||||
.toggle * { |
||||
cursor: pointer; |
||||
} |
||||
|
||||
.error-popover h3 { |
||||
text-align: center; |
||||
color: rgb(200 30 30); |
||||
} |
||||
.error-popover > div:first-child { |
||||
background-color: rgb(200 30 30); |
||||
} |
||||
.error-popover > div:first-child > h3 { |
||||
color: white; |
||||
} |
||||
|
||||
|
||||
|
||||
.logo { |
||||
padding: 1.5em; |
||||
will-change: filter; |
||||
transition: 0.75s; |
||||
padding-bottom: 1em; |
||||
} |
||||
|
||||
@keyframes pulse-logo-color { |
||||
0%, |
||||
100% { |
||||
fill: rgb(73, 114, 165); |
||||
stroke: rgb(73, 114, 165); |
||||
} |
||||
50% { |
||||
/* Mid-transition color */ |
||||
stroke: #bbb; |
||||
fill: #bbb; |
||||
} |
||||
} |
||||
|
||||
.logo-pulse path { |
||||
animation: pulse-logo-color 2s infinite; |
||||
animation-timing-function: cubic-bezier(0.65, 0.01, 0.59, 0.83); |
||||
} |
||||
|
||||
.logo-gray path { |
||||
fill: #bbb; |
||||
stroke: #bbb; |
||||
} |
||||
|
||||
.logo-blue path { |
||||
fill: rgb(73, 114, 165); |
||||
stroke: rgb(73, 114, 165); |
||||
} |
||||
|
||||
.jse-absolute-popup-content { |
||||
left: 0 !important; |
||||
} |
||||
|
||||
.container3 { |
||||
margin: 0; |
||||
|
||||
display: flex; |
||||
flex-direction: column; |
||||
justify-content: center; |
||||
text-align: center; |
||||
} |
||||
|
||||
.container3 aside { |
||||
width: 20rem !important; |
||||
} |
||||
|
||||
div[role="alert"] div { |
||||
display: block; |
||||
} |
||||
|
||||
.spinner-overlay button { |
||||
display: none; |
||||
} |
||||
|
||||
.choice-button { |
||||
min-width: 305px; |
||||
} |
||||
|
||||
.clickable { |
||||
cursor: pointer; |
||||
} |
||||
|
||||
.row { |
||||
display: flex; |
||||
justify-content: center; |
||||
} |
||||
|
||||
.deactivated-menu > svg { |
||||
color: rgb(156 163 175) !important; |
||||
} |
||||
|
||||
:root { |
||||
font-family: Inter, Avenir, Helvetica, Arial, sans-serif; |
||||
font-size: 16px; |
||||
line-height: 24px; |
||||
font-weight: 400; |
||||
|
||||
color: #0f0f0f; |
||||
background-color: white; |
||||
|
||||
font-synthesis: none; |
||||
text-rendering: optimizeLegibility; |
||||
-webkit-font-smoothing: antialiased; |
||||
-moz-osx-font-smoothing: grayscale; |
||||
-webkit-text-size-adjust: 100%; |
||||
} |
||||
|
||||
body { |
||||
margin: 0; |
||||
display: flex; |
||||
place-items: center; |
||||
min-width: 305px; |
||||
min-height: 100vh; |
||||
} |
||||
|
||||
#app { |
||||
width: 100%; |
||||
} |
||||
/* #app { |
||||
/*max-width: 1280px; |
||||
margin: 0 auto; |
||||
padding: 0rem; |
||||
text-align: center; |
||||
} */ |
||||
|
||||
/* .container2 { |
||||
padding-top: 10vh; |
||||
} */ |
||||
|
||||
a { |
||||
font-weight: 500; |
||||
color: #646cff; |
||||
text-decoration: inherit; |
||||
} |
||||
|
||||
a:hover { |
||||
color: #535bf2; |
||||
} |
||||
|
||||
.toast { |
||||
left: 50%; |
||||
transform: translateX(-50%); |
||||
z-index: 49; |
||||
cursor: pointer; |
||||
} |
||||
|
||||
input, |
||||
button { |
||||
border-radius: 8px; |
||||
border: 1px solid transparent; |
||||
padding: 0.6em 1.2em; |
||||
font-size: 1em; |
||||
font-weight: 500; |
||||
font-family: inherit; |
||||
color: #0f0f0f; |
||||
background-color: #ffffff; |
||||
transition: border-color 0.25s; |
||||
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2); |
||||
} |
||||
|
||||
button { |
||||
cursor: pointer; |
||||
} |
||||
|
||||
button:hover { |
||||
border-color: #396cd8; |
||||
} |
||||
button:active { |
||||
border-color: #396cd8; |
||||
background-color: #e8e8e8; |
||||
} |
||||
|
||||
/* input, |
||||
button { |
||||
outline: none; |
||||
} */ |
||||
|
||||
button:focus, |
||||
button:focus-visible { |
||||
outline: 4px auto -webkit-focus-ring-color; |
||||
} |
||||
|
||||
/* @media (prefers-color-scheme: dark) { |
||||
:root { |
||||
color: #f6f6f6; |
||||
background-color: #2f2f2f; |
||||
} |
||||
|
||||
a:hover { |
||||
color: #24c8db; |
||||
} |
||||
|
||||
input, |
||||
button { |
||||
color: #ffffff; |
||||
background-color: #0f0f0f98; |
||||
} |
||||
button:active { |
||||
background-color: #0f0f0f69; |
||||
} |
||||
} */ |
@ -0,0 +1 @@ |
||||
/// <reference types="vite/client" />
|
@ -0,0 +1,12 @@ |
||||
/** @type {import('tailwindcss').Config} */ |
||||
export default { |
||||
content: [ |
||||
"./index.html", |
||||
"./src/**/*.{js,jsx,ts,tsx}" |
||||
], |
||||
theme: { |
||||
extend: {}, |
||||
}, |
||||
plugins: [], |
||||
} |
||||
|
@ -0,0 +1,26 @@ |
||||
{ |
||||
"compilerOptions": { |
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", |
||||
"target": "ES2020", |
||||
"useDefineForClassFields": true, |
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"], |
||||
"module": "ESNext", |
||||
"skipLibCheck": true, |
||||
|
||||
/* Bundler mode */ |
||||
"moduleResolution": "bundler", |
||||
"allowImportingTsExtensions": true, |
||||
"moduleDetection": "force", |
||||
"noEmit": true, |
||||
"jsx": "react-jsx", |
||||
|
||||
/* Linting */ |
||||
"strict": true, |
||||
"noUnusedLocals": true, |
||||
"noUnusedParameters": true, |
||||
"erasableSyntaxOnly": true, |
||||
"noFallthroughCasesInSwitch": true, |
||||
"noUncheckedSideEffectImports": true |
||||
}, |
||||
"include": ["src"] |
||||
} |
@ -0,0 +1,7 @@ |
||||
{ |
||||
"files": [], |
||||
"references": [ |
||||
{ "path": "./tsconfig.app.json" }, |
||||
{ "path": "./tsconfig.node.json" } |
||||
] |
||||
} |
@ -0,0 +1,25 @@ |
||||
{ |
||||
"compilerOptions": { |
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", |
||||
"target": "ES2022", |
||||
"lib": ["ES2023"], |
||||
"module": "ESNext", |
||||
"skipLibCheck": true, |
||||
|
||||
/* Bundler mode */ |
||||
"moduleResolution": "bundler", |
||||
"allowImportingTsExtensions": true, |
||||
"verbatimModuleSyntax": true, |
||||
"moduleDetection": "force", |
||||
"noEmit": true, |
||||
|
||||
/* Linting */ |
||||
"strict": true, |
||||
"noUnusedLocals": true, |
||||
"noUnusedParameters": true, |
||||
"erasableSyntaxOnly": true, |
||||
"noFallthroughCasesInSwitch": true, |
||||
"noUncheckedSideEffectImports": true |
||||
}, |
||||
"include": ["vite.config.ts"] |
||||
} |
@ -0,0 +1,7 @@ |
||||
import { defineConfig } from 'vite' |
||||
import react from '@vitejs/plugin-react' |
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({ |
||||
plugins: [react()], |
||||
}) |
Loading…
Reference in new issue