From 31d2eb640fba862ac22aa0300be32792e952087e Mon Sep 17 00:00:00 2001 From: misuzu Date: Fri, 25 Oct 2024 13:29:50 +0800 Subject: [PATCH 01/10] feat: jsx msg and rescript --- .gitignore | 2 +- packages/core/src/index.ts | 1 + packages/core/src/utils/jsx.ts | 119 ++++++++++++++++++++ packages/myy/rescript.json | 14 --- packages/myy/src/Main.res | 66 ----------- packages/{myy => rescript}/README.md | 0 packages/{myy => rescript}/package.json | 11 +- packages/rescript/rescript.json | 18 +++ packages/{myy => rescript}/src/Kotori.res | 129 +++++++++++++++------- packages/rescript/src/KotoriMsg.res | 29 +++++ pnpm-lock.yaml | 45 +++++++- tsconfig.base.json | 4 +- tsup.config.ts | 4 + 13 files changed, 313 insertions(+), 129 deletions(-) create mode 100644 packages/core/src/utils/jsx.ts delete mode 100644 packages/myy/rescript.json delete mode 100644 packages/myy/src/Main.res rename packages/{myy => rescript}/README.md (100%) rename packages/{myy => rescript}/package.json (80%) create mode 100644 packages/rescript/rescript.json rename packages/{myy => rescript}/src/Kotori.res (68%) create mode 100644 packages/rescript/src/KotoriMsg.res diff --git a/.gitignore b/.gitignore index 213ba494..339c8259 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,4 @@ tsconfig.tsbuildinfo test.http kotori.dev.* -*.mjs +*.res.js diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index af78c6df..c72b6392 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -11,6 +11,7 @@ import 'reflect-metadata' export * from './app' export * from './components' export * from './decorators' +export * from './utils/jsx' export * from './utils/error' export * from './utils/factory' export * from './utils/container' diff --git a/packages/core/src/utils/jsx.ts b/packages/core/src/utils/jsx.ts new file mode 100644 index 00000000..86740716 --- /dev/null +++ b/packages/core/src/utils/jsx.ts @@ -0,0 +1,119 @@ +import { MessageList, MessageSingle, Messages } from '../components/messages' + +declare namespace JSX { + type Node = string | number | boolean | null | undefined | Element | Node[] + type Element = + | import('../components/messages').MessageList + | import('../components/messages').MessageSingle + + interface ElementAttributesProperty { + // biome-ignore lint: + props: any + } + + interface ElementChildrenAttribute { + children: unknown + } + + interface IntrinsicElements { + text: { + children: string + } + br: never + image: { + src: string + } + reply: { + messageId: string + } + mention: { + userId: string + } + mentionAll: never + video: { + src: string + } + audio: { + src: string + } + voice: { + src: string + } + file: { + src: string + } + location: { + latitude: number + longitude: number + title: string + content: string + } + seg: { + children: Node + } + } +} + +const Fragment = Symbol('Fragment') + +export function h( + type: string | typeof Fragment | ((props: Record) => JSX.Element), + props: Record, + // biome-ignore lint: + ...children: any[] +): JSX.Element { + if (type === Fragment) return h('list', props, ...children) + if (typeof type === 'function') return type(Object.assign({}, props, { children })) + const flattenedChildren = children + .flat(Number.MAX_SAFE_INTEGER) + .filter((child) => child != null && child !== false && child !== true) + switch (type) { + case 'text': + return Messages.text(flattenedChildren.map((child) => String(child)).join('')) + case 'mention': + return Messages.mention((props as JSX.IntrinsicElements['mention']).userId) + case 'mentionAll': + return Messages.mentionAll() + case 'reply': + return Messages.reply((props as JSX.IntrinsicElements['reply']).messageId) + case 'image': + return Messages.image((props as JSX.IntrinsicElements['image']).src) + case 'audio': + return Messages.audio((props as JSX.IntrinsicElements['audio']).src) + case 'video': + return Messages.video((props as JSX.IntrinsicElements['video']).src) + case 'file': + return Messages.file((props as JSX.IntrinsicElements['file']).src) + case 'location': + // biome-ignore lint: + const locationProps = props as JSX.IntrinsicElements['location'] + return Messages.location( + locationProps.latitude, + locationProps.longitude, + locationProps.title, + locationProps.content + ) + case 'seg': + return Messages( + ...flattenedChildren.map((child) => + child instanceof MessageSingle || child instanceof MessageList + ? child + : child && typeof child === 'object' && 'type' in child + ? h(child.type, child.props, child.children) + : String(child) + ) + ) + case 'br': + return h('text', {}, '\n') + default: + throw new Error(`Unknown element type: ${type}`) + } +} + +export function hRes(type: string, props: Record) { + return h( + type, + props, + ...('children' in props ? (Array.isArray(props.children) ? props.children : [props.children]) : []) + ) +} diff --git a/packages/myy/rescript.json b/packages/myy/rescript.json deleted file mode 100644 index 5f160168..00000000 --- a/packages/myy/rescript.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "name": "kotori", - "sources": { - "dir": "src", - "subdirs": true - }, - "package-specs": { - "module": "esmodule", - "in-source": true - }, - "suffix": ".res.mjs", - "bs-dependencies": ["@rescript/core", "@rescript/react"], - "bsc-flags": ["-open RescriptCore"] -} diff --git a/packages/myy/src/Main.res b/packages/myy/src/Main.res deleted file mode 100644 index f3561c4f..00000000 --- a/packages/myy/src/Main.res +++ /dev/null @@ -1,66 +0,0 @@ -let resHookerProps = [ - "loadExport", - "unloadExport", - "cmd_new", - "cmd_action", - "cmd_option", - "cmd_help", - "cmd_scope", - "cmd_access", - "cmd_alias", - "cmd_shortcut", - "cmd_hide", -] - -let main = (ctx: Kotori.context) => { - open Kotori.Msg - - let provideSucceed = ctx.provide("res_hooker", ctx->Kotori.createResHooker->Kotori.Utils.toAny) - let mixinSucceed = ctx.mixin("res_hooker", resHookerProps, true) - - Console.log( - if provideSucceed && mixinSucceed { - "Res hooker is ready." - } else { - "Res hooker is not ready." - }, - ) - - ctx.loadExport({ - main: ctx2 => { - let identity = switch ctx2.identity { - | Some(identity) => identity - | None => "Unknown" - } - Console.log(`Hello, world! from ${identity}`) - }, - }) - - "greet - get a greeting" - ->ctx.cmd_new - ->ctx.cmd_action(async (_, session) => { - let res = await "http://hotaru.icu/api/hitokoto/v2?format=text"->ctx.http.get - switch res->Type.typeof { - | #string => session.format("Greet: \n{0}", [res->Kotori.Utils.toAny]) - | _ => "Sorry, I cannot get a greeting right now." - }->text - }) - ->ctx.cmd_help("Get a greeting from hotaru.icu") - ->ctx.cmd_scope(#all) - ->ctx.cmd_alias(["hi", "hey", "hello"]) - ->ignore - - "res [saying=functional]" - ->ctx.cmd_new - ->ctx.cmd_action(async ({args}, session) => { - switch session.userId { - | Some(userId) => Console.log(`User ID: ${userId}`) - | None => Console.log("No user ID found") - } - switch args { - | [String(saying)] => `Hello, ${saying} from rescript!` - | _ => "Invalid arguments" - }->text - }) - ->ignore -} diff --git a/packages/myy/README.md b/packages/rescript/README.md similarity index 100% rename from packages/myy/README.md rename to packages/rescript/README.md diff --git a/packages/myy/package.json b/packages/rescript/package.json similarity index 80% rename from packages/myy/package.json rename to packages/rescript/package.json index dc6bd186..fa53c25a 100644 --- a/packages/myy/package.json +++ b/packages/rescript/package.json @@ -1,5 +1,5 @@ { - "name": "kotori-rescript", + "name": "rescript-kotori", "version": "1.0.0", "description": "supports rescript for kotori-bot", "main": "src/Main.res.mjs", @@ -16,9 +16,8 @@ ], "license": "GPL-3.0", "files": [ - "lib", - "locales", - "LICENSE", + "dist", + "rescript.json", "README.md" ], "author": "Hatsuyuki ", @@ -26,7 +25,7 @@ "@rescript/core": "^1.3.0", "rescript": "^11.1.0" }, - "peerDependencies": { - "kotori-bot": "^1.6.3" + "dependencies": { + "@kotori-bot/core": "workspace:^" } } diff --git a/packages/rescript/rescript.json b/packages/rescript/rescript.json new file mode 100644 index 00000000..44605639 --- /dev/null +++ b/packages/rescript/rescript.json @@ -0,0 +1,18 @@ +{ + "$schema": "./node_modules/rescript/docs/docson/build-schema.json", + "name": "rescript-kotori", + "sources": { + "dir": "src", + "subdirs": true + }, + "package-specs": { + "module": "commonjs", + "in-source": true + }, + "suffix": ".res.js", + "bs-dependencies": ["@rescript/core"], + "bsc-flags": ["-open RescriptCore"], + "jsx": { + "module": "KotoriMsg" + } +} diff --git a/packages/myy/src/Kotori.res b/packages/rescript/src/Kotori.res similarity index 68% rename from packages/myy/src/Kotori.res rename to packages/rescript/src/Kotori.res index cd6904ba..58a4a3d8 100644 --- a/packages/myy/src/Kotori.res +++ b/packages/rescript/src/Kotori.res @@ -39,26 +39,89 @@ type i18n = { } module Msg = { - type message = {toString: unit => string} - @module("kotori-bot") external list: array => message = "Messages" - @module("kotori-bot") @scope("Messages") external text: string => message = "text" - @module("kotori-bot") @scope("Messages") external mention: string => message = "mention" - @module("kotori-bot") @scope("Messages") external mentionAll: unit => message = "mentionAll" - @module("kotori-bot") @scope("Messages") external image: string => message = "image" - @module("kotori-bot") @scope("Messages") external voice: string => message = "voice" - @module("kotori-bot") @scope("Messages") external audio: string => message = "audio" - @module("kotori-bot") @scope("Messages") external video: string => message = "video" - @module("kotori-bot") @scope("Messages") external file: string => message = "file" - @module("kotori-bot") @scope("Messages") - external location: ( - ~latitude: float, - ~longitude: float, - ~title: string, - ~content: string, - ) => message = "location" - @module("kotori-bot") @scope("Messages") external reply: string => message = "reply" + external el: 'a => KotoriMsg.element = "%identity" - type confirmConfig = {message: message, sure: message} + module Seg = { + @jsx.component + let make = (~children: KotoriMsg.element) => {el(children)} + } + + module Text = { + @jsx.component + let make = (~children: string) => {el(children)} + } + + module Br = { + @jsx.component + let make = () =>
+ } + + module Image = { + @jsx.component + let make = (~src: string) => + } + + module Voice = { + @jsx.component + let make = (~src: string) => + } + + module Audio = { + @jsx.component + let make = (~src: string) =>