Compare commits
2 Commits
files
...
new-api-pa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d6e2f083bc | ||
|
|
723d663d3c |
@@ -1,25 +0,0 @@
|
||||
require("@rushstack/eslint-patch/modern-module-resolution")
|
||||
|
||||
/** @type {import('eslint').Linter.Config} */
|
||||
module.exports = {
|
||||
extends: [require.resolve("@itsmapleleaf/configs/eslint")],
|
||||
ignorePatterns: [
|
||||
"**/node_modules/**",
|
||||
"**/.cache/**",
|
||||
"**/build/**",
|
||||
"**/dist/**",
|
||||
"**/coverage/**",
|
||||
"**/public/**",
|
||||
],
|
||||
parserOptions: {
|
||||
project: require.resolve("./tsconfig.base.json"),
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ["packages/website/cypress/**"],
|
||||
parserOptions: {
|
||||
project: require.resolve("./packages/website/cypress/tsconfig.json"),
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
26
.eslintrc.json
Normal file
26
.eslintrc.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"extends": ["./node_modules/@itsmapleleaf/configs/eslint"],
|
||||
"ignorePatterns": [
|
||||
"**/node_modules/**",
|
||||
"**/.cache/**",
|
||||
"**/build/**",
|
||||
"**/dist/**",
|
||||
"**/coverage/**",
|
||||
"**/public/**"
|
||||
],
|
||||
"parserOptions": {
|
||||
"project": "./tsconfig.base.json"
|
||||
},
|
||||
"rules": {
|
||||
"import/no-unused-modules": "off",
|
||||
"unicorn/prevent-abbreviations": "off"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["packages/website/cypress/**"],
|
||||
"parserOptions": {
|
||||
"project": "./packages/website/cypress/tsconfig.json"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
17
.github/workflows/deploy-website.yml
vendored
Normal file
17
.github/workflows/deploy-website.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
name: deploy website
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- "packages/website/**"
|
||||
- "reacord/library/**/*.{ts,tsx}"
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: superfly/flyctl-actions@master
|
||||
env:
|
||||
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
|
||||
with:
|
||||
args: "deploy"
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -3,7 +3,4 @@ node_modules
|
||||
.vscode
|
||||
coverage
|
||||
.env
|
||||
*.code-workspace
|
||||
|
||||
build
|
||||
.cache
|
||||
|
||||
@@ -4,4 +4,3 @@ coverage
|
||||
pnpm-lock.yaml
|
||||
build
|
||||
.cache
|
||||
packages/website/public/api
|
||||
|
||||
@@ -18,7 +18,7 @@ pnpm add reacord react discord.js
|
||||
|
||||
## Get Started
|
||||
|
||||
[Visit the docs to get started.](https://reacord.mapleleaf.dev/guides/getting-started)
|
||||
[Visit the docs to get started.](https://reacord.fly.dev/guides/getting-started)
|
||||
|
||||
## Example
|
||||
|
||||
|
||||
40
fly.toml
Normal file
40
fly.toml
Normal file
@@ -0,0 +1,40 @@
|
||||
# fly.toml file generated for reacord on 2021-12-29T14:06:41-06:00
|
||||
|
||||
app = "reacord"
|
||||
|
||||
kill_signal = "SIGINT"
|
||||
kill_timeout = 5
|
||||
processes = []
|
||||
|
||||
[env]
|
||||
PORT = 8080
|
||||
|
||||
[experimental]
|
||||
allowed_public_ports = []
|
||||
auto_rollback = true
|
||||
|
||||
[[services]]
|
||||
http_checks = []
|
||||
internal_port = 8080
|
||||
processes = ["app"]
|
||||
protocol = "tcp"
|
||||
script_checks = []
|
||||
|
||||
[services.concurrency]
|
||||
hard_limit = 25
|
||||
soft_limit = 20
|
||||
type = "connections"
|
||||
|
||||
[[services.ports]]
|
||||
handlers = ["http"]
|
||||
port = 80
|
||||
|
||||
[[services.ports]]
|
||||
handlers = ["tls", "http"]
|
||||
port = 443
|
||||
|
||||
[[services.tcp_checks]]
|
||||
grace_period = "1s"
|
||||
interval = "15s"
|
||||
restart_limit = 0
|
||||
timeout = "2s"
|
||||
21
package.json
21
package.json
@@ -5,13 +5,22 @@
|
||||
"lint-fix": "pnpm lint -- --fix",
|
||||
"format": "prettier --write ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@itsmapleleaf/configs": "^1.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@itsmapleleaf/configs": "^1.1.3",
|
||||
"@rushstack/eslint-patch": "^1.1.3",
|
||||
"@types/eslint": "^8.4.1",
|
||||
"eslint": "^8.14.0",
|
||||
"prettier": "^2.6.2",
|
||||
"typescript": "^4.6.3"
|
||||
"@typescript-eslint/eslint-plugin": "^5.9.1",
|
||||
"@typescript-eslint/parser": "^5.9.1",
|
||||
"eslint": "^8.6.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-import-resolver-typescript": "^2.5.0",
|
||||
"eslint-plugin-import": "^2.25.4",
|
||||
"eslint-plugin-jsx-a11y": "^6.5.1",
|
||||
"eslint-plugin-react": "^7.28.0",
|
||||
"eslint-plugin-react-hooks": "^4.3.0",
|
||||
"eslint-plugin-unicorn": "^40.0.0",
|
||||
"prettier": "^2.5.1",
|
||||
"typescript": "^4.5.4"
|
||||
},
|
||||
"resolutions": {
|
||||
"esbuild": "latest"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { inspect } from "node:util"
|
||||
|
||||
// eslint-disable-next-line import/no-unused-modules
|
||||
export function logPretty(value: unknown) {
|
||||
console.info(
|
||||
inspect(value, {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// eslint-disable-next-line import/no-unused-modules
|
||||
export function omit<Subject extends object, Key extends PropertyKey>(
|
||||
subject: Subject,
|
||||
keys: Key[],
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { LoosePick, UnknownRecord } from "./types"
|
||||
|
||||
// eslint-disable-next-line import/no-unused-modules
|
||||
export function pick<T, K extends keyof T | PropertyKey>(
|
||||
object: T,
|
||||
keys: K[],
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import { expect, test } from "vitest"
|
||||
import { PruneNullishValues, pruneNullishValues } from "./prune-nullish-values"
|
||||
|
||||
test("pruneNullishValues", () => {
|
||||
type InputType = {
|
||||
a: string
|
||||
b: string | null | undefined
|
||||
c?: string
|
||||
d: {
|
||||
a: string
|
||||
b: string | undefined
|
||||
}
|
||||
}
|
||||
|
||||
const input: InputType = {
|
||||
a: "a",
|
||||
b: null,
|
||||
c: undefined,
|
||||
d: {
|
||||
a: "a",
|
||||
b: undefined,
|
||||
},
|
||||
}
|
||||
|
||||
const output: PruneNullishValues<InputType> = {
|
||||
a: "a",
|
||||
d: {
|
||||
a: "a",
|
||||
},
|
||||
}
|
||||
|
||||
expect(pruneNullishValues(input)).toEqual(output)
|
||||
})
|
||||
@@ -18,25 +18,10 @@ export function pruneNullishValues<T>(input: T): PruneNullishValues<T> {
|
||||
return result
|
||||
}
|
||||
|
||||
export type PruneNullishValues<Input> = Input extends object
|
||||
? OptionalKeys<
|
||||
{ [Key in keyof Input]: NonNullable<PruneNullishValues<Input[Key]>> },
|
||||
KeysWithNullishValues<Input>
|
||||
>
|
||||
: Input
|
||||
|
||||
type OptionalKeys<Input, Keys extends keyof Input> = Omit<Input, Keys> & {
|
||||
[Key in Keys]?: Input[Key]
|
||||
type PruneNullishValues<Input> = Input extends ReadonlyArray<infer Value>
|
||||
? ReadonlyArray<NonNullable<Value>>
|
||||
: Input extends object
|
||||
? {
|
||||
[Key in keyof Input]: NonNullable<Input[Key]>
|
||||
}
|
||||
|
||||
type KeysWithNullishValues<Input> = NonNullable<
|
||||
Values<{
|
||||
[Key in keyof Input]: null extends Input[Key]
|
||||
? Key
|
||||
: undefined extends Input[Key]
|
||||
? Key
|
||||
: never
|
||||
}>
|
||||
>
|
||||
|
||||
type Values<Input> = Input[keyof Input]
|
||||
: Input
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
import { setTimeout } from "timers/promises"
|
||||
|
||||
const maxTime = 1000
|
||||
|
||||
export async function waitFor<Result>(
|
||||
predicate: () => Result,
|
||||
): Promise<Awaited<Result>> {
|
||||
const startTime = Date.now()
|
||||
let lastError: unknown
|
||||
|
||||
while (Date.now() - startTime < maxTime) {
|
||||
try {
|
||||
return await predicate()
|
||||
} catch (error) {
|
||||
lastError = error
|
||||
await setTimeout(50)
|
||||
}
|
||||
}
|
||||
|
||||
throw lastError ?? new Error("Timeout")
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { inspect } from "node:util"
|
||||
|
||||
// eslint-disable-next-line import/no-unused-modules
|
||||
export function withLoggedMethodCalls<T extends object>(value: T) {
|
||||
return new Proxy(value as Record<string | symbol, unknown>, {
|
||||
get(target, property) {
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import { Readable } from "node:stream"
|
||||
|
||||
export type ReacordFile = {
|
||||
name?: string
|
||||
description?: string
|
||||
data: Buffer | Readable | string
|
||||
}
|
||||
@@ -10,7 +10,7 @@ export const InstanceProvider = Context.Provider
|
||||
* Get the associated instance for the current component.
|
||||
*
|
||||
* @category Core
|
||||
* @see https://reacord.mapleleaf.dev/guides/use-instance
|
||||
* @see https://reacord.fly.dev/guides/use-instance
|
||||
*/
|
||||
export function useInstance(): ReacordInstance {
|
||||
return (
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { ReactNode } from "react"
|
||||
import { ReacordFile } from "./file"
|
||||
|
||||
/**
|
||||
* Represents an interactive message, which can later be replaced or deleted.
|
||||
@@ -17,7 +16,4 @@ export type ReacordInstance = {
|
||||
* This prevents it from listening to user interactions.
|
||||
*/
|
||||
deactivate: () => void
|
||||
|
||||
/** Attach a file to the message for this instance */
|
||||
attach: (file: ReacordFile) => void
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ export class ReacordDiscordJs extends Reacord {
|
||||
|
||||
/**
|
||||
* Sends a message to a channel.
|
||||
* @see https://reacord.mapleleaf.dev/guides/sending-messages
|
||||
* @see https://reacord.fly.dev/guides/sending-messages
|
||||
*/
|
||||
override send(
|
||||
channelId: string,
|
||||
@@ -54,7 +54,7 @@ export class ReacordDiscordJs extends Reacord {
|
||||
|
||||
/**
|
||||
* Sends a message as a reply to a command interaction.
|
||||
* @see https://reacord.mapleleaf.dev/guides/sending-messages
|
||||
* @see https://reacord.fly.dev/guides/sending-messages
|
||||
*/
|
||||
override reply(
|
||||
interaction: Discord.CommandInteraction,
|
||||
@@ -68,7 +68,7 @@ export class ReacordDiscordJs extends Reacord {
|
||||
|
||||
/**
|
||||
* Sends an ephemeral message as a reply to a command interaction.
|
||||
* @see https://reacord.mapleleaf.dev/guides/sending-messages
|
||||
* @see https://reacord.fly.dev/guides/sending-messages
|
||||
*/
|
||||
override ephemeralReply(
|
||||
interaction: Discord.CommandInteraction,
|
||||
@@ -298,18 +298,20 @@ function createReacordMessage(message: Discord.Message): Message {
|
||||
edit: async (options) => {
|
||||
await message.edit(getDiscordMessageOptions(options))
|
||||
},
|
||||
disableComponents: async () => {
|
||||
for (const actionRow of message.components) {
|
||||
for (const component of actionRow.components) {
|
||||
component.setDisabled(true)
|
||||
}
|
||||
}
|
||||
|
||||
await message.edit({
|
||||
components: message.components,
|
||||
})
|
||||
},
|
||||
delete: async () => {
|
||||
await message.delete()
|
||||
},
|
||||
updateFiles: async (files) => {
|
||||
await message.edit({
|
||||
files: files.map(({ name, description, data }) => ({
|
||||
name,
|
||||
description,
|
||||
attachment: data,
|
||||
})),
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,7 +321,7 @@ function createEphemeralReacordMessage(): Message {
|
||||
console.warn("Ephemeral messages can't be edited")
|
||||
return Promise.resolve()
|
||||
},
|
||||
updateFiles: () => {
|
||||
disableComponents: () => {
|
||||
console.warn("Ephemeral messages can't be edited")
|
||||
return Promise.resolve()
|
||||
},
|
||||
@@ -371,10 +373,7 @@ function getDiscordMessageOptions(
|
||||
})),
|
||||
}
|
||||
|
||||
const hasContent =
|
||||
options.content || options.embeds?.length || options.files?.length
|
||||
|
||||
if (!hasContent) {
|
||||
if (!options.content && !options.embeds?.length) {
|
||||
options.content = "_ _"
|
||||
}
|
||||
|
||||
|
||||
@@ -63,9 +63,6 @@ export abstract class Reacord {
|
||||
this.renderers = this.renderers.filter((it) => it !== renderer)
|
||||
renderer.destroy()
|
||||
},
|
||||
attach: (file) => {
|
||||
renderer.attach(file)
|
||||
},
|
||||
}
|
||||
|
||||
if (initialContent !== undefined) {
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { ReacordFile } from "../core/file"
|
||||
import type { Message, MessageOptions } from "./message"
|
||||
|
||||
export type Channel = {
|
||||
send(message: MessageOptions): Promise<Message>
|
||||
sendFiles(files: readonly ReacordFile[]): Promise<Message>
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import type { Except } from "type-fest"
|
||||
import { last } from "../../helpers/last"
|
||||
import type { EmbedOptions } from "../core/components/embed-options"
|
||||
import type { SelectProps } from "../core/components/select"
|
||||
import { ReacordFile } from "../main"
|
||||
|
||||
export type MessageOptions = {
|
||||
content: string
|
||||
@@ -50,7 +49,7 @@ export type MessageSelectOptionOptions = {
|
||||
export type Message = {
|
||||
edit(options: MessageOptions): Promise<void>
|
||||
delete(): Promise<void>
|
||||
updateFiles(files: readonly ReacordFile[]): Promise<void>
|
||||
disableComponents(): Promise<void>
|
||||
}
|
||||
|
||||
export function getNextActionRow(options: MessageOptions): ActionRow {
|
||||
|
||||
@@ -52,8 +52,6 @@ const config: HostConfig<
|
||||
},
|
||||
createTextInstance: (text) => new TextNode(text),
|
||||
shouldSetTextContent: () => false,
|
||||
// @ts-expect-error
|
||||
detachDeletedInstance: (instance) => {},
|
||||
|
||||
clearContainer: (renderer) => {
|
||||
renderer.nodes.clear()
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { ReacordFile } from "../../core/file"
|
||||
import type { Channel } from "../channel"
|
||||
import type { Message, MessageOptions } from "../message"
|
||||
import { Renderer } from "./renderer"
|
||||
@@ -11,10 +10,4 @@ export class ChannelMessageRenderer extends Renderer {
|
||||
protected createMessage(options: MessageOptions): Promise<Message> {
|
||||
return this.channel.send(options)
|
||||
}
|
||||
|
||||
protected createMessageFromFiles(
|
||||
files: readonly ReacordFile[],
|
||||
): Promise<Message> {
|
||||
return this.channel.sendFiles(files)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Subject } from "rxjs"
|
||||
import { concatMap } from "rxjs/operators"
|
||||
import { ReacordFile } from "../../core/file"
|
||||
import { Container } from "../container.js"
|
||||
import type { ComponentInteraction } from "../interaction"
|
||||
import type { Message, MessageOptions } from "../message"
|
||||
@@ -8,21 +7,15 @@ import type { Node } from "../node.js"
|
||||
|
||||
type UpdatePayload =
|
||||
| { action: "update" | "deactivate"; options: MessageOptions }
|
||||
| { action: "files"; files: readonly ReacordFile[] }
|
||||
| { action: "deferUpdate"; interaction: ComponentInteraction }
|
||||
| { action: "destroy" }
|
||||
|
||||
type NewMessagePayload =
|
||||
| { source: "content"; messageOptions?: MessageOptions }
|
||||
| { source: "files"; files?: readonly ReacordFile[] }
|
||||
|
||||
export abstract class Renderer {
|
||||
readonly nodes = new Container<Node<unknown>>()
|
||||
private componentInteraction?: ComponentInteraction
|
||||
private message?: Message
|
||||
private active = true
|
||||
private updates = new Subject<UpdatePayload>()
|
||||
private files: readonly ReacordFile[] = []
|
||||
|
||||
private updateSubscription = this.updates
|
||||
.pipe(concatMap((payload) => this.updateMessage(payload)))
|
||||
@@ -53,11 +46,6 @@ export abstract class Renderer {
|
||||
this.updates.next({ action: "destroy" })
|
||||
}
|
||||
|
||||
attach(file: ReacordFile) {
|
||||
const newFiles = (this.files = [...this.files, file])
|
||||
this.updates.next({ action: "files", files: newFiles })
|
||||
}
|
||||
|
||||
handleComponentInteraction(interaction: ComponentInteraction) {
|
||||
this.componentInteraction = interaction
|
||||
|
||||
@@ -72,7 +60,7 @@ export abstract class Renderer {
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract createMessage(options: NewMessagePayload): Promise<Message>
|
||||
protected abstract createMessage(options: MessageOptions): Promise<Message>
|
||||
|
||||
private getMessageOptions(): MessageOptions {
|
||||
const options: MessageOptions = {
|
||||
@@ -95,17 +83,7 @@ export abstract class Renderer {
|
||||
|
||||
if (payload.action === "deactivate") {
|
||||
this.updateSubscription.unsubscribe()
|
||||
|
||||
await this.message?.edit({
|
||||
...payload.options,
|
||||
actionRows: payload.options.actionRows.map((row) =>
|
||||
row.map((component) => ({
|
||||
...component,
|
||||
disabled: true,
|
||||
})),
|
||||
),
|
||||
})
|
||||
|
||||
await this.message?.disableComponents()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -114,15 +92,6 @@ export abstract class Renderer {
|
||||
return
|
||||
}
|
||||
|
||||
if (payload.action === "files") {
|
||||
if (this.message) {
|
||||
await this.message?.updateFiles(payload.files)
|
||||
} else {
|
||||
this.message = await this.createMessageFromFiles(payload.files)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (this.componentInteraction) {
|
||||
const promise = this.componentInteraction.update(payload.options)
|
||||
this.componentInteraction = undefined
|
||||
|
||||
@@ -12,7 +12,6 @@ export * from "./core/components/embed-title"
|
||||
export * from "./core/components/link"
|
||||
export * from "./core/components/option"
|
||||
export * from "./core/components/select"
|
||||
export * from "./core/file"
|
||||
export * from "./core/instance"
|
||||
export { useInstance } from "./core/instance-context"
|
||||
export * from "./core/reacord"
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
"name": "reacord",
|
||||
"type": "module",
|
||||
"description": "Create interactive Discord messages using React.",
|
||||
"version": "0.3.5",
|
||||
"version": "0.3.4",
|
||||
"types": "./dist/main.d.ts",
|
||||
"homepage": "https://reacord.mapleleaf.dev",
|
||||
"homepage": "https://reacord.fly.dev",
|
||||
"repository": "https://github.com/itsMapleLeaf/reacord.git",
|
||||
"changelog": "https://github.com/itsMapleLeaf/reacord/releases",
|
||||
"license": "MIT",
|
||||
@@ -46,10 +46,10 @@
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"@types/react": "*",
|
||||
"@types/react-reconciler": "^0.26.6",
|
||||
"nanoid": "^3.3.3",
|
||||
"react-reconciler": "^0.27.0",
|
||||
"rxjs": "^7.5.5"
|
||||
"@types/react-reconciler": "^0.26.4",
|
||||
"nanoid": "^3.1.31",
|
||||
"react-reconciler": "^0.26.2",
|
||||
"rxjs": "^7.5.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"discord.js": "^13.3",
|
||||
@@ -61,24 +61,24 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/lodash-es": "^4.17.6",
|
||||
"c8": "^7.11.2",
|
||||
"discord.js": "^13.6.0",
|
||||
"dotenv": "^16.0.0",
|
||||
"@types/lodash-es": "^4.17.5",
|
||||
"c8": "^7.11.0",
|
||||
"discord.js": "^13.5.1",
|
||||
"dotenv": "^11.0.0",
|
||||
"esbuild": "latest",
|
||||
"esbuild-jest": "^0.5.0",
|
||||
"esmo": "^0.14.1",
|
||||
"esmo": "^0.13.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"nodemon": "^2.0.15",
|
||||
"prettier": "^2.6.2",
|
||||
"prettier": "^2.5.1",
|
||||
"pretty-ms": "^7.0.1",
|
||||
"react": "^18.0.0",
|
||||
"release-it": "^14.14.2",
|
||||
"tsup": "^5.12.6",
|
||||
"type-fest": "^2.12.2",
|
||||
"typescript": "^4.6.3",
|
||||
"vite": "^2.9.5",
|
||||
"vitest": "^0.9.4"
|
||||
"react": "^17.0.2",
|
||||
"release-it": "^14.12.1",
|
||||
"tsup": "^5.11.11",
|
||||
"type-fest": "^2.9.0",
|
||||
"typescript": "^4.5.4",
|
||||
"vite": "^2.7.10",
|
||||
"vitest": "^0.0.141"
|
||||
},
|
||||
"resolutions": {
|
||||
"esbuild": "latest"
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 954 KiB |
@@ -8,15 +8,17 @@ type Command = {
|
||||
|
||||
export function createCommandHandler(client: Client, commands: Command[]) {
|
||||
client.on("ready", async () => {
|
||||
for (const command of commands) {
|
||||
for (const guild of client.guilds.cache.values()) {
|
||||
client.application!.commands.set(
|
||||
commands.map(({ name, description }) => ({
|
||||
name,
|
||||
description,
|
||||
})),
|
||||
await client.application?.commands.create(
|
||||
{
|
||||
name: command.name,
|
||||
description: command.description,
|
||||
},
|
||||
guild.id,
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
client.on("interactionCreate", async (interaction) => {
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { Client } from "discord.js"
|
||||
import "dotenv/config"
|
||||
import { readFile } from "fs/promises"
|
||||
import { join } from "path"
|
||||
import React from "react"
|
||||
import { fileURLToPath } from "url"
|
||||
import { Button, ReacordDiscordJs, useInstance } from "../library/main"
|
||||
import { createCommandHandler } from "./command-handler"
|
||||
import { Counter } from "./counter"
|
||||
@@ -107,19 +104,6 @@ createCommandHandler(client, [
|
||||
reacord.reply(interaction, <DeleteThis />)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "anime",
|
||||
description: "shows an anime image",
|
||||
run: async (interaction) => {
|
||||
const reply = reacord.reply(interaction)
|
||||
const image = await readFile(
|
||||
join(fileURLToPath(import.meta.url), "../anime.jpg"),
|
||||
)
|
||||
|
||||
reply.attach({ name: "anime.jpg", data: image })
|
||||
// reply.render("anime")
|
||||
},
|
||||
},
|
||||
])
|
||||
|
||||
await client.login(process.env.TEST_BOT_TOKEN)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React, { useState } from "react"
|
||||
import { expect, test, vi } from "vitest"
|
||||
import { expect, fn, test } from "vitest"
|
||||
import { Button, Option, Select } from "../library/main"
|
||||
import { ReacordTester } from "./test-adapter"
|
||||
|
||||
test("single select", async () => {
|
||||
const tester = new ReacordTester()
|
||||
const onSelect = vi.fn()
|
||||
const onSelect = fn()
|
||||
|
||||
function TestSelect() {
|
||||
const [value, setValue] = useState<string>()
|
||||
@@ -75,7 +75,7 @@ test("single select", async () => {
|
||||
|
||||
test("multiple select", async () => {
|
||||
const tester = new ReacordTester()
|
||||
const onSelect = vi.fn()
|
||||
const onSelect = fn()
|
||||
|
||||
function TestSelect() {
|
||||
const [values, setValues] = useState<string[]>([])
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
/* eslint-disable class-methods-use-this */
|
||||
/* eslint-disable require-await */
|
||||
import { nanoid } from "nanoid"
|
||||
import { setTimeout } from "node:timers/promises"
|
||||
import { nextTick } from "node:process"
|
||||
import { promisify } from "node:util"
|
||||
import type { ReactNode } from "react"
|
||||
import { expect } from "vitest"
|
||||
import { logPretty } from "../helpers/log-pretty"
|
||||
import { omit } from "../helpers/omit"
|
||||
import { pruneNullishValues } from "../helpers/prune-nullish-values"
|
||||
import { raise } from "../helpers/raise"
|
||||
import { waitFor } from "../helpers/wait-for"
|
||||
import type {
|
||||
ChannelInfo,
|
||||
GuildInfo,
|
||||
@@ -26,10 +26,17 @@ import type {
|
||||
CommandInteraction,
|
||||
SelectInteraction,
|
||||
} from "../library/internal/interaction"
|
||||
import type { Message, MessageOptions } from "../library/internal/message"
|
||||
import type {
|
||||
Message,
|
||||
MessageButtonOptions,
|
||||
MessageOptions,
|
||||
MessageSelectOptions,
|
||||
} from "../library/internal/message"
|
||||
import { ChannelMessageRenderer } from "../library/internal/renderers/channel-message-renderer"
|
||||
import { InteractionReplyRenderer } from "../library/internal/renderers/interaction-reply-renderer"
|
||||
|
||||
const nextTickPromise = promisify(nextTick)
|
||||
|
||||
export type MessageSample = ReturnType<ReacordTester["sampleMessages"]>[0]
|
||||
|
||||
/**
|
||||
@@ -66,10 +73,9 @@ export class ReacordTester extends Reacord {
|
||||
return this.reply(initialContent)
|
||||
}
|
||||
|
||||
assertMessages(expected: MessageSample[]) {
|
||||
return waitFor(() => {
|
||||
async assertMessages(expected: MessageSample[]) {
|
||||
await nextTickPromise()
|
||||
expect(this.sampleMessages()).toEqual(expected)
|
||||
})
|
||||
}
|
||||
|
||||
async assertRender(content: ReactNode, expected: MessageSample[]) {
|
||||
@@ -102,59 +108,58 @@ export class ReacordTester extends Reacord {
|
||||
}
|
||||
|
||||
findButtonByLabel(label: string) {
|
||||
return {
|
||||
click: () => {
|
||||
return waitFor(() => {
|
||||
for (const [component, message] of this.eachComponent()) {
|
||||
for (const message of this.messageContainer) {
|
||||
for (const component of message.options.actionRows.flat()) {
|
||||
if (component.type === "button" && component.label === label) {
|
||||
this.handleComponentInteraction(
|
||||
new TestButtonInteraction(component.customId, message, this),
|
||||
)
|
||||
return
|
||||
return this.createButtonActions(component, message)
|
||||
}
|
||||
}
|
||||
}
|
||||
raise(`Couldn't find button with label "${label}"`)
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
findSelectByPlaceholder(placeholder: string) {
|
||||
return {
|
||||
select: (...values: string[]) => {
|
||||
return waitFor(() => {
|
||||
for (const [component, message] of this.eachComponent()) {
|
||||
for (const message of this.messageContainer) {
|
||||
for (const component of message.options.actionRows.flat()) {
|
||||
if (
|
||||
component.type === "select" &&
|
||||
component.placeholder === placeholder
|
||||
) {
|
||||
this.handleComponentInteraction(
|
||||
new TestSelectInteraction(
|
||||
component.customId,
|
||||
message,
|
||||
values,
|
||||
this,
|
||||
),
|
||||
)
|
||||
return
|
||||
return this.createSelectActions(component, message)
|
||||
}
|
||||
}
|
||||
}
|
||||
raise(`Couldn't find select with placeholder "${placeholder}"`)
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
createMessage(options: MessageOptions) {
|
||||
return new TestMessage(options, this.messageContainer)
|
||||
}
|
||||
|
||||
private *eachComponent() {
|
||||
for (const message of this.messageContainer) {
|
||||
for (const component of message.options.actionRows.flat()) {
|
||||
yield [component, message] as const
|
||||
private createButtonActions(
|
||||
button: MessageButtonOptions,
|
||||
message: TestMessage,
|
||||
) {
|
||||
return {
|
||||
click: () => {
|
||||
this.handleComponentInteraction(
|
||||
new TestButtonInteraction(button.customId, message, this),
|
||||
)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
private createSelectActions(
|
||||
component: MessageSelectOptions,
|
||||
message: TestMessage,
|
||||
) {
|
||||
return {
|
||||
select: (...values: string[]) => {
|
||||
this.handleComponentInteraction(
|
||||
new TestSelectInteraction(component.customId, message, values, this),
|
||||
)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,6 +175,16 @@ class TestMessage implements Message {
|
||||
this.options = options
|
||||
}
|
||||
|
||||
async disableComponents(): Promise<void> {
|
||||
for (const row of this.options.actionRows) {
|
||||
for (const action of row) {
|
||||
if (action.type === "button") {
|
||||
action.disabled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async delete(): Promise<void> {
|
||||
this.container.remove(this)
|
||||
}
|
||||
@@ -182,14 +197,16 @@ class TestCommandInteraction implements CommandInteraction {
|
||||
|
||||
constructor(private messageContainer: Container<TestMessage>) {}
|
||||
|
||||
async reply(messageOptions: MessageOptions): Promise<Message> {
|
||||
await setTimeout()
|
||||
return new TestMessage(messageOptions, this.messageContainer)
|
||||
reply(messageOptions: MessageOptions): Promise<Message> {
|
||||
return Promise.resolve(
|
||||
new TestMessage(messageOptions, this.messageContainer),
|
||||
)
|
||||
}
|
||||
|
||||
async followUp(messageOptions: MessageOptions): Promise<Message> {
|
||||
await setTimeout()
|
||||
return new TestMessage(messageOptions, this.messageContainer)
|
||||
followUp(messageOptions: MessageOptions): Promise<Message> {
|
||||
return Promise.resolve(
|
||||
new TestMessage(messageOptions, this.messageContainer),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ describe("useInstance", () => {
|
||||
await tester.assertMessages([messageOutput("parent")])
|
||||
expect(instanceFromHook).toBe(instance)
|
||||
|
||||
await tester.findButtonByLabel("create parent").click()
|
||||
tester.findButtonByLabel("create parent").click()
|
||||
await tester.assertMessages([
|
||||
messageOutput("parent"),
|
||||
messageOutput("child"),
|
||||
@@ -63,10 +63,10 @@ describe("useInstance", () => {
|
||||
|
||||
// this test ensures that the only the child instance is destroyed,
|
||||
// and not the parent instance
|
||||
await tester.findButtonByLabel("destroy child").click()
|
||||
tester.findButtonByLabel("destroy child").click()
|
||||
await tester.assertMessages([messageOutput("parent")])
|
||||
|
||||
await tester.findButtonByLabel("destroy parent").click()
|
||||
tester.findButtonByLabel("destroy parent").click()
|
||||
await tester.assertMessages([])
|
||||
})
|
||||
})
|
||||
|
||||
5
packages/website/.gitignore
vendored
5
packages/website/.gitignore
vendored
@@ -4,7 +4,10 @@ node_modules
|
||||
/build
|
||||
/public/build
|
||||
.env
|
||||
/public/api
|
||||
cypress/videos
|
||||
cypress/screenshots
|
||||
*.out.css
|
||||
|
||||
# typedoc output
|
||||
/public/api
|
||||
/app/assets/api.json
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { hydrate } from "react-dom"
|
||||
import { RemixBrowser } from "@remix-run/react"
|
||||
import { hydrate } from "react-dom";
|
||||
import { RemixBrowser } from "remix";
|
||||
|
||||
hydrate(<RemixBrowser />, document)
|
||||
hydrate(<RemixBrowser />, document);
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import { renderToString } from "react-dom/server"
|
||||
import type { EntryContext } from "@remix-run/node"
|
||||
import { RemixServer } from "@remix-run/react"
|
||||
import { renderToString } from "react-dom/server";
|
||||
import { RemixServer } from "remix";
|
||||
import type { EntryContext } from "remix";
|
||||
|
||||
export default function handleRequest(
|
||||
request: Request,
|
||||
responseStatusCode: number,
|
||||
responseHeaders: Headers,
|
||||
remixContext: EntryContext,
|
||||
remixContext: EntryContext
|
||||
) {
|
||||
const markup = renderToString(
|
||||
<RemixServer context={remixContext} url={request.url} />,
|
||||
)
|
||||
<RemixServer context={remixContext} url={request.url} />
|
||||
);
|
||||
|
||||
responseHeaders.set("Content-Type", "text/html")
|
||||
responseHeaders.set("Content-Type", "text/html");
|
||||
|
||||
return new Response("<!DOCTYPE html>" + markup, {
|
||||
status: responseStatusCode,
|
||||
headers: responseHeaders,
|
||||
})
|
||||
headers: responseHeaders
|
||||
});
|
||||
}
|
||||
|
||||
8
packages/website/app/modules/api/api-data.server.ts
Normal file
8
packages/website/app/modules/api/api-data.server.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { JSONOutput } from "typedoc"
|
||||
import apiData from "~/assets/api.json"
|
||||
|
||||
export type ApiData = JSONOutput.ContainerReflection
|
||||
|
||||
export function getApiData(): ApiData {
|
||||
return apiData as ApiData
|
||||
}
|
||||
13
packages/website/app/modules/helpers/promise-all-object.ts
Normal file
13
packages/website/app/modules/helpers/promise-all-object.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export async function promiseAllObject<Input extends object>(
|
||||
input: Input,
|
||||
): Promise<{
|
||||
[K in keyof Input]: Awaited<Input[K]>
|
||||
}> {
|
||||
const result: any = {}
|
||||
await Promise.all(
|
||||
Object.entries(input).map(async ([key, promise]) => {
|
||||
result[key] = await promise
|
||||
}),
|
||||
)
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
export async function renderMarkdown(
|
||||
markdown: string,
|
||||
): Promise<{ __html: string }> {
|
||||
const rehypePrism = import("rehype-prism-plus").then(
|
||||
(module) => module.default,
|
||||
)
|
||||
const rehypeStringify = import("rehype-stringify").then(
|
||||
(module) => module.default,
|
||||
)
|
||||
const remarkParse = import("remark-parse").then((module) => module.default)
|
||||
const remarkRehype = import("remark-rehype").then((module) => module.default)
|
||||
const { unified } = await import("unified")
|
||||
|
||||
const processor = unified()
|
||||
.use(await remarkParse)
|
||||
.use(await remarkRehype)
|
||||
.use(await rehypeStringify)
|
||||
.use(await rehypePrism)
|
||||
|
||||
const result = await processor.process(markdown)
|
||||
|
||||
return { __html: result.toString() }
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { ComponentPropsWithoutRef } from "react"
|
||||
import { Link } from "@remix-run/react"
|
||||
import { Link } from "remix"
|
||||
import { ExternalLink } from "~/modules/dom/external-link"
|
||||
|
||||
export type AppLinkProps = ComponentPropsWithoutRef<"a"> & {
|
||||
|
||||
@@ -9,7 +9,6 @@ export function MainNavigation() {
|
||||
<nav className="flex justify-between items-center h-16">
|
||||
<a href="/">
|
||||
<AppLogo className="w-32" />
|
||||
<span className="sr-only">Home</span>
|
||||
</a>
|
||||
<div className="hidden md:flex gap-4">
|
||||
{mainLinks.map((link) => (
|
||||
|
||||
@@ -61,7 +61,7 @@ export function Modal({
|
||||
)
|
||||
}
|
||||
|
||||
export function UncontrolledModal({
|
||||
export function ControlledModal({
|
||||
children,
|
||||
button,
|
||||
}: {
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import packageJson from "reacord/package.json"
|
||||
import type {
|
||||
LinksFunction,
|
||||
LoaderFunction,
|
||||
MetaFunction,
|
||||
} from "@remix-run/node"
|
||||
import type { LinksFunction, LoaderFunction, MetaFunction } from "remix"
|
||||
import {
|
||||
Links,
|
||||
LiveReload,
|
||||
@@ -12,7 +8,7 @@ import {
|
||||
Scripts,
|
||||
ScrollRestoration,
|
||||
useLoaderData,
|
||||
} from "@remix-run/react"
|
||||
} from "remix"
|
||||
import bannerUrl from "~/assets/banner.png"
|
||||
import faviconUrl from "~/assets/favicon.png"
|
||||
import { GuideLinksProvider } from "~/modules/navigation/guide-links-context"
|
||||
@@ -26,15 +22,15 @@ export const meta: MetaFunction = () => ({
|
||||
"description": packageJson.description,
|
||||
"theme-color": "#21754b",
|
||||
|
||||
"og:url": "https://reacord.mapleleaf.dev/",
|
||||
"og:url": "https://reacord.fly.dev/",
|
||||
"og:type": "website",
|
||||
"og:title": "Reacord",
|
||||
"og:description": "Create interactive Discord messages using React",
|
||||
"og:image": bannerUrl,
|
||||
|
||||
"twitter:card": "summary_large_image",
|
||||
"twitter:domain": "reacord.mapleleaf.dev",
|
||||
"twitter:url": "https://reacord.mapleleaf.dev/",
|
||||
"twitter:domain": "reacord.fly.dev",
|
||||
"twitter:url": "https://reacord.fly.dev/",
|
||||
"twitter:title": "Reacord",
|
||||
"twitter:description": "Create interactive Discord messages using React",
|
||||
"twitter:image": bannerUrl,
|
||||
@@ -55,10 +51,6 @@ export const links: LinksFunction = () => [
|
||||
as: "style",
|
||||
href: "https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@500&family=Rubik:ital,wght@0,300;0,400;0,500;1,300;1,400;1,500&display=swap",
|
||||
},
|
||||
{
|
||||
rel: "stylesheet",
|
||||
href: "https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@500&family=Rubik:ital,wght@0,300;0,400;0,500;1,300;1,400;1,500&display=swap",
|
||||
},
|
||||
]
|
||||
|
||||
type LoaderData = {
|
||||
@@ -85,7 +77,7 @@ export default function App() {
|
||||
<script
|
||||
async
|
||||
data-website-id="49c69ade-5593-4853-9686-c9ca9d519a18"
|
||||
src="https://umami-production-265f.up.railway.app/umami.js"
|
||||
src="https://maple-umami.fly.dev/umami.js"
|
||||
/>
|
||||
)}
|
||||
</head>
|
||||
|
||||
78
packages/website/app/routes/api.tsx
Normal file
78
packages/website/app/routes/api.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
import clsx from "clsx"
|
||||
import { Fragment } from "react"
|
||||
import type { LoaderFunction } from "remix"
|
||||
import { Outlet, useLoaderData } from "remix"
|
||||
import { getApiData } from "~/modules/api/api-data.server"
|
||||
import { ActiveLink } from "~/modules/navigation/active-link"
|
||||
import type { AppLinkProps } from "~/modules/navigation/app-link"
|
||||
import { AppLink } from "~/modules/navigation/app-link"
|
||||
import { MainNavigation } from "~/modules/navigation/main-navigation"
|
||||
import { linkClass, maxWidthContainer } from "~/modules/ui/components"
|
||||
|
||||
type LoaderData = {
|
||||
categorySections: Array<{
|
||||
title: string
|
||||
links: AppLinkProps[]
|
||||
}>
|
||||
// [key: string]: unknown
|
||||
}
|
||||
|
||||
export const loader: LoaderFunction = async () => {
|
||||
const apiData = getApiData()
|
||||
|
||||
const childrenById = Object.fromEntries(
|
||||
apiData.children.map((child) => [child.id, { name: child.name }]),
|
||||
)
|
||||
|
||||
const data: LoaderData = {
|
||||
categorySections: apiData.categories.map((category) => ({
|
||||
title: category.title,
|
||||
links: category.children
|
||||
.map((childId) => childrenById[childId])
|
||||
.flatMap<AppLinkProps>((child) =>
|
||||
child
|
||||
? { to: `/api/${child.name}`, type: "router", children: child.name }
|
||||
: [],
|
||||
),
|
||||
})),
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
export default function ApiReferencePage() {
|
||||
const data = useLoaderData<LoaderData>()
|
||||
return (
|
||||
<div className="isolate">
|
||||
<header className="bg-slate-700/30 shadow sticky top-0 backdrop-blur-sm transition z-10 flex">
|
||||
<div className={maxWidthContainer}>
|
||||
<MainNavigation />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className={clsx(maxWidthContainer, "mt-8 flex items-start gap-4")}>
|
||||
<nav className="w-48 sticky top-24 hidden md:block">
|
||||
{data.categorySections.map((category) => (
|
||||
<Fragment key={category.title}>
|
||||
<h2 className="text-2xl">{category.title}</h2>
|
||||
<ul className="mt-3 mb-6 flex flex-col gap-2 items-start">
|
||||
{category.links.map((link) => (
|
||||
<li key={link.to}>
|
||||
<ActiveLink to={link.to}>
|
||||
{({ active }) => (
|
||||
<AppLink {...link} className={linkClass({ active })} />
|
||||
)}
|
||||
</ActiveLink>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</Fragment>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
<main className="pb-8 flex-1 min-w-0">
|
||||
<Outlet />
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
92
packages/website/app/routes/api/$name.tsx
Normal file
92
packages/website/app/routes/api/$name.tsx
Normal file
@@ -0,0 +1,92 @@
|
||||
import type { LoaderFunction } from "remix"
|
||||
import { useLoaderData } from "remix"
|
||||
import { getApiData } from "~/modules/api/api-data.server"
|
||||
import { renderMarkdown } from "~/modules/markdown/render-markdown.server"
|
||||
import { docsProseClass } from "~/modules/ui/components"
|
||||
|
||||
type LoaderData = {
|
||||
title: string
|
||||
description?: { __html: string }
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export const loader: LoaderFunction = async ({ params }) => {
|
||||
const apiData = getApiData()
|
||||
|
||||
const entityName = params.name!
|
||||
|
||||
const info = apiData.children?.find((child) => child.name === entityName)
|
||||
|
||||
const description = [
|
||||
info?.comment?.shortText,
|
||||
info?.comment?.text,
|
||||
info?.signatures?.[0]?.comment?.shortText,
|
||||
info?.signatures?.[0]?.comment?.text,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n\n")
|
||||
|
||||
const data: LoaderData = {
|
||||
title: entityName,
|
||||
description: description ? await renderMarkdown(description) : undefined,
|
||||
sig: await renderMarkdown(`
|
||||
\`\`\`tsx
|
||||
function ActionRow(props: ActionRowProps): ReactElement
|
||||
\`\`\`
|
||||
`),
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
export default function ApiDetailPage() {
|
||||
const data = useLoaderData<LoaderData>()
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1 className="text-3xl font-light">{data.title}</h1>
|
||||
<p className="text-sm font-bold opacity-50 uppercase mt-1">Component</p>
|
||||
<section
|
||||
className={docsProseClass}
|
||||
dangerouslySetInnerHTML={data.description}
|
||||
/>
|
||||
<section className={docsProseClass}>
|
||||
<ul>
|
||||
<li>
|
||||
<p>
|
||||
<code>children?: ReactNode</code>
|
||||
</p>
|
||||
<p>
|
||||
This should be a list of <code>{`<Option />`}</code> components.
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<p>
|
||||
<code>disabled?: boolean</code>
|
||||
</p>
|
||||
<p>
|
||||
When true, the select will be slightly faded, and cannot be
|
||||
interacted with.
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<p>
|
||||
<code>minValues?: number</code>
|
||||
</p>
|
||||
<p>
|
||||
With <code>multiple</code>, the minimum number of values that can
|
||||
be selected. When <code>multiple</code> is false or not defined,
|
||||
this is always 1.
|
||||
</p>
|
||||
<p>
|
||||
This only limits the number of values that can be received by the
|
||||
user. This does not limit the number of values that can be
|
||||
displayed by you.
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import clsx from "clsx"
|
||||
import { Outlet } from "@remix-run/react"
|
||||
import { Outlet } from "remix"
|
||||
import { ActiveLink } from "~/modules/navigation/active-link"
|
||||
import { AppLink } from "~/modules/navigation/app-link"
|
||||
import { useGuideLinksContext } from "~/modules/navigation/guide-links-context"
|
||||
|
||||
@@ -5,7 +5,7 @@ import LandingCode from "~/modules/landing/landing-code.mdx"
|
||||
import { MainNavigation } from "~/modules/navigation/main-navigation"
|
||||
import { buttonClass, maxWidthContainer } from "~/modules/ui/components"
|
||||
import { LandingAnimation } from "../modules/landing/landing-animation"
|
||||
import { UncontrolledModal } from "../modules/ui/modal"
|
||||
import { ControlledModal } from "../modules/ui/modal"
|
||||
|
||||
export default function Landing() {
|
||||
return (
|
||||
@@ -37,7 +37,7 @@ export default function Landing() {
|
||||
Get Started
|
||||
</a>
|
||||
|
||||
<UncontrolledModal
|
||||
<ControlledModal
|
||||
button={(button) => (
|
||||
<button
|
||||
{...button}
|
||||
@@ -50,7 +50,7 @@ export default function Landing() {
|
||||
<div className="text-sm sm:text-base">
|
||||
<LandingCode />
|
||||
</div>
|
||||
</UncontrolledModal>
|
||||
</ControlledModal>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import "./commands"
|
||||
import './commands'
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
"private": true,
|
||||
"name": "website",
|
||||
"scripts": {
|
||||
"dev": "concurrently 'typedoc --watch' 'pnpm tailwind -- --watch' 'remix dev'",
|
||||
"prepare": "remix setup node",
|
||||
"dev": "NODE_OPTIONS=--enable-source-maps concurrently 'typedoc --watch' 'pnpm tailwind -- --watch' 'remix dev'",
|
||||
"test": "node ./scripts/test.js",
|
||||
"test-dev": "pnpm dev & wait-on http-get://localhost:3000 && cypress open",
|
||||
"build": "typedoc && pnpm tailwind -- --minify && remix build",
|
||||
@@ -11,42 +12,46 @@
|
||||
"typecheck": "tsc --noEmit && tsc --project cypress/tsconfig.json --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@headlessui/react": "^1.5.0",
|
||||
"@heroicons/react": "^1.0.6",
|
||||
"@reach/rect": "^0.17.0",
|
||||
"@remix-run/node": "^1.4.1",
|
||||
"@remix-run/react": "^1.4.1",
|
||||
"@remix-run/serve": "^1.4.1",
|
||||
"@tailwindcss/typography": "^0.5.2",
|
||||
"@headlessui/react": "^1.4.2",
|
||||
"@heroicons/react": "^1.0.5",
|
||||
"@reach/rect": "^0.16.0",
|
||||
"@remix-run/react": "^1.1.1",
|
||||
"@remix-run/serve": "^1.1.1",
|
||||
"@tailwindcss/typography": "^0.5.0",
|
||||
"clsx": "^1.1.1",
|
||||
"fast-glob": "^3.2.11",
|
||||
"fast-glob": "^3.2.10",
|
||||
"gray-matter": "^4.0.3",
|
||||
"reacord": "workspace:*",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-focus-on": "^3.5.4",
|
||||
"react-router": "^6.3.0",
|
||||
"react-router-dom": "^6.3.0"
|
||||
"react-router": "^6.2.1",
|
||||
"react-router-dom": "^6.2.1",
|
||||
"rehype-stringify": "^9.0.2",
|
||||
"remark-parse": "^10.0.1",
|
||||
"rehype-prism-plus": "^1.3.1",
|
||||
"remark-rehype": "^10.1.0",
|
||||
"remix": "^1.1.1",
|
||||
"unified": "^10.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@remix-run/dev": "^1.4.1",
|
||||
"@remix-run/node": "^1.4.1",
|
||||
"@remix-run/dev": "^1.1.1",
|
||||
"@remix-run/node": "^1.1.1",
|
||||
"@testing-library/cypress": "^8.0.2",
|
||||
"@types/node": "*",
|
||||
"@types/react": "^18.0.6",
|
||||
"@types/react-dom": "^18.0.2",
|
||||
"@types/tailwindcss": "^3.0.10",
|
||||
"@types/react": "^17.0.38",
|
||||
"@types/react-dom": "^17.0.11",
|
||||
"@types/tailwindcss": "^3.0.2",
|
||||
"@types/wait-on": "^5.3.1",
|
||||
"autoprefixer": "^10.4.4",
|
||||
"concurrently": "^7.1.0",
|
||||
"cypress": "^9.5.4",
|
||||
"execa": "^6.1.0",
|
||||
"postcss": "^8.4.12",
|
||||
"rehype-prism-plus": "^1.3.2",
|
||||
"tailwindcss": "^3.0.24",
|
||||
"typedoc": "^0.22.15",
|
||||
"typescript": "^4.6.3",
|
||||
"wait-on": "^6.0.1"
|
||||
"autoprefixer": "^10.4.2",
|
||||
"concurrently": "^7.0.0",
|
||||
"cypress": "^9.2.1",
|
||||
"execa": "^6.0.0",
|
||||
"postcss": "^8.4.5",
|
||||
"tailwindcss": "^3.0.13",
|
||||
"typedoc": "^0.22.10",
|
||||
"typescript": "^4.5.4",
|
||||
"wait-on": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"~/*": ["./app/*"]
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"$schema": "https://typedoc.org/schema.json",
|
||||
"entryPoints": ["../reacord/library/main.ts"],
|
||||
"out": ["public/api"],
|
||||
"tsconfig": "../reacord/tsconfig.json",
|
||||
"json": "./app/assets/api.json",
|
||||
"excludeInternal": true,
|
||||
"excludePrivate": true,
|
||||
"excludeProtected": true,
|
||||
|
||||
3397
pnpm-lock.yaml
generated
3397
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user