=> {
37 | const req = await fetch(
38 | `${api_url}/farcaster/user-by-username?username=${encodeURIComponent(
39 | username
40 | )}`
41 | );
42 |
43 | const reqJson = await req.json();
44 |
45 | return reqJson.data;
46 | })
47 | );
48 |
49 | return reqs;
50 | };
51 | }
52 |
--------------------------------------------------------------------------------
/packages/farcaster/src/channels.ts:
--------------------------------------------------------------------------------
1 | // old, deprecated
2 | export type Channelv1 = {
3 | // null represents the default, "Home" channel
4 | channel_id: string | null;
5 | parent_url: string | null;
6 | name: string;
7 | image: string | null;
8 | description?: string | null;
9 | };
10 |
11 | export type Channel = VirtualChannel | RealizedChannel;
12 |
13 | export type VirtualChannel = {
14 | id: string;
15 | description: string;
16 | name: string;
17 | object: "channel";
18 | parent_url: string | null;
19 | image_url: string;
20 | channel_id: string;
21 | };
22 |
23 | export const homeVirtualChannel: VirtualChannel = {
24 | id: "",
25 | description: "followers",
26 | name: "Home",
27 | object: "channel",
28 | parent_url: null,
29 | image_url: "https://warpcast.com/~/channel-images/home.png",
30 | channel_id: "home",
31 | } as const;
32 |
33 | // type is copied from Neynar response type
34 | export type RealizedChannel = {
35 | id: string;
36 | url: string;
37 | name: string;
38 | description: string;
39 | object: "channel";
40 | image_url: string;
41 | created_at: number;
42 | parent_url: string;
43 | lead: {
44 | object: "user";
45 | fid: number;
46 | username: string;
47 | display_name: string;
48 | pfp_url: string;
49 | profile: {
50 | bio: {
51 | text: string;
52 | };
53 | };
54 | follower_count: number;
55 | following_count: number;
56 | verifications: string[];
57 | active_status: "active" | "inactive";
58 | };
59 | };
60 |
--------------------------------------------------------------------------------
/packages/farcaster/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./structure-cast";
2 | export * from "./format-cast-for-hub";
3 | export * from "./channels";
4 | export * from "./types";
5 | export * from "./api-helpers";
6 | // export * from "./regexps";
7 | export * from "./mentions";
8 |
--------------------------------------------------------------------------------
/packages/farcaster/src/mentions.ts:
--------------------------------------------------------------------------------
1 | export type FarcasterMention = {
2 | fid: number;
3 | display_name: string;
4 | username: string;
5 | avatar_url: string;
6 | };
7 |
--------------------------------------------------------------------------------
/packages/farcaster/src/regexps.ts:
--------------------------------------------------------------------------------
1 | /** https://github.com/farcasterxyz/protocol/discussions/90 **/
2 | export const ENS_L1 = /^[a-z0-9][a-z0-9-]{0,15}$/;
3 | export const ENS_FNAME = /^[a-z0-9][a-z0-9-]{0,15}$/;
4 |
--------------------------------------------------------------------------------
/packages/farcaster/src/types.ts:
--------------------------------------------------------------------------------
1 | // https://github.com/farcasterxyz/protocol/blob/main/docs/SPECIFICATION.md#14-casts
2 | export type FarcasterUrlEmbed = { url: string };
3 | export type FarcasterCastIdEmbed = {
4 | castId: { fid: number; hash: Uint8Array };
5 | };
6 | export type FarcasterEmbed = FarcasterCastIdEmbed | FarcasterUrlEmbed;
7 |
8 | export function isFarcasterUrlEmbed(
9 | embed: FarcasterEmbed
10 | ): embed is FarcasterUrlEmbed {
11 | return embed.hasOwnProperty("url");
12 | }
13 |
14 | export function isFarcasterCastIdEmbed(
15 | embed: FarcasterEmbed
16 | ): embed is FarcasterCastIdEmbed {
17 | return embed.hasOwnProperty("castId");
18 | }
19 |
20 | export const FARCASTER_MAX_EMBEDS = 2;
21 |
--------------------------------------------------------------------------------
/packages/farcaster/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "tsconfig/base.json",
3 | "include": ["."],
4 | "exclude": ["dist", "build", "node_modules"]
5 | }
6 |
--------------------------------------------------------------------------------
/packages/mod-registry/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # @mod-protocol/mod-registry
2 |
3 | ## 0.2.2
4 |
5 | ### Patch Changes
6 |
7 | - Fix aspect ratio of frame mod
8 |
9 | ## 0.2.1
10 |
11 | ### Patch Changes
12 |
13 | - Fix aspect ratio
14 |
15 | ## 0.2.0
16 |
17 | ### Minor Changes
18 |
19 | - 2901ee9: Adds Farcaster Frames
20 | - 2ff629a: Add Farcaster frames support, including a new action Mods can call, ADDFCFRAMEACTION
21 |
22 | ### Patch Changes
23 |
24 | - Updated dependencies [df98c06]
25 | - Updated dependencies [2901ee9]
26 | - Updated dependencies [2ff629a]
27 | - @mod-protocol/core@0.2.0
28 |
29 | ## 0.1.1
30 |
31 | ### Patch Changes
32 |
33 | - 1b019e8: miniapp: zora create
34 | - Updated dependencies [1b019e8]
35 | - Updated dependencies [7053840]
36 | - @mod-protocol/core@0.1.2
37 |
38 | ## 0.1.0
39 |
40 | ### Minor Changes
41 |
42 | - 63b4864: Renames Content rendering to Rich Embeds, Renames Mini-apps to Mods, updates docs
43 |
44 | ### Patch Changes
45 |
46 | - 584ffc2: feat: add `SENDETHTRANSACTION` action
47 | - Updated dependencies [584ffc2]
48 | - Updated dependencies [f2141a9]
49 | - Updated dependencies [2b7c514]
50 | - Updated dependencies [f2141a9]
51 | - Updated dependencies [584ffc2]
52 | - Updated dependencies [69be77d]
53 | - Updated dependencies [63b4864]
54 | - @mod-protocol/core@0.1.0
55 |
56 | ## 0.0.2
57 |
58 | ### Patch Changes
59 |
60 | - 4a50bdd: feat: npm readiness
61 | - Updated dependencies [8ae8453]
62 | - Updated dependencies [15d9d7f]
63 | - Updated dependencies [4a50bdd]
64 | - @mod-protocol/core@0.0.2
65 |
--------------------------------------------------------------------------------
/packages/mod-registry/README.md:
--------------------------------------------------------------------------------
1 | # @mod-protocol/mod-registry
2 |
3 | This package of [Mod Protocol](https://github.com/mod-protocol/mod) is responsible for exporting all reviewed Mods in the registries
4 |
--------------------------------------------------------------------------------
/packages/mod-registry/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@mod-protocol/mod-registry",
3 | "version": "0.2.2",
4 | "main": "./dist/index.js",
5 | "module": "./dist/index.mjs",
6 | "types": "./dist/index.d.ts",
7 | "scripts": {
8 | "lint": "eslint \"**/*.ts*\" && tsc --noEmit",
9 | "build": "tsup src/index.ts --format cjs,esm --dts",
10 | "dev": "npm run build -- --watch ../../mods"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "https://github.com/mod-protocol/mod/tree/main/packages/mod-registry"
15 | },
16 | "dependencies": {
17 | "@mod-protocol/core": "*"
18 | },
19 | "devDependencies": {
20 | "eslint": "^7.32.0",
21 | "eslint-config-custom": "*",
22 | "tsconfig": "*",
23 | "typescript": "^5.2.2"
24 | }
25 | }
--------------------------------------------------------------------------------
/packages/mod-registry/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "tsconfig/base.json",
3 | "include": [
4 | "."
5 | ],
6 | "exclude": [
7 | "dist",
8 | "build",
9 | "node_modules"
10 | ],
11 | "compilerOptions": {
12 | "baseUrl": ".",
13 | "paths": {
14 | "@mods/*": [
15 | "../../mods/*"
16 | ]
17 | }
18 | }
19 | }
--------------------------------------------------------------------------------
/packages/react-editor/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | // This tells ESLint to load the config from the package `eslint-config-custom`
4 | extends: ["custom"],
5 | };
6 |
--------------------------------------------------------------------------------
/packages/react-editor/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # @mod-protocol/react-editor
2 |
3 | ## 0.1.0
4 |
5 | ### Minor Changes
6 |
7 | - cbfe215: add channel mentions
8 |
9 | ### Patch Changes
10 |
11 | - 7f7d4c9: fix: editor shouldn't take mentions into account when calculating remaining characters in cast text
12 | - Updated dependencies [cbfe215]
13 | - Updated dependencies [6ae4441]
14 | - @mod-protocol/farcaster@0.1.0
15 | - @mod-protocol/core@0.1.1
16 |
17 | ## 0.0.3
18 |
19 | ### Patch Changes
20 |
21 | - 9d4d7e1: feat: expose tiptap editor config
22 | - 2b7c514: Adds the combobox, select, textarea mod elements. Adds `Model Definitions` to Manifests. Adds the `ethPersonalSign` action. Adds `json-ld` indexing to metadata. Adds a `loadingLabel` prop to buttons
23 | - 9d4d7e1: fix: placeholder text
24 | - Updated dependencies [584ffc2]
25 | - Updated dependencies [f2141a9]
26 | - Updated dependencies [2b7c514]
27 | - Updated dependencies [f2141a9]
28 | - Updated dependencies [584ffc2]
29 | - Updated dependencies [69be77d]
30 | - Updated dependencies [63b4864]
31 | - @mod-protocol/core@0.1.0
32 |
33 | ## 0.0.2
34 |
35 | ### Patch Changes
36 |
37 | - 390b483: fix: improve URL embed loading by only showing the embed when the Open Graph data is fetched
38 | - 4a50bdd: feat: npm readiness
39 | - Updated dependencies [8ae8453]
40 | - Updated dependencies [15d9d7f]
41 | - Updated dependencies [4a50bdd]
42 | - @mod-protocol/core@0.0.2
43 |
--------------------------------------------------------------------------------
/packages/react-editor/README.md:
--------------------------------------------------------------------------------
1 | # @mod-protocol/react-editor
2 |
3 | This package of [Mod Protocol](https://github.com/mod-protocol/mod) renders a Farcaster native text editor for creating Farcaster casts
4 |
--------------------------------------------------------------------------------
/packages/react-editor/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@mod-protocol/react-editor",
3 | "version": "0.1.0",
4 | "main": "./dist/index.js",
5 | "module": "./dist/index.mjs",
6 | "types": "./dist/index.d.ts",
7 | "scripts": {
8 | "lint": "eslint \"**/*.ts*\" && tsc --noEmit",
9 | "build": "tsup src/index.tsx --format cjs,esm --dts",
10 | "dev": "npm run build -- --watch . --watch ../tiptap-extension-channel-mention --watch ../tiptap-extension-link"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "https://github.com/mod-protocol/mod/tree/main/package/react-editor"
15 | },
16 | "dependencies": {
17 | "@tiptap/core": "^2.0.4",
18 | "@mod-protocol/core": "*",
19 | "@mod-protocol/farcaster": "*",
20 | "@tiptap/extension-document": "^2.0.4",
21 | "@tiptap/extension-hard-break": "^2.0.4",
22 | "@tiptap/extension-history": "^2.0.4",
23 | "@tiptap/extension-link": "^2.0.4",
24 | "@tiptap/extension-mention": "^2.0.4",
25 | "@tiptap/extension-paragraph": "^2.0.4",
26 | "@tiptap/extension-placeholder": "^2.0.4",
27 | "@tiptap/extension-text": "^2.0.4",
28 | "@tiptap/pm": "^2.0.4",
29 | "@tiptap/suggestion": "^2.0.4",
30 | "@tiptap/react": "^2.0.4",
31 | "prosemirror-model": "^1.19.3"
32 | },
33 | "peerDependencies": {
34 | "react": "^18.2.0",
35 | "react-dom": "^18.2.0"
36 | },
37 | "workspaces": [
38 | "configs/*",
39 | "packages/*",
40 | "examples/*"
41 | ],
42 | "devDependencies": {
43 | "@types/react": "^18.2.0",
44 | "@types/react-dom": "^18.2.0",
45 | "eslint": "^7.32.0",
46 | "eslint-config-custom": "*",
47 | "tsconfig": "*",
48 | "typescript": "^5.2.2"
49 | }
50 | }
--------------------------------------------------------------------------------
/packages/react-editor/src/errors.ts:
--------------------------------------------------------------------------------
1 | export const MAX_EMBEDS_REACHED_ERROR = {
2 | code: 1,
3 | message:
4 | "Max number of embeds reached. Please remove an embed to add another",
5 | };
6 |
7 | export type ErrorType = typeof MAX_EMBEDS_REACHED_ERROR;
8 |
--------------------------------------------------------------------------------
/packages/react-editor/src/extension-clipboard.tsx:
--------------------------------------------------------------------------------
1 | import { Slice, Fragment, Node as ProseMirrorNode } from "prosemirror-model";
2 |
3 | // https://github.com/ueberdosis/tiptap/issues/775
4 | export function clipboardTextParser(text: any, context: any, plain: any): any {
5 | const blocks = text.replace().split(/(?:\r\n?|\n)/);
6 | const nodes = [];
7 | // console.log(blocks, text, context)
8 | let content: any[] = [];
9 | blocks.forEach((line: any, i: number) => {
10 | if (i !== 0) content.push({ type: "hardBreak" });
11 | if (line.length > 0) content.push({ type: "text", text: line });
12 | });
13 |
14 | let node = ProseMirrorNode.fromJSON(context.doc.type.schema, {
15 | type: "paragraph",
16 | content,
17 | });
18 | nodes.push(node);
19 |
20 | const fragment = Fragment.fromArray(nodes);
21 | return Slice.maxOpen(fragment);
22 | }
23 |
24 | export function transformPastedHTML(str: string): string {
25 | const transformed = str
26 | // replace
with
since the previous will falsely double new line.
27 | .replace(/]*>\s*
]*>\s*<\/p>/gi, "
")
28 | // replace with
and
with empty
29 | .replace(/<\/p>\s*]*>/gi, "
");
30 | return transformed;
31 | }
32 |
--------------------------------------------------------------------------------
/packages/react-editor/src/extension-savedraft.tsx:
--------------------------------------------------------------------------------
1 | // async function handleSaveDraft(e?: FormEvent | null) {
2 | // e?.preventDefault();
3 | // const fullText = getText();
4 | // const submission = await onSaveDraft?.({ text: fullText });
5 |
6 | // return submission;
7 | // }
8 |
--------------------------------------------------------------------------------
/packages/react-editor/src/extension-warnonnavigate.tsx:
--------------------------------------------------------------------------------
1 | // import { useEffect } from "react";
2 |
3 | // export const useWarnIfUnsavedChanges = (
4 | // unsavedChanges: boolean,
5 | // callback: () => boolean
6 | // ) => {
7 | // const router = useRouter();
8 | // useEffect(() => {
9 | // if (unsavedChanges) {
10 | // const routeChangeStart = (url: any, { shallow }: any) => {
11 | // if (url !== router.asPath) {
12 | // const ok = callback();
13 | // if (!ok) {
14 | // Router.events.emit("routeChangeError");
15 | // throw "Abort route change. Please ignore this error.";
16 | // }
17 | // }
18 | // };
19 | // Router.events.on("routeChangeStart", routeChangeStart);
20 |
21 | // return () => {
22 | // Router.events.off("routeChangeStart", routeChangeStart);
23 | // };
24 | // }
25 | // }, [unsavedChanges]);
26 | // };
27 |
28 | // useWarnIfUnsavedChanges(!editor?.isEmpty && !isSubmitting, warnOnNavigate);
29 |
--------------------------------------------------------------------------------
/packages/react-editor/src/index.tsx:
--------------------------------------------------------------------------------
1 | import { useEditor } from "./use-editor";
2 | import { useTextLength } from "./use-text-length";
3 | import { EditorContent } from "@tiptap/react";
4 | export type { Editor } from "@tiptap/core";
5 |
6 | export { useEditor, useTextLength, EditorContent };
7 |
--------------------------------------------------------------------------------
/packages/react-editor/src/use-key-press.tsx:
--------------------------------------------------------------------------------
1 | import { useCallback, useEffect, useRef } from "react";
2 |
3 | export const useKeyPress = ({
4 | metaKey,
5 | shiftKey,
6 | keys,
7 | callback,
8 | node = null,
9 | }: {
10 | metaKey?: boolean;
11 | shiftKey?: boolean;
12 | keys: string[];
13 | callback: (ev: KeyboardEvent) => void;
14 | node?: HTMLElement | null;
15 | }) => {
16 | // implement the callback ref pattern
17 | const callbackRef = useRef(callback);
18 | useEffect(() => {
19 | callbackRef.current = callback;
20 | }, [callback]);
21 |
22 | // handle what happens on key press
23 | const handleKeyPress = useCallback(
24 | (event: KeyboardEvent) => {
25 | // check if one of the key is part of the ones we want
26 | if (
27 | keys.every((key) => event.key === key) &&
28 | (!metaKey || event.metaKey || event.ctrlKey) &&
29 | (!shiftKey || event.shiftKey)
30 | ) {
31 | callbackRef.current(event);
32 | }
33 | },
34 | [keys, metaKey, shiftKey]
35 | );
36 |
37 | useEffect(() => {
38 | // target is either the provided node or the document
39 | const targetNode = node ?? document;
40 | // attach the event listener
41 | targetNode && targetNode.addEventListener("keydown", handleKeyPress as any);
42 |
43 | // remove the event listener
44 | return () =>
45 | targetNode &&
46 | targetNode.removeEventListener("keydown", handleKeyPress as any);
47 | }, [handleKeyPress, node]);
48 | };
49 |
--------------------------------------------------------------------------------
/packages/react-editor/src/use-text-length.tsx:
--------------------------------------------------------------------------------
1 | import { convertCastPlainTextToStructured } from "@mod-protocol/farcaster";
2 |
3 | export function useTextLength({
4 | getText,
5 | maxByteLength,
6 | }: {
7 | getText: () => string;
8 | maxByteLength: number;
9 | }) {
10 | const text = getText();
11 |
12 | // Mentions don't occupy space in the cast, so we need to ignore them for our length calculation
13 | const structuredTextUnits = convertCastPlainTextToStructured({ text });
14 | const textWithoutMentions = structuredTextUnits.reduce((acc, unit) => {
15 | if (unit.type !== "mention") acc += unit.serializedContent;
16 | return acc;
17 | }, "");
18 |
19 | const lengthInBytes = new TextEncoder().encode(textWithoutMentions).length;
20 |
21 | const eightyFivePercentComplete = maxByteLength * 0.85;
22 |
23 | return {
24 | length: lengthInBytes,
25 | isValid: lengthInBytes <= maxByteLength,
26 | tailwindColor:
27 | lengthInBytes > maxByteLength
28 | ? "red"
29 | : lengthInBytes > eightyFivePercentComplete
30 | ? `orange.${
31 | 2 + Math.floor((lengthInBytes - eightyFivePercentComplete) / 10)
32 | }00`
33 | : "gray",
34 | label:
35 | lengthInBytes > maxByteLength
36 | ? `-${lengthInBytes - maxByteLength} characters left`
37 | : lengthInBytes > eightyFivePercentComplete
38 | ? `${maxByteLength - lengthInBytes} characters left`
39 | : ``,
40 | };
41 | }
42 |
--------------------------------------------------------------------------------
/packages/react-editor/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "tsconfig/react-library.json",
3 | "include": [
4 | "."
5 | ],
6 | "exclude": [
7 | "dist",
8 | "build",
9 | "node_modules"
10 | ],
11 | "compilerOptions": {
12 | "paths": {
13 | "@mod-protocol/tiptap-extension-link": [
14 | "../tiptap-extension-link/src"
15 | ],
16 | "@mod-protocol/tiptap-extension-channel-mention": [
17 | "../tiptap-extension-channel-mention/src"
18 | ],
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/packages/react-ui-shadcn/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | // This tells ESLint to load the config from the package `eslint-config-custom`
4 | extends: ["custom"],
5 | };
6 |
--------------------------------------------------------------------------------
/packages/react-ui-shadcn/README.md:
--------------------------------------------------------------------------------
1 | # @mod-protocol/react-ui-shadcn
2 |
3 | This package of [Mod Protocol](https://github.com/mod-protocol/mod) is a UI implementation of the Mod Elements using [Shadcn](https://ui.shadcn.com/) which uses [Tailwind CSS](https://tailwindcss.com/) and [Radix UI](https://www.radix-ui.com/) under the hood.
4 |
--------------------------------------------------------------------------------
/packages/react-ui-shadcn/components.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://ui.shadcn.com/schema.json",
3 | "style": "new-york",
4 | "rsc": true,
5 | "tsx": true,
6 | "tailwind": {
7 | "config": "tailwind.config.js",
8 | "css": "src/app/globals.css",
9 | "baseColor": "slate",
10 | "cssVariables": true
11 | },
12 | "aliases": {
13 | "components": "src/components",
14 | "utils": "src/lib/utils"
15 | }
16 | }
--------------------------------------------------------------------------------
/packages/react-ui-shadcn/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@mod-protocol/react-ui-shadcn",
3 | "version": "0.3.1",
4 | "license": "MIT",
5 | "scripts": {
6 | "lint": "eslint \"**/*.ts*\" && tsc --noEmit",
7 | "build": "tsup `find ./src \\( -name '*.ts' -o -name '*.tsx' \\)` --format cjs --dts --onSuccess \"cp -a src/public/. dist/public\"",
8 | "dev": "npm run build -- --watch"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "https://github.com/mod-protocol/mod/tree/main/packages/react-ui-shadcn"
13 | },
14 | "dependencies": {
15 | "@mod-protocol/core": "^0.2.0",
16 | "@mod-protocol/farcaster": "^0.2.0",
17 | "@mod-protocol/react": "^0.2.0",
18 | "@mod-protocol/react-editor": "^0.1.0",
19 | "@mod-protocol/mod-registry": "*",
20 | "@primer/octicons-react": "^19.5.0",
21 | "@radix-ui/react-aspect-ratio": "^1.0.3",
22 | "@radix-ui/react-avatar": "^1.0.4",
23 | "@radix-ui/react-dialog": "^1.0.5",
24 | "@radix-ui/react-icons": "^1.3.0",
25 | "@radix-ui/react-popover": "^1.0.6",
26 | "@radix-ui/react-scroll-area": "^1.0.4",
27 | "@radix-ui/react-select": "^2.0.0",
28 | "@radix-ui/react-slot": "^1.0.2",
29 | "@radix-ui/react-tabs": "^1.0.4",
30 | "class-variance-authority": "^0.7.0",
31 | "clsx": "^2.0.0",
32 | "cmdk": "^0.2.0",
33 | "tailwind-merge": "^1.14.0",
34 | "tailwindcss-animate": "^1.0.6",
35 | "video.js": "^8.0.4"
36 | },
37 | "peerDependencies": {
38 | "react": "^18.2.0",
39 | "react-dom": "^18.2.0"
40 | },
41 | "devDependencies": {
42 | "@types/react": "^18.2.0",
43 | "@types/react-dom": "^18.2.0",
44 | "eslint": "^7.32.0",
45 | "eslint-config-custom": "*",
46 | "postcss": "^8.4.31",
47 | "tailwindcss": "^3.3.3",
48 | "tsconfig": "*",
49 | "typescript": "^5.2.2"
50 | }
51 | }
--------------------------------------------------------------------------------
/packages/react-ui-shadcn/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | }
7 |
--------------------------------------------------------------------------------
/packages/react-ui-shadcn/src/components/cast-length-ui-indicator.tsx:
--------------------------------------------------------------------------------
1 | import { useTextLength } from "@mod-protocol/react-editor";
2 |
3 | type Props = {
4 | getText: () => string;
5 | };
6 |
7 | export function CastLengthUIIndicator({ getText }: Props) {
8 | const { length, label, tailwindColor } = useTextLength({
9 | getText,
10 | maxByteLength: 320,
11 | });
12 | return {label}
;
13 | }
14 |
--------------------------------------------------------------------------------
/packages/react-ui-shadcn/src/components/theme-provider.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import { ThemeProvider as NextThemesProvider } from "next-themes";
5 | import { type ThemeProviderProps } from "next-themes/dist/types";
6 |
7 | export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
8 | return {children};
9 | }
10 |
11 | export { useTheme } from "next-themes";
12 |
--------------------------------------------------------------------------------
/packages/react-ui-shadcn/src/components/ui/aspect-ratio.tsx:
--------------------------------------------------------------------------------
1 | "use client"
2 |
3 | import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
4 |
5 | const AspectRatio = AspectRatioPrimitive.Root
6 |
7 | export { AspectRatio }
8 |
--------------------------------------------------------------------------------
/packages/react-ui-shadcn/src/components/ui/circular-progress.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { cn } from "lib/utils";
3 | import { cva, type VariantProps } from "class-variance-authority";
4 |
5 | const circularProgressVariants = cva(
6 | "inline-block animate-spin rounded-full border-solid border-current border-r-transparent align-[-0.125em] motion-reduce:animate-[spin_1.5s_linear_infinite]",
7 | {
8 | variants: {
9 | size: {
10 | default: "h-8 w-8 border-4",
11 | sm: "h-4 w-4 border-2",
12 | lg: "h-12 w-12 border-6",
13 | },
14 | },
15 | defaultVariants: {
16 | size: "default",
17 | },
18 | }
19 | );
20 |
21 | const CircularProgress = React.forwardRef<
22 | HTMLDivElement,
23 | React.HTMLAttributes &
24 | VariantProps
25 | >(({ className, size, ...props }, ref) => (
26 |
32 |
33 | Loading...
34 |
35 |
36 | ));
37 |
38 | CircularProgress.displayName = "CircularProgress";
39 |
40 | export { CircularProgress };
41 |
--------------------------------------------------------------------------------
/packages/react-ui-shadcn/src/components/ui/input.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import { cn } from "lib/utils";
4 |
5 | export interface InputProps
6 | extends React.InputHTMLAttributes {}
7 |
8 | const Input = React.forwardRef(
9 | ({ className, type, ...props }, ref) => {
10 | return (
11 |
20 | );
21 | }
22 | );
23 | Input.displayName = "Input";
24 |
25 | export { Input };
26 |
--------------------------------------------------------------------------------
/packages/react-ui-shadcn/src/components/ui/link.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { type VariantProps } from "class-variance-authority";
3 |
4 | import { cn } from "lib/utils";
5 | import { buttonVariants } from "./button";
6 |
7 | export interface LinkProps
8 | extends React.AnchorHTMLAttributes,
9 | VariantProps {
10 | asChild?: boolean;
11 | }
12 |
13 | const Link = React.forwardRef(
14 | ({ className, variant = "link", size, asChild = false, ...props }, ref) => {
15 | return (
16 |
21 | );
22 | }
23 | );
24 | Link.displayName = "Link";
25 |
26 | export { Link };
27 |
--------------------------------------------------------------------------------
/packages/react-ui-shadcn/src/components/ui/popover.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as PopoverPrimitive from "@radix-ui/react-popover";
5 |
6 | import { cn } from "lib/utils";
7 |
8 | const Popover = PopoverPrimitive.Root;
9 |
10 | const PopoverTrigger = PopoverPrimitive.Trigger;
11 |
12 | const PopoverContent = React.forwardRef<
13 | React.ElementRef,
14 | React.ComponentPropsWithoutRef
15 | >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
16 |
17 |
27 |
28 | ));
29 | PopoverContent.displayName = PopoverPrimitive.Content.displayName;
30 |
31 | export { Popover, PopoverTrigger, PopoverContent };
32 |
--------------------------------------------------------------------------------
/packages/react-ui-shadcn/src/components/ui/scroll-area.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import * as React from "react";
4 | import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
5 |
6 | import { cn } from "lib/utils";
7 |
8 | const ScrollArea = React.forwardRef<
9 | React.ElementRef,
10 | React.ComponentPropsWithoutRef
11 | >(({ className, children, ...props }, ref) => (
12 |
17 |
18 | {children}
19 |
20 |
21 |
22 |
23 | ));
24 | ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
25 |
26 | const ScrollBar = React.forwardRef<
27 | React.ElementRef,
28 | React.ComponentPropsWithoutRef
29 | >(({ className, orientation = "vertical", ...props }, ref) => (
30 |
43 |
44 |
45 | ));
46 | ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
47 |
48 | export { ScrollArea, ScrollBar };
49 |
--------------------------------------------------------------------------------
/packages/react-ui-shadcn/src/components/ui/skeleton.tsx:
--------------------------------------------------------------------------------
1 | import { cn } from "lib/utils";
2 |
3 | function Skeleton({
4 | className,
5 | ...props
6 | }: React.HTMLAttributes) {
7 | return (
8 |
12 | );
13 | }
14 |
15 | export { Skeleton };
16 |
--------------------------------------------------------------------------------
/packages/react-ui-shadcn/src/components/ui/textarea.tsx:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import { cn } from "lib/utils";
4 |
5 | export interface TextareaProps
6 | extends React.TextareaHTMLAttributes {}
7 |
8 | const Textarea = React.forwardRef(
9 | ({ className, ...props }, ref) => {
10 | return (
11 |
19 | );
20 | }
21 | );
22 | Textarea.displayName = "Textarea";
23 |
24 | export { Textarea };
25 |
--------------------------------------------------------------------------------
/packages/react-ui-shadcn/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { type ClassValue, clsx } from "clsx"
2 | import { twMerge } from "tailwind-merge"
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs))
6 | }
7 |
--------------------------------------------------------------------------------
/packages/react-ui-shadcn/src/renderers/alert.tsx:
--------------------------------------------------------------------------------
1 | import {
2 | ExclamationTriangleIcon,
3 | CheckCircledIcon,
4 | } from "@radix-ui/react-icons";
5 | import { Alert, AlertDescription, AlertTitle } from "components/ui/alert";
6 | import React from "react";
7 | import { Renderers } from "@mod-protocol/react";
8 |
9 | export const AlertRenderer = (
10 | props: React.ComponentProps
11 | ) => {
12 | const { description, title, variant } = props;
13 | return (
14 |
15 | {variant === "error" ? (
16 |
17 | ) : (
18 |
19 | )}
20 | {title}
21 | {description}
22 |
23 | );
24 | };
25 |
--------------------------------------------------------------------------------
/packages/react-ui-shadcn/src/renderers/avatar.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Renderers } from "@mod-protocol/react";
3 | import { Avatar, AvatarFallback, AvatarImage } from "components/ui/avatar";
4 |
5 | export const AvatarRenderer = (
6 | props: React.ComponentProps
7 | ) => {
8 | return (
9 |
10 |
11 |
12 |
13 | );
14 | };
15 |
--------------------------------------------------------------------------------
/packages/react-ui-shadcn/src/renderers/button.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Renderers } from "@mod-protocol/react";
3 | import { CircularProgress } from "components/ui/circular-progress";
4 | import { Button } from "components/ui/button";
5 |
6 | export const ButtonRenderer = (
7 | props: React.ComponentProps
8 | ) => {
9 | const {
10 | label,
11 | isDisabled,
12 | isLoading,
13 | onClick,
14 | variant = "primary",
15 | loadingLabel = "",
16 | } = props;
17 | return (
18 |
36 | );
37 | };
38 |
--------------------------------------------------------------------------------
/packages/react-ui-shadcn/src/renderers/circular-progress.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Renderers } from "@mod-protocol/react";
3 | import { CircularProgress } from "components/ui/circular-progress";
4 |
5 | export const CircularProgressRenderer = (
6 | props: React.ComponentProps
7 | ) => {
8 | return ;
9 | };
10 |
--------------------------------------------------------------------------------
/packages/react-ui-shadcn/src/renderers/container.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Renderers } from "@mod-protocol/react";
3 |
4 | export const ContainerRenderer = (
5 | props: React.ComponentProps
6 | ) => {
7 | return (
8 |
9 | {props.children}
10 |
11 | );
12 | };
13 |
--------------------------------------------------------------------------------
/packages/react-ui-shadcn/src/renderers/dialog.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Renderers } from "@mod-protocol/react";
3 | import { Dialog, DialogContent } from "components/ui/dialog";
4 |
5 | export const DialogRenderer = (
6 | props: React.ComponentProps
7 | ) => {
8 | const { children, onClose } = props;
9 | return (
10 |
22 | );
23 | };
24 |
--------------------------------------------------------------------------------
/packages/react-ui-shadcn/src/renderers/horizontal-layout.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Renderers } from "@mod-protocol/react";
3 |
4 | export const HorizontalLayoutRenderer = (
5 | props: React.ComponentProps
6 | ) => {
7 | return (
8 |
9 | {props.children}
10 |
11 | );
12 | };
13 |
--------------------------------------------------------------------------------
/packages/react-ui-shadcn/src/renderers/image-grid-list.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Renderers } from "@mod-protocol/react";
3 | import { ScrollArea } from "components/ui/scroll-area";
4 | import { AspectRatio } from "components/ui/aspect-ratio";
5 | import { Skeleton } from "components/ui/skeleton";
6 | import { MagnifyingGlassIcon } from "@radix-ui/react-icons";
7 |
8 | export const ImageGridListRenderer = (
9 | props: React.ComponentProps
10 | ) => {
11 | const { images, isLoading, onPick } = props;
12 |
13 | return (
14 |
15 |
16 | {isLoading ? (
17 | Array.from(Array(24).keys()).map((item) => (
18 |
19 |
20 |
21 | ))
22 | ) : images && images.length ? (
23 | images.map((item) => (
24 |
25 |
36 |
37 | ))
38 | ) : (
39 |
40 |
41 | No images to display.
42 |
43 | )}
44 |
45 |
46 | );
47 | };
48 |
--------------------------------------------------------------------------------
/packages/react-ui-shadcn/src/renderers/image.tsx:
--------------------------------------------------------------------------------
1 | import { Renderers } from "@mod-protocol/react";
2 | import React from "react";
3 |
4 | export const ImageRenderer = (
5 | props: React.ComponentProps
6 | ) => {
7 | const { imageSrc } = props;
8 | return (
9 |
10 |

17 |
18 | );
19 | };
20 |
--------------------------------------------------------------------------------
/packages/react-ui-shadcn/src/renderers/index.tsx:
--------------------------------------------------------------------------------
1 | import { Renderers } from "@mod-protocol/react";
2 | import { TextRenderer } from "./text";
3 | import { ButtonRenderer } from "./button";
4 | import { CircularProgressRenderer } from "./circular-progress";
5 | import { HorizontalLayoutRenderer } from "./horizontal-layout";
6 | import { VerticalLayoutRenderer } from "./vertical-layout";
7 | import { TabsRenderer } from "./tabs";
8 | import { InputRenderer } from "./input";
9 | import { LinkRenderer } from "./link";
10 | import { ImageGridListRenderer } from "./image-grid-list";
11 | import { DialogRenderer } from "./dialog";
12 | import { AlertRenderer } from "./alert";
13 | import { VideoRenderer } from "./video";
14 | import { CardRenderer } from "./card";
15 | import { AvatarRenderer } from "./avatar";
16 | import { ImageRenderer } from "./image";
17 | import { ContainerRenderer } from "./container";
18 | import { SelectRenderer } from "./select";
19 | import { TextareaRenderer } from "./textarea";
20 | import { ComboboxRenderer } from "./combobox";
21 |
22 | export const renderers: Renderers = {
23 | Select: SelectRenderer,
24 | Link: LinkRenderer,
25 | Combobox: ComboboxRenderer,
26 | Textarea: TextareaRenderer,
27 | Container: ContainerRenderer,
28 | Text: TextRenderer,
29 | Image: ImageRenderer,
30 | Card: CardRenderer,
31 | Avatar: AvatarRenderer,
32 | Video: VideoRenderer,
33 | Button: ButtonRenderer,
34 | CircularProgress: CircularProgressRenderer,
35 | HorizontalLayout: HorizontalLayoutRenderer,
36 | VerticalLayout: VerticalLayoutRenderer,
37 | Tabs: TabsRenderer,
38 | Input: InputRenderer,
39 | ImageGridList: ImageGridListRenderer,
40 | Dialog: DialogRenderer,
41 | Alert: AlertRenderer,
42 | };
43 |
--------------------------------------------------------------------------------
/packages/react-ui-shadcn/src/renderers/input.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Renderers } from "@mod-protocol/react";
3 | import { Input } from "components/ui/input";
4 | import { Button } from "components/ui/button";
5 | import { Cross1Icon } from "@radix-ui/react-icons";
6 | import { cn } from "lib/utils";
7 |
8 | export const InputRenderer = (
9 | props: React.ComponentProps
10 | ) => {
11 | const { isClearable, placeholder, onChange, onSubmit } = props;
12 | const [value, setValue] = React.useState("");
13 | const inputRef = React.useRef(null);
14 | return (
15 |
16 | {
21 | onChange(ev.currentTarget.value);
22 | setValue(ev.currentTarget.value);
23 | }}
24 | onSubmit={(ev) => {
25 | onSubmit(ev.currentTarget.value);
26 | setValue(ev.currentTarget.value);
27 | }}
28 | value={value}
29 | />
30 | {isClearable && value ? (
31 |
44 | ) : null}
45 |
46 | );
47 | };
48 |
--------------------------------------------------------------------------------
/packages/react-ui-shadcn/src/renderers/link.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Renderers } from "@mod-protocol/react";
3 | import { Link } from "components/ui/link";
4 |
5 | export const LinkRenderer = (
6 | props: React.ComponentProps
7 | ) => {
8 | const { label, url, onClick, variant = "default" } = props;
9 | return (
10 | {
14 | // prevent an anchor inside a parent with an onclick also triggering the parent onclick
15 | e.stopPropagation();
16 | return onClick();
17 | }}
18 | target="_blank"
19 | rel="noopener noreferrer"
20 | >
21 | {label}
22 |
23 | );
24 | };
25 |
--------------------------------------------------------------------------------
/packages/react-ui-shadcn/src/renderers/tabs.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Renderers } from "@mod-protocol/react";
3 | import { Tabs, TabsContent, TabsList, TabsTrigger } from "components/ui/tabs";
4 |
5 | export const TabsRenderer = (
6 | props: React.ComponentProps
7 | ) => {
8 | const { values, names, children, onChange } = props;
9 |
10 | return (
11 |
12 |
13 | {values.map((value, index) => (
14 | onChange(value)}
18 | >
19 | {names[index]}
20 |
21 | ))}
22 |
23 | {values.map((value) => (
24 |
25 | {children}
26 |
27 | ))}
28 |
29 | );
30 | };
31 |
--------------------------------------------------------------------------------
/packages/react-ui-shadcn/src/renderers/text.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Renderers } from "@mod-protocol/react";
3 | import { cva } from "class-variance-authority";
4 | import { cn } from "../lib/utils";
5 |
6 | const textVariants = cva("my-0 flex-1 text-sm break-words", {
7 | variants: {
8 | variant: {
9 | bold: "font-bold",
10 | regular: "",
11 | secondary: "text-muted-foreground",
12 | },
13 | },
14 | defaultVariants: {
15 | variant: "regular",
16 | },
17 | });
18 |
19 | export const TextRenderer = (
20 | props: React.ComponentProps
21 | ) => {
22 | const { label, variant = "regular" } = props;
23 |
24 | return {label}
;
25 | };
26 |
--------------------------------------------------------------------------------
/packages/react-ui-shadcn/src/renderers/textarea.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Renderers } from "@mod-protocol/react";
3 | import { Textarea } from "components/ui/textarea";
4 |
5 | export const TextareaRenderer = (
6 | props: React.ComponentProps
7 | ) => {
8 | const { placeholder, onChange, onSubmit } = props;
9 | const [value, setValue] = React.useState("");
10 | const inputRef = React.useRef(null);
11 | return (
12 |
13 |
28 | );
29 | };
30 |
--------------------------------------------------------------------------------
/packages/react-ui-shadcn/src/renderers/vertical-layout.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Renderers } from "@mod-protocol/react";
3 |
4 | export const VerticalLayoutRenderer = (
5 | props: React.ComponentProps
6 | ) => {
7 | return {props.children}
;
8 | };
9 |
--------------------------------------------------------------------------------
/packages/react-ui-shadcn/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "tsconfig/react-library.json",
3 | "compilerOptions": {
4 | "paths": {
5 | "components/*": [
6 | "./src/components/*"
7 | ],
8 | "lib/*": [
9 | "./src/lib/*"
10 | ],
11 | "src/lib/*": [
12 | "./src/lib/*"
13 | ]
14 | }
15 | },
16 | "include": [
17 | "**/*.ts",
18 | "**/*.tsx",
19 | ],
20 | "exclude": [
21 | "node_modules"
22 | ]
23 | }
--------------------------------------------------------------------------------
/packages/react/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | // This tells ESLint to load the config from the package `eslint-config-custom`
4 | extends: ["custom"],
5 | };
6 |
--------------------------------------------------------------------------------
/packages/react/README.md:
--------------------------------------------------------------------------------
1 | # @mod-protocol/react
2 |
3 | This package of [Mod Protocol](https://github.com/mod-protocol/mod) is a React implementation, taking the core Mod renderer and a UI library and producing React components
4 |
--------------------------------------------------------------------------------
/packages/react/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@mod-protocol/react",
3 | "version": "0.2.0",
4 | "main": "./dist/index.js",
5 | "module": "./dist/index.mjs",
6 | "types": "./dist/index.d.ts",
7 | "scripts": {
8 | "lint": "eslint \"**/*.ts*\" && tsc --noEmit",
9 | "build": "tsup src/index.tsx --format cjs,esm --dts",
10 | "dev": "npm run build -- --watch"
11 | },
12 | "dependencies": {
13 | "@mod-protocol/core": "*"
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "https://github.com/mod-protocol/mod/tree/main/packages/react"
18 | },
19 | "peerDependencies": {
20 | "react": "^18.2.0",
21 | "react-dom": "^18.2.0"
22 | },
23 | "devDependencies": {
24 | "@types/react": "^18.2.0",
25 | "@types/react-dom": "^18.2.0",
26 | "eslint": "^7.32.0",
27 | "eslint-config-custom": "*",
28 | "tsconfig": "*",
29 | "typescript": "^5.2.2"
30 | }
31 | }
--------------------------------------------------------------------------------
/packages/react/src/action-resolver-add-embed.ts:
--------------------------------------------------------------------------------
1 | import {
2 | AddEmbedActionResolverInit,
3 | AddEmbedActionResolverEvents,
4 | } from "@mod-protocol/core";
5 |
6 | // This resolver is called when the mod calls the "ADDEMBED" action type.
7 | //
8 | // The action expected from the user is to update the current input field
9 | // (or equivalent) to embed the file specified in the init.
10 | export default function actionResolverAddEmbed(
11 | init: AddEmbedActionResolverInit,
12 | events: AddEmbedActionResolverEvents
13 | ) {
14 | // eslint-disable-next-line no-console
15 | console.warn(
16 | "Please implement 'AddEmbedActionResolver' and configure the Mod to use it"
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/packages/react/src/action-resolver-eth-personal-sign.ts:
--------------------------------------------------------------------------------
1 | import {
2 | EthPersonalSignActionResolverInit,
3 | EthPersonalSignActionResolverEvents,
4 | } from "@mod-protocol/core";
5 |
6 | export default function actionResolverEthPersonalSign(
7 | init: EthPersonalSignActionResolverInit,
8 | events: EthPersonalSignActionResolverEvents
9 | ) {
10 | // eslint-disable-next-line no-console
11 | console.warn(
12 | "Please implement 'EthPersonalSignActionResolver' and configure the Mod to use it"
13 | );
14 | }
15 |
--------------------------------------------------------------------------------
/packages/react/src/action-resolver-exit.ts:
--------------------------------------------------------------------------------
1 | // This resolver is called when the mod calls the "EXIT" action type.
2 | //
3 | // The action expected from the user is to close the mod and clear
4 | // any state related to it. For example, in react implementation, the user
5 | // can simply toggle a flag that chooses which mod is rendering atm.
6 | export default function actionResolverExit() {
7 | // eslint-disable-next-line no-console
8 | console.warn(
9 | "Please implement 'ExitActionResolver' and configure the Mod to use it"
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/packages/react/src/action-resolver-open-file.ts:
--------------------------------------------------------------------------------
1 | import {
2 | OpenFileActionResolverEvents,
3 | OpenFileActionResolverInit,
4 | } from "@mod-protocol/core";
5 |
6 | // This resolver is called when the mod calls the "OPENFILE" action type.
7 | //
8 | // The action expected from the user is to open a native file picker and select
9 | // file(s). The app should make sure that it only chooses the files with mime-types
10 | // specified in the accepts array and the app should also pick up to maxFiles amount
11 | // of files. The format the app should return is:
12 | //
13 | // { name: string; type: string; blob: any }
14 | //
15 | export default function actionResolverOpenFile(
16 | init: OpenFileActionResolverInit,
17 | events: OpenFileActionResolverEvents
18 | ) {
19 | // eslint-disable-next-line no-console
20 | console.warn(
21 | "Please implement 'OpenFileActionResolver' and configure the Mod to use it"
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/packages/react/src/action-resolver-open-link.ts:
--------------------------------------------------------------------------------
1 | import {
2 | OpenLinkActionResolverInit,
3 | OpenLinkActionResolverEvents,
4 | } from "@mod-protocol/core";
5 |
6 | export default function actionResolverOpenLink(
7 | init: OpenLinkActionResolverInit,
8 | events: OpenLinkActionResolverEvents
9 | ) {
10 | window.open(init.url, "_blank");
11 | events.onSuccess();
12 | }
13 |
--------------------------------------------------------------------------------
/packages/react/src/action-resolver-send-eth-transaction.ts:
--------------------------------------------------------------------------------
1 | import {
2 | SendEthTransactionActionResolverInit,
3 | SendEthTransactionActionResolverEvents,
4 | } from "@mod-protocol/core";
5 |
6 | export default function actionResolverSendEthTransaction(
7 | init: SendEthTransactionActionResolverInit,
8 | events: SendEthTransactionActionResolverEvents
9 | ) {
10 | // eslint-disable-next-line no-console
11 | console.warn(
12 | "Please implement 'SendEthTransactionActionResolver' and configure the Mod to use it"
13 | );
14 | }
15 |
--------------------------------------------------------------------------------
/packages/react/src/action-resolver-send-fc-frame-action.ts:
--------------------------------------------------------------------------------
1 | import {
2 | SendFcFrameActionResolverInit,
3 | SendFcFrameActionResolverEvents,
4 | } from "@mod-protocol/core";
5 |
6 | export default function actionResolverSendFcFrame(
7 | init: SendFcFrameActionResolverInit,
8 | events: SendFcFrameActionResolverEvents
9 | ) {
10 | // eslint-disable-next-line no-console
11 | console.warn(
12 | "Please implement 'SendFcFrameActionResolver' and configure the Mod to use it, using https://github.com/mod-protocol/mod/blob/a96dc6b4482d6ede3fee7baada9d5abe8bb430a1/examples/nextjs-shadcn/src/app/embeds.tsx#L51 as reference code."
13 | );
14 | }
15 |
--------------------------------------------------------------------------------
/packages/react/src/action-resolver-set-input.ts:
--------------------------------------------------------------------------------
1 | import {
2 | SetInputActionResolverInit,
3 | SetInputActionResolverEvents,
4 | } from "@mod-protocol/core";
5 |
6 | // This resolver is called when the mod calls the "SETINPUT" action type.
7 | //
8 | // The action expected from the user is to update the current input field with
9 | // the result of this action.
10 | export default function actionResolverSetInput(
11 | init: SetInputActionResolverInit,
12 | events: SetInputActionResolverEvents
13 | ) {
14 | // eslint-disable-next-line no-console
15 | console.warn(
16 | "Please implement 'SetInputActionResolver' and configure the Mod to use it"
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/packages/react/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "tsconfig/react-library.json",
3 | "include": ["."],
4 | "exclude": ["dist", "build", "node_modules"],
5 | "compilerOptions": {
6 | "jsx": "react-jsx"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/tiptap-extension-channel-mention/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | // This tells ESLint to load the config from the package `eslint-config-custom`
4 | extends: ["custom"],
5 | };
6 |
--------------------------------------------------------------------------------
/packages/tiptap-extension-channel-mention/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # @mod-protocol/tiptap-extension-channel-mention
2 |
3 | ## 0.1.0
4 |
5 | ### Minor Changes
6 |
7 | - cbfe215: add channel mentions
8 |
--------------------------------------------------------------------------------
/packages/tiptap-extension-channel-mention/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@mod-protocol/tiptap-extension-channel-mention",
3 | "version": "0.1.0",
4 | "private": true,
5 | "license": "MIT",
6 | "main": "src/index.ts",
7 | "dependencies": {
8 | "linkifyjs": "^4.1.0"
9 | },
10 | "peerDependencies": {
11 | "@tiptap/core": "^2.0.0",
12 | "@tiptap/pm": "^2.0.0"
13 | },
14 | "workspaces": [
15 | "configs/*",
16 | "packages/*",
17 | "examples/*"
18 | ],
19 | "scripts": {
20 | "clean": "rm -rf dist"
21 | }
22 | }
--------------------------------------------------------------------------------
/packages/tiptap-extension-channel-mention/src/index.ts:
--------------------------------------------------------------------------------
1 | import { Mention } from "./mention.js";
2 |
3 | export * from "./mention.js";
4 |
5 | export default Mention;
6 |
--------------------------------------------------------------------------------
/packages/tiptap-extension-channel-mention/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "tsconfig/react-library.json",
3 | "include": [
4 | "."
5 | ],
6 | "exclude": [
7 | "dist",
8 | "build",
9 | "node_modules"
10 | ]
11 | }
--------------------------------------------------------------------------------
/packages/tiptap-extension-link/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | // This tells ESLint to load the config from the package `eslint-config-custom`
4 | extends: ["custom"],
5 | };
6 |
--------------------------------------------------------------------------------
/packages/tiptap-extension-link/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # @mod-protocol/tiptap-extension-link
2 |
3 | ## 0.0.1
4 |
5 | ### Patch Changes
6 |
7 | - 0d400f0: fix: inconsistent textcut detection
8 |
--------------------------------------------------------------------------------
/packages/tiptap-extension-link/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@mod-protocol/tiptap-extension-link",
3 | "version": "0.0.1",
4 | "private": true,
5 | "license": "MIT",
6 | "main": "src/index.ts",
7 | "dependencies": {
8 | "linkifyjs": "^4.1.0"
9 | },
10 | "peerDependencies": {
11 | "@tiptap/core": "^2.0.0",
12 | "@tiptap/pm": "^2.0.0"
13 | },
14 | "workspaces": [
15 | "configs/*",
16 | "packages/*",
17 | "examples/*"
18 | ],
19 | "scripts": {
20 | "clean": "rm -rf dist"
21 | }
22 | }
--------------------------------------------------------------------------------
/packages/tiptap-extension-link/src/helpers/clickHandler.ts:
--------------------------------------------------------------------------------
1 | import { getAttributes } from '@tiptap/core'
2 | import { MarkType } from '@tiptap/pm/model'
3 | import { Plugin, PluginKey } from '@tiptap/pm/state'
4 |
5 | type ClickHandlerOptions = {
6 | type: MarkType
7 | }
8 |
9 | export function clickHandler(options: ClickHandlerOptions): Plugin {
10 | return new Plugin({
11 | key: new PluginKey('handleClickLink'),
12 | props: {
13 | handleClick: (view, pos, event) => {
14 | if (event.button !== 0) {
15 | return false
16 | }
17 |
18 | const eventTarget = event.target as HTMLElement
19 |
20 | if (eventTarget.nodeName !== 'A') {
21 | return false
22 | }
23 |
24 | const attrs = getAttributes(view.state, options.type.name)
25 | const link = (event.target as HTMLLinkElement)
26 |
27 | const href = link?.href ?? attrs.href
28 | const target = link?.target ?? attrs.target
29 |
30 | if (link && href) {
31 | if (view.editable) {
32 | window.open(href, target)
33 | }
34 |
35 | return true
36 | }
37 |
38 | return false
39 | },
40 | },
41 | })
42 | }
43 |
--------------------------------------------------------------------------------
/packages/tiptap-extension-link/src/helpers/textcuts.ts:
--------------------------------------------------------------------------------
1 | import { MultiToken, Options, tokenize } from "linkifyjs";
2 | import { LinkifyLink } from "../link";
3 |
4 | const textcuts =
5 | /(\b)([^ \.\n,]+)(\.)(twitter|github|lens|telegram|eth)($| |\n)/i;
6 |
7 | function isTokenTextcut(str: MultiToken): boolean {
8 | return textcuts.test(str.toString());
9 | }
10 |
11 | export function findLinksAndTextcuts(str: string): LinkifyLink[] {
12 | const tokens = tokenize(str);
13 | const filtered = [];
14 |
15 | for (let i = 0; i < tokens.length; i++) {
16 | const token = tokens[i];
17 | if (token.isLink) {
18 | filtered.push({
19 | ...token.toFormattedObject(new Options()),
20 | isTextcut: false,
21 | });
22 | } else if (isTokenTextcut(token)) {
23 | filtered.push({
24 | ...token.toFormattedObject(new Options()),
25 | isLink: true,
26 | isTextcut: true,
27 | href: "#",
28 | });
29 | }
30 | }
31 |
32 | return filtered;
33 | }
34 |
--------------------------------------------------------------------------------
/packages/tiptap-extension-link/src/index.ts:
--------------------------------------------------------------------------------
1 | import { Link } from "./link";
2 |
3 | export * from "./link";
4 |
5 | export default Link;
6 |
--------------------------------------------------------------------------------
/packages/tiptap-extension-link/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "tsconfig/react-library.json",
3 | "include": ["."],
4 | "exclude": ["dist", "build", "node_modules"]
5 | }
6 |
--------------------------------------------------------------------------------
/patches/@zoralabs+protocol-sdk+0.5.0.patch:
--------------------------------------------------------------------------------
1 | diff --git a/node_modules/@zoralabs/protocol-sdk/dist/index.js b/node_modules/@zoralabs/protocol-sdk/dist/index.js
2 | index 9b61491..b409135 100644
3 | --- a/node_modules/@zoralabs/protocol-sdk/dist/index.js
4 | +++ b/node_modules/@zoralabs/protocol-sdk/dist/index.js
5 | @@ -1192,8 +1192,9 @@ async function signAndSubmitPremint({
6 | if (!account) {
7 | throw new Error("No account provided");
8 | }
9 | +
10 | const signature = await walletClient.signTypedData({
11 | - account,
12 | + account: walletClient.account,
13 | ...premintTypedDataDefinition({
14 | verifyingContract,
15 | ...premintConfigAndVersion,
16 | diff --git a/node_modules/@zoralabs/protocol-sdk/package.json b/node_modules/@zoralabs/protocol-sdk/package.json
17 | index c91b841..1d59c07 100644
18 | --- a/node_modules/@zoralabs/protocol-sdk/package.json
19 | +++ b/node_modules/@zoralabs/protocol-sdk/package.json
20 | @@ -4,7 +4,7 @@
21 | "repository": "https://github.com/ourzora/zora-protocol",
22 | "license": "MIT",
23 | "main": "./dist/index.js",
24 | - "types": "./dist/index.d.ts",
25 | + "types": "./dist/src/index.d.ts",
26 | "type": "module",
27 | "scripts": {
28 | "build": "tsup",
29 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "rootDir": ".",
5 | "importHelpers": true,
6 | "allowJs": false,
7 | "allowSyntheticDefaultImports": true,
8 | "downlevelIteration": true,
9 | "esModuleInterop": true,
10 | "preserveSymlinks": true,
11 | "incremental": true,
12 | "composite": true,
13 | "declarationMap": true,
14 | "paths": {
15 | "@mods/*": ["./mods/*"]
16 | },
17 | "jsx": "react-jsx",
18 | "module": "ESNext",
19 | "moduleResolution": "node",
20 | "noEmitOnError": false,
21 | "noImplicitAny": false,
22 | "noImplicitReturns": false,
23 | "noUnusedLocals": false,
24 | "noUnusedParameters": false,
25 | "experimentalDecorators": true,
26 | "useUnknownInCatchVariables": false,
27 | "preserveConstEnums": true,
28 | "removeComments": false,
29 | "skipLibCheck": true,
30 | "sourceMap": false,
31 | "strictNullChecks": true,
32 | "target": "es2019",
33 | "types": ["node"],
34 | "lib": ["es2021", "dom", "esnext"],
35 | "strict": true,
36 | "noFallthroughCasesInSwitch": true,
37 | "noUncheckedIndexedAccess": true
38 | },
39 | "exclude": ["_"],
40 | "typeAcquisition": {
41 | "enable": true
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "globalDependencies": [
4 | "**/.env.*local"
5 | ],
6 | "pipeline": {
7 | "build": {
8 | "dependsOn": [
9 | "^build"
10 | ],
11 | "outputs": [
12 | ".next/**",
13 | "!.next/cache/**",
14 | "dist/**"
15 | ]
16 | },
17 | "lint": {},
18 | "dev": {
19 | "cache": false,
20 | "persistent": true
21 | }
22 | },
23 | "globalEnv": [
24 | "CHATGPT_API_SECRET",
25 | "NEYNAR_API_SECRET",
26 | "CHATGPT_ORGANIZATION_ID",
27 | "GIPHY_API_KEY",
28 | "INFURA_API_KEY",
29 | "INFURA_API_SECRET",
30 | "LIVEPEER_API_SECRET",
31 | "NEXT_PUBLIC_API_URL",
32 | "OPENSEA_API_KEY",
33 | "MICROLINK_API_KEY",
34 | "DATABASE_URL",
35 | "NEYNAR_API_KEY",
36 | "FARCASTER_DEVELOPER_FID",
37 | "FARCASTER_DEVELOPER_MNEMONIC",
38 | "NODE_ENV",
39 | "NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID",
40 | "NEXT_PUBLIC_URL",
41 | "NEXT_PUBLIC_HOST",
42 | "IMGUR_CLIENT_ID",
43 | "NEXT_PUBLIC_EXPERIMENTAL_MODS",
44 | "ZORA_ADMIN_PRIVATE_KEY",
45 | "NFT_STORAGE_API_KEY",
46 | "SKIP_INDEXED"
47 | ]
48 | }
--------------------------------------------------------------------------------