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
|
.vscode
|
||||||
coverage
|
coverage
|
||||||
.env
|
.env
|
||||||
*.code-workspace
|
|
||||||
|
|
||||||
build
|
|
||||||
.cache
|
|
||||||
|
|||||||
@@ -4,4 +4,3 @@ coverage
|
|||||||
pnpm-lock.yaml
|
pnpm-lock.yaml
|
||||||
build
|
build
|
||||||
.cache
|
.cache
|
||||||
packages/website/public/api
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ pnpm add reacord react discord.js
|
|||||||
|
|
||||||
## Get Started
|
## 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
|
## 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",
|
"lint-fix": "pnpm lint -- --fix",
|
||||||
"format": "prettier --write ."
|
"format": "prettier --write ."
|
||||||
},
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@itsmapleleaf/configs": "^1.1.2"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@itsmapleleaf/configs": "^1.1.3",
|
"@typescript-eslint/eslint-plugin": "^5.9.1",
|
||||||
"@rushstack/eslint-patch": "^1.1.3",
|
"@typescript-eslint/parser": "^5.9.1",
|
||||||
"@types/eslint": "^8.4.1",
|
"eslint": "^8.6.0",
|
||||||
"eslint": "^8.14.0",
|
"eslint-config-prettier": "^8.3.0",
|
||||||
"prettier": "^2.6.2",
|
"eslint-import-resolver-typescript": "^2.5.0",
|
||||||
"typescript": "^4.6.3"
|
"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": {
|
"resolutions": {
|
||||||
"esbuild": "latest"
|
"esbuild": "latest"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { inspect } from "node:util"
|
import { inspect } from "node:util"
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-unused-modules
|
||||||
export function logPretty(value: unknown) {
|
export function logPretty(value: unknown) {
|
||||||
console.info(
|
console.info(
|
||||||
inspect(value, {
|
inspect(value, {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// eslint-disable-next-line import/no-unused-modules
|
||||||
export function omit<Subject extends object, Key extends PropertyKey>(
|
export function omit<Subject extends object, Key extends PropertyKey>(
|
||||||
subject: Subject,
|
subject: Subject,
|
||||||
keys: Key[],
|
keys: Key[],
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { LoosePick, UnknownRecord } from "./types"
|
import type { LoosePick, UnknownRecord } from "./types"
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-unused-modules
|
||||||
export function pick<T, K extends keyof T | PropertyKey>(
|
export function pick<T, K extends keyof T | PropertyKey>(
|
||||||
object: T,
|
object: T,
|
||||||
keys: K[],
|
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
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
export type PruneNullishValues<Input> = Input extends object
|
type PruneNullishValues<Input> = Input extends ReadonlyArray<infer Value>
|
||||||
? OptionalKeys<
|
? ReadonlyArray<NonNullable<Value>>
|
||||||
{ [Key in keyof Input]: NonNullable<PruneNullishValues<Input[Key]>> },
|
: Input extends object
|
||||||
KeysWithNullishValues<Input>
|
? {
|
||||||
>
|
[Key in keyof Input]: NonNullable<Input[Key]>
|
||||||
: Input
|
|
||||||
|
|
||||||
type OptionalKeys<Input, Keys extends keyof Input> = Omit<Input, Keys> & {
|
|
||||||
[Key in Keys]?: Input[Key]
|
|
||||||
}
|
}
|
||||||
|
: Input
|
||||||
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]
|
|
||||||
|
|||||||
@@ -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"
|
import { inspect } from "node:util"
|
||||||
|
|
||||||
|
// eslint-disable-next-line import/no-unused-modules
|
||||||
export function withLoggedMethodCalls<T extends object>(value: T) {
|
export function withLoggedMethodCalls<T extends object>(value: T) {
|
||||||
return new Proxy(value as Record<string | symbol, unknown>, {
|
return new Proxy(value as Record<string | symbol, unknown>, {
|
||||||
get(target, property) {
|
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.
|
* Get the associated instance for the current component.
|
||||||
*
|
*
|
||||||
* @category Core
|
* @category Core
|
||||||
* @see https://reacord.mapleleaf.dev/guides/use-instance
|
* @see https://reacord.fly.dev/guides/use-instance
|
||||||
*/
|
*/
|
||||||
export function useInstance(): ReacordInstance {
|
export function useInstance(): ReacordInstance {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import type { ReactNode } from "react"
|
import type { ReactNode } from "react"
|
||||||
import { ReacordFile } from "./file"
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents an interactive message, which can later be replaced or deleted.
|
* 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.
|
* This prevents it from listening to user interactions.
|
||||||
*/
|
*/
|
||||||
deactivate: () => void
|
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.
|
* Sends a message to a channel.
|
||||||
* @see https://reacord.mapleleaf.dev/guides/sending-messages
|
* @see https://reacord.fly.dev/guides/sending-messages
|
||||||
*/
|
*/
|
||||||
override send(
|
override send(
|
||||||
channelId: string,
|
channelId: string,
|
||||||
@@ -54,7 +54,7 @@ export class ReacordDiscordJs extends Reacord {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a message as a reply to a command interaction.
|
* 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(
|
override reply(
|
||||||
interaction: Discord.CommandInteraction,
|
interaction: Discord.CommandInteraction,
|
||||||
@@ -68,7 +68,7 @@ export class ReacordDiscordJs extends Reacord {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends an ephemeral message as a reply to a command interaction.
|
* 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(
|
override ephemeralReply(
|
||||||
interaction: Discord.CommandInteraction,
|
interaction: Discord.CommandInteraction,
|
||||||
@@ -298,18 +298,20 @@ function createReacordMessage(message: Discord.Message): Message {
|
|||||||
edit: async (options) => {
|
edit: async (options) => {
|
||||||
await message.edit(getDiscordMessageOptions(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 () => {
|
delete: async () => {
|
||||||
await message.delete()
|
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")
|
console.warn("Ephemeral messages can't be edited")
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
},
|
},
|
||||||
updateFiles: () => {
|
disableComponents: () => {
|
||||||
console.warn("Ephemeral messages can't be edited")
|
console.warn("Ephemeral messages can't be edited")
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
},
|
},
|
||||||
@@ -371,10 +373,7 @@ function getDiscordMessageOptions(
|
|||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasContent =
|
if (!options.content && !options.embeds?.length) {
|
||||||
options.content || options.embeds?.length || options.files?.length
|
|
||||||
|
|
||||||
if (!hasContent) {
|
|
||||||
options.content = "_ _"
|
options.content = "_ _"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,9 +63,6 @@ export abstract class Reacord {
|
|||||||
this.renderers = this.renderers.filter((it) => it !== renderer)
|
this.renderers = this.renderers.filter((it) => it !== renderer)
|
||||||
renderer.destroy()
|
renderer.destroy()
|
||||||
},
|
},
|
||||||
attach: (file) => {
|
|
||||||
renderer.attach(file)
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (initialContent !== undefined) {
|
if (initialContent !== undefined) {
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import { ReacordFile } from "../core/file"
|
|
||||||
import type { Message, MessageOptions } from "./message"
|
import type { Message, MessageOptions } from "./message"
|
||||||
|
|
||||||
export type Channel = {
|
export type Channel = {
|
||||||
send(message: MessageOptions): Promise<Message>
|
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 { last } from "../../helpers/last"
|
||||||
import type { EmbedOptions } from "../core/components/embed-options"
|
import type { EmbedOptions } from "../core/components/embed-options"
|
||||||
import type { SelectProps } from "../core/components/select"
|
import type { SelectProps } from "../core/components/select"
|
||||||
import { ReacordFile } from "../main"
|
|
||||||
|
|
||||||
export type MessageOptions = {
|
export type MessageOptions = {
|
||||||
content: string
|
content: string
|
||||||
@@ -50,7 +49,7 @@ export type MessageSelectOptionOptions = {
|
|||||||
export type Message = {
|
export type Message = {
|
||||||
edit(options: MessageOptions): Promise<void>
|
edit(options: MessageOptions): Promise<void>
|
||||||
delete(): Promise<void>
|
delete(): Promise<void>
|
||||||
updateFiles(files: readonly ReacordFile[]): Promise<void>
|
disableComponents(): Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getNextActionRow(options: MessageOptions): ActionRow {
|
export function getNextActionRow(options: MessageOptions): ActionRow {
|
||||||
|
|||||||
@@ -52,8 +52,6 @@ const config: HostConfig<
|
|||||||
},
|
},
|
||||||
createTextInstance: (text) => new TextNode(text),
|
createTextInstance: (text) => new TextNode(text),
|
||||||
shouldSetTextContent: () => false,
|
shouldSetTextContent: () => false,
|
||||||
// @ts-expect-error
|
|
||||||
detachDeletedInstance: (instance) => {},
|
|
||||||
|
|
||||||
clearContainer: (renderer) => {
|
clearContainer: (renderer) => {
|
||||||
renderer.nodes.clear()
|
renderer.nodes.clear()
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { ReacordFile } from "../../core/file"
|
|
||||||
import type { Channel } from "../channel"
|
import type { Channel } from "../channel"
|
||||||
import type { Message, MessageOptions } from "../message"
|
import type { Message, MessageOptions } from "../message"
|
||||||
import { Renderer } from "./renderer"
|
import { Renderer } from "./renderer"
|
||||||
@@ -11,10 +10,4 @@ export class ChannelMessageRenderer extends Renderer {
|
|||||||
protected createMessage(options: MessageOptions): Promise<Message> {
|
protected createMessage(options: MessageOptions): Promise<Message> {
|
||||||
return this.channel.send(options)
|
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 { Subject } from "rxjs"
|
||||||
import { concatMap } from "rxjs/operators"
|
import { concatMap } from "rxjs/operators"
|
||||||
import { ReacordFile } from "../../core/file"
|
|
||||||
import { Container } from "../container.js"
|
import { Container } from "../container.js"
|
||||||
import type { ComponentInteraction } from "../interaction"
|
import type { ComponentInteraction } from "../interaction"
|
||||||
import type { Message, MessageOptions } from "../message"
|
import type { Message, MessageOptions } from "../message"
|
||||||
@@ -8,21 +7,15 @@ import type { Node } from "../node.js"
|
|||||||
|
|
||||||
type UpdatePayload =
|
type UpdatePayload =
|
||||||
| { action: "update" | "deactivate"; options: MessageOptions }
|
| { action: "update" | "deactivate"; options: MessageOptions }
|
||||||
| { action: "files"; files: readonly ReacordFile[] }
|
|
||||||
| { action: "deferUpdate"; interaction: ComponentInteraction }
|
| { action: "deferUpdate"; interaction: ComponentInteraction }
|
||||||
| { action: "destroy" }
|
| { action: "destroy" }
|
||||||
|
|
||||||
type NewMessagePayload =
|
|
||||||
| { source: "content"; messageOptions?: MessageOptions }
|
|
||||||
| { source: "files"; files?: readonly ReacordFile[] }
|
|
||||||
|
|
||||||
export abstract class Renderer {
|
export abstract class Renderer {
|
||||||
readonly nodes = new Container<Node<unknown>>()
|
readonly nodes = new Container<Node<unknown>>()
|
||||||
private componentInteraction?: ComponentInteraction
|
private componentInteraction?: ComponentInteraction
|
||||||
private message?: Message
|
private message?: Message
|
||||||
private active = true
|
private active = true
|
||||||
private updates = new Subject<UpdatePayload>()
|
private updates = new Subject<UpdatePayload>()
|
||||||
private files: readonly ReacordFile[] = []
|
|
||||||
|
|
||||||
private updateSubscription = this.updates
|
private updateSubscription = this.updates
|
||||||
.pipe(concatMap((payload) => this.updateMessage(payload)))
|
.pipe(concatMap((payload) => this.updateMessage(payload)))
|
||||||
@@ -53,11 +46,6 @@ export abstract class Renderer {
|
|||||||
this.updates.next({ action: "destroy" })
|
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) {
|
handleComponentInteraction(interaction: ComponentInteraction) {
|
||||||
this.componentInteraction = interaction
|
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 {
|
private getMessageOptions(): MessageOptions {
|
||||||
const options: MessageOptions = {
|
const options: MessageOptions = {
|
||||||
@@ -95,17 +83,7 @@ export abstract class Renderer {
|
|||||||
|
|
||||||
if (payload.action === "deactivate") {
|
if (payload.action === "deactivate") {
|
||||||
this.updateSubscription.unsubscribe()
|
this.updateSubscription.unsubscribe()
|
||||||
|
await this.message?.disableComponents()
|
||||||
await this.message?.edit({
|
|
||||||
...payload.options,
|
|
||||||
actionRows: payload.options.actionRows.map((row) =>
|
|
||||||
row.map((component) => ({
|
|
||||||
...component,
|
|
||||||
disabled: true,
|
|
||||||
})),
|
|
||||||
),
|
|
||||||
})
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,15 +92,6 @@ export abstract class Renderer {
|
|||||||
return
|
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) {
|
if (this.componentInteraction) {
|
||||||
const promise = this.componentInteraction.update(payload.options)
|
const promise = this.componentInteraction.update(payload.options)
|
||||||
this.componentInteraction = undefined
|
this.componentInteraction = undefined
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ export * from "./core/components/embed-title"
|
|||||||
export * from "./core/components/link"
|
export * from "./core/components/link"
|
||||||
export * from "./core/components/option"
|
export * from "./core/components/option"
|
||||||
export * from "./core/components/select"
|
export * from "./core/components/select"
|
||||||
export * from "./core/file"
|
|
||||||
export * from "./core/instance"
|
export * from "./core/instance"
|
||||||
export { useInstance } from "./core/instance-context"
|
export { useInstance } from "./core/instance-context"
|
||||||
export * from "./core/reacord"
|
export * from "./core/reacord"
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
"name": "reacord",
|
"name": "reacord",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Create interactive Discord messages using React.",
|
"description": "Create interactive Discord messages using React.",
|
||||||
"version": "0.3.5",
|
"version": "0.3.4",
|
||||||
"types": "./dist/main.d.ts",
|
"types": "./dist/main.d.ts",
|
||||||
"homepage": "https://reacord.mapleleaf.dev",
|
"homepage": "https://reacord.fly.dev",
|
||||||
"repository": "https://github.com/itsMapleLeaf/reacord.git",
|
"repository": "https://github.com/itsMapleLeaf/reacord.git",
|
||||||
"changelog": "https://github.com/itsMapleLeaf/reacord/releases",
|
"changelog": "https://github.com/itsMapleLeaf/reacord/releases",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -46,10 +46,10 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
"@types/react": "*",
|
"@types/react": "*",
|
||||||
"@types/react-reconciler": "^0.26.6",
|
"@types/react-reconciler": "^0.26.4",
|
||||||
"nanoid": "^3.3.3",
|
"nanoid": "^3.1.31",
|
||||||
"react-reconciler": "^0.27.0",
|
"react-reconciler": "^0.26.2",
|
||||||
"rxjs": "^7.5.5"
|
"rxjs": "^7.5.2"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"discord.js": "^13.3",
|
"discord.js": "^13.3",
|
||||||
@@ -61,24 +61,24 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/lodash-es": "^4.17.6",
|
"@types/lodash-es": "^4.17.5",
|
||||||
"c8": "^7.11.2",
|
"c8": "^7.11.0",
|
||||||
"discord.js": "^13.6.0",
|
"discord.js": "^13.5.1",
|
||||||
"dotenv": "^16.0.0",
|
"dotenv": "^11.0.0",
|
||||||
"esbuild": "latest",
|
"esbuild": "latest",
|
||||||
"esbuild-jest": "^0.5.0",
|
"esbuild-jest": "^0.5.0",
|
||||||
"esmo": "^0.14.1",
|
"esmo": "^0.13.0",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"nodemon": "^2.0.15",
|
"nodemon": "^2.0.15",
|
||||||
"prettier": "^2.6.2",
|
"prettier": "^2.5.1",
|
||||||
"pretty-ms": "^7.0.1",
|
"pretty-ms": "^7.0.1",
|
||||||
"react": "^18.0.0",
|
"react": "^17.0.2",
|
||||||
"release-it": "^14.14.2",
|
"release-it": "^14.12.1",
|
||||||
"tsup": "^5.12.6",
|
"tsup": "^5.11.11",
|
||||||
"type-fest": "^2.12.2",
|
"type-fest": "^2.9.0",
|
||||||
"typescript": "^4.6.3",
|
"typescript": "^4.5.4",
|
||||||
"vite": "^2.9.5",
|
"vite": "^2.7.10",
|
||||||
"vitest": "^0.9.4"
|
"vitest": "^0.0.141"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"esbuild": "latest"
|
"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[]) {
|
export function createCommandHandler(client: Client, commands: Command[]) {
|
||||||
client.on("ready", async () => {
|
client.on("ready", async () => {
|
||||||
|
for (const command of commands) {
|
||||||
for (const guild of client.guilds.cache.values()) {
|
for (const guild of client.guilds.cache.values()) {
|
||||||
client.application!.commands.set(
|
await client.application?.commands.create(
|
||||||
commands.map(({ name, description }) => ({
|
{
|
||||||
name,
|
name: command.name,
|
||||||
description,
|
description: command.description,
|
||||||
})),
|
},
|
||||||
guild.id,
|
guild.id,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
client.on("interactionCreate", async (interaction) => {
|
client.on("interactionCreate", async (interaction) => {
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import { Client } from "discord.js"
|
import { Client } from "discord.js"
|
||||||
import "dotenv/config"
|
import "dotenv/config"
|
||||||
import { readFile } from "fs/promises"
|
|
||||||
import { join } from "path"
|
|
||||||
import React from "react"
|
import React from "react"
|
||||||
import { fileURLToPath } from "url"
|
|
||||||
import { Button, ReacordDiscordJs, useInstance } from "../library/main"
|
import { Button, ReacordDiscordJs, useInstance } from "../library/main"
|
||||||
import { createCommandHandler } from "./command-handler"
|
import { createCommandHandler } from "./command-handler"
|
||||||
import { Counter } from "./counter"
|
import { Counter } from "./counter"
|
||||||
@@ -107,19 +104,6 @@ createCommandHandler(client, [
|
|||||||
reacord.reply(interaction, <DeleteThis />)
|
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)
|
await client.login(process.env.TEST_BOT_TOKEN)
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import React, { useState } from "react"
|
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 { Button, Option, Select } from "../library/main"
|
||||||
import { ReacordTester } from "./test-adapter"
|
import { ReacordTester } from "./test-adapter"
|
||||||
|
|
||||||
test("single select", async () => {
|
test("single select", async () => {
|
||||||
const tester = new ReacordTester()
|
const tester = new ReacordTester()
|
||||||
const onSelect = vi.fn()
|
const onSelect = fn()
|
||||||
|
|
||||||
function TestSelect() {
|
function TestSelect() {
|
||||||
const [value, setValue] = useState<string>()
|
const [value, setValue] = useState<string>()
|
||||||
@@ -75,7 +75,7 @@ test("single select", async () => {
|
|||||||
|
|
||||||
test("multiple select", async () => {
|
test("multiple select", async () => {
|
||||||
const tester = new ReacordTester()
|
const tester = new ReacordTester()
|
||||||
const onSelect = vi.fn()
|
const onSelect = fn()
|
||||||
|
|
||||||
function TestSelect() {
|
function TestSelect() {
|
||||||
const [values, setValues] = useState<string[]>([])
|
const [values, setValues] = useState<string[]>([])
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
/* eslint-disable class-methods-use-this */
|
/* eslint-disable class-methods-use-this */
|
||||||
/* eslint-disable require-await */
|
/* eslint-disable require-await */
|
||||||
import { nanoid } from "nanoid"
|
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 type { ReactNode } from "react"
|
||||||
import { expect } from "vitest"
|
import { expect } from "vitest"
|
||||||
import { logPretty } from "../helpers/log-pretty"
|
import { logPretty } from "../helpers/log-pretty"
|
||||||
import { omit } from "../helpers/omit"
|
import { omit } from "../helpers/omit"
|
||||||
import { pruneNullishValues } from "../helpers/prune-nullish-values"
|
import { pruneNullishValues } from "../helpers/prune-nullish-values"
|
||||||
import { raise } from "../helpers/raise"
|
import { raise } from "../helpers/raise"
|
||||||
import { waitFor } from "../helpers/wait-for"
|
|
||||||
import type {
|
import type {
|
||||||
ChannelInfo,
|
ChannelInfo,
|
||||||
GuildInfo,
|
GuildInfo,
|
||||||
@@ -26,10 +26,17 @@ import type {
|
|||||||
CommandInteraction,
|
CommandInteraction,
|
||||||
SelectInteraction,
|
SelectInteraction,
|
||||||
} from "../library/internal/interaction"
|
} 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 { ChannelMessageRenderer } from "../library/internal/renderers/channel-message-renderer"
|
||||||
import { InteractionReplyRenderer } from "../library/internal/renderers/interaction-reply-renderer"
|
import { InteractionReplyRenderer } from "../library/internal/renderers/interaction-reply-renderer"
|
||||||
|
|
||||||
|
const nextTickPromise = promisify(nextTick)
|
||||||
|
|
||||||
export type MessageSample = ReturnType<ReacordTester["sampleMessages"]>[0]
|
export type MessageSample = ReturnType<ReacordTester["sampleMessages"]>[0]
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -66,10 +73,9 @@ export class ReacordTester extends Reacord {
|
|||||||
return this.reply(initialContent)
|
return this.reply(initialContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
assertMessages(expected: MessageSample[]) {
|
async assertMessages(expected: MessageSample[]) {
|
||||||
return waitFor(() => {
|
await nextTickPromise()
|
||||||
expect(this.sampleMessages()).toEqual(expected)
|
expect(this.sampleMessages()).toEqual(expected)
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async assertRender(content: ReactNode, expected: MessageSample[]) {
|
async assertRender(content: ReactNode, expected: MessageSample[]) {
|
||||||
@@ -102,59 +108,58 @@ export class ReacordTester extends Reacord {
|
|||||||
}
|
}
|
||||||
|
|
||||||
findButtonByLabel(label: string) {
|
findButtonByLabel(label: string) {
|
||||||
return {
|
for (const message of this.messageContainer) {
|
||||||
click: () => {
|
for (const component of message.options.actionRows.flat()) {
|
||||||
return waitFor(() => {
|
|
||||||
for (const [component, message] of this.eachComponent()) {
|
|
||||||
if (component.type === "button" && component.label === label) {
|
if (component.type === "button" && component.label === label) {
|
||||||
this.handleComponentInteraction(
|
return this.createButtonActions(component, message)
|
||||||
new TestButtonInteraction(component.customId, message, this),
|
}
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
raise(`Couldn't find button with label "${label}"`)
|
raise(`Couldn't find button with label "${label}"`)
|
||||||
})
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
findSelectByPlaceholder(placeholder: string) {
|
findSelectByPlaceholder(placeholder: string) {
|
||||||
return {
|
for (const message of this.messageContainer) {
|
||||||
select: (...values: string[]) => {
|
for (const component of message.options.actionRows.flat()) {
|
||||||
return waitFor(() => {
|
|
||||||
for (const [component, message] of this.eachComponent()) {
|
|
||||||
if (
|
if (
|
||||||
component.type === "select" &&
|
component.type === "select" &&
|
||||||
component.placeholder === placeholder
|
component.placeholder === placeholder
|
||||||
) {
|
) {
|
||||||
this.handleComponentInteraction(
|
return this.createSelectActions(component, message)
|
||||||
new TestSelectInteraction(
|
}
|
||||||
component.customId,
|
|
||||||
message,
|
|
||||||
values,
|
|
||||||
this,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
raise(`Couldn't find select with placeholder "${placeholder}"`)
|
raise(`Couldn't find select with placeholder "${placeholder}"`)
|
||||||
})
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
createMessage(options: MessageOptions) {
|
createMessage(options: MessageOptions) {
|
||||||
return new TestMessage(options, this.messageContainer)
|
return new TestMessage(options, this.messageContainer)
|
||||||
}
|
}
|
||||||
|
|
||||||
private *eachComponent() {
|
private createButtonActions(
|
||||||
for (const message of this.messageContainer) {
|
button: MessageButtonOptions,
|
||||||
for (const component of message.options.actionRows.flat()) {
|
message: TestMessage,
|
||||||
yield [component, message] as const
|
) {
|
||||||
|
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
|
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> {
|
async delete(): Promise<void> {
|
||||||
this.container.remove(this)
|
this.container.remove(this)
|
||||||
}
|
}
|
||||||
@@ -182,14 +197,16 @@ class TestCommandInteraction implements CommandInteraction {
|
|||||||
|
|
||||||
constructor(private messageContainer: Container<TestMessage>) {}
|
constructor(private messageContainer: Container<TestMessage>) {}
|
||||||
|
|
||||||
async reply(messageOptions: MessageOptions): Promise<Message> {
|
reply(messageOptions: MessageOptions): Promise<Message> {
|
||||||
await setTimeout()
|
return Promise.resolve(
|
||||||
return new TestMessage(messageOptions, this.messageContainer)
|
new TestMessage(messageOptions, this.messageContainer),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
async followUp(messageOptions: MessageOptions): Promise<Message> {
|
followUp(messageOptions: MessageOptions): Promise<Message> {
|
||||||
await setTimeout()
|
return Promise.resolve(
|
||||||
return new TestMessage(messageOptions, this.messageContainer)
|
new TestMessage(messageOptions, this.messageContainer),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ describe("useInstance", () => {
|
|||||||
await tester.assertMessages([messageOutput("parent")])
|
await tester.assertMessages([messageOutput("parent")])
|
||||||
expect(instanceFromHook).toBe(instance)
|
expect(instanceFromHook).toBe(instance)
|
||||||
|
|
||||||
await tester.findButtonByLabel("create parent").click()
|
tester.findButtonByLabel("create parent").click()
|
||||||
await tester.assertMessages([
|
await tester.assertMessages([
|
||||||
messageOutput("parent"),
|
messageOutput("parent"),
|
||||||
messageOutput("child"),
|
messageOutput("child"),
|
||||||
@@ -63,10 +63,10 @@ describe("useInstance", () => {
|
|||||||
|
|
||||||
// this test ensures that the only the child instance is destroyed,
|
// this test ensures that the only the child instance is destroyed,
|
||||||
// and not the parent instance
|
// and not the parent instance
|
||||||
await tester.findButtonByLabel("destroy child").click()
|
tester.findButtonByLabel("destroy child").click()
|
||||||
await tester.assertMessages([messageOutput("parent")])
|
await tester.assertMessages([messageOutput("parent")])
|
||||||
|
|
||||||
await tester.findButtonByLabel("destroy parent").click()
|
tester.findButtonByLabel("destroy parent").click()
|
||||||
await tester.assertMessages([])
|
await tester.assertMessages([])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
5
packages/website/.gitignore
vendored
5
packages/website/.gitignore
vendored
@@ -4,7 +4,10 @@ node_modules
|
|||||||
/build
|
/build
|
||||||
/public/build
|
/public/build
|
||||||
.env
|
.env
|
||||||
/public/api
|
|
||||||
cypress/videos
|
cypress/videos
|
||||||
cypress/screenshots
|
cypress/screenshots
|
||||||
*.out.css
|
*.out.css
|
||||||
|
|
||||||
|
# typedoc output
|
||||||
|
/public/api
|
||||||
|
/app/assets/api.json
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { hydrate } from "react-dom"
|
import { hydrate } from "react-dom";
|
||||||
import { RemixBrowser } from "@remix-run/react"
|
import { RemixBrowser } from "remix";
|
||||||
|
|
||||||
hydrate(<RemixBrowser />, document)
|
hydrate(<RemixBrowser />, document);
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
import { renderToString } from "react-dom/server"
|
import { renderToString } from "react-dom/server";
|
||||||
import type { EntryContext } from "@remix-run/node"
|
import { RemixServer } from "remix";
|
||||||
import { RemixServer } from "@remix-run/react"
|
import type { EntryContext } from "remix";
|
||||||
|
|
||||||
export default function handleRequest(
|
export default function handleRequest(
|
||||||
request: Request,
|
request: Request,
|
||||||
responseStatusCode: number,
|
responseStatusCode: number,
|
||||||
responseHeaders: Headers,
|
responseHeaders: Headers,
|
||||||
remixContext: EntryContext,
|
remixContext: EntryContext
|
||||||
) {
|
) {
|
||||||
const markup = renderToString(
|
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, {
|
return new Response("<!DOCTYPE html>" + markup, {
|
||||||
status: responseStatusCode,
|
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 type { ComponentPropsWithoutRef } from "react"
|
||||||
import { Link } from "@remix-run/react"
|
import { Link } from "remix"
|
||||||
import { ExternalLink } from "~/modules/dom/external-link"
|
import { ExternalLink } from "~/modules/dom/external-link"
|
||||||
|
|
||||||
export type AppLinkProps = ComponentPropsWithoutRef<"a"> & {
|
export type AppLinkProps = ComponentPropsWithoutRef<"a"> & {
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ export function MainNavigation() {
|
|||||||
<nav className="flex justify-between items-center h-16">
|
<nav className="flex justify-between items-center h-16">
|
||||||
<a href="/">
|
<a href="/">
|
||||||
<AppLogo className="w-32" />
|
<AppLogo className="w-32" />
|
||||||
<span className="sr-only">Home</span>
|
|
||||||
</a>
|
</a>
|
||||||
<div className="hidden md:flex gap-4">
|
<div className="hidden md:flex gap-4">
|
||||||
{mainLinks.map((link) => (
|
{mainLinks.map((link) => (
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ export function Modal({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function UncontrolledModal({
|
export function ControlledModal({
|
||||||
children,
|
children,
|
||||||
button,
|
button,
|
||||||
}: {
|
}: {
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
import packageJson from "reacord/package.json"
|
import packageJson from "reacord/package.json"
|
||||||
import type {
|
import type { LinksFunction, LoaderFunction, MetaFunction } from "remix"
|
||||||
LinksFunction,
|
|
||||||
LoaderFunction,
|
|
||||||
MetaFunction,
|
|
||||||
} from "@remix-run/node"
|
|
||||||
import {
|
import {
|
||||||
Links,
|
Links,
|
||||||
LiveReload,
|
LiveReload,
|
||||||
@@ -12,7 +8,7 @@ import {
|
|||||||
Scripts,
|
Scripts,
|
||||||
ScrollRestoration,
|
ScrollRestoration,
|
||||||
useLoaderData,
|
useLoaderData,
|
||||||
} from "@remix-run/react"
|
} from "remix"
|
||||||
import bannerUrl from "~/assets/banner.png"
|
import bannerUrl from "~/assets/banner.png"
|
||||||
import faviconUrl from "~/assets/favicon.png"
|
import faviconUrl from "~/assets/favicon.png"
|
||||||
import { GuideLinksProvider } from "~/modules/navigation/guide-links-context"
|
import { GuideLinksProvider } from "~/modules/navigation/guide-links-context"
|
||||||
@@ -26,15 +22,15 @@ export const meta: MetaFunction = () => ({
|
|||||||
"description": packageJson.description,
|
"description": packageJson.description,
|
||||||
"theme-color": "#21754b",
|
"theme-color": "#21754b",
|
||||||
|
|
||||||
"og:url": "https://reacord.mapleleaf.dev/",
|
"og:url": "https://reacord.fly.dev/",
|
||||||
"og:type": "website",
|
"og:type": "website",
|
||||||
"og:title": "Reacord",
|
"og:title": "Reacord",
|
||||||
"og:description": "Create interactive Discord messages using React",
|
"og:description": "Create interactive Discord messages using React",
|
||||||
"og:image": bannerUrl,
|
"og:image": bannerUrl,
|
||||||
|
|
||||||
"twitter:card": "summary_large_image",
|
"twitter:card": "summary_large_image",
|
||||||
"twitter:domain": "reacord.mapleleaf.dev",
|
"twitter:domain": "reacord.fly.dev",
|
||||||
"twitter:url": "https://reacord.mapleleaf.dev/",
|
"twitter:url": "https://reacord.fly.dev/",
|
||||||
"twitter:title": "Reacord",
|
"twitter:title": "Reacord",
|
||||||
"twitter:description": "Create interactive Discord messages using React",
|
"twitter:description": "Create interactive Discord messages using React",
|
||||||
"twitter:image": bannerUrl,
|
"twitter:image": bannerUrl,
|
||||||
@@ -55,10 +51,6 @@ export const links: LinksFunction = () => [
|
|||||||
as: "style",
|
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",
|
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 = {
|
type LoaderData = {
|
||||||
@@ -85,7 +77,7 @@ export default function App() {
|
|||||||
<script
|
<script
|
||||||
async
|
async
|
||||||
data-website-id="49c69ade-5593-4853-9686-c9ca9d519a18"
|
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>
|
</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 clsx from "clsx"
|
||||||
import { Outlet } from "@remix-run/react"
|
import { Outlet } from "remix"
|
||||||
import { ActiveLink } from "~/modules/navigation/active-link"
|
import { ActiveLink } from "~/modules/navigation/active-link"
|
||||||
import { AppLink } from "~/modules/navigation/app-link"
|
import { AppLink } from "~/modules/navigation/app-link"
|
||||||
import { useGuideLinksContext } from "~/modules/navigation/guide-links-context"
|
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 { MainNavigation } from "~/modules/navigation/main-navigation"
|
||||||
import { buttonClass, maxWidthContainer } from "~/modules/ui/components"
|
import { buttonClass, maxWidthContainer } from "~/modules/ui/components"
|
||||||
import { LandingAnimation } from "../modules/landing/landing-animation"
|
import { LandingAnimation } from "../modules/landing/landing-animation"
|
||||||
import { UncontrolledModal } from "../modules/ui/modal"
|
import { ControlledModal } from "../modules/ui/modal"
|
||||||
|
|
||||||
export default function Landing() {
|
export default function Landing() {
|
||||||
return (
|
return (
|
||||||
@@ -37,7 +37,7 @@ export default function Landing() {
|
|||||||
Get Started
|
Get Started
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<UncontrolledModal
|
<ControlledModal
|
||||||
button={(button) => (
|
button={(button) => (
|
||||||
<button
|
<button
|
||||||
{...button}
|
{...button}
|
||||||
@@ -50,7 +50,7 @@ export default function Landing() {
|
|||||||
<div className="text-sm sm:text-base">
|
<div className="text-sm sm:text-base">
|
||||||
<LandingCode />
|
<LandingCode />
|
||||||
</div>
|
</div>
|
||||||
</UncontrolledModal>
|
</ControlledModal>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
// ***********************************************************
|
// ***********************************************************
|
||||||
|
|
||||||
// Import commands.js using ES2015 syntax:
|
// Import commands.js using ES2015 syntax:
|
||||||
import "./commands"
|
import './commands'
|
||||||
|
|
||||||
// Alternatively you can use CommonJS syntax:
|
// Alternatively you can use CommonJS syntax:
|
||||||
// require('./commands')
|
// require('./commands')
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"name": "website",
|
"name": "website",
|
||||||
"scripts": {
|
"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": "node ./scripts/test.js",
|
||||||
"test-dev": "pnpm dev & wait-on http-get://localhost:3000 && cypress open",
|
"test-dev": "pnpm dev & wait-on http-get://localhost:3000 && cypress open",
|
||||||
"build": "typedoc && pnpm tailwind -- --minify && remix build",
|
"build": "typedoc && pnpm tailwind -- --minify && remix build",
|
||||||
@@ -11,42 +12,46 @@
|
|||||||
"typecheck": "tsc --noEmit && tsc --project cypress/tsconfig.json --noEmit"
|
"typecheck": "tsc --noEmit && tsc --project cypress/tsconfig.json --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@headlessui/react": "^1.5.0",
|
"@headlessui/react": "^1.4.2",
|
||||||
"@heroicons/react": "^1.0.6",
|
"@heroicons/react": "^1.0.5",
|
||||||
"@reach/rect": "^0.17.0",
|
"@reach/rect": "^0.16.0",
|
||||||
"@remix-run/node": "^1.4.1",
|
"@remix-run/react": "^1.1.1",
|
||||||
"@remix-run/react": "^1.4.1",
|
"@remix-run/serve": "^1.1.1",
|
||||||
"@remix-run/serve": "^1.4.1",
|
"@tailwindcss/typography": "^0.5.0",
|
||||||
"@tailwindcss/typography": "^0.5.2",
|
|
||||||
"clsx": "^1.1.1",
|
"clsx": "^1.1.1",
|
||||||
"fast-glob": "^3.2.11",
|
"fast-glob": "^3.2.10",
|
||||||
"gray-matter": "^4.0.3",
|
"gray-matter": "^4.0.3",
|
||||||
"reacord": "workspace:*",
|
"reacord": "workspace:*",
|
||||||
"react": "^18.0.0",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^18.0.0",
|
"react-dom": "^17.0.2",
|
||||||
"react-focus-on": "^3.5.4",
|
"react-focus-on": "^3.5.4",
|
||||||
"react-router": "^6.3.0",
|
"react-router": "^6.2.1",
|
||||||
"react-router-dom": "^6.3.0"
|
"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": {
|
"devDependencies": {
|
||||||
"@remix-run/dev": "^1.4.1",
|
"@remix-run/dev": "^1.1.1",
|
||||||
"@remix-run/node": "^1.4.1",
|
"@remix-run/node": "^1.1.1",
|
||||||
"@testing-library/cypress": "^8.0.2",
|
"@testing-library/cypress": "^8.0.2",
|
||||||
"@types/node": "*",
|
"@types/node": "*",
|
||||||
"@types/react": "^18.0.6",
|
"@types/react": "^17.0.38",
|
||||||
"@types/react-dom": "^18.0.2",
|
"@types/react-dom": "^17.0.11",
|
||||||
"@types/tailwindcss": "^3.0.10",
|
"@types/tailwindcss": "^3.0.2",
|
||||||
"@types/wait-on": "^5.3.1",
|
"@types/wait-on": "^5.3.1",
|
||||||
"autoprefixer": "^10.4.4",
|
"autoprefixer": "^10.4.2",
|
||||||
"concurrently": "^7.1.0",
|
"concurrently": "^7.0.0",
|
||||||
"cypress": "^9.5.4",
|
"cypress": "^9.2.1",
|
||||||
"execa": "^6.1.0",
|
"execa": "^6.0.0",
|
||||||
"postcss": "^8.4.12",
|
"postcss": "^8.4.5",
|
||||||
"rehype-prism-plus": "^1.3.2",
|
"tailwindcss": "^3.0.13",
|
||||||
"tailwindcss": "^3.0.24",
|
"typedoc": "^0.22.10",
|
||||||
"typedoc": "^0.22.15",
|
"typescript": "^4.5.4",
|
||||||
"typescript": "^4.6.3",
|
"wait-on": "^6.0.0"
|
||||||
"wait-on": "^6.0.1"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
"extends": "../../tsconfig.base.json",
|
"extends": "../../tsconfig.base.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
"baseUrl": ".",
|
|
||||||
"paths": {
|
"paths": {
|
||||||
"~/*": ["./app/*"]
|
"~/*": ["./app/*"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
|
"$schema": "https://typedoc.org/schema.json",
|
||||||
"entryPoints": ["../reacord/library/main.ts"],
|
"entryPoints": ["../reacord/library/main.ts"],
|
||||||
"out": ["public/api"],
|
|
||||||
"tsconfig": "../reacord/tsconfig.json",
|
"tsconfig": "../reacord/tsconfig.json",
|
||||||
|
"json": "./app/assets/api.json",
|
||||||
"excludeInternal": true,
|
"excludeInternal": true,
|
||||||
"excludePrivate": true,
|
"excludePrivate": true,
|
||||||
"excludeProtected": 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