diff --git a/lib/phoenix/react.ex b/lib/phoenix/react.ex index f5fc048..13ad2d5 100644 --- a/lib/phoenix/react.ex +++ b/lib/phoenix/react.ex @@ -153,6 +153,7 @@ defmodule Phoenix.React do def init(_init_arg) do children = [ {Phoenix.React.Cache, []}, + {Phoenix.React.Runtime, []}, {Phoenix.React.Server, []} ] diff --git a/lib/phoenix/react/runtime.ex b/lib/phoenix/react/runtime.ex index 20ddddd..ef9eeca 100644 --- a/lib/phoenix/react/runtime.ex +++ b/lib/phoenix/react/runtime.ex @@ -4,11 +4,32 @@ defmodule Phoenix.React.Runtime do """ - defstruct [:component_base, :port, render_timeout: 300_000] + use DynamicSupervisor + + def start_link(init_arg) do + DynamicSupervisor.start_link(__MODULE__, init_arg, name: __MODULE__) + end + + def init(_init_arg) do + DynamicSupervisor.init(strategy: :one_for_one) + end + + def start_runtime(runtime, args) do + spec = {runtime, args} + DynamicSupervisor.start_child(__MODULE__, spec) + end + + def start_file_watcher(args) do + spec = {Phoenix.React.Runtime.FileWatcher, args} + DynamicSupervisor.start_child(__MODULE__, spec) + end + + defstruct [:component_base, :port, :server_js, render_timeout: 300_000] @type t :: %__MODULE__{ render_timeout: integer(), component_base: path(), + server_js: path(), port: port() } @@ -20,6 +41,8 @@ defmodule Phoenix.React.Runtime do @callback start([{:component_base, path()}, {:render_timeout, integer()}]) :: port() + @callback start_file_watcher(path()) :: :ok + @callback config() :: list() @callback render_to_string(component(), map(), GenServer.from(), t()) :: diff --git a/lib/phoenix/react/runtime/bun.ex b/lib/phoenix/react/runtime/bun.ex index a0192c3..6e7b0b8 100644 --- a/lib/phoenix/react/runtime/bun.ex +++ b/lib/phoenix/react/runtime/bun.ex @@ -21,25 +21,36 @@ defmodule Phoenix.React.Runtime.Bun do use Phoenix.React.Runtime def start_link(init_arg) do + IO.inspect(init_arg) GenServer.start_link(__MODULE__, init_arg, name: __MODULE__) end @impl true def init(component_base: component_base, render_timeout: render_timeout) do - {:ok, %Runtime{component_base: component_base, render_timeout: render_timeout}, - {:continue, :start_port}} + {:ok, + %Runtime{ + component_base: component_base, + render_timeout: render_timeout, + server_js: config()[:server_js] + }, {:continue, :start_port}} end @impl true @spec handle_continue(:start_port, Phoenix.React.Runtime.t()) :: {:noreply, Phoenix.React.Runtime.t()} def handle_continue(:start_port, %Runtime{component_base: component_base} = state) do + if config()[:env] == :dev do + start_file_watcher(component_base) + end + port = start(component_base: component_base) Logger.debug( "Bun.Server started on port: #{inspect(port)} and OS pid: #{get_port_os_pid(port)}" ) + Phoenix.React.Server.set_runtime_process(self()) + {:noreply, %Runtime{state | port: port}} end @@ -64,8 +75,12 @@ defmodule Phoenix.React.Runtime.Bun do def start(component_base: component_base) do cd = config()[:cd] cmd = config()[:cmd] - args = ["--port", Integer.to_string(config()[:port]), config()[:server_js]] - bun_env = if(config()[:env] == :dev, do: "development", else: "production") + bun_port = Integer.to_string(config()[:port]) + args = ["--port", bun_port, config()[:server_js]] + + is_dev = config()[:env] == :dev + + bun_env = if(is_dev, do: "development", else: "production") args = if config()[:env] == :dev do @@ -75,6 +90,9 @@ defmodule Phoenix.React.Runtime.Bun do end env = [ + {~c"no_proxy", ~c"10.*,127.*,192.168.*,172.16.0.0/12,localhost,127.0.0.1,::1"}, + {~c"PORT", ~c"#{bun_port}"}, + {~c"BUN_PORT", ~c"#{bun_port}"}, {~c"BUN_ENV", ~c"#{bun_env}"}, {~c"COMPONENT_BASE", ~c"#{component_base}"} ] @@ -96,6 +114,36 @@ defmodule Phoenix.React.Runtime.Bun do end @impl true + def start_file_watcher(component_base) do + Logger.debug("Building server.js bundle") + + Mix.Task.run("phx.react.bun.bundle", [ + "--component-base", + component_base, + "--output", + config()[:server_js] + ]) + + Logger.debug("Starting file watcher") + Runtime.start_file_watcher(ref: self(), path: component_base) + end + + @impl true + def handle_info({:component_base_changed, path}, state) do + Logger.debug("component_base changed: #{path}") + + Task.async(fn -> + Mix.Task.run("phx.react.bun.bundle", [ + "--component-base", + state.component_base, + "--output", + config()[:server_js] + ]) + end) + + {:noreply, state} + end + def handle_info({_port, {:data, msg}}, state) do Logger.debug(msg) {:noreply, state} diff --git a/lib/phoenix/react/runtime/file_watcher.ex b/lib/phoenix/react/runtime/file_watcher.ex new file mode 100644 index 0000000..65cafed --- /dev/null +++ b/lib/phoenix/react/runtime/file_watcher.ex @@ -0,0 +1,44 @@ +defmodule Phoenix.React.Runtime.FileWatcher do + use GenServer + + def start_link(args) do + GenServer.start_link(__MODULE__, args, name: __MODULE__) + end + + def init(args) do + path = Keyword.fetch!(args, :path) + {:ok, watcher_pid} = FileSystem.start_link(dirs: [path]) + FileSystem.subscribe(watcher_pid) + IO.puts("Watching #{path} for changes...") + + {:ok, + args + |> Keyword.put(:watcher_pid, watcher_pid) + |> Keyword.put(:update_time, System.os_time(:second))} + end + + def handle_info({:file_event, _watcher_pid, {path, [:modified, :closed]}}, state) do + IO.puts("File changed: #{path} - Events: [:modified, :closed]") + send(self(), {:throttle_update, path}) + {:noreply, state} + end + + def handle_info({:file_event, _watcher_pid, {_path, _events}}, state) do + # ref = Keyword.fetch!(state, :ref) + # IO.puts("File changed: #{path} - Events: #{inspect(events)}") + # send(self(), {:throttle_update, path, events}) + {:noreply, state} + end + + def handle_info({:throttle_update, path}, state) do + update_time = Keyword.fetch!(state, :update_time) + now = System.os_time(:second) + + if now > update_time do + Process.send_after(state[:ref], {:component_base_changed, path}, 3_000) + {:noreply, state |> Keyword.put(:update_time, now + 3)} + else + {:noreply, state} + end + end +end diff --git a/lib/phoenix/react/server.ex b/lib/phoenix/react/server.ex index e7ad378..92b8709 100644 --- a/lib/phoenix/react/server.ex +++ b/lib/phoenix/react/server.ex @@ -8,12 +8,17 @@ defmodule Phoenix.React.Server do require Logger alias Phoenix.React.Cache + alias Phoenix.React.Runtime use GenServer @type second() :: integer() @type millisecond() :: integer() + def set_runtime_process(pid) do + GenServer.cast(__MODULE__, {:set_runtime_process, pid}) + end + @doc """ Return the configuration of the React Render Server from `Application.get_env(:phoenix_react_server, Phoenix.React)` """ @@ -46,14 +51,21 @@ defmodule Phoenix.React.Server do runtime = cfg[:runtime] component_base = cfg[:component_base] render_timeout = cfg[:render_timeout] + args = [component_base: component_base, render_timeout: render_timeout] - {:ok, runtiem_process} = - GenServer.start_link(runtime, - component_base: component_base, - render_timeout: render_timeout - ) + Runtime.start_runtime(runtime, args) - {:ok, %{runtiem_process: runtiem_process}} + {:ok, + %{ + runtime: runtime, + component_base: component_base, + render_timeout: render_timeout + }} + end + + @impl true + def handle_cast({:set_runtime_process, pid}, state) do + {:noreply, Map.put(state, :runtiem_process, pid)} end @impl true diff --git a/mix.exs b/mix.exs index c9f51aa..db53021 100644 --- a/mix.exs +++ b/mix.exs @@ -2,7 +2,7 @@ defmodule Phoenix.React.Mixfile do use Mix.Project @source_url "https://github.com/gsmlg-dev/phoenix-react.git" - @version "0.3.0" + @version "0.4.0" def project do [ @@ -47,7 +47,8 @@ defmodule Phoenix.React.Mixfile do {:httpoison, "~> 2.0"}, {:phoenix_html, "~> 4.1"}, {:phoenix_live_view, "~> 1.0"}, - {:ex_doc, ">= 0.0.0", only: :prod, runtime: false} + {:file_system, "~> 1.0"}, + {:ex_doc, ">= 0.0.0", only: :doc, runtime: false} ] end diff --git a/react_demo/assets/component/live_form.js b/react_demo/assets/component/live_form.js new file mode 100644 index 0000000..229f319 --- /dev/null +++ b/react_demo/assets/component/live_form.js @@ -0,0 +1,29 @@ +import React from "react"; +import MDEditor from '@uiw/react-md-editor'; +import "@uiw/react-md-editor/markdown-editor.css"; +import "@uiw/react-markdown-preview/markdown.css"; + +export function Component({ data = "", setData }) { + const { content } = data; + + return ( +
+

Live Form

+
+ setData({ content: value })} + /> +
+
+ + +
+ ); +} diff --git a/react_demo/assets/css/app.css b/react_demo/assets/css/app.css index 0e86075..66b416d 100644 --- a/react_demo/assets/css/app.css +++ b/react_demo/assets/css/app.css @@ -4,6 +4,9 @@ @import "./github-markdown.css"; +@import "@uiw/react-md-editor/markdown-editor.css"; +@import "@uiw/react-markdown-preview/markdown.css"; + @layer components { .table-primary-border { th, td { diff --git a/react_demo/assets/js/app.js b/react_demo/assets/js/app.js index 0619d50..050ec12 100644 --- a/react_demo/assets/js/app.js +++ b/react_demo/assets/js/app.js @@ -3,10 +3,13 @@ import "phoenix_html" import {Socket} from "phoenix" import {LiveSocket} from "phoenix_live_view" +import { hooks } from './hooks'; + let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content") let liveSocket = new LiveSocket("/live", Socket, { longPollFallbackMs: 2500, - params: {_csrf_token: csrfToken} + params: {_csrf_token: csrfToken}, + hooks, }) // connect if there are any LiveViews on the page diff --git a/react_demo/assets/js/hooks.js b/react_demo/assets/js/hooks.js new file mode 100644 index 0000000..5da8ce5 --- /dev/null +++ b/react_demo/assets/js/hooks.js @@ -0,0 +1,83 @@ +import * as React from 'react'; +import { useSyncExternalStore } from 'react'; +import { hydrateRoot } from 'react-dom/client'; + +import { Component as LiveForm } from '../component/live_form'; + +const LiveFormHook = { + LiveFormHook: { + mounted() { + const formState = new FormState(); + + formState.setData = (data) => { + this.pushEvent("form:input", data); + }; + + function LiveViewForm(props) { + const data = useSyncExternalStore(formState.subscribe, formState.getSnapshot, formState.getServerSnapshot); + return ; + } + + this.pushEvent("form:init", {}, (reply, ref) => { + formState.reset(reply); + console.log("form:init", reply); + this.reactRoot = hydrateRoot(this.el, ); + }); + this.handleEvent("form:update", (data) => { + console.log("form:update", data); + formState.assign(data); + }); + }, + destroyed() { + this.reactRoot?.unmount(); + }, + } +} + +const hooks = { + ...LiveFormHook, +}; + +export { hooks }; + +class FormState { + constructor() { + this.subscribers = []; + this.data = {}; + this.subscribe = this.subscribe.bind(this); + this.getSnapshot = this.getSnapshot.bind(this); + this.getServerSnapshot = this.getServerSnapshot.bind(this); + } + + assign(data) { + this.data = { + ...this.data, + ...data, + }; + console.log('assign', data); + this.subscribers.forEach(cb => { + console.log('assign cb', cb, this.data); + cb(this.data); + }); + } + + reset(data) { + this.data = data; + this.subscribers.forEach(cb => cb(data)); + } + + getSnapshot() { + return this.data; + } + + subscribe(callback) { + this.subscribers.push(callback); + return () => { + this.subscribers = this.subscribers.filter(s => s !== callback); + }; + } + + getServerSnapshot() { + return this.data; + } +} diff --git a/react_demo/bun.lock b/react_demo/bun.lock index c2ebcc3..91c06eb 100644 --- a/react_demo/bun.lock +++ b/react_demo/bun.lock @@ -11,6 +11,8 @@ "@mui/x-charts": "^7.26.0", "@nivo/line": "^0.88.0", "@tailwindcss/typography": "^0.5.16", + "@uiw/react-markdown-preview": "^5.1.3", + "@uiw/react-md-editor": "^4.0.5", "@visx/xychart": "^3.12.0", "d3": "^7.9.0", "daisyui": "^4.12.23", @@ -218,6 +220,8 @@ "@types/parse-json": ["@types/parse-json@4.0.2", "", {}, "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw=="], + "@types/prismjs": ["@types/prismjs@1.26.5", "https://nexus.gsmlg.net/repository/npm-org/@types/prismjs/-/prismjs-1.26.5.tgz", {}, "sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ=="], + "@types/prop-types": ["@types/prop-types@15.7.14", "", {}, "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ=="], "@types/react": ["@types/react@19.0.8", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-9P/o1IGdfmQxrujGbIMDyYaaCykhLKc0NGCtYcECNUr9UAaDe4gwvV9bR6tvd5Br1SG0j+PBpbKr2UYY8CwqSw=="], @@ -228,6 +232,12 @@ "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], + "@uiw/copy-to-clipboard": ["@uiw/copy-to-clipboard@1.0.17", "https://nexus.gsmlg.net/repository/npm-org/@uiw/copy-to-clipboard/-/copy-to-clipboard-1.0.17.tgz", {}, "sha512-O2GUHV90Iw2VrSLVLK0OmNIMdZ5fgEg4NhvtwINsX+eZ/Wf6DWD0TdsK9xwV7dNRnK/UI2mQtl0a2/kRgm1m1A=="], + + "@uiw/react-markdown-preview": ["@uiw/react-markdown-preview@5.1.3", "https://nexus.gsmlg.net/repository/npm-org/@uiw/react-markdown-preview/-/react-markdown-preview-5.1.3.tgz", { "dependencies": { "@babel/runtime": "^7.17.2", "@uiw/copy-to-clipboard": "~1.0.12", "react-markdown": "~9.0.1", "rehype-attr": "~3.0.1", "rehype-autolink-headings": "~7.1.0", "rehype-ignore": "^2.0.0", "rehype-prism-plus": "2.0.0", "rehype-raw": "^7.0.0", "rehype-rewrite": "~4.0.0", "rehype-slug": "~6.0.0", "remark-gfm": "~4.0.0", "remark-github-blockquote-alert": "^1.0.0", "unist-util-visit": "^5.0.0" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-jV02wO4XHWFk54kz7sLqOkdPgJLttSfKLyen47XgjcyGgQXU2I4WJBygmdpV2AT9m/MiQ8qrN1Y+E5Syv9ZDpw=="], + + "@uiw/react-md-editor": ["@uiw/react-md-editor@4.0.5", "https://nexus.gsmlg.net/repository/npm-org/@uiw/react-md-editor/-/react-md-editor-4.0.5.tgz", { "dependencies": { "@babel/runtime": "^7.14.6", "@uiw/react-markdown-preview": "^5.0.6", "rehype": "~13.0.0", "rehype-prism-plus": "~2.0.0" }, "peerDependencies": { "react": ">=16.8.0", "react-dom": ">=16.8.0" } }, "sha512-x+S7ZMz1B+KwVODOZk663H2gdOZhcFsrPUF8V69K4L2BbTZ3A4sBCw/uTrBE9dHH0gajAzRAYbAYCHsUQaTcyA=="], + "@ungap/structured-clone": ["@ungap/structured-clone@1.2.1", "", {}, "sha512-fEzPV3hSkSMltkw152tJKNARhOupqbH96MZWyRjNaYZOMIzbrTeQDG+MTc6Mr2pgzFQzFxAfmhGDNP5QK++2ZA=="], "@visx/annotation": ["@visx/annotation@3.12.0", "", { "dependencies": { "@types/react": "*", "@visx/drag": "3.12.0", "@visx/group": "3.12.0", "@visx/text": "3.12.0", "classnames": "^2.3.1", "prop-types": "^15.5.10", "react-use-measure": "^2.0.4" }, "peerDependencies": { "react": "^16.0.0-0 || ^17.0.0-0 || ^18.0.0-0" } }, "sha512-ZH6Y4jfrb47iEUV9O2itU9TATE5IPzhs5qvP6J7vmv26qkqwDcuE7xN3S3l9R70WjyEKGbpO8js4EijA3FJWkA=="], @@ -274,6 +284,10 @@ "balanced-match": ["balanced-match@0.4.2", "", {}, "sha512-STw03mQKnGUYtoNjmowo4F2cRmIIxYEGiMsjjwla/u5P1lxadj/05WkNaFjNiKTgJkj8KiXbgAiRTmcQRwQNtg=="], + "bcp-47-match": ["bcp-47-match@2.0.3", "https://nexus.gsmlg.net/repository/npm-org/bcp-47-match/-/bcp-47-match-2.0.3.tgz", {}, "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ=="], + + "boolbase": ["boolbase@1.0.0", "https://nexus.gsmlg.net/repository/npm-org/boolbase/-/boolbase-1.0.0.tgz", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], + "buffer-from": ["buffer-from@0.1.2", "", {}, "sha512-RiWIenusJsmI2KcvqQABB83tLxCByE3upSP8QU3rJDMVFGPWLvPQJt/O1Su9moRWeH7d+Q2HYb68f6+v+tw2vg=="], "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], @@ -304,6 +318,8 @@ "cosmiconfig": ["cosmiconfig@7.1.0", "", { "dependencies": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", "parse-json": "^5.0.0", "path-type": "^4.0.0", "yaml": "^1.10.0" } }, "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA=="], + "css-selector-parser": ["css-selector-parser@3.0.5", "https://nexus.gsmlg.net/repository/npm-org/css-selector-parser/-/css-selector-parser-3.0.5.tgz", {}, "sha512-3itoDFbKUNx1eKmVpYMFyqKX04Ww9osZ+dLgrk6GEv6KMVeXUhUnp4I5X+evw+u3ZxVU6RFXSSRxlTeMh8bA+g=="], + "css-selector-tokenizer": ["css-selector-tokenizer@0.8.0", "", { "dependencies": { "cssesc": "^3.0.0", "fastparse": "^1.1.2" } }, "sha512-Jd6Ig3/pe62/qe5SBPTN8h8LeUg/pT4lLgtavPf7updwwHpvFzxvOQBHYj2LZDMjUnBzgvIUSjRcf6oT5HzHFg=="], "cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="], @@ -394,10 +410,14 @@ "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], + "direction": ["direction@2.0.1", "https://nexus.gsmlg.net/repository/npm-org/direction/-/direction-2.0.1.tgz", { "bin": { "direction": "cli.js" } }, "sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA=="], + "dom-helpers": ["dom-helpers@5.2.1", "", { "dependencies": { "@babel/runtime": "^7.8.7", "csstype": "^3.0.2" } }, "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA=="], "duplexer2": ["duplexer2@0.1.4", "", { "dependencies": { "readable-stream": "^2.0.2" } }, "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA=="], + "entities": ["entities@4.5.0", "https://nexus.gsmlg.net/repository/npm-org/entities/-/entities-4.5.0.tgz", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + "error-ex": ["error-ex@1.3.2", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g=="], "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], @@ -420,14 +440,36 @@ "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + "github-slugger": ["github-slugger@2.0.0", "https://nexus.gsmlg.net/repository/npm-org/github-slugger/-/github-slugger-2.0.0.tgz", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="], + "globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="], "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + "hast-util-from-html": ["hast-util-from-html@2.0.3", "https://nexus.gsmlg.net/repository/npm-org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", { "dependencies": { "@types/hast": "^3.0.0", "devlop": "^1.1.0", "hast-util-from-parse5": "^8.0.0", "parse5": "^7.0.0", "vfile": "^6.0.0", "vfile-message": "^4.0.0" } }, "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw=="], + + "hast-util-from-parse5": ["hast-util-from-parse5@8.0.2", "https://nexus.gsmlg.net/repository/npm-org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.2.tgz", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "devlop": "^1.0.0", "hastscript": "^9.0.0", "property-information": "^6.0.0", "vfile": "^6.0.0", "vfile-location": "^5.0.0", "web-namespaces": "^2.0.0" } }, "sha512-SfMzfdAi/zAoZ1KkFEyyeXBn7u/ShQrfd675ZEE9M3qj+PMFX05xubzRyF76CCSJu8au9jgVxDV1+okFvgZU4A=="], + + "hast-util-has-property": ["hast-util-has-property@3.0.0", "https://nexus.gsmlg.net/repository/npm-org/hast-util-has-property/-/hast-util-has-property-3.0.0.tgz", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA=="], + + "hast-util-heading-rank": ["hast-util-heading-rank@3.0.0", "https://nexus.gsmlg.net/repository/npm-org/hast-util-heading-rank/-/hast-util-heading-rank-3.0.0.tgz", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA=="], + + "hast-util-is-element": ["hast-util-is-element@3.0.0", "https://nexus.gsmlg.net/repository/npm-org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g=="], + "hast-util-parse-selector": ["hast-util-parse-selector@2.2.5", "", {}, "sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ=="], + "hast-util-raw": ["hast-util-raw@9.1.0", "https://nexus.gsmlg.net/repository/npm-org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "@ungap/structured-clone": "^1.0.0", "hast-util-from-parse5": "^8.0.0", "hast-util-to-parse5": "^8.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "parse5": "^7.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0", "web-namespaces": "^2.0.0", "zwitch": "^2.0.0" } }, "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw=="], + + "hast-util-select": ["hast-util-select@6.0.3", "https://nexus.gsmlg.net/repository/npm-org/hast-util-select/-/hast-util-select-6.0.3.tgz", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "bcp-47-match": "^2.0.0", "comma-separated-tokens": "^2.0.0", "css-selector-parser": "^3.0.0", "devlop": "^1.0.0", "direction": "^2.0.0", "hast-util-has-property": "^3.0.0", "hast-util-to-string": "^3.0.0", "hast-util-whitespace": "^3.0.0", "nth-check": "^2.0.0", "property-information": "^6.0.0", "space-separated-tokens": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-OVRQlQ1XuuLP8aFVLYmC2atrfWHS5UD3shonxpnyrjcCkwtvmt/+N6kYJdcY4mkMJhxp4kj2EFIxQ9kvkkt/eQ=="], + + "hast-util-to-html": ["hast-util-to-html@9.0.4", "https://nexus.gsmlg.net/repository/npm-org/hast-util-to-html/-/hast-util-to-html-9.0.4.tgz", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^6.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-wxQzXtdbhiwGAUKrnQJXlOPmHnEehzphwkK7aluUPQ+lEc1xefC8pblMgpp2w5ldBTEfveRIrADcrhGIWrlTDA=="], + "hast-util-to-jsx-runtime": ["hast-util-to-jsx-runtime@2.3.2", "", { "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" } }, "sha512-1ngXYb+V9UT5h+PxNRa1O1FYguZK/XL+gkeqvp7EdHlB9oHUG0eYRo/vY5inBdcqo3RkPMC58/H94HvkbfGdyg=="], + "hast-util-to-parse5": ["hast-util-to-parse5@8.0.0", "https://nexus.gsmlg.net/repository/npm-org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "property-information": "^6.0.0", "space-separated-tokens": "^2.0.0", "web-namespaces": "^2.0.0", "zwitch": "^2.0.0" } }, "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw=="], + + "hast-util-to-string": ["hast-util-to-string@3.0.1", "https://nexus.gsmlg.net/repository/npm-org/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A=="], + "hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="], "hastscript": ["hastscript@6.0.0", "", { "dependencies": { "@types/hast": "^2.0.0", "comma-separated-tokens": "^1.0.0", "hast-util-parse-selector": "^2.0.0", "property-information": "^5.0.0", "space-separated-tokens": "^1.0.0" } }, "sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w=="], @@ -442,6 +484,8 @@ "html-url-attributes": ["html-url-attributes@3.0.1", "", {}, "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ=="], + "html-void-elements": ["html-void-elements@3.0.0", "https://nexus.gsmlg.net/repository/npm-org/html-void-elements/-/html-void-elements-3.0.0.tgz", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="], + "iconv-lite": ["iconv-lite@0.6.3", "https://nexus.gsmlg.net/repository/npm-org/iconv-lite/-/iconv-lite-0.6.3.tgz", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], @@ -590,6 +634,8 @@ "nanoid": ["nanoid@3.3.8", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w=="], + "nth-check": ["nth-check@2.1.1", "https://nexus.gsmlg.net/repository/npm-org/nth-check/-/nth-check-2.1.1.tgz", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="], + "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], "object-keys": ["object-keys@0.4.0", "", {}, "sha512-ncrLw+X55z7bkl5PnUvHwFK9FcGuFYo9gtjws2XtSzL+aZ8tm830P60WJ0dSmFVaSalWieW5MD7kEdnXda9yJw=="], @@ -600,6 +646,10 @@ "parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="], + "parse-numeric-range": ["parse-numeric-range@1.3.0", "https://nexus.gsmlg.net/repository/npm-org/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz", {}, "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ=="], + + "parse5": ["parse5@7.2.1", "https://nexus.gsmlg.net/repository/npm-org/parse5/-/parse5-7.2.1.tgz", { "dependencies": { "entities": "^4.5.0" } }, "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ=="], + "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="], "path-type": ["path-type@4.0.0", "", {}, "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw=="], @@ -650,8 +700,30 @@ "regenerator-runtime": ["regenerator-runtime@0.14.1", "", {}, "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw=="], + "rehype": ["rehype@13.0.2", "https://nexus.gsmlg.net/repository/npm-org/rehype/-/rehype-13.0.2.tgz", { "dependencies": { "@types/hast": "^3.0.0", "rehype-parse": "^9.0.0", "rehype-stringify": "^10.0.0", "unified": "^11.0.0" } }, "sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A=="], + + "rehype-attr": ["rehype-attr@3.0.3", "https://nexus.gsmlg.net/repository/npm-org/rehype-attr/-/rehype-attr-3.0.3.tgz", { "dependencies": { "unified": "~11.0.0", "unist-util-visit": "~5.0.0" } }, "sha512-Up50Xfra8tyxnkJdCzLBIBtxOcB2M1xdeKe1324U06RAvSjYm7ULSeoM+b/nYPQPVd7jsXJ9+39IG1WAJPXONw=="], + + "rehype-autolink-headings": ["rehype-autolink-headings@7.1.0", "https://nexus.gsmlg.net/repository/npm-org/rehype-autolink-headings/-/rehype-autolink-headings-7.1.0.tgz", { "dependencies": { "@types/hast": "^3.0.0", "@ungap/structured-clone": "^1.0.0", "hast-util-heading-rank": "^3.0.0", "hast-util-is-element": "^3.0.0", "unified": "^11.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-rItO/pSdvnvsP4QRB1pmPiNHUskikqtPojZKJPPPAVx9Hj8i8TwMBhofrrAYRhYOOBZH9tgmG5lPqDLuIWPWmw=="], + + "rehype-ignore": ["rehype-ignore@2.0.2", "https://nexus.gsmlg.net/repository/npm-org/rehype-ignore/-/rehype-ignore-2.0.2.tgz", { "dependencies": { "hast-util-select": "^6.0.0", "unified": "^11.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-BpAT/3lU9DMJ2siYVD/dSR0A/zQgD6Fb+fxkJd4j+wDVy6TYbYpK+FZqu8eM9EuNKGvi4BJR7XTZ/+zF02Dq8w=="], + + "rehype-parse": ["rehype-parse@9.0.1", "https://nexus.gsmlg.net/repository/npm-org/rehype-parse/-/rehype-parse-9.0.1.tgz", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-from-html": "^2.0.0", "unified": "^11.0.0" } }, "sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag=="], + + "rehype-prism-plus": ["rehype-prism-plus@2.0.0", "https://nexus.gsmlg.net/repository/npm-org/rehype-prism-plus/-/rehype-prism-plus-2.0.0.tgz", { "dependencies": { "hast-util-to-string": "^3.0.0", "parse-numeric-range": "^1.3.0", "refractor": "^4.8.0", "rehype-parse": "^9.0.0", "unist-util-filter": "^5.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-FeM/9V2N7EvDZVdR2dqhAzlw5YI49m9Tgn7ZrYJeYHIahM6gcXpH0K1y2gNnKanZCydOMluJvX2cB9z3lhY8XQ=="], + + "rehype-raw": ["rehype-raw@7.0.0", "https://nexus.gsmlg.net/repository/npm-org/rehype-raw/-/rehype-raw-7.0.0.tgz", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-raw": "^9.0.0", "vfile": "^6.0.0" } }, "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww=="], + + "rehype-rewrite": ["rehype-rewrite@4.0.2", "https://nexus.gsmlg.net/repository/npm-org/rehype-rewrite/-/rehype-rewrite-4.0.2.tgz", { "dependencies": { "hast-util-select": "^6.0.0", "unified": "^11.0.3", "unist-util-visit": "^5.0.0" } }, "sha512-rjLJ3z6fIV11phwCqHp/KRo8xuUCO8o9bFJCNw5o6O2wlLk6g8r323aRswdGBQwfXPFYeSuZdAjp4tzo6RGqEg=="], + + "rehype-slug": ["rehype-slug@6.0.0", "https://nexus.gsmlg.net/repository/npm-org/rehype-slug/-/rehype-slug-6.0.0.tgz", { "dependencies": { "@types/hast": "^3.0.0", "github-slugger": "^2.0.0", "hast-util-heading-rank": "^3.0.0", "hast-util-to-string": "^3.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A=="], + + "rehype-stringify": ["rehype-stringify@10.0.1", "https://nexus.gsmlg.net/repository/npm-org/rehype-stringify/-/rehype-stringify-10.0.1.tgz", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-to-html": "^9.0.0", "unified": "^11.0.0" } }, "sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA=="], + "remark-gfm": ["remark-gfm@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA=="], + "remark-github-blockquote-alert": ["remark-github-blockquote-alert@1.3.0", "https://nexus.gsmlg.net/repository/npm-org/remark-github-blockquote-alert/-/remark-github-blockquote-alert-1.3.0.tgz", { "dependencies": { "unist-util-visit": "^5.0.0" } }, "sha512-cwkBA4x+VH4J2VAMzhbmSeAmK5tBd5iwesgSUUQuRtuQ48XQm6sXXNLY9PR7ohZmZiqMeoDMUGCTur5zwR4lTQ=="], + "remark-parse": ["remark-parse@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="], "remark-rehype": ["remark-rehype@11.1.1", "", { "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" } }, "sha512-g/osARvjkBXb6Wo0XvAeXQohVta8i84ACbenPpoSsxTOQH/Ae0/RGP4WZgnMH5pMLpsj4FG7OHmcIcXxpza8eQ=="], @@ -706,6 +778,8 @@ "unified": ["unified@11.0.5", "", { "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" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="], + "unist-util-filter": ["unist-util-filter@5.0.1", "https://nexus.gsmlg.net/repository/npm-org/unist-util-filter/-/unist-util-filter-5.0.1.tgz", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-pHx7D4Zt6+TsfwylH9+lYhBhzyhEnCXs/lbq/Hstxno5z4gVdyc2WEW0asfjGKPyG4pEKrnBv5hdkO6+aRnQJw=="], + "unist-util-is": ["unist-util-is@6.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw=="], "unist-util-position": ["unist-util-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA=="], @@ -720,10 +794,14 @@ "vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="], + "vfile-location": ["vfile-location@5.0.3", "https://nexus.gsmlg.net/repository/npm-org/vfile-location/-/vfile-location-5.0.3.tgz", { "dependencies": { "@types/unist": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg=="], + "vfile-message": ["vfile-message@4.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw=="], "victory-vendor": ["victory-vendor@36.9.2", "", { "dependencies": { "@types/d3-array": "^3.0.3", "@types/d3-ease": "^3.0.0", "@types/d3-interpolate": "^3.0.1", "@types/d3-scale": "^4.0.2", "@types/d3-shape": "^3.1.0", "@types/d3-time": "^3.0.0", "@types/d3-timer": "^3.0.0", "d3-array": "^3.1.6", "d3-ease": "^3.0.1", "d3-interpolate": "^3.0.1", "d3-scale": "^4.0.2", "d3-shape": "^3.1.0", "d3-time": "^3.0.0", "d3-timer": "^3.0.1" } }, "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ=="], + "web-namespaces": ["web-namespaces@2.0.1", "https://nexus.gsmlg.net/repository/npm-org/web-namespaces/-/web-namespaces-2.0.1.tgz", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="], + "xtend": ["xtend@2.1.2", "", { "dependencies": { "object-keys": "~0.4.0" } }, "sha512-vMNKzr2rHP9Dp/e1NQFnLQlwlhp9L/LfvnsVdHxN1f+uggyVI3i08uD14GPvCToPkdsRfyPqIyYGmIk58V98ZQ=="], "yaml": ["yaml@1.10.2", "", {}, "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="], @@ -782,6 +860,8 @@ "duplexer2/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], + "hast-util-from-parse5/hastscript": ["hastscript@9.0.0", "https://nexus.gsmlg.net/repository/npm-org/hastscript/-/hastscript-9.0.0.tgz", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^6.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-jzaLBGavEDKHrc5EfFImKN7nZKKBdSLIdGvCwDZ9TfzbF2ffXiov8CKE445L2Z1Ek2t/m4SKQ2j6Ipv7NyUolw=="], + "hastscript/@types/hast": ["@types/hast@2.3.10", "", { "dependencies": { "@types/unist": "^2" } }, "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw=="], "hastscript/comma-separated-tokens": ["comma-separated-tokens@1.0.8", "", {}, "sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw=="], @@ -804,6 +884,8 @@ "refractor/prismjs": ["prismjs@1.27.0", "", {}, "sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA=="], + "rehype-prism-plus/refractor": ["refractor@4.8.1", "https://nexus.gsmlg.net/repository/npm-org/refractor/-/refractor-4.8.1.tgz", { "dependencies": { "@types/hast": "^2.0.0", "@types/prismjs": "^1.0.0", "hastscript": "^7.0.0", "parse-entities": "^4.0.0" } }, "sha512-/fk5sI0iTgFYlmVGYVew90AoYnNMP6pooClx/XKqyeeCQXrL0Kvgn8V0VEht5ccdljbzzF1i3Q213gcntkRExg=="], + "stringify-entities/character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="], "@nivo/axes/d3-time-format/d3-time": ["d3-time@1.1.0", "", {}, "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA=="], @@ -820,6 +902,8 @@ "duplexer2/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], + "hast-util-from-parse5/hastscript/hast-util-parse-selector": ["hast-util-parse-selector@4.0.0", "https://nexus.gsmlg.net/repository/npm-org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A=="], + "hastscript/@types/hast/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], "hastscript/property-information/xtend": ["xtend@4.0.2", "", {}, "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="], @@ -836,6 +920,30 @@ "mdast-util-mdx-jsx/parse-entities/is-hexadecimal": ["is-hexadecimal@2.0.1", "", {}, "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="], + "rehype-prism-plus/refractor/@types/hast": ["@types/hast@2.3.10", "", { "dependencies": { "@types/unist": "^2" } }, "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw=="], + + "rehype-prism-plus/refractor/hastscript": ["hastscript@7.2.0", "https://nexus.gsmlg.net/repository/npm-org/hastscript/-/hastscript-7.2.0.tgz", { "dependencies": { "@types/hast": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^3.0.0", "property-information": "^6.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw=="], + + "rehype-prism-plus/refractor/parse-entities": ["parse-entities@4.0.2", "", { "dependencies": { "@types/unist": "^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" } }, "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw=="], + "mdast-util-mdx-jsx/parse-entities/is-alphanumerical/is-alphabetical": ["is-alphabetical@2.0.1", "", {}, "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="], + + "rehype-prism-plus/refractor/@types/hast/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], + + "rehype-prism-plus/refractor/hastscript/hast-util-parse-selector": ["hast-util-parse-selector@3.1.1", "https://nexus.gsmlg.net/repository/npm-org/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz", { "dependencies": { "@types/hast": "^2.0.0" } }, "sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA=="], + + "rehype-prism-plus/refractor/parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], + + "rehype-prism-plus/refractor/parse-entities/character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="], + + "rehype-prism-plus/refractor/parse-entities/character-reference-invalid": ["character-reference-invalid@2.0.1", "", {}, "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw=="], + + "rehype-prism-plus/refractor/parse-entities/is-alphanumerical": ["is-alphanumerical@2.0.1", "", { "dependencies": { "is-alphabetical": "^2.0.0", "is-decimal": "^2.0.0" } }, "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw=="], + + "rehype-prism-plus/refractor/parse-entities/is-decimal": ["is-decimal@2.0.1", "", {}, "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A=="], + + "rehype-prism-plus/refractor/parse-entities/is-hexadecimal": ["is-hexadecimal@2.0.1", "", {}, "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg=="], + + "rehype-prism-plus/refractor/parse-entities/is-alphanumerical/is-alphabetical": ["is-alphabetical@2.0.1", "", {}, "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ=="], } } diff --git a/react_demo/config/dev.exs b/react_demo/config/dev.exs index 9fecbcc..bdc1548 100644 --- a/react_demo/config/dev.exs +++ b/react_demo/config/dev.exs @@ -38,4 +38,5 @@ config :phoenix_react_server, Phoenix.React, config :phoenix_react_server, Phoenix.React.Runtime.Bun, cd: Path.expand("..", __DIR__), cmd: System.find_executable("bun"), + server_js: Path.expand("../priv/react/dev/server.js", __DIR__), env: :dev diff --git a/react_demo/lib/react_demo/application.ex b/react_demo/lib/react_demo/application.ex index ec9a791..bf4f4f2 100644 --- a/react_demo/lib/react_demo/application.ex +++ b/react_demo/lib/react_demo/application.ex @@ -12,6 +12,7 @@ defmodule ReactDemo.Application do {DNSCluster, query: Application.get_env(:react_demo, :dns_cluster_query) || :ignore}, {Phoenix.PubSub, name: ReactDemo.PubSub}, SystemStats, + FormProcess, Phoenix.React, ReactDemoWeb.Endpoint ] diff --git a/react_demo/lib/react_demo/form_process.ex b/react_demo/lib/react_demo/form_process.ex new file mode 100644 index 0000000..b285bfc --- /dev/null +++ b/react_demo/lib/react_demo/form_process.ex @@ -0,0 +1,41 @@ + +defmodule FormProcess do + use GenServer + + @init_form_data %{ + "content" => """ + # Hello, this is Phoenix.React! + Phoenix.React is a library that allows you to render React components in your Phoenix application. + + You can live input and preview the markdown content in this form. + + What you see is what you get! + + Form data is set in server, so no need to worry about losing data when you refresh the page. + """ + } + + def get_form_data do + GenServer.call(__MODULE__, :get_form_data) + end + + def set_form_data(form_data) when is_map(form_data) do + GenServer.cast(__MODULE__, {:set_form_data, form_data}) + end + + def start_link(_) do + GenServer.start_link(__MODULE__, @init_form_data, name: __MODULE__) + end + + def init(state) do + {:ok, state} + end + + def handle_call(:get_form_data, _from, state) do + {:reply, state, state} + end + + def handle_cast({:set_form_data, form_data}, state) do + {:noreply, state |> Map.merge(form_data)} + end +end diff --git a/react_demo/lib/react_demo_web/components/layouts/app.html.heex b/react_demo/lib/react_demo_web/components/layouts/app.html.heex index 4027461..83494e9 100644 --- a/react_demo/lib/react_demo_web/components/layouts/app.html.heex +++ b/react_demo/lib/react_demo_web/components/layouts/app.html.heex @@ -5,6 +5,7 @@