applesauce-react 0.0.0-next-20241103143210

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2024 hzrd149
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # applesauce-react
2
+
3
+ React hooks for applesauce
4
+
5
+ ## Example
6
+
7
+ ```jsx
8
+ import { EventStore, QueryStore, Queries } from "applesauce-core";
9
+ import { QueryStoreProvider, useStoreQuery } from "applesauce-react";
10
+
11
+ const events = new EventStore();
12
+ const store = new QueryStore(events);
13
+
14
+ function UserName({ pubkey }) {
15
+ const profile = useStoreQuery(Queries.ProfileQuery, [pubkey]);
16
+
17
+ return <span>{profile.name || "loading..."}</span>;
18
+ }
19
+
20
+ function App() {
21
+ return (
22
+ <QueryStoreProvider store={store}>
23
+ <h1>App</h1>
24
+
25
+ <UserName pubkey="82341f882b6eabcd2ba7f1ef90aad961cf074af15b9ef44a09f9d2a8fbfbe6a2" />
26
+ </QueryStoreProvider>
27
+ );
28
+ }
29
+ ```
@@ -0,0 +1,3 @@
1
+ import { Link } from "applesauce-content/nast";
2
+ export type LinkRenderer = (url: URL, node: Link) => JSX.Element | false | null;
3
+ export declare function buildLinkRenderer(handlers: LinkRenderer[]): import("react").NamedExoticComponent<import("./nast.js").ExtraProps<Link>>;
@@ -0,0 +1,23 @@
1
+ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
+ import { memo, useMemo } from "react";
3
+ export function buildLinkRenderer(handlers) {
4
+ const LinkRenderer = ({ node }) => {
5
+ const content = useMemo(() => {
6
+ try {
7
+ const url = new URL(node.href);
8
+ for (const handler of handlers) {
9
+ try {
10
+ const content = handler(url, node);
11
+ if (content)
12
+ return content;
13
+ }
14
+ catch (e) { }
15
+ }
16
+ }
17
+ catch (error) { }
18
+ return null;
19
+ }, [node.href, node.value]);
20
+ return content || _jsx(_Fragment, { children: node.value });
21
+ };
22
+ return memo(LinkRenderer);
23
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./nast.js";
2
+ export * from "./build-link-renderer.js";
@@ -0,0 +1,2 @@
1
+ export * from "./nast.js";
2
+ export * from "./build-link-renderer.js";
@@ -0,0 +1,12 @@
1
+ import React from "react";
2
+ import { Content, ContentMap, Root } from "applesauce-content/nast";
3
+ type Component<ComponentProps> = React.FunctionComponent<ComponentProps> | React.ComponentClass;
4
+ export type ExtraProps<T extends Content> = {
5
+ node: T;
6
+ };
7
+ export type ComponentMap = Partial<{
8
+ [k in keyof ContentMap]: Component<ExtraProps<ContentMap[k]>>;
9
+ }>;
10
+ /** Render a nostr syntax tree to JSX components */
11
+ export declare function renderNast(root: Root, components: ComponentMap): import("react/jsx-runtime").JSX.Element;
12
+ export {};
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /** Render a nostr syntax tree to JSX components */
3
+ export function renderNast(root, components) {
4
+ const indexes = {};
5
+ return (_jsx(_Fragment, { children: root.children.map((node) => {
6
+ indexes[node.type] = indexes[node.type] ?? 0;
7
+ const index = indexes[node.type];
8
+ indexes[node.type]++;
9
+ const Component = components[node.type];
10
+ if (!Component)
11
+ return null;
12
+ // @ts-expect-error
13
+ return _jsx(Component, { node: node }, node.type + "-" + index);
14
+ }) }));
15
+ }
@@ -0,0 +1,5 @@
1
+ export * from "./use-observable.js";
2
+ export * from "./use-store-query.js";
3
+ export * from "./use-render-nast.js";
4
+ export * from "./use-query-store.js";
5
+ export * from "./use-rendered-text-content.js";
@@ -0,0 +1,5 @@
1
+ export * from "./use-observable.js";
2
+ export * from "./use-store-query.js";
3
+ export * from "./use-render-nast.js";
4
+ export * from "./use-query-store.js";
5
+ export * from "./use-rendered-text-content.js";
@@ -0,0 +1,4 @@
1
+ import { type BehaviorSubject, type Observable } from "rxjs";
2
+ /** Subscribe to the value of an observable */
3
+ export declare function useObservable<T extends unknown>(observable?: BehaviorSubject<T>): T;
4
+ export declare function useObservable<T extends unknown>(observable?: Observable<T>): T | undefined;
@@ -0,0 +1,13 @@
1
+ import { useState, useEffect } from "react";
2
+ export function useObservable(observable) {
3
+ const [_count, update] = useState(0);
4
+ const [value, setValue] = useState(observable && Reflect.has(observable, "value") ? Reflect.get(observable, "value") : undefined);
5
+ useEffect(() => {
6
+ const sub = observable?.subscribe((v) => {
7
+ setValue(v);
8
+ update((c) => c + 1);
9
+ });
10
+ return () => sub?.unsubscribe();
11
+ }, [observable]);
12
+ return value;
13
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Gets the QueryStore from a parent {@link QueryStoreProvider} component
3
+ * If there is none it throws an error
4
+ */
5
+ export declare function useQueryStore(): import("applesauce-core").QueryStore;
@@ -0,0 +1,12 @@
1
+ import { useContext } from "react";
2
+ import { QueryStoreContext } from "../provider.js";
3
+ /**
4
+ * Gets the QueryStore from a parent {@link QueryStoreProvider} component
5
+ * If there is none it throws an error
6
+ */
7
+ export function useQueryStore() {
8
+ const store = useContext(QueryStoreContext);
9
+ if (!store)
10
+ throw new Error("Missing QueryStoreProvider");
11
+ return store;
12
+ }
@@ -0,0 +1,5 @@
1
+ import { Root } from "applesauce-content/nast";
2
+ import { ComponentMap } from "../helpers/nast.js";
3
+ export { ComponentMap };
4
+ /** A hook to get the rendered output of a nostr syntax tree */
5
+ export declare function useRenderNast(root: Root | undefined, components: ComponentMap): JSX.Element | null;
@@ -0,0 +1,6 @@
1
+ import { useMemo } from "react";
2
+ import { renderNast } from "../helpers/nast.js";
3
+ /** A hook to get the rendered output of a nostr syntax tree */
4
+ export function useRenderNast(root, components) {
5
+ return useMemo(() => (root ? renderNast(root, components) : null), [root, Object.keys(components).join("|")]);
6
+ }
@@ -0,0 +1,17 @@
1
+ import { getParsedTextContent } from "applesauce-content/text";
2
+ import { EventTemplate, NostrEvent } from "nostr-tools";
3
+ import { ComponentMap } from "../helpers/nast.js";
4
+ import { LinkRenderer } from "../helpers/build-link-renderer.js";
5
+ export { ComponentMap };
6
+ type Options = {
7
+ /** Override transformers */
8
+ transformers?: Parameters<typeof getParsedTextContent>[2];
9
+ /** If set will use {@link buildLinkRenderer} to render links */
10
+ linkRenderers?: LinkRenderer[];
11
+ /** Override event content */
12
+ content?: string;
13
+ /** Maximum length */
14
+ maxLength?: number;
15
+ };
16
+ /** Returns the parsed and render text content for an event */
17
+ export declare function useRenderedContent(event: NostrEvent | EventTemplate | string | undefined, components: ComponentMap, opts?: Options): JSX.Element | null;
@@ -0,0 +1,16 @@
1
+ import { useMemo } from "react";
2
+ import { getParsedTextContent } from "applesauce-content/text";
3
+ import { useRenderNast } from "./use-render-nast.js";
4
+ import { buildLinkRenderer } from "../helpers/build-link-renderer.js";
5
+ import { truncateContent } from "applesauce-content/nast";
6
+ /** Returns the parsed and render text content for an event */
7
+ export function useRenderedContent(event, components, opts) {
8
+ // if link renderers are set, override the link components
9
+ const _components = useMemo(() => (opts?.linkRenderers ? { ...components, link: buildLinkRenderer(opts.linkRenderers) } : components), [opts?.linkRenderers, components]);
10
+ // add additional transformers
11
+ const nast = useMemo(() => (event ? getParsedTextContent(event, opts?.content, opts?.transformers) : undefined), [event, opts?.content, opts?.transformers]);
12
+ let truncated = nast;
13
+ if (opts?.maxLength && nast)
14
+ truncated = truncateContent(nast, opts.maxLength);
15
+ return useRenderNast(truncated, _components);
16
+ }
@@ -0,0 +1,7 @@
1
+ import { QueryConstructor } from "applesauce-core";
2
+ /**
3
+ * Runs and subscribes to a query in the query store
4
+ * @example
5
+ * const events = useStoreQuery(TimelineQuery, [{kinds: [1]}])
6
+ */
7
+ export declare function useStoreQuery<T extends unknown, Args extends Array<any>>(queryConstructor: QueryConstructor<T, Args>, args?: Args | null): T | undefined;
@@ -0,0 +1,18 @@
1
+ import { useMemo } from "react";
2
+ import { useObservable } from "./use-observable.js";
3
+ import { useQueryStore } from "./use-query-store.js";
4
+ /**
5
+ * Runs and subscribes to a query in the query store
6
+ * @example
7
+ * const events = useStoreQuery(TimelineQuery, [{kinds: [1]}])
8
+ */
9
+ export function useStoreQuery(queryConstructor, args) {
10
+ const store = useQueryStore();
11
+ const observable = useMemo(() => {
12
+ if (args)
13
+ return store.runQuery(queryConstructor)(...args);
14
+ else
15
+ return undefined;
16
+ }, [args, store]);
17
+ return useObservable(observable);
18
+ }
@@ -0,0 +1,6 @@
1
+ import { ParseTextContentOptions } from "applesauce-content/text";
2
+ import { EventTemplate, NostrEvent } from "nostr-tools";
3
+ import { ComponentMap } from "../helpers/nast.js";
4
+ export { ComponentMap };
5
+ /** Returns the parsed and render text content for an event */
6
+ export declare function useRenderedContent(event: NostrEvent | EventTemplate, components: ComponentMap, opts?: ParseTextContentOptions): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,8 @@
1
+ import { useMemo } from "react";
2
+ import { getParedTextContent } from "applesauce-content/text";
3
+ import { useRenderNast } from "./use-render-nast.js";
4
+ /** Returns the parsed and render text content for an event */
5
+ export function useRenderedContent(event, components, opts) {
6
+ const nast = useMemo(() => getParedTextContent(event, opts), [event, opts?.overrideContent, opts?.transformers]);
7
+ return useRenderNast(nast, components);
8
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./provider.js";
2
+ export * as Helpers from "./helpers/index.js";
3
+ export * as Hooks from "./hooks/index.js";
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export * from "./provider.js";
2
+ export * as Helpers from "./helpers/index.js";
3
+ export * as Hooks from "./hooks/index.js";
@@ -0,0 +1,7 @@
1
+ import { QueryStore } from "applesauce-core";
2
+ import { PropsWithChildren } from "react";
3
+ export declare const QueryStoreContext: import("react").Context<QueryStore | null>;
4
+ /** Provides a QueryStore to the component tree */
5
+ export declare function QueryStoreProvider({ store, children }: PropsWithChildren<{
6
+ store: QueryStore;
7
+ }>): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext } from "react";
3
+ export const QueryStoreContext = createContext(null);
4
+ /** Provides a QueryStore to the component tree */
5
+ export function QueryStoreProvider({ store, children }) {
6
+ return _jsx(QueryStoreContext.Provider, { value: store, children: children });
7
+ }
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "applesauce-react",
3
+ "version": "0.0.0-next-20241103143210",
4
+ "description": "React hooks for applesauce",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "keywords": [
9
+ "nostr",
10
+ "react"
11
+ ],
12
+ "author": "hzrd149",
13
+ "license": "MIT",
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "exports": {
18
+ ".": {
19
+ "import": "./dist/index.js",
20
+ "types": "./dist/index.d.ts"
21
+ },
22
+ "./hooks": {
23
+ "import": "./dist/hooks/index.js",
24
+ "types": "./dist/hooks/index.d.ts"
25
+ },
26
+ "./hooks/*": {
27
+ "import": "./dist/hooks/*.js",
28
+ "types": "./dist/hooks/*.d.ts"
29
+ },
30
+ "./helpers": {
31
+ "import": "./dist/helpers/index.js",
32
+ "types": "./dist/helpers/index.d.ts"
33
+ }
34
+ },
35
+ "dependencies": {
36
+ "applesauce-content": "0.0.0-next-20241103143210",
37
+ "applesauce-core": "0.0.0-next-20241103143210",
38
+ "nostr-tools": "^2.7.2",
39
+ "react": "^18.3.1",
40
+ "rxjs": "^7.8.1"
41
+ },
42
+ "devDependencies": {
43
+ "@jest/globals": "^29.7.0",
44
+ "@types/jest": "^29.5.13",
45
+ "@types/react": "^18.3.11",
46
+ "jest": "^29.7.0",
47
+ "jest-extended": "^4.0.2"
48
+ },
49
+ "jest": {
50
+ "roots": [
51
+ "dist"
52
+ ],
53
+ "setupFilesAfterEnv": [
54
+ "jest-extended/all"
55
+ ]
56
+ },
57
+ "scripts": {
58
+ "build": "tsc",
59
+ "watch:build": "tsc --watch > /dev/null",
60
+ "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --passWithNoTests",
61
+ "watch:test": "(trap 'kill 0' SIGINT; pnpm run build -w > /dev/null & pnpm run test --watch)"
62
+ }
63
+ }