From fd4e0c3112d69061d495dfcf79f6ef62e3c6d5e6 Mon Sep 17 00:00:00 2001 From: Alexander Neonxp Kiryukhin Date: Tue, 29 Oct 2024 01:21:05 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9D=D0=B0=D1=87=D0=B0=D0=BB=D1=8C=D0=BD?= =?UTF-8?q?=D1=8B=D0=B9=20=D0=B2=D0=B5=D0=B1=20=D0=BA=D0=BB=D0=B8=D0=B5?= =?UTF-8?q?=D0=BD=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 +- cmd/api/api.go | 18 +- cmd/frontend/frontend.go | 68 ++ go.mod | 10 +- go.sum | 18 +- main.go | 2 + pkg/api/api.go | 63 -- pkg/api/echo.go | 57 -- pkg/api/list.go | 29 - pkg/api/message.go | 68 -- pkg/api/misc.go | 15 - pkg/apiv1/api.go | 44 ++ pkg/apiv1/echo.go | 61 ++ pkg/{api => apiv1}/file.go | 2 +- pkg/apiv1/list.go | 32 + pkg/apiv1/message.go | 63 ++ pkg/apiv1/misc.go | 13 + pkg/apiv2/api.go | 27 + pkg/apiv2/echo.go | 36 + pkg/apiv2/message.go | 28 + pkg/fetcher/fetcher.go | 2 +- pkg/idec/echo.go | 4 +- pkg/idec/message.go | 89 ++- pkg/model/echo.go | 8 +- pkg/model/file.go | 6 +- pkg/model/message.go | 18 +- pkg/model/point.go | 8 +- web/package-lock.json | 1302 +++++++++++++++++++++++++++++++++ web/package.json | 18 + web/src/components/message.js | 20 + web/src/index.css | 6 + web/src/index.html | 13 + web/src/index.js | 49 ++ web/src/pages/echo.js | 22 + web/src/pages/list.js | 21 + web/src/root.js | 10 + 36 files changed, 1983 insertions(+), 271 deletions(-) create mode 100644 cmd/frontend/frontend.go delete mode 100644 pkg/api/api.go delete mode 100644 pkg/api/echo.go delete mode 100644 pkg/api/list.go delete mode 100644 pkg/api/message.go delete mode 100644 pkg/api/misc.go create mode 100644 pkg/apiv1/api.go create mode 100644 pkg/apiv1/echo.go rename pkg/{api => apiv1}/file.go (98%) create mode 100644 pkg/apiv1/list.go create mode 100644 pkg/apiv1/message.go create mode 100644 pkg/apiv1/misc.go create mode 100644 pkg/apiv2/api.go create mode 100644 pkg/apiv2/echo.go create mode 100644 pkg/apiv2/message.go create mode 100644 web/package-lock.json create mode 100644 web/package.json create mode 100644 web/src/components/message.js create mode 100644 web/src/index.css create mode 100644 web/src/index.html create mode 100644 web/src/index.js create mode 100644 web/src/pages/echo.js create mode 100644 web/src/pages/list.js create mode 100644 web/src/root.js diff --git a/.gitignore b/.gitignore index 3997bea..3dd9dc0 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -*.db \ No newline at end of file +*.db +node_modules +dist \ No newline at end of file diff --git a/cmd/api/api.go b/cmd/api/api.go index ed0cb98..7c31593 100644 --- a/cmd/api/api.go +++ b/cmd/api/api.go @@ -1,8 +1,11 @@ package api import ( + "github.com/labstack/echo/v4" + "github.com/labstack/echo/v4/middleware" "github.com/urfave/cli/v2" - "gitrepo.ru/neonxp/idecnode/pkg/api" + "gitrepo.ru/neonxp/idecnode/pkg/apiv1" + "gitrepo.ru/neonxp/idecnode/pkg/apiv2" "gitrepo.ru/neonxp/idecnode/pkg/config" "gitrepo.ru/neonxp/idecnode/pkg/idec" ) @@ -22,8 +25,19 @@ var APICommand *cli.Command = &cli.Command{ return err } defer idecApi.Close() + e := echo.New() - return api.New(idecApi, cfg).Run(c.Context) + e.Use( + middleware.Recover(), + middleware.Logger(), + ) + apiv1.New(idecApi, cfg).Register(e) + + apiv2.New(idecApi, cfg).Register(e) + + e.Static("/", "./web/dist") + + return e.Start(cfg.Listen) }, Flags: []cli.Flag{ &cli.StringFlag{ diff --git a/cmd/frontend/frontend.go b/cmd/frontend/frontend.go new file mode 100644 index 0000000..9679d6f --- /dev/null +++ b/cmd/frontend/frontend.go @@ -0,0 +1,68 @@ +package frontend + +import ( + "errors" + "log" + + "github.com/evanw/esbuild/pkg/api" + "github.com/urfave/cli/v2" +) + +var FrontendCommand *cli.Command = &cli.Command{ + Name: "frontend", + Description: "Build/watch frontend", + Action: func(c *cli.Context) error { + dev := c.Bool("dev") + watch := c.Bool("watch") + buildOpts := api.BuildOptions{ + EntryPoints: []string{ + "./web/src/index.js", + "./web/src/index.css", + "./web/src/index.html", + }, + Outdir: "./web/dist", + Bundle: true, + Write: true, + MinifyWhitespace: !dev, + MinifyIdentifiers: !dev, + MinifySyntax: !dev, + JSX: api.JSXAutomatic, + Loader: map[string]api.Loader{ + ".js": api.LoaderJSX, + ".css": api.LoaderCSS, + ".html": api.LoaderCopy, + }, + } + if watch { + ctx, err := api.Context(buildOpts) + if err != nil { + return err + } + if err := ctx.Watch(api.WatchOptions{}); err != nil { + return err + } + log.Println("watching frontend") + <-c.Done() + } else { + if br := api.Build(buildOpts); br.Errors != nil { + for _, e := range br.Errors { + log.Println(e.Location, e.Detail, e.Text) + } + + return errors.New("build failed") + } + } + + return nil + }, + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "dev", + Value: false, + }, + &cli.BoolFlag{ + Name: "watch", + Value: false, + }, + }, +} diff --git a/go.mod b/go.mod index ab611b0..72e141c 100644 --- a/go.mod +++ b/go.mod @@ -2,23 +2,27 @@ module gitrepo.ru/neonxp/idecnode go 1.23.1 -require github.com/urfave/cli/v2 v2.27.5 +require ( + github.com/urfave/cli/v2 v2.27.5 + golang.org/x/crypto v0.22.0 +) require ( + github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/labstack/gommon v0.4.2 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect - golang.org/x/crypto v0.22.0 // indirect golang.org/x/net v0.24.0 // indirect golang.org/x/sys v0.19.0 // indirect golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.5.0 // indirect ) require ( github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect - github.com/go-http-utils/logger v0.0.0-20161128092850-f3a42dcdeae6 + github.com/evanw/esbuild v0.24.0 github.com/google/uuid v1.6.0 github.com/labstack/echo/v4 v4.12.0 github.com/russross/blackfriday/v2 v2.1.0 // indirect diff --git a/go.sum b/go.sum index 00c1322..ecc37f9 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,11 @@ github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc= github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/go-http-utils/logger v0.0.0-20161128092850-f3a42dcdeae6 h1:R/ypabUA7vskKTRSlgP6rMUHTU6PBRgIcHVSU9qQ6qM= -github.com/go-http-utils/logger v0.0.0-20161128092850-f3a42dcdeae6/go.mod h1:CpBLxS3WrxouNECP/Y1A3i6qDnUYs8BvcXjgOW4Vqcw= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/evanw/esbuild v0.24.0 h1:GZ78naTLp7FKr+K7eNuM/SLs5maeiHYRPsTg6kmdsSE= +github.com/evanw/esbuild v0.24.0/go.mod h1:D2vIQZqV/vIf/VRHtViaUtViZmG7o+kKmlBfVQuRi48= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/labstack/echo/v4 v4.12.0 h1:IKpw49IMryVB2p1a4dzwlhP1O2Tf2E0Ir/450lH+kI0= @@ -13,8 +17,12 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w= github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= @@ -29,12 +37,18 @@ golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index d36a385..bf07b3c 100644 --- a/main.go +++ b/main.go @@ -7,6 +7,7 @@ import ( "github.com/urfave/cli/v2" "gitrepo.ru/neonxp/idecnode/cmd/api" "gitrepo.ru/neonxp/idecnode/cmd/fetcher" + "gitrepo.ru/neonxp/idecnode/cmd/frontend" "gitrepo.ru/neonxp/idecnode/cmd/point" ) @@ -18,6 +19,7 @@ func main() { api.APICommand, fetcher.FetcherCommand, point.PointCommand, + frontend.FrontendCommand, }, } diff --git a/pkg/api/api.go b/pkg/api/api.go deleted file mode 100644 index fd55f1d..0000000 --- a/pkg/api/api.go +++ /dev/null @@ -1,63 +0,0 @@ -package api - -import ( - "context" - "log" - "net/http" - "os" - - "github.com/go-http-utils/logger" - - "gitrepo.ru/neonxp/idecnode/pkg/config" - "gitrepo.ru/neonxp/idecnode/pkg/idec" -) - -type API struct { - config *config.Config - idec *idec.IDEC -} - -func New(i *idec.IDEC, cfg *config.Config) *API { - return &API{ - config: cfg, - idec: i, - } -} - -func (a *API) Run(ctx context.Context) error { - errorLog := log.New(os.Stderr, "ERROR\t", log.Ldate|log.Ltime|log.Lshortfile) - - mux := http.NewServeMux() - - mux.HandleFunc(`GET /list.txt`, a.getListHandler) - mux.HandleFunc(`GET /blacklist.txt`, a.getBlacklistHandler) - mux.HandleFunc(`GET /u/e/{ids...}`, a.getEchosHandler) - mux.HandleFunc(`GET /u/m/{ids...}`, a.getBundleHandler) - mux.HandleFunc(`GET /u/point/{pauth}/{tmsg}`, a.postPointHandler) - mux.HandleFunc(`POST /u/point`, a.postPointHandler) - mux.HandleFunc(`GET /m/{msgID}`, a.getMessageHandler) - mux.HandleFunc(`GET /e/{id}`, a.getEchoHandler) - mux.HandleFunc(`GET /x/features`, a.getFeaturesHandler) - mux.HandleFunc(`GET /x/c/{ids...}`, a.getEchosInfo) - mux.HandleFunc(`POST /x/filelist`, a.getFilelistHandler) - mux.HandleFunc(`GET /x/filelist/{pauth}`, a.getFilelistHandler) - mux.HandleFunc(`POST /x/file`, a.getFileHandler) - mux.HandleFunc(`GET /x/file/{filename}`, a.getFileHandler) - - srv := http.Server{ - Addr: a.config.Listen, - Handler: logger.Handler(mux, os.Stdout, logger.Type(a.config.LoggerType)), - ErrorLog: errorLog, - } - - go func() { - <-ctx.Done() - srv.Close() - }() - log.Println("started IDEC node at", a.config.Listen) - if err := srv.ListenAndServe(); err != http.ErrServerClosed { - return err - } - - return nil -} diff --git a/pkg/api/echo.go b/pkg/api/echo.go deleted file mode 100644 index 8f7a852..0000000 --- a/pkg/api/echo.go +++ /dev/null @@ -1,57 +0,0 @@ -package api - -import ( - "fmt" - "net/http" - "strings" -) - -func (a *API) getEchoHandler(w http.ResponseWriter, r *http.Request) { - echoID := r.PathValue("id") - echos, err := a.idec.GetEchosByIDs([]string{echoID}, 0, 0) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - if len(echos) == 0 { - return - } - - fmt.Fprint(w, strings.Join(echos[echoID].Messages, "\n")) -} - -func (a *API) getEchosHandler(w http.ResponseWriter, r *http.Request) { - ids := strings.Split(r.PathValue("ids"), "/") - last := ids[len(ids)-1] - offset, limit := 0, 0 - if _, err := fmt.Sscanf(last, "%d:%d", &offset, &limit); err == nil { - ids = ids[:len(ids)-1] - } - echos, err := a.idec.GetEchosByIDs(ids, offset, limit) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - for _, echoID := range ids { - e := echos[echoID] - fmt.Fprintln(w, e.Name) - if len(e.Messages) > 0 { - fmt.Fprintln(w, strings.Join(e.Messages, "\n")) - } - } -} - -func (a *API) getEchosInfo(w http.ResponseWriter, r *http.Request) { - ids := strings.Split(r.PathValue("ids"), "/") - echos, err := a.idec.GetEchosByIDs(ids, 0, 0) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - for _, e := range echos { - fmt.Fprintf(w, "%s:%d\n", e.Name, e.Count) - } -} diff --git a/pkg/api/list.go b/pkg/api/list.go deleted file mode 100644 index 80a34ea..0000000 --- a/pkg/api/list.go +++ /dev/null @@ -1,29 +0,0 @@ -package api - -import ( - "fmt" - "net/http" - "strings" -) - -func (a *API) getListHandler(w http.ResponseWriter, r *http.Request) { - echos, err := a.idec.GetEchos() - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - for _, e := range echos { - fmt.Fprintf(w, "%s:%d:%s\n", e.Name, e.Count, e.Description) - } -} - -func (a *API) getBlacklistHandler(w http.ResponseWriter, r *http.Request) { - list, err := a.idec.GetBlacklist() - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - fmt.Fprint(w, strings.Join(list, "\n")) -} diff --git a/pkg/api/message.go b/pkg/api/message.go deleted file mode 100644 index 81e863f..0000000 --- a/pkg/api/message.go +++ /dev/null @@ -1,68 +0,0 @@ -package api - -import ( - "encoding/base64" - "fmt" - "log" - "net/http" - "strings" -) - -func (a *API) getBundleHandler(w http.ResponseWriter, r *http.Request) { - ids := strings.Split(r.PathValue("ids"), "/") - - for _, messageID := range ids { - msg, err := a.idec.GetMessage(messageID) - if err != nil { - log.Println("cant read file for message", messageID, err) - continue - } - - b64msg := base64.StdEncoding.EncodeToString([]byte(msg.Bundle())) - fmt.Fprintf(w, "%s:%s\n", messageID, b64msg) - } -} - -func (a *API) getMessageHandler(w http.ResponseWriter, r *http.Request) { - msgID := r.PathValue("msgID") - - msg, err := a.idec.GetMessage(msgID) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - } - - _, err = fmt.Fprintln(w, msg.Bundle()) -} - -func (a *API) postPointHandler(w http.ResponseWriter, r *http.Request) { - msg, pauth := r.PathValue("tmsg"), r.PathValue("pauth") - if err := r.ParseForm(); err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - form := r.PostForm - if form.Has("tmsg") { - msg = form.Get("tmsg") - } - if form.Has("pauth") { - pauth = form.Get("pauth") - } - - a.savePointMessage(w, msg, pauth) -} - -func (a *API) savePointMessage(w http.ResponseWriter, rawMessage, auth string) error { - point, err := a.idec.GetPointByAuth(auth) - if err != nil { - fmt.Fprintln(w, "error: no auth - wrong authstring") - return err - } - - if err := a.idec.SavePointMessage(point.Username, rawMessage); err != nil { - return err - } - fmt.Fprintln(w, "msg ok") - - return nil -} diff --git a/pkg/api/misc.go b/pkg/api/misc.go deleted file mode 100644 index 85f7a88..0000000 --- a/pkg/api/misc.go +++ /dev/null @@ -1,15 +0,0 @@ -package api - -import ( - "fmt" - "net/http" - "strings" - - "gitrepo.ru/neonxp/idecnode/pkg/idec" -) - -func (a *API) getFeaturesHandler(w http.ResponseWriter, r *http.Request) { - if _, err := fmt.Fprint(w, strings.Join(idec.Features, "\n")); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } -} diff --git a/pkg/apiv1/api.go b/pkg/apiv1/api.go new file mode 100644 index 0000000..70e9cc1 --- /dev/null +++ b/pkg/apiv1/api.go @@ -0,0 +1,44 @@ +package apiv1 + +import ( + "github.com/labstack/echo/v4" + + "gitrepo.ru/neonxp/idecnode/pkg/config" + "gitrepo.ru/neonxp/idecnode/pkg/idec" +) + +type API struct { + config *config.Config + idec *idec.IDEC +} + +func New(i *idec.IDEC, cfg *config.Config) *API { + return &API{ + config: cfg, + idec: i, + } +} + +func (a *API) Register(e *echo.Echo) { + e.GET(`/list.txt`, a.getListHandler) + e.GET(`/blacklist.txt`, a.getBlacklistHandler) + + func(g *echo.Group) { + g.GET(`/e/*`, a.getEchosHandler) + g.GET(`/m/*`, a.getBundleHandler) + g.GET(`/point/:pauth/:tmsg`, a.postPointHandler) + g.POST(`/point`, a.postPointHandler) + }(e.Group("/u")) + + e.GET(`/e/:id`, a.getEchoHandler) + e.GET(`/m/:msgID`, a.getMessageHandler) + + func(g *echo.Group) { + e.GET(`/features`, a.getFeaturesHandler) + e.GET(`/c/*`, a.getEchosInfo) + // e.POST(`/filelist`, a.getFilelistHandler) + // e.GET(`/filelist/:pauth`, a.getFilelistHandler) + // e.POST(`/file`, a.getFileHandler) + // e.GET(`/file/:filename`, a.getFileHandler) + }(e.Group("/x")) +} diff --git a/pkg/apiv1/echo.go b/pkg/apiv1/echo.go new file mode 100644 index 0000000..68e7f48 --- /dev/null +++ b/pkg/apiv1/echo.go @@ -0,0 +1,61 @@ +package apiv1 + +import ( + "fmt" + "net/http" + "strings" + + "github.com/labstack/echo/v4" +) + +func (a *API) getEchoHandler(c echo.Context) error { + echoID := c.Param("id") + + echos, err := a.idec.GetEchosByIDs([]string{echoID}, 0, 0) + if err != nil { + return echo.ErrBadGateway + } + + if len(echos) == 0 { + return nil + } + + return c.String(http.StatusOK, strings.Join(echos[echoID].Messages, "\n")) +} + +func (a *API) getEchosHandler(c echo.Context) error { + ids := strings.Split(c.Param("*"), "/") + last := ids[len(ids)-1] + offset, limit := 0, 0 + if _, err := fmt.Sscanf(last, "%d:%d", &offset, &limit); err == nil { + ids = ids[:len(ids)-1] + } + echos, err := a.idec.GetEchosByIDs(ids, offset, limit) + if err != nil { + return echo.ErrBadRequest + } + + for _, echoID := range ids { + e := echos[echoID] + fmt.Fprintln(c.Response(), e.Name) + if len(e.Messages) > 0 { + fmt.Fprintln(c.Response(), strings.Join(e.Messages, "\n")) + } + } + + return nil +} + +func (a *API) getEchosInfo(c echo.Context) error { + ids := strings.Split(c.Param("*"), "/") + echos, err := a.idec.GetEchosByIDs(ids, 0, 0) + if err != nil { + return echo.ErrBadRequest + } + + for _, e := range echos { + fmt.Fprintf(c.Response(), "%s:%d\n", e.Name, e.Count) + } + + return nil +} diff --git a/pkg/api/file.go b/pkg/apiv1/file.go similarity index 98% rename from pkg/api/file.go rename to pkg/apiv1/file.go index e2fa8d9..eb1a8d8 100644 --- a/pkg/api/file.go +++ b/pkg/apiv1/file.go @@ -1,4 +1,4 @@ -package api +package apiv1 import ( "fmt" diff --git a/pkg/apiv1/list.go b/pkg/apiv1/list.go new file mode 100644 index 0000000..1e5dafc --- /dev/null +++ b/pkg/apiv1/list.go @@ -0,0 +1,32 @@ +package apiv1 + +import ( + "fmt" + "strings" + + "github.com/labstack/echo/v4" +) + +func (a *API) getListHandler(c echo.Context) error { + echos, err := a.idec.GetEchos() + if err != nil { + return echo.ErrInternalServerError + } + + for _, e := range echos { + fmt.Fprintf(c.Response(), "%s:%d:%s\n", e.Name, e.Count, e.Description) + } + + return nil +} + +func (a *API) getBlacklistHandler(c echo.Context) error { + list, err := a.idec.GetBlacklist() + if err != nil { + return echo.ErrInternalServerError + } + + fmt.Fprint(c.Response(), strings.Join(list, "\n")) + + return nil +} diff --git a/pkg/apiv1/message.go b/pkg/apiv1/message.go new file mode 100644 index 0000000..301b5cb --- /dev/null +++ b/pkg/apiv1/message.go @@ -0,0 +1,63 @@ +package apiv1 + +import ( + "encoding/base64" + "fmt" + "log" + "net/http" + "strings" + + "github.com/labstack/echo/v4" +) + +func (a *API) getBundleHandler(c echo.Context) error { + ids := strings.Split(c.Param("*"), "/") + + for _, messageID := range ids { + msg, err := a.idec.GetMessage(messageID) + if err != nil { + log.Println("cant read file for message", messageID, err) + continue + } + + b64msg := base64.StdEncoding.EncodeToString([]byte(msg.Bundle())) + fmt.Fprintf(c.Response(), "%s:%s\n", messageID, b64msg) + } + + return nil +} + +func (a *API) getMessageHandler(c echo.Context) error { + msgID := c.Param("msgID") + + msg, err := a.idec.GetMessage(msgID) + if err != nil { + return echo.ErrBadRequest + } + + return c.String(http.StatusOK, msg.Bundle()) +} + +func (a *API) postPointHandler(c echo.Context) error { + form := new(postForm) + + if err := c.Bind(form); err != nil { + return err + } + + point, err := a.idec.GetPointByAuth(form.PAuth) + if err != nil { + return c.String(http.StatusForbidden, "error: no auth - wrong authstring") + } + + if err := a.idec.SavePointMessage(point.Username, form.TMsg); err != nil { + return err + } + + return c.String(http.StatusOK, "msg ok") +} + +type postForm struct { + TMsg string `form:"tmsg"` + PAuth string `form:"pauth"` +} diff --git a/pkg/apiv1/misc.go b/pkg/apiv1/misc.go new file mode 100644 index 0000000..fa3ca21 --- /dev/null +++ b/pkg/apiv1/misc.go @@ -0,0 +1,13 @@ +package apiv1 + +import ( + "net/http" + "strings" + + "github.com/labstack/echo/v4" + "gitrepo.ru/neonxp/idecnode/pkg/idec" +) + +func (a *API) getFeaturesHandler(c echo.Context) error { + return c.String(http.StatusOK, strings.Join(idec.Features, "\n")) +} diff --git a/pkg/apiv2/api.go b/pkg/apiv2/api.go new file mode 100644 index 0000000..8fbd759 --- /dev/null +++ b/pkg/apiv2/api.go @@ -0,0 +1,27 @@ +package apiv2 + +import ( + "github.com/labstack/echo/v4" + "gitrepo.ru/neonxp/idecnode/pkg/config" + "gitrepo.ru/neonxp/idecnode/pkg/idec" +) + +type API struct { + config *config.Config + idec *idec.IDEC +} + +func New(i *idec.IDEC, cfg *config.Config) *API { + return &API{ + config: cfg, + idec: i, + } +} + +func (a *API) Register(e *echo.Echo) { + func(g *echo.Group) { + g.GET("/list", a.getListHandler) + g.GET("/e", a.getEchoHandler) + g.GET("/m", a.getMessagesHandler) + }(e.Group("/api")) +} diff --git a/pkg/apiv2/echo.go b/pkg/apiv2/echo.go new file mode 100644 index 0000000..daa68a8 --- /dev/null +++ b/pkg/apiv2/echo.go @@ -0,0 +1,36 @@ +package apiv2 + +import ( + "net/http" + + "github.com/labstack/echo/v4" +) + +func (a *API) getListHandler(c echo.Context) error { + echos, err := a.idec.GetEchos() + if err != nil { + return err + } + + return c.JSON(http.StatusOK, echos) +} + +func (a *API) getEchoHandler(c echo.Context) error { + q := new(getEchosRequest) + if err := c.Bind(q); err != nil { + return err + } + + echos, err := a.idec.GetEchosByIDs(q.EchoIDs, q.Offset, q.Limit) + if err != nil { + return err + } + + return c.JSON(http.StatusOK, echos) +} + +type getEchosRequest struct { + EchoIDs []string `query:"e"` + Offset int `query:"offset"` + Limit int `query:"limit"` +} diff --git a/pkg/apiv2/message.go b/pkg/apiv2/message.go new file mode 100644 index 0000000..7b06fde --- /dev/null +++ b/pkg/apiv2/message.go @@ -0,0 +1,28 @@ +package apiv2 + +import ( + "net/http" + + "github.com/labstack/echo/v4" +) + +func (a *API) getMessagesHandler(c echo.Context) error { + q := new(getMessagesRequest) + if err := c.Bind(q); err != nil { + return err + } + + msgs, err := a.idec.GetMessagesByEcho(q.Echo, q.Message, q.Offset, q.Limit) + if err != nil { + return err + } + + return c.JSON(http.StatusOK, msgs) +} + +type getMessagesRequest struct { + Echo string `query:"e"` + Message string `query:"m"` + Offset int `query:"offset"` + Limit int `query:"limit"` +} diff --git a/pkg/fetcher/fetcher.go b/pkg/fetcher/fetcher.go index 5083889..276b80b 100644 --- a/pkg/fetcher/fetcher.go +++ b/pkg/fetcher/fetcher.go @@ -67,7 +67,7 @@ func (f *Fetcher) downloadMessages(node config.Node, messagesToDownloads []strin func (f *Fetcher) getMissedEchoMessages(node config.Node, echoID string) ([]string, error) { missed := []string{} - messages, err := f.idec.GetMessagesByEcho(echoID, 0, 0) + messages, _, err := f.idec.GetMessageIDsByEcho(echoID, 0, 0) if err != nil { return nil, err } diff --git a/pkg/idec/echo.go b/pkg/idec/echo.go index db3cd70..04ed48c 100644 --- a/pkg/idec/echo.go +++ b/pkg/idec/echo.go @@ -18,7 +18,7 @@ func (i *IDEC) GetEchosByIDs(echoIDs []string, offset, limit int) (map[string]mo continue } - messages, err := i.GetMessagesByEcho(echoID, offset, limit) + messages, count, err := i.GetMessageIDsByEcho(echoID, offset, limit) if err != nil { return nil, err } @@ -27,7 +27,7 @@ func (i *IDEC) GetEchosByIDs(echoIDs []string, offset, limit int) (map[string]mo Name: echoID, Description: echoCfg.Description, Messages: messages, - Count: len(messages), + Count: count, } } diff --git a/pkg/idec/message.go b/pkg/idec/message.go index d018df2..48236e4 100644 --- a/pkg/idec/message.go +++ b/pkg/idec/message.go @@ -3,16 +3,17 @@ package idec import ( "encoding/base64" "fmt" + "log" "strings" "gitrepo.ru/neonxp/idecnode/pkg/model" "go.etcd.io/bbolt" ) -func (i *IDEC) GetMessagesByEcho(echoID string, offset, limit int) ([]string, error) { - messages := make([]string, 0) - - return messages, i.db.View(func(tx *bbolt.Tx) error { +func (i *IDEC) GetMessageIDsByEcho(echoID string, offset, limit int) ([]string, int, error) { + messages := make([]string, 0, limit) + count := 0 + return messages, count, i.db.View(func(tx *bbolt.Tx) error { if _, ok := i.config.Echos[echoID]; !ok { return nil } @@ -22,14 +23,15 @@ func (i *IDEC) GetMessagesByEcho(echoID string, offset, limit int) ([]string, er return nil } + count = bEcho.Stats().KeyN + cur := bEcho.Cursor() cur.First() - all := bEcho.Stats().KeyN if limit == 0 { - limit = all + limit = count } if offset < 0 { - offset = max(0, all+offset-1) + offset = max(0, count+offset-1) } for i := 0; i < offset; i++ { // skip offset entries @@ -47,6 +49,57 @@ func (i *IDEC) GetMessagesByEcho(echoID string, offset, limit int) ([]string, er }) } +func (i *IDEC) GetMessagesByEcho(echoID string, parent string, offset, limit int) ([]*model.Message, error) { + messages := make([]*model.Message, 0, limit) + return messages, i.db.View(func(tx *bbolt.Tx) error { + if _, ok := i.config.Echos[echoID]; !ok { + return nil + } + + bEcho := tx.Bucket([]byte(echoID)) + if bEcho == nil { + return nil + } + bMessages := tx.Bucket([]byte(msgBucket)) + if bMessages == nil { + return nil + } + + count := bEcho.Stats().KeyN + + cur := bEcho.Cursor() + cur.First() + if limit == 0 { + limit = count + } + if offset < 0 { + offset = max(0, count+offset-1) + } + for i := 0; i < offset; i++ { + // skip offset entries + cur.Next() + } + for i := 0; len(messages) < limit; i++ { + _, v := cur.Next() + if v == nil { + break + } + + msgText := bMessages.Get(v) + + msg, err := model.MessageFromBundle(string(v), string(msgText)) + if err != nil { + return err + } + if msg.RepTo == parent { + messages = append(messages, msg) + } + } + + return nil + }) +} + func (i *IDEC) GetMessage(messageID string) (*model.Message, error) { var msg *model.Message return msg, i.db.View(func(tx *bbolt.Tx) error { @@ -62,6 +115,28 @@ func (i *IDEC) GetMessage(messageID string) (*model.Message, error) { }) } +func (i *IDEC) GetMessages(messageIDs []string) ([]*model.Message, error) { + msgs := make([]*model.Message, 0, len(messageIDs)) + return msgs, i.db.View(func(tx *bbolt.Tx) error { + bucket := tx.Bucket([]byte(msgBucket)) + if bucket == nil { + return ErrMessageNotFound + } + for _, messageID := range messageIDs { + b := bucket.Get([]byte(messageID)) + msg, err := model.MessageFromBundle(messageID, string(b)) + if err != nil { + log.Println(err) + continue + } + + msgs = append(msgs, msg) + } + + return nil + }) +} + func (i *IDEC) SavePointMessage(point string, rawMessage string) error { rawMessage = strings.NewReplacer("-", "+", "_", "/").Replace(rawMessage) diff --git a/pkg/model/echo.go b/pkg/model/echo.go index 5f642da..21dcda6 100644 --- a/pkg/model/echo.go +++ b/pkg/model/echo.go @@ -6,10 +6,10 @@ import ( ) type Echo struct { - Name string - Description string - Count int - Messages []string + Name string `json:"name"` + Description string `json:"description"` + Count int `json:"count"` + Messages []string `json:"messages,omitempty"` } func (e *Echo) Format() string { diff --git a/pkg/model/file.go b/pkg/model/file.go index d87f581..b19461b 100644 --- a/pkg/model/file.go +++ b/pkg/model/file.go @@ -1,7 +1,7 @@ package model type File struct { - Name string - Size int64 - FullName string + Name string `json:"name"` + Size int64 `json:"size"` + FullName string `json:"full_name"` } diff --git a/pkg/model/message.go b/pkg/model/message.go index 3d15c46..3753419 100644 --- a/pkg/model/message.go +++ b/pkg/model/message.go @@ -8,15 +8,15 @@ import ( ) type Message struct { - ID string - RepTo string - EchoArea string - Date time.Time - From string - Addr string - MsgTo string - Subject string - Message string + ID string `json:"id"` + RepTo string `json:"rep_to"` + EchoArea string `json:"echo_area"` + Date time.Time `json:"date"` + From string `json:"from"` + Addr string `json:"addr"` + MsgTo string `json:"msg_to"` + Subject string `json:"subject"` + Message string `json:"message"` } func (m *Message) Bundle() string { diff --git a/pkg/model/point.go b/pkg/model/point.go index 02e9d43..3975ed9 100644 --- a/pkg/model/point.go +++ b/pkg/model/point.go @@ -7,8 +7,8 @@ func init() { } type Point struct { - Username string - Email string - Password []byte - AuthString string + Username string `json:"username"` + Email string `json:"email"` + Password []byte `json:"-"` + AuthString string `json:"auth_string"` } diff --git a/web/package-lock.json b/web/package-lock.json new file mode 100644 index 0000000..219938f --- /dev/null +++ b/web/package-lock.json @@ -0,0 +1,1302 @@ +{ + "name": "idec", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "idec", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@picocss/pico": "^2.0.6", + "react": "^18.3.1", + "react-markdown": "^9.0.1", + "react-router-dom": "^6.27.0", + "remark-breaks": "^4.0.0" + } + }, + "node_modules/@picocss/pico": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@picocss/pico/-/pico-2.0.6.tgz", + "integrity": "sha512-/d8qsykowelD6g8k8JYgmCagOIulCPHMEc2NC4u7OjmpQLmtSetLhEbt0j1n3fPNJVcrT84dRp0RfJBn3wJROA==", + "engines": { + "node": ">=18.19.0" + } + }, + "node_modules/@remix-run/router": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.20.0.tgz", + "integrity": "sha512-mUnk8rPJBI9loFDZ+YzPGdeniYK+FTmRD1TMCz7ev2SNIozyKKpnGgsxO34u6Z4z/t0ITuu7voi/AshfsGsgFg==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "0.7.34", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", + "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==" + }, + "node_modules/@types/prop-types": { + "version": "15.7.13", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", + "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==", + "peer": true + }, + "node_modules/@types/react": { + "version": "18.3.12", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz", + "integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==", + "peer": true, + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.0.2" + } + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==" + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "peer": true + }, + "node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", + "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.2.tgz", + "integrity": "sha512-1ngXYb+V9UT5h+PxNRa1O1FYguZK/XL+gkeqvp7EdHlB9oHUG0eYRo/vY5inBdcqo3RkPMC58/H94HvkbfGdyg==", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-object": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/inline-style-parser": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", + "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==" + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.1.tgz", + "integrity": "sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.1.3.tgz", + "integrity": "sha512-bfOjvNt+1AcbPLTFMFWY149nJz0OjmewJs3LQQ5pIyVGxP4CdOqNVJL6kTaM5c68p8q82Xv3nCyFfUnuEcH3UQ==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-newline-to-break": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-newline-to-break/-/mdast-util-newline-to-break-2.0.0.tgz", + "integrity": "sha512-MbgeFca0hLYIEx/2zGsszCSEJJ1JSCdiY5xQxRcLDDGa8EPvlLPupJ4DSajbMPAnC0je8jfb9TiUATnxxrHUog==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-find-and-replace": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.0.tgz", + "integrity": "sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ==", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.0.tgz", + "integrity": "sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.1.tgz", + "integrity": "sha512-CUQyKr1e///ZODyD1U3xit6zXwy1a8q2a1S1HKtIlmgvurrEpaw/Y9y6KSIbF8P59cn/NjzHyO+Q2fAyYLQrAA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.0.tgz", + "integrity": "sha512-j9DGrQLm/Uhl2tCzcbLhy5kXsgkHUrjJHg4fFAeoMRwJmJerT9aw4FEhIbZStWN8A3qMwOp1uzHr4UL8AInxtA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.0.tgz", + "integrity": "sha512-RR3i96ohZGde//4WSe/dJsxOX6vxIg9TimLAS3i4EhBAFx8Sm5SmqVfR8E87DPSR31nEAjZfbt91OMZWcNgdZw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz", + "integrity": "sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.0.tgz", + "integrity": "sha512-jY8CSxmpWLOxS+t8W+FG3Xigc0RDQA9bKMY/EwILvsesiRniiVMejYTE4wumNc2f4UbAa4WsHqe3J1QS1sli+A==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.0.tgz", + "integrity": "sha512-28kbwaBjc5yAI1XadbdPYHX/eDnqaUFVikLwrO7FDnKG7lpgxnvk/XGRhX/PN0mOZ+dBSZ+LgunHS+6tYQAzhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.0.tgz", + "integrity": "sha512-anK8SWmNphkXdaKgz5hJvGa7l00qmcaUQoMYsBwDlSKFKjc6gjGXPDw3FNL3Nbwq5L8gE+RCbGqTw49FK5Qyvg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.0.tgz", + "integrity": "sha512-S0ze2R9GH+fu41FA7pbSqNWObo/kzwf8rN/+IGlW/4tC6oACOs8B++bh+i9bVyNnwCcuksbFwsBme5OCKXCwIw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.0.tgz", + "integrity": "sha512-vZZio48k7ON0fVS3CUgFatWHoKbbLTK/rT7pzpJ4Bjp5JjkZeasRfrS9wsBdDJK2cJLHMckXZdzPSSr1B8a4oQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.1.tgz", + "integrity": "sha512-bmkNc7z8Wn6kgjZmVHOX3SowGmVdhYS7yBpMnuMnPzDq/6xwVA604DuOXMZTO1lvq01g+Adfa0pE2UKGlxL1XQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.0.tgz", + "integrity": "sha512-r4Sc6leeUTn3P6gk20aFMj2ntPwn6qpDZqWvYmAG6NgvFTIlj4WtrAudLi65qYoaGdXYViXYw2pkmn7QnIFasA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.0.tgz", + "integrity": "sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.0.tgz", + "integrity": "sha512-xNn4Pqkj2puRhKdKTm8t1YHC/BAjx6CEwRFXntTaRf/x16aqka6ouVoutm+QdkISTlT7e2zU7U4ZdlDLJd2Mcw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.0.tgz", + "integrity": "sha512-2xhYT0sfo85FMrUPtHcPo2rrp1lwbDEEzpx7jiH2xXJLqBuy4H0GgXk5ToU8IEwoROtXuL8ND0ttVa4rNqYK3w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.0.tgz", + "integrity": "sha512-6KU6qO7DZ7GJkaCgwBNtplXCvGkJToU86ybBAUdavvgsCiG8lSSvYxr9MhwmQ+udpzywHsl4RpGJsYWG1pDOcA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.0.tgz", + "integrity": "sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.1.tgz", + "integrity": "sha512-jZNtiFl/1aY73yS3UGQkutD0UbhTt68qnRpw2Pifmz5wV9h8gOVsN70v+Lq/f1rKaU/W8pxRe8y8Q9FX1AOe1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.0.tgz", + "integrity": "sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/parse-entities": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.1.tgz", + "integrity": "sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + }, + "node_modules/property-information": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", + "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-markdown": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-9.0.1.tgz", + "integrity": "sha512-186Gw/vF1uRkydbsOIkcGXw7aHq0sZOCRFFjGrr7b9+nVZg4UfA4enXCaxm4fUzecU38sWfrNDitGhshuU7rdg==", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, + "node_modules/react-router": { + "version": "6.27.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.27.0.tgz", + "integrity": "sha512-YA+HGZXz4jaAkVoYBE98VQl+nVzI+cVI2Oj/06F5ZM+0u3TgedN9Y9kmMRo2mnkSK2nCpNQn0DVob4HCsY/WLw==", + "dependencies": { + "@remix-run/router": "1.20.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.27.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.27.0.tgz", + "integrity": "sha512-+bvtFWMC0DgAFrfKXKG9Fc+BcXWRUO1aJIihbB79xaeq0v5UzfvnM5houGUm1Y461WVRcgAQ+Clh5rdb1eCx4g==", + "dependencies": { + "@remix-run/router": "1.20.0", + "react-router": "6.27.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/remark-breaks": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/remark-breaks/-/remark-breaks-4.0.0.tgz", + "integrity": "sha512-IjEjJOkH4FuJvHZVIW0QCDWxcG96kCq7An/KVH2NfJe6rKZU2AsHeB3OEjPNRxi4QC34Xdx7I2KGYn6IpT7gxQ==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-newline-to-break": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.1.tgz", + "integrity": "sha512-g/osARvjkBXb6Wo0XvAeXQohVta8i84ACbenPpoSsxTOQH/Ae0/RGP4WZgnMH5pMLpsj4FG7OHmcIcXxpza8eQ==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/style-to-object": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz", + "integrity": "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==", + "dependencies": { + "inline-style-parser": "0.2.4" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/web/package.json b/web/package.json new file mode 100644 index 0000000..bc0a22a --- /dev/null +++ b/web/package.json @@ -0,0 +1,18 @@ +{ + "name": "idec", + "version": "1.0.0", + "main": "dist/index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "description": "", + "dependencies": { + "@picocss/pico": "^2.0.6", + "react": "^18.3.1", + "react-markdown": "^9.0.1", + "react-router-dom": "^6.27.0", + "remark-breaks": "^4.0.0" + } +} diff --git a/web/src/components/message.js b/web/src/components/message.js new file mode 100644 index 0000000..92c15be --- /dev/null +++ b/web/src/components/message.js @@ -0,0 +1,20 @@ +import React from "react"; +import Markdown from "react-markdown"; +import remarkBreaks from "remark-breaks"; + +const Message = ({message}) => { + return ( +
+
+ {message.subject} + {(new Date(message.date)).toLocaleDateString()} +
+ {message.message} +
+                {JSON.stringify(message, null, 4)}
+            
+
+ ); +} + +export default Message; \ No newline at end of file diff --git a/web/src/index.css b/web/src/index.css new file mode 100644 index 0000000..66b008c --- /dev/null +++ b/web/src/index.css @@ -0,0 +1,6 @@ +@import url("@picocss/pico"); + +.msg-header { + display: flex; + justify-content: space-between; +} \ No newline at end of file diff --git a/web/src/index.html b/web/src/index.html new file mode 100644 index 0000000..8d59bf1 --- /dev/null +++ b/web/src/index.html @@ -0,0 +1,13 @@ + + + + + + IDEC Client + + + +
Для этого приложения необходим JavaScript
+ + + diff --git a/web/src/index.js b/web/src/index.js new file mode 100644 index 0000000..a43ed75 --- /dev/null +++ b/web/src/index.js @@ -0,0 +1,49 @@ +import React from "react"; +import { createRoot } from "react-dom/client"; +import { createHashRouter, RouterProvider } from "react-router-dom"; +import Root from "./root"; +import List from "./pages/list"; +import Echo from "./pages/echo"; + +const router = createHashRouter([ + { + path: "/", + element: , + // loader: rootLoader, + children: [ + { + path: "", + element: , + loader: () => { + return fetch("/api/list").then((x) => x.json()); + }, + }, + { + path: "e/:echoID", + element: , + loader: async ({ params }) => { + const echoData = await fetch( + `/api/e?e=${params.echoID}&limit=10` + ).then((x) => x.json()); + let echo = []; + if (echoData[params.echoID]) { + echo = echoData[params.echoID]; + } + const messages = await fetch( + `/api/m?e=${params.echoID}` + ).then((x) => x.json()); + + return { echo, messages: messages.reverse() }; + }, + }, + ], + }, +]); + +const root = createRoot(document.getElementById("app")); +root.render( + Загрузка} + /> +); diff --git a/web/src/pages/echo.js b/web/src/pages/echo.js new file mode 100644 index 0000000..6ba7e67 --- /dev/null +++ b/web/src/pages/echo.js @@ -0,0 +1,22 @@ +import React from "react"; +import { useLoaderData, useParams } from "react-router"; +import Message from "../components/message"; + +const Echo = () => { + const {echo, messages} = useLoaderData(); + const params = useParams(); + if (!echo) { + return (
Загрузка списка сообщений
) + } + return ( + <> +

{echo.name}

+

Сообщений: {echo.count}

+ {messages.map((message) => ( + + ))} + + ); +}; + +export default Echo; diff --git a/web/src/pages/list.js b/web/src/pages/list.js new file mode 100644 index 0000000..a806622 --- /dev/null +++ b/web/src/pages/list.js @@ -0,0 +1,21 @@ +import React from "react"; +import { useLoaderData } from "react-router"; +import { Link } from "react-router-dom"; + +const List = () => { + const list = useLoaderData(); + + return list + .sort((e1, e2) => e2.count - e1.count) + .map((e) => ( +
+
+ {e.name} +   [{e.count}] +
+ {e.description} +
+ )); +}; + +export default List; diff --git a/web/src/root.js b/web/src/root.js new file mode 100644 index 0000000..ce1104b --- /dev/null +++ b/web/src/root.js @@ -0,0 +1,10 @@ +import React from "react"; +import { Outlet } from "react-router"; + +const Root = () => ( +
+ +
+) + +export default Root; \ No newline at end of file