diff --git a/core/actions/_lib/types.ts b/core/actions/_lib/zodTypes.ts
similarity index 100%
rename from core/actions/_lib/types.ts
rename to core/actions/_lib/zodTypes.ts
diff --git a/core/actions/email/action.ts b/core/actions/email/action.ts
index 24a034596..05ee43c09 100644
--- a/core/actions/email/action.ts
+++ b/core/actions/email/action.ts
@@ -2,7 +2,7 @@ import * as z from "zod";
import { Mail } from "ui/icon";
-import { markdown } from "../_lib/types";
+import { markdown } from "../_lib/zodTypes";
import * as corePubFields from "../corePubFields";
import { defineAction } from "../types";
@@ -34,7 +34,7 @@ export const action = defineAction({
.optional(),
})
.optional(),
- pubFields: [corePubFields.title],
+ pubFields: [],
icon: Mail,
});
diff --git a/packages/ui/package.json b/packages/ui/package.json
index 5f56d7fff..def5e75ee 100644
--- a/packages/ui/package.json
+++ b/packages/ui/package.json
@@ -250,6 +250,7 @@
"@lexical/markdown": "^0.15.0",
"@lexical/react": "^0.15.0",
"@lexical/rich-text": "^0.15.0",
+ "@lexical/utils": "^0.15.0",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-alert-dialog": "^1.0.5",
"@radix-ui/react-avatar": "^1.0.3",
diff --git a/packages/ui/src/auto-form/fields/markdown/MarkdownEditor.tsx b/packages/ui/src/auto-form/fields/markdown/MarkdownEditor.tsx
index 4dae135d3..aae96c94d 100644
--- a/packages/ui/src/auto-form/fields/markdown/MarkdownEditor.tsx
+++ b/packages/ui/src/auto-form/fields/markdown/MarkdownEditor.tsx
@@ -19,9 +19,20 @@ import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin";
import { HeadingNode, QuoteNode } from "@lexical/rich-text";
import { EditorState } from "lexical";
+import { cn } from "utils";
+
+import { FormControl, FormItem, FormMessage } from "../../../form";
+import AutoFormDescription from "../../common/description";
+import AutoFormLabel from "../../common/label";
+import AutoFormTooltip from "../../common/tooltip";
import { AutoFormInputComponentProps } from "../../types";
+import { TokenProvider } from "./TokenContext";
+import { TokenNode } from "./TokenNode";
+import { TokenPlugin } from "./TokenPlugin";
-const theme = {};
+const theme = {
+ token: "token",
+};
function onError(error: unknown) {
console.error(error);
@@ -35,6 +46,7 @@ const NODES = [
ListNode,
ListItemNode,
QuoteNode,
+ TokenNode,
];
const makeSyntheticChangeEvent = (value: string) => {
@@ -46,6 +58,8 @@ const makeSyntheticChangeEvent = (value: string) => {
};
export const MarkdownEditor = (props: AutoFormInputComponentProps) => {
+ const { showLabel: _showLabel, ...fieldPropsWithoutShowLabel } = props.fieldProps;
+ const showLabel = _showLabel === undefined ? true : _showLabel;
const initialValue = React.useMemo(() => props.field.value ?? "", []);
const initialConfig = React.useMemo(() => {
return {
@@ -61,23 +75,60 @@ export const MarkdownEditor = (props: AutoFormInputComponentProps) => {
(editorState: EditorState) => {
editorState.read(() => {
const markdown = $convertToMarkdownString(TRANSFORMERS);
- props.fieldProps.onChange(makeSyntheticChangeEvent(markdown));
+ fieldPropsWithoutShowLabel.onChange(makeSyntheticChangeEvent(markdown));
});
},
- [props.fieldProps.onChange]
+ [fieldPropsWithoutShowLabel.onChange]
);
return (
-
- }
- placeholder={Enter some text...
}
- ErrorBoundary={LexicalErrorBoundary}
- />
-
-
-
-
-
+
+
+ {showLabel && (
+ <>
+
+ {props.description && (
+
+ )}
+ >
+ )}
+
+
+
+
+ }
+ placeholder={null}
+ ErrorBoundary={LexicalErrorBoundary}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
);
};
diff --git a/packages/ui/src/auto-form/fields/markdown/TokenContext.tsx b/packages/ui/src/auto-form/fields/markdown/TokenContext.tsx
new file mode 100644
index 000000000..0f9bf720a
--- /dev/null
+++ b/packages/ui/src/auto-form/fields/markdown/TokenContext.tsx
@@ -0,0 +1,19 @@
+import React, { createContext, PropsWithChildren, useContext } from "react";
+
+export type TokenContext = {
+ staticTokens: string[];
+ dynamicTokens: RegExp | null;
+};
+
+export const TokenContext = createContext({
+ staticTokens: [],
+ dynamicTokens: /^.$/,
+});
+
+export const useTokenContext = () => {
+ return useContext(TokenContext);
+};
+
+export function TokenProvider(props: PropsWithChildren) {
+ return {props.children};
+}
diff --git a/packages/ui/src/auto-form/fields/markdown/TokenNode.ts b/packages/ui/src/auto-form/fields/markdown/TokenNode.ts
new file mode 100644
index 000000000..1f790ec9b
--- /dev/null
+++ b/packages/ui/src/auto-form/fields/markdown/TokenNode.ts
@@ -0,0 +1,57 @@
+import type { EditorConfig, LexicalNode, NodeKey, SerializedTextNode } from "lexical";
+
+import { addClassNamesToElement } from "@lexical/utils";
+import { $applyNodeReplacement, TextNode } from "lexical";
+
+export class TokenNode extends TextNode {
+ static getType(): string {
+ return "token";
+ }
+
+ static clone(node: TokenNode): TokenNode {
+ return new TokenNode(node.__text, node.__key);
+ }
+
+ constructor(text: string, key?: NodeKey) {
+ super(text, key);
+ console.log(text, key);
+ }
+
+ createDOM(config: EditorConfig): HTMLElement {
+ const element = super.createDOM(config);
+ addClassNamesToElement(element, config.theme.token);
+ return element;
+ }
+
+ static importJSON(serializedNode: SerializedTextNode): TokenNode {
+ const node = $createTokenNode(serializedNode.text);
+ node.setFormat(serializedNode.format);
+ node.setDetail(serializedNode.detail);
+ node.setMode(serializedNode.mode);
+ node.setStyle(serializedNode.style);
+ return node;
+ }
+
+ exportJSON(): SerializedTextNode {
+ return {
+ ...super.exportJSON(),
+ type: "token",
+ };
+ }
+
+ canInsertTextBefore(): boolean {
+ return false;
+ }
+
+ isTextEntity(): true {
+ return true;
+ }
+}
+
+export function $createTokenNode(text = ""): TokenNode {
+ return $applyNodeReplacement(new TokenNode(text));
+}
+
+export function $isTokenNode(node: LexicalNode | null | undefined): node is TokenNode {
+ return node instanceof TokenNode;
+}
diff --git a/packages/ui/src/auto-form/fields/markdown/TokenPlugin.tsx b/packages/ui/src/auto-form/fields/markdown/TokenPlugin.tsx
new file mode 100644
index 000000000..4dc2af690
--- /dev/null
+++ b/packages/ui/src/auto-form/fields/markdown/TokenPlugin.tsx
@@ -0,0 +1,50 @@
+import { useCallback, useEffect, useMemo } from "react";
+import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
+import { useLexicalTextEntity } from "@lexical/react/useLexicalTextEntity";
+import { TextNode } from "lexical";
+
+import { useTokenContext } from "./TokenContext";
+import { $createTokenNode, TokenNode } from "./TokenNode";
+
+const boundary = "^|$|[^&/" + "*" + "]";
+
+const $createTokenNode_ = (textNode: TextNode): TokenNode => {
+ return $createTokenNode(textNode.getTextContent());
+};
+
+export function TokenPlugin() {
+ const [editor] = useLexicalComposerContext();
+ const { staticTokens, dynamicTokens } = useTokenContext();
+
+ const REGEX = useMemo(
+ () => new RegExp(`(${boundary})\{(${staticTokens.join("|")})\}`, "i"),
+ [staticTokens]
+ );
+
+ useEffect(() => {
+ if (!editor.hasNodes([TokenNode])) {
+ throw new Error("TokenPlugin: TokenNode not registered on editor");
+ }
+ }, [editor]);
+
+ const getTokenMatch = useCallback((text: string) => {
+ const matchArr = REGEX.exec(text);
+
+ if (matchArr === null) {
+ return null;
+ }
+
+ const tokenLength = matchArr[2].length + 2; // add two for the curly braces
+ const startOffset = matchArr.index + matchArr[1].length;
+ const endOffset = startOffset + tokenLength;
+
+ return {
+ end: endOffset,
+ start: startOffset,
+ };
+ }, []);
+
+ useLexicalTextEntity(getTokenMatch, TokenNode, $createTokenNode_);
+
+ return null;
+}
diff --git a/packages/ui/styles.css b/packages/ui/styles.css
index 6a310cad4..f830cc6d9 100644
--- a/packages/ui/styles.css
+++ b/packages/ui/styles.css
@@ -49,3 +49,11 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
+
+.editor .token {
+ color: blue;
+}
+
+.editor.markdown {
+ min-height: 200px;
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 2dae963e2..bd6bb76ea 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -697,6 +697,9 @@ importers:
'@lexical/rich-text':
specifier: ^0.15.0
version: 0.15.0
+ '@lexical/utils':
+ specifier: ^0.15.0
+ version: 0.15.0
'@radix-ui/react-accordion':
specifier: ^1.1.2
version: 1.1.2(@types/react@18.2.78)(react-dom@18.2.0)(react@18.2.0)