add modal to show code
This commit is contained in:
20
packages/website/app/modules/dom/portal.tsx
Normal file
20
packages/website/app/modules/dom/portal.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import type { ReactNode } from "react"
|
||||||
|
import { useEffect, useRef } from "react"
|
||||||
|
import { createPortal } from "react-dom"
|
||||||
|
|
||||||
|
export function Portal({ children }: { children: ReactNode }) {
|
||||||
|
const containerRef = useRef<Element>()
|
||||||
|
|
||||||
|
if (!containerRef.current && typeof document !== "undefined") {
|
||||||
|
containerRef.current = document.createElement("react-portal")
|
||||||
|
document.body.append(containerRef.current)
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => () => containerRef.current!.remove(), [])
|
||||||
|
|
||||||
|
return containerRef.current ? (
|
||||||
|
createPortal(children, containerRef.current)
|
||||||
|
) : (
|
||||||
|
<>{children}</>
|
||||||
|
)
|
||||||
|
}
|
||||||
197
packages/website/app/modules/landing/landing-animation.tsx
Normal file
197
packages/website/app/modules/landing/landing-animation.tsx
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
import clsx from "clsx"
|
||||||
|
import { useEffect, useRef, useState } from "react"
|
||||||
|
import blobComfyUrl from "~/assets/blob-comfy.png"
|
||||||
|
import cursorIbeamUrl from "~/assets/cursor-ibeam.png"
|
||||||
|
import cursorUrl from "~/assets/cursor.png"
|
||||||
|
|
||||||
|
const defaultState = {
|
||||||
|
chatInputText: "",
|
||||||
|
chatInputCursorVisible: true,
|
||||||
|
messageVisible: false,
|
||||||
|
count: 0,
|
||||||
|
cursorLeft: "25%",
|
||||||
|
cursorBottom: "-15px",
|
||||||
|
}
|
||||||
|
|
||||||
|
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
|
||||||
|
|
||||||
|
const animationFrame = () =>
|
||||||
|
new Promise((resolve) => requestAnimationFrame(resolve))
|
||||||
|
|
||||||
|
export function LandingAnimation() {
|
||||||
|
const [state, setState] = useState(defaultState)
|
||||||
|
const chatInputRef = useRef<HTMLDivElement>(null)
|
||||||
|
const addRef = useRef<HTMLDivElement>(null)
|
||||||
|
const deleteRef = useRef<HTMLDivElement>(null)
|
||||||
|
const cursorRef = useRef<HTMLImageElement>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const animateClick = (element: HTMLElement) =>
|
||||||
|
element.animate(
|
||||||
|
[{ transform: `translateY(2px)` }, { transform: `translateY(0px)` }],
|
||||||
|
300,
|
||||||
|
)
|
||||||
|
|
||||||
|
let running = true
|
||||||
|
|
||||||
|
void (async () => {
|
||||||
|
await delay(1000)
|
||||||
|
|
||||||
|
while (running) {
|
||||||
|
setState(defaultState)
|
||||||
|
await delay(1000)
|
||||||
|
|
||||||
|
for (const letter of "/counter") {
|
||||||
|
setState((state) => ({
|
||||||
|
...state,
|
||||||
|
chatInputText: state.chatInputText + letter,
|
||||||
|
}))
|
||||||
|
await delay(100)
|
||||||
|
}
|
||||||
|
|
||||||
|
await delay(1000)
|
||||||
|
|
||||||
|
setState((state) => ({
|
||||||
|
...state,
|
||||||
|
messageVisible: true,
|
||||||
|
chatInputText: "",
|
||||||
|
}))
|
||||||
|
await delay(1000)
|
||||||
|
|
||||||
|
setState((state) => ({
|
||||||
|
...state,
|
||||||
|
cursorLeft: "70px",
|
||||||
|
cursorBottom: "40px",
|
||||||
|
}))
|
||||||
|
await delay(1500)
|
||||||
|
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
setState((state) => ({
|
||||||
|
...state,
|
||||||
|
count: state.count + 1,
|
||||||
|
chatInputCursorVisible: false,
|
||||||
|
}))
|
||||||
|
animateClick(addRef.current!)
|
||||||
|
await delay(700)
|
||||||
|
}
|
||||||
|
|
||||||
|
await delay(500)
|
||||||
|
|
||||||
|
setState((state) => ({
|
||||||
|
...state,
|
||||||
|
cursorLeft: "140px",
|
||||||
|
}))
|
||||||
|
await delay(1000)
|
||||||
|
|
||||||
|
animateClick(deleteRef.current!)
|
||||||
|
setState((state) => ({ ...state, messageVisible: false }))
|
||||||
|
await delay(1000)
|
||||||
|
|
||||||
|
setState(() => ({
|
||||||
|
...defaultState,
|
||||||
|
chatInputCursorVisible: false,
|
||||||
|
}))
|
||||||
|
await delay(500)
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
running = false
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let running = true
|
||||||
|
|
||||||
|
void (async () => {
|
||||||
|
while (running) {
|
||||||
|
// check if the cursor is in the input
|
||||||
|
const cursorRect = cursorRef.current!.getBoundingClientRect()
|
||||||
|
const chatInputRect = chatInputRef.current!.getBoundingClientRect()
|
||||||
|
|
||||||
|
const isOverInput =
|
||||||
|
cursorRef.current &&
|
||||||
|
chatInputRef.current &&
|
||||||
|
cursorRect.top + cursorRect.height / 2 > chatInputRect.top
|
||||||
|
|
||||||
|
cursorRef.current!.src = isOverInput ? cursorIbeamUrl : cursorUrl
|
||||||
|
|
||||||
|
await animationFrame()
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
running = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="grid gap-2 relative pointer-events-none select-none"
|
||||||
|
role="presentation"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
"bg-slate-800 p-4 rounded-lg shadow transition",
|
||||||
|
state.messageVisible ? "opacity-100" : "opacity-0 -translate-y-2",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="flex items-start gap-4">
|
||||||
|
<div className="w-12 h-12 p-2 rounded-full bg-no-repeat bg-contain bg-black/25">
|
||||||
|
<img
|
||||||
|
src={blobComfyUrl}
|
||||||
|
alt=""
|
||||||
|
className="object-contain scale-90 w-full h-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<p className="font-bold">comfybot</p>
|
||||||
|
<p>this button was clicked {state.count} times</p>
|
||||||
|
<div className="mt-2 flex flex-row gap-3">
|
||||||
|
<div
|
||||||
|
ref={addRef}
|
||||||
|
className="bg-emerald-700 text-white py-1.5 px-3 text-sm rounded"
|
||||||
|
>
|
||||||
|
+1
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
ref={deleteRef}
|
||||||
|
className="bg-red-700 text-white py-1.5 px-3 text-sm rounded"
|
||||||
|
>
|
||||||
|
🗑 delete
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="bg-slate-700 pb-2 pt-1.5 px-4 rounded-lg shadow"
|
||||||
|
ref={chatInputRef}
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
className={clsx(
|
||||||
|
"text-sm after:content-[attr(data-after)] after:relative after:-top-px after:-left-[2px]",
|
||||||
|
state.chatInputCursorVisible
|
||||||
|
? "after:opacity-100"
|
||||||
|
: "after:opacity-0",
|
||||||
|
)}
|
||||||
|
data-after="|"
|
||||||
|
>
|
||||||
|
{state.chatInputText || (
|
||||||
|
<span className="opacity-50 block absolute translate-y-1">
|
||||||
|
Message #showing-off-reacord
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<img
|
||||||
|
src={cursorUrl}
|
||||||
|
alt=""
|
||||||
|
className="transition-all duration-500 absolute scale-75"
|
||||||
|
style={{ left: state.cursorLeft, bottom: state.cursorBottom }}
|
||||||
|
ref={cursorRef}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
26
packages/website/app/modules/landing/landing-code.mdx
Normal file
26
packages/website/app/modules/landing/landing-code.mdx
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{/* prettier-ignore */}
|
||||||
|
```tsx
|
||||||
|
import * as React from "react"
|
||||||
|
import { Button, useInstance } from "reacord"
|
||||||
|
|
||||||
|
function Counter() {
|
||||||
|
const [count, setCount] = React.useState(0)
|
||||||
|
const instance = useInstance()
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
this button was clicked {count} times
|
||||||
|
<Button
|
||||||
|
label="+1"
|
||||||
|
style="success"
|
||||||
|
onClick={() => setCount(count + 1)}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
label="delete"
|
||||||
|
emoji="🗑"
|
||||||
|
style="danger"
|
||||||
|
onClick={() => instance.destroy()}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
{/* prettier-ignore */}
|
|
||||||
```tsx
|
|
||||||
import * as React from "react"
|
|
||||||
import { Embed, Button } from "reacord"
|
|
||||||
|
|
||||||
function Counter() {
|
|
||||||
const [count, setCount] = React.useState(0)
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Embed title="Counter">
|
|
||||||
This button has been clicked {count} times.
|
|
||||||
</Embed>
|
|
||||||
<Button onClick={() => setCount(count + 1)}>
|
|
||||||
+1
|
|
||||||
</Button>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
80
packages/website/app/modules/ui/modal.tsx
Normal file
80
packages/website/app/modules/ui/modal.tsx
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import { XIcon } from "@heroicons/react/outline"
|
||||||
|
import clsx from "clsx"
|
||||||
|
import type { ReactNode } from "react"
|
||||||
|
import { useEffect, useRef, useState } from "react"
|
||||||
|
import { FocusOn } from "react-focus-on"
|
||||||
|
import { Portal } from "~/modules/dom/portal"
|
||||||
|
|
||||||
|
export function Modal({
|
||||||
|
children,
|
||||||
|
visible,
|
||||||
|
onClose,
|
||||||
|
}: {
|
||||||
|
children: ReactNode
|
||||||
|
visible: boolean
|
||||||
|
onClose: () => void
|
||||||
|
}) {
|
||||||
|
const closeButtonRef = useRef<HTMLButtonElement>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (visible) {
|
||||||
|
// trying to immediately focus doesn't work for whatever reason
|
||||||
|
// neither did requestAnimationFrame
|
||||||
|
setTimeout(() => {
|
||||||
|
closeButtonRef.current?.focus()
|
||||||
|
}, 50)
|
||||||
|
}
|
||||||
|
}, [visible])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Portal>
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
"bg-black/70 fixed inset-0 transition-all flex flex-col p-4",
|
||||||
|
visible ? "opacity-100 visible" : "opacity-0 invisible",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<FocusOn
|
||||||
|
className={clsx(
|
||||||
|
"m-auto flex flex-col gap-2 w-full max-h-full max-w-screen-sm overflow-y-auto transition",
|
||||||
|
visible ? "translate-y-0" : "translate-y-3",
|
||||||
|
)}
|
||||||
|
enabled={visible}
|
||||||
|
onClickOutside={onClose}
|
||||||
|
onEscapeKey={onClose}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="self-end"
|
||||||
|
onClick={onClose}
|
||||||
|
ref={closeButtonRef}
|
||||||
|
>
|
||||||
|
<span className="sr-only">Close</span>
|
||||||
|
<XIcon aria-hidden className="w-6 text-white" />
|
||||||
|
</button>
|
||||||
|
<div className={clsx("bg-slate-700 rounded-md shadow p-4")}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</FocusOn>
|
||||||
|
</div>
|
||||||
|
</Portal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ControlledModal({
|
||||||
|
children,
|
||||||
|
button,
|
||||||
|
}: {
|
||||||
|
children: ReactNode
|
||||||
|
button: (buttonProps: { onClick: () => void }) => void
|
||||||
|
}) {
|
||||||
|
const [visible, setVisible] = useState(false)
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{button({ onClick: () => setVisible(true) })}
|
||||||
|
<Modal visible={visible} onClose={() => setVisible(false)}>
|
||||||
|
{children}
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,13 +1,11 @@
|
|||||||
import clsx from "clsx"
|
|
||||||
import { useEffect, useRef, useState } from "react"
|
|
||||||
import blobComfyUrl from "~/assets/blob-comfy.png"
|
|
||||||
import cursorIbeamUrl from "~/assets/cursor-ibeam.png"
|
|
||||||
import cursorUrl from "~/assets/cursor.png"
|
|
||||||
import dotsBackgroundUrl from "~/assets/dots-background.svg"
|
import dotsBackgroundUrl from "~/assets/dots-background.svg"
|
||||||
import { AppFooter } from "~/modules/app/app-footer"
|
import { AppFooter } from "~/modules/app/app-footer"
|
||||||
import { AppLogo } from "~/modules/app/app-logo"
|
import { AppLogo } from "~/modules/app/app-logo"
|
||||||
|
import LandingCode from "~/modules/landing/landing-code.mdx"
|
||||||
import { MainNavigation } from "~/modules/navigation/main-navigation"
|
import { MainNavigation } from "~/modules/navigation/main-navigation"
|
||||||
import { maxWidthContainer } from "~/modules/ui/components"
|
import { maxWidthContainer } from "~/modules/ui/components"
|
||||||
|
import { LandingAnimation } from "../modules/landing/landing-animation"
|
||||||
|
import { ControlledModal } from "../modules/ui/modal"
|
||||||
|
|
||||||
export default function Landing() {
|
export default function Landing() {
|
||||||
return (
|
return (
|
||||||
@@ -28,9 +26,21 @@ export default function Landing() {
|
|||||||
<p className="text-center text-lg font-light -mb-1">
|
<p className="text-center text-lg font-light -mb-1">
|
||||||
Create interactive Discord messages with React.
|
Create interactive Discord messages with React.
|
||||||
</p>
|
</p>
|
||||||
{/* <button className="px-3 py-1.5 font-medium bg-black/25 text-sm rounded-full self-center hover:bg-black/40 transition active:transition-none active:translate-y-[2px]">
|
|
||||||
Show Code
|
<ControlledModal
|
||||||
</button> */}
|
button={(button) => (
|
||||||
|
<button
|
||||||
|
{...button}
|
||||||
|
className="px-3 py-1.5 font-medium bg-black/25 text-sm rounded-full self-center hover:bg-black/40 transition active:transition-none active:translate-y-[2px]"
|
||||||
|
>
|
||||||
|
Show Code
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="text-sm sm:text-base">
|
||||||
|
<LandingCode />
|
||||||
|
</div>
|
||||||
|
</ControlledModal>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<AppFooter />
|
<AppFooter />
|
||||||
@@ -39,195 +49,3 @@ export default function Landing() {
|
|||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultState = {
|
|
||||||
chatInputText: "",
|
|
||||||
chatInputCursorVisible: true,
|
|
||||||
messageVisible: false,
|
|
||||||
count: 0,
|
|
||||||
cursorLeft: "25%",
|
|
||||||
cursorBottom: "-15px",
|
|
||||||
}
|
|
||||||
|
|
||||||
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
|
|
||||||
|
|
||||||
const animationFrame = () =>
|
|
||||||
new Promise((resolve) => requestAnimationFrame(resolve))
|
|
||||||
|
|
||||||
function LandingAnimation() {
|
|
||||||
const [state, setState] = useState(defaultState)
|
|
||||||
const chatInputRef = useRef<HTMLDivElement>(null)
|
|
||||||
const addRef = useRef<HTMLButtonElement>(null)
|
|
||||||
const deleteRef = useRef<HTMLButtonElement>(null)
|
|
||||||
const cursorRef = useRef<HTMLImageElement>(null)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const animateClick = (element: HTMLElement) =>
|
|
||||||
element.animate(
|
|
||||||
[{ transform: `translateY(2px)` }, { transform: `translateY(0px)` }],
|
|
||||||
300,
|
|
||||||
)
|
|
||||||
|
|
||||||
let running = true
|
|
||||||
|
|
||||||
void (async () => {
|
|
||||||
await delay(1000)
|
|
||||||
|
|
||||||
while (running) {
|
|
||||||
setState(defaultState)
|
|
||||||
await delay(1000)
|
|
||||||
|
|
||||||
for (const letter of "/counter") {
|
|
||||||
setState((state) => ({
|
|
||||||
...state,
|
|
||||||
chatInputText: state.chatInputText + letter,
|
|
||||||
}))
|
|
||||||
await delay(100)
|
|
||||||
}
|
|
||||||
|
|
||||||
await delay(1000)
|
|
||||||
|
|
||||||
setState((state) => ({
|
|
||||||
...state,
|
|
||||||
messageVisible: true,
|
|
||||||
chatInputText: "",
|
|
||||||
}))
|
|
||||||
await delay(1000)
|
|
||||||
|
|
||||||
setState((state) => ({
|
|
||||||
...state,
|
|
||||||
cursorLeft: "70px",
|
|
||||||
cursorBottom: "40px",
|
|
||||||
}))
|
|
||||||
await delay(1500)
|
|
||||||
|
|
||||||
for (let i = 0; i < 3; i++) {
|
|
||||||
setState((state) => ({
|
|
||||||
...state,
|
|
||||||
count: state.count + 1,
|
|
||||||
chatInputCursorVisible: false,
|
|
||||||
}))
|
|
||||||
animateClick(addRef.current!)
|
|
||||||
await delay(700)
|
|
||||||
}
|
|
||||||
|
|
||||||
await delay(500)
|
|
||||||
|
|
||||||
setState((state) => ({
|
|
||||||
...state,
|
|
||||||
cursorLeft: "140px",
|
|
||||||
}))
|
|
||||||
await delay(1000)
|
|
||||||
|
|
||||||
animateClick(deleteRef.current!)
|
|
||||||
setState((state) => ({ ...state, messageVisible: false }))
|
|
||||||
await delay(1000)
|
|
||||||
|
|
||||||
setState(() => ({
|
|
||||||
...defaultState,
|
|
||||||
chatInputCursorVisible: false,
|
|
||||||
}))
|
|
||||||
await delay(500)
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
running = false
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let running = true
|
|
||||||
|
|
||||||
void (async () => {
|
|
||||||
while (running) {
|
|
||||||
// check if the cursor is in the input
|
|
||||||
const cursorRect = cursorRef.current!.getBoundingClientRect()
|
|
||||||
const chatInputRect = chatInputRef.current!.getBoundingClientRect()
|
|
||||||
|
|
||||||
const isOverInput =
|
|
||||||
cursorRef.current &&
|
|
||||||
chatInputRef.current &&
|
|
||||||
cursorRect.top + cursorRect.height / 2 > chatInputRect.top
|
|
||||||
|
|
||||||
cursorRef.current!.src = isOverInput ? cursorIbeamUrl : cursorUrl
|
|
||||||
|
|
||||||
await animationFrame()
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
running = false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="grid gap-2 relative pointer-events-none"
|
|
||||||
role="presentation"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={clsx(
|
|
||||||
"bg-slate-800 p-4 rounded-lg shadow transition",
|
|
||||||
state.messageVisible ? "opacity-100" : "opacity-0 -translate-y-2",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<div className="flex items-start gap-4">
|
|
||||||
<div className="w-12 h-12 p-2 rounded-full bg-no-repeat bg-contain bg-black/25">
|
|
||||||
<img
|
|
||||||
src={blobComfyUrl}
|
|
||||||
alt=""
|
|
||||||
className="object-contain scale-90 w-full h-full"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<p className="font-bold">comfybot</p>
|
|
||||||
<p>this button was clicked {state.count} times</p>
|
|
||||||
<div className="mt-2 flex flex-row gap-3">
|
|
||||||
<button
|
|
||||||
ref={addRef}
|
|
||||||
className="bg-emerald-700 text-white py-1.5 px-3 text-sm rounded"
|
|
||||||
>
|
|
||||||
+1
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
ref={deleteRef}
|
|
||||||
className="bg-red-700 text-white py-1.5 px-3 text-sm rounded"
|
|
||||||
>
|
|
||||||
🗑 delete
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="bg-slate-700 pb-2 pt-1.5 px-4 rounded-lg shadow"
|
|
||||||
ref={chatInputRef}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className={clsx(
|
|
||||||
"text-sm after:content-[attr(data-after)] after:relative after:-top-px after:-left-[2px]",
|
|
||||||
state.chatInputCursorVisible
|
|
||||||
? "after:opacity-100"
|
|
||||||
: "after:opacity-0",
|
|
||||||
)}
|
|
||||||
data-after="|"
|
|
||||||
>
|
|
||||||
{state.chatInputText || (
|
|
||||||
<span className="opacity-50 block absolute translate-y-1">
|
|
||||||
Message #showing-off-reacord
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<img
|
|
||||||
src={cursorUrl}
|
|
||||||
alt=""
|
|
||||||
className="transition-all duration-500 absolute scale-75"
|
|
||||||
style={{ left: state.cursorLeft, bottom: state.cursorBottom }}
|
|
||||||
ref={cursorRef}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
"reacord": "workspace:*",
|
"reacord": "workspace:*",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.2",
|
"react-dom": "^17.0.2",
|
||||||
|
"react-focus-on": "^3.5.4",
|
||||||
"remix": "^1.1.1"
|
"remix": "^1.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
156
pnpm-lock.yaml
generated
156
pnpm-lock.yaml
generated
@@ -114,6 +114,7 @@ importers:
|
|||||||
reacord: workspace:*
|
reacord: workspace:*
|
||||||
react: ^17.0.2
|
react: ^17.0.2
|
||||||
react-dom: ^17.0.2
|
react-dom: ^17.0.2
|
||||||
|
react-focus-on: ^3.5.4
|
||||||
rehype-prism-plus: ^1.3.1
|
rehype-prism-plus: ^1.3.1
|
||||||
remix: ^1.1.1
|
remix: ^1.1.1
|
||||||
tailwindcss: ^3.0.13
|
tailwindcss: ^3.0.13
|
||||||
@@ -131,6 +132,7 @@ importers:
|
|||||||
reacord: link:../reacord
|
reacord: link:../reacord
|
||||||
react: 17.0.2
|
react: 17.0.2
|
||||||
react-dom: 17.0.2_react@17.0.2
|
react-dom: 17.0.2_react@17.0.2
|
||||||
|
react-focus-on: 3.5.4_b08e3c15324cbe90a6ff8fcd416c932c
|
||||||
remix: 1.1.1
|
remix: 1.1.1
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@remix-run/dev': 1.1.1
|
'@remix-run/dev': 1.1.1
|
||||||
@@ -1593,6 +1595,13 @@ packages:
|
|||||||
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
|
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/aria-hidden/1.1.3:
|
||||||
|
resolution: {integrity: sha512-RhVWFtKH5BiGMycI72q2RAFMLQi8JP9bLuQXgR5a8Znp7P5KOIADSJeyfI8PCVxLEp067B2HbP5JIiI/PXIZeA==}
|
||||||
|
engines: {node: '>=8.5.0'}
|
||||||
|
dependencies:
|
||||||
|
tslib: 1.14.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/aria-query/4.2.2:
|
/aria-query/4.2.2:
|
||||||
resolution: {integrity: sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==}
|
resolution: {integrity: sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==}
|
||||||
engines: {node: '>=6.0'}
|
engines: {node: '>=6.0'}
|
||||||
@@ -2875,6 +2884,10 @@ packages:
|
|||||||
resolution: {integrity: sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=}
|
resolution: {integrity: sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/detect-node-es/1.1.0:
|
||||||
|
resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/detective/5.2.0:
|
/detective/5.2.0:
|
||||||
resolution: {integrity: sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==}
|
resolution: {integrity: sha512-6SsIx+nUUbuK0EthKjv0zrdnajCCXVYGmbYYiYjFVpzcjwEs/JMDZ8tPRG29J/HhN56t3GJp2cGSWDRjjot8Pg==}
|
||||||
engines: {node: '>=0.8.0'}
|
engines: {node: '>=0.8.0'}
|
||||||
@@ -3943,6 +3956,13 @@ packages:
|
|||||||
resolution: {integrity: sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==}
|
resolution: {integrity: sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/focus-lock/0.10.1:
|
||||||
|
resolution: {integrity: sha512-b9yUklCi4fTu2GXn7dnaVf4hiLVVBp7xTiZarAHMODV2To6Bitf6F/UI67RmKbdgJQeVwI1UO0d9HYNbXt3GkA==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
dependencies:
|
||||||
|
tslib: 2.3.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/follow-redirects/1.14.7:
|
/follow-redirects/1.14.7:
|
||||||
resolution: {integrity: sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==}
|
resolution: {integrity: sha512-+hbxoLbFMbRKDwohX8GkTataGqO6Jb7jGwpAlwgy2bIz25XtRm7KEzJM76R1WiNT5SwZkX4Y75SwBolkpmE7iQ==}
|
||||||
engines: {node: '>=4.0'}
|
engines: {node: '>=4.0'}
|
||||||
@@ -4086,6 +4106,11 @@ packages:
|
|||||||
has: 1.0.3
|
has: 1.0.3
|
||||||
has-symbols: 1.0.2
|
has-symbols: 1.0.2
|
||||||
|
|
||||||
|
/get-nonce/1.0.1:
|
||||||
|
resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/get-package-type/0.1.0:
|
/get-package-type/0.1.0:
|
||||||
resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==}
|
resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==}
|
||||||
engines: {node: '>=8.0.0'}
|
engines: {node: '>=8.0.0'}
|
||||||
@@ -4607,6 +4632,12 @@ packages:
|
|||||||
engines: {node: '>= 0.10'}
|
engines: {node: '>= 0.10'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/invariant/2.2.4:
|
||||||
|
resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==}
|
||||||
|
dependencies:
|
||||||
|
loose-envify: 1.4.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/ipaddr.js/1.9.1:
|
/ipaddr.js/1.9.1:
|
||||||
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
|
resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
|
||||||
engines: {node: '>= 0.10'}
|
engines: {node: '>= 0.10'}
|
||||||
@@ -6781,7 +6812,6 @@ packages:
|
|||||||
loose-envify: 1.4.0
|
loose-envify: 1.4.0
|
||||||
object-assign: 4.1.1
|
object-assign: 4.1.1
|
||||||
react-is: 16.13.1
|
react-is: 16.13.1
|
||||||
dev: true
|
|
||||||
|
|
||||||
/property-information/6.1.1:
|
/property-information/6.1.1:
|
||||||
resolution: {integrity: sha512-hrzC564QIl0r0vy4l6MvRLhafmUowhO/O3KgVSoXIbbA2Sz4j8HGpJc6T2cubRVwMwpdiG/vKGfhT4IixmKN9w==}
|
resolution: {integrity: sha512-hrzC564QIl0r0vy4l6MvRLhafmUowhO/O3KgVSoXIbbA2Sz4j8HGpJc6T2cubRVwMwpdiG/vKGfhT4IixmKN9w==}
|
||||||
@@ -6905,6 +6935,15 @@ packages:
|
|||||||
strip-json-comments: 2.0.1
|
strip-json-comments: 2.0.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/react-clientside-effect/1.2.5_react@17.0.2:
|
||||||
|
resolution: {integrity: sha512-2bL8qFW1TGBHozGGbVeyvnggRpMjibeZM2536AKNENLECutp2yfs44IL8Hmpn8qjFQ2K7A9PnYf3vc7aQq/cPA==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^15.3.0 || ^16.0.0 || ^17.0.0
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.16.7
|
||||||
|
react: 17.0.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
/react-dom/17.0.2_react@17.0.2:
|
/react-dom/17.0.2_react@17.0.2:
|
||||||
resolution: {integrity: sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==}
|
resolution: {integrity: sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -6916,9 +6955,45 @@ packages:
|
|||||||
scheduler: 0.20.2
|
scheduler: 0.20.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/react-focus-lock/2.7.1_b08e3c15324cbe90a6ff8fcd416c932c:
|
||||||
|
resolution: {integrity: sha512-ImSeVmcrLKNMqzUsIdqOkXwTVltj79OPu43oT8tVun7eIckA4VdM7UmYUFo3H/UC2nRVgagMZGFnAOQEDiDYcA==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.16.7
|
||||||
|
focus-lock: 0.10.1
|
||||||
|
prop-types: 15.8.1
|
||||||
|
react: 17.0.2
|
||||||
|
react-clientside-effect: 1.2.5_react@17.0.2
|
||||||
|
use-callback-ref: 1.2.5_b08e3c15324cbe90a6ff8fcd416c932c
|
||||||
|
use-sidecar: 1.0.5_react@17.0.2
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@types/react'
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/react-focus-on/3.5.4_b08e3c15324cbe90a6ff8fcd416c932c:
|
||||||
|
resolution: {integrity: sha512-HnU0YGKhNSUsC4k6K8L+2wk8mC/qdg+CsS7A1bWLMgK7UuBphdECs2esnS6cLmBoVNjsFnCm/vMypeezKOdK3A==}
|
||||||
|
engines: {node: '>=8.5.0'}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
|
react: ^16.8.0 || ^17.0.0 || ^18.0.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@types/react': 17.0.38
|
||||||
|
aria-hidden: 1.1.3
|
||||||
|
react: 17.0.2
|
||||||
|
react-focus-lock: 2.7.1_b08e3c15324cbe90a6ff8fcd416c932c
|
||||||
|
react-remove-scroll: 2.4.3_b08e3c15324cbe90a6ff8fcd416c932c
|
||||||
|
react-style-singleton: 2.1.1_b08e3c15324cbe90a6ff8fcd416c932c
|
||||||
|
tslib: 2.3.1
|
||||||
|
use-callback-ref: 1.2.5_b08e3c15324cbe90a6ff8fcd416c932c
|
||||||
|
use-sidecar: 1.0.5_react@17.0.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
/react-is/16.13.1:
|
/react-is/16.13.1:
|
||||||
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/react-is/17.0.2:
|
/react-is/17.0.2:
|
||||||
resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
|
resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==}
|
||||||
@@ -6936,6 +7011,41 @@ packages:
|
|||||||
scheduler: 0.20.2
|
scheduler: 0.20.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/react-remove-scroll-bar/2.2.0_b08e3c15324cbe90a6ff8fcd416c932c:
|
||||||
|
resolution: {integrity: sha512-UU9ZBP1wdMR8qoUs7owiVcpaPwsQxUDC2lypP6mmixaGlARZa7ZIBx1jcuObLdhMOvCsnZcvetOho0wzPa9PYg==}
|
||||||
|
engines: {node: '>=8.5.0'}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': ^16.8.0 || ^17.0.0
|
||||||
|
react: ^16.8.0 || ^17.0.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@types/react': 17.0.38
|
||||||
|
react: 17.0.2
|
||||||
|
react-style-singleton: 2.1.1_b08e3c15324cbe90a6ff8fcd416c932c
|
||||||
|
tslib: 1.14.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/react-remove-scroll/2.4.3_b08e3c15324cbe90a6ff8fcd416c932c:
|
||||||
|
resolution: {integrity: sha512-lGWYXfV6jykJwbFpsuPdexKKzp96f3RbvGapDSIdcyGvHb7/eqyn46C7/6h+rUzYar1j5mdU+XECITHXCKBk9Q==}
|
||||||
|
engines: {node: '>=8.5.0'}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': ^16.8.0 || ^17.0.0
|
||||||
|
react: ^16.8.0 || ^17.0.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@types/react': 17.0.38
|
||||||
|
react: 17.0.2
|
||||||
|
react-remove-scroll-bar: 2.2.0_b08e3c15324cbe90a6ff8fcd416c932c
|
||||||
|
react-style-singleton: 2.1.1_b08e3c15324cbe90a6ff8fcd416c932c
|
||||||
|
tslib: 1.14.1
|
||||||
|
use-callback-ref: 1.2.5_b08e3c15324cbe90a6ff8fcd416c932c
|
||||||
|
use-sidecar: 1.0.5_react@17.0.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
/react-router-dom/6.2.1_react-dom@17.0.2+react@17.0.2:
|
/react-router-dom/6.2.1_react-dom@17.0.2+react@17.0.2:
|
||||||
resolution: {integrity: sha512-I6Zax+/TH/cZMDpj3/4Fl2eaNdcvoxxHoH1tYOREsQ22OKDYofGebrNm6CTPUcvLvZm63NL/vzCYdjf9CUhqmA==}
|
resolution: {integrity: sha512-I6Zax+/TH/cZMDpj3/4Fl2eaNdcvoxxHoH1tYOREsQ22OKDYofGebrNm6CTPUcvLvZm63NL/vzCYdjf9CUhqmA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -6955,6 +7065,23 @@ packages:
|
|||||||
history: 5.2.0
|
history: 5.2.0
|
||||||
react: 17.0.2
|
react: 17.0.2
|
||||||
|
|
||||||
|
/react-style-singleton/2.1.1_b08e3c15324cbe90a6ff8fcd416c932c:
|
||||||
|
resolution: {integrity: sha512-jNRp07Jza6CBqdRKNgGhT3u9umWvils1xsuMOjZlghBDH2MU0PL2WZor4PGYjXpnRCa9DQSlHMs/xnABWOwYbA==}
|
||||||
|
engines: {node: '>=8.5.0'}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': ^16.8.0 || ^17.0.0
|
||||||
|
react: ^16.8.0 || ^17.0.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@types/react': 17.0.38
|
||||||
|
get-nonce: 1.0.1
|
||||||
|
invariant: 2.2.4
|
||||||
|
react: 17.0.2
|
||||||
|
tslib: 1.14.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/react/17.0.2:
|
/react/17.0.2:
|
||||||
resolution: {integrity: sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==}
|
resolution: {integrity: sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -8347,6 +8474,31 @@ packages:
|
|||||||
querystring: 0.2.0
|
querystring: 0.2.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/use-callback-ref/1.2.5_b08e3c15324cbe90a6ff8fcd416c932c:
|
||||||
|
resolution: {integrity: sha512-gN3vgMISAgacF7sqsLPByqoePooY3n2emTH59Ur5d/M8eg4WTWu1xp8i8DHjohftIyEx0S08RiYxbffr4j8Peg==}
|
||||||
|
engines: {node: '>=8.5.0'}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': ^16.8.0 || ^17.0.0
|
||||||
|
react: ^16.8.0 || ^17.0.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/react':
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@types/react': 17.0.38
|
||||||
|
react: 17.0.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/use-sidecar/1.0.5_react@17.0.2:
|
||||||
|
resolution: {integrity: sha512-k9jnrjYNwN6xYLj1iaGhonDghfvmeTmYjAiGvOr7clwKfPjMXJf4/HOr7oT5tJwYafgp2tG2l3eZEOfoELiMcA==}
|
||||||
|
engines: {node: '>=8.5.0'}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^16.8.0 || ^17.0.0
|
||||||
|
dependencies:
|
||||||
|
detect-node-es: 1.1.0
|
||||||
|
react: 17.0.2
|
||||||
|
tslib: 1.14.1
|
||||||
|
dev: false
|
||||||
|
|
||||||
/use/3.1.1:
|
/use/3.1.1:
|
||||||
resolution: {integrity: sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==}
|
resolution: {integrity: sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|||||||
Reference in New Issue
Block a user