From f102c8033b89f96abe5f9049b6a0066f3c5cf01b Mon Sep 17 00:00:00 2001
From: jkcs <1778768609@qq.com>
Date: Thu, 18 Jul 2024 10:41:53 +0800
Subject: [PATCH 1/5] test equation
---
.../13-equation/.bnexample.json | 11 +
examples/03-ui-components/13-equation/App.tsx | 98 +++++++++
.../03-ui-components/13-equation/Equation.tsx | 206 ++++++++++++++++++
.../03-ui-components/13-equation/README.md | 11 +
.../03-ui-components/13-equation/index.html | 14 ++
.../03-ui-components/13-equation/main.tsx | 11 +
.../03-ui-components/13-equation/package.json | 40 ++++
.../03-ui-components/13-equation/styles.css | 74 +++++++
.../13-equation/tsconfig.json | 36 +++
.../13-equation/vite.config.ts | 32 +++
package-lock.json | 10 +-
playground/package.json | 3 +
playground/src/examples.gen.tsx | 27 +++
13 files changed, 570 insertions(+), 3 deletions(-)
create mode 100644 examples/03-ui-components/13-equation/.bnexample.json
create mode 100644 examples/03-ui-components/13-equation/App.tsx
create mode 100644 examples/03-ui-components/13-equation/Equation.tsx
create mode 100644 examples/03-ui-components/13-equation/README.md
create mode 100644 examples/03-ui-components/13-equation/index.html
create mode 100644 examples/03-ui-components/13-equation/main.tsx
create mode 100644 examples/03-ui-components/13-equation/package.json
create mode 100644 examples/03-ui-components/13-equation/styles.css
create mode 100644 examples/03-ui-components/13-equation/tsconfig.json
create mode 100644 examples/03-ui-components/13-equation/vite.config.ts
diff --git a/examples/03-ui-components/13-equation/.bnexample.json b/examples/03-ui-components/13-equation/.bnexample.json
new file mode 100644
index 0000000000..1aadbc234b
--- /dev/null
+++ b/examples/03-ui-components/13-equation/.bnexample.json
@@ -0,0 +1,11 @@
+{
+ "playground": true,
+ "docs": false,
+ "author": "matthewlipski",
+ "tags": ["Intermediate", "Blocks", "Custom Schemas", "Suggestion Menus", "Slash Menu"],
+ "dependencies": {
+ "katex": "^0.16.11",
+ "@types/katex": "^0.16.7",
+ "react-icons": "^5.2.1"
+ }
+}
diff --git a/examples/03-ui-components/13-equation/App.tsx b/examples/03-ui-components/13-equation/App.tsx
new file mode 100644
index 0000000000..567bb52e9a
--- /dev/null
+++ b/examples/03-ui-components/13-equation/App.tsx
@@ -0,0 +1,98 @@
+import {
+ BlockNoteSchema,
+ defaultInlineContentSpecs,
+ filterSuggestionItems,
+ insertOrUpdateBlock,
+} from "@blocknote/core";
+import "@blocknote/core/fonts/inter.css";
+import {
+ SuggestionMenuController,
+ getDefaultReactSlashMenuItems,
+ useCreateBlockNote,
+} from "@blocknote/react";
+import { BlockNoteView } from "@blocknote/mantine";
+import "@blocknote/mantine/style.css";
+
+import { LaTex } from "./Equation";
+
+// Our schema with block specs, which contain the configs and implementations for blocks
+// that we want our editor to use.
+const schema = BlockNoteSchema.create({
+ inlineContentSpecs: {
+ ...defaultInlineContentSpecs,
+ latex: LaTex,
+ },
+});
+
+// Slash menu item to insert an Alert block
+const insertLaTex = (editor: typeof schema.BlockNoteEditor) => ({
+ title: "latex",
+ key: "latex",
+ subtext: "Used for a top-level heading",
+ aliases: ["latex", "heading1", "h1"],
+ group: "Other",
+ onItemClick: () => {
+ insertOrUpdateBlock(editor, {
+ type: "paragraph",
+ content: [
+ {
+ type: "latex",
+ props: {
+ open: true,
+ },
+ content: "\\sqrt{a^2 + b^2}",
+ },
+ ],
+ });
+ },
+});
+
+export default function App() {
+ // Creates a new editor instance.
+ const editor = useCreateBlockNote({
+ schema,
+ initialContent: [
+ {
+ type: "paragraph",
+ content: [
+ "latex text editor ",
+ {
+ type: "latex",
+ content: "c = \\pm\\sqrt{a^2 + b^2}",
+ },
+ ],
+ },
+ {
+ type: "paragraph",
+ },
+ {
+ type: "paragraph",
+ content: [
+ {
+ type: "latex",
+ content:
+ "\\int \\frac{1}{\\sqrt{1-x^{2}}}\\mathrm{d}x= \\arcsin x +C",
+ },
+ ],
+ },
+ {
+ type: "paragraph",
+ },
+ ],
+ });
+
+ // Renders the editor instance.
+ return (
+
+
+ filterSuggestionItems(
+ [...getDefaultReactSlashMenuItems(editor), insertLaTex(editor)],
+ query
+ )
+ }
+ />
+
+ );
+}
diff --git a/examples/03-ui-components/13-equation/Equation.tsx b/examples/03-ui-components/13-equation/Equation.tsx
new file mode 100644
index 0000000000..8bd958ded7
--- /dev/null
+++ b/examples/03-ui-components/13-equation/Equation.tsx
@@ -0,0 +1,206 @@
+import { useComponentsContext } from "@blocknote/react";
+import { NodeView } from "prosemirror-view";
+import { BlockNoteEditor, propsToAttributes } from "@blocknote/core";
+import {
+ NodeViewProps,
+ NodeViewRenderer,
+ NodeViewWrapper,
+ ReactNodeViewRenderer,
+} from "@tiptap/react";
+import {
+ createStronglyTypedTiptapNode,
+ createInternalInlineContentSpec,
+} from "@blocknote/core";
+import { mergeAttributes } from "@tiptap/core";
+import { ChangeEvent, useEffect, useState, useRef } from "react";
+import katex from "katex";
+import "katex/dist/katex.min.css";
+import "./styles.css";
+
+function LaTexView() {
+ const nodeView:
+ | ((this: {
+ name: string;
+ options: any;
+ storage: any;
+ editor: any;
+ type: any;
+ parent: any;
+ }) => NodeViewRenderer)
+ | null = function () {
+ const BlockContent = (props: NodeViewProps & { selectionHack: any }) => {
+ /* eslint-disable react-hooks/rules-of-hooks */
+ const editor: BlockNoteEditor = this.options.editor;
+ const content = props.node.textContent;
+ const open = props.node.attrs.open;
+ const textareaRef = useRef(null);
+ const contentRef = useRef(null);
+ const [html, setHtml] = useState("");
+ const [loading, setLoading] = useState(false);
+ const Components = useComponentsContext()!;
+
+ useEffect(() => {
+ setLoading(true);
+ const html = katex.renderToString(content, {
+ throwOnError: false,
+ });
+ setHtml(html);
+ setLoading(false);
+ }, [content]);
+
+ useEffect(() => {
+ if (open) {
+ if (contentRef.current) {
+ contentRef.current.click();
+ }
+ setTimeout(() => {
+ if (textareaRef.current) {
+ textareaRef.current?.focus();
+ textareaRef.current?.setSelectionRange(
+ textareaRef.current.value.length,
+ textareaRef.current.value.length
+ );
+ }
+ });
+ }
+ }, [open]);
+
+ const handleChange = (e: ChangeEvent) => {
+ const val = e.target.value;
+ const pos = props.getPos?.();
+ const node = props.node;
+ const view = editor._tiptapEditor.view;
+
+ const tr = view.state.tr.replaceWith(
+ pos,
+ pos + node.nodeSize,
+ view.state.schema.nodes.latex.create(
+ {
+ ...node.attrs,
+ },
+ val ? view.state.schema.text(val) : null
+ )
+ );
+
+ view.dispatch(tr);
+ };
+
+ return (
+
+
+
+
+ {loading ? (
+ latex loading...
+ ) : (
+
+ )}
+
+
+
+
+
+
+
+ );
+ };
+
+ return (props) => {
+ if (!(props.editor as any).contentComponent) {
+ return {};
+ }
+ const ret = ReactNodeViewRenderer(BlockContent, {
+ stopEvent: () => true,
+ })(props) as NodeView;
+ ret.setSelection = (anchor, head) => {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ (ret as any).renderer.updateProps({
+ selectionHack: { anchor, head },
+ });
+ };
+
+ (ret as any).contentDOMElement = undefined;
+
+ const oldUpdated = ret.update!.bind(ret);
+ ret.update = (node, outerDeco, innerDeco) => {
+ const retAsAny = ret as any;
+ let decorations = retAsAny.decorations;
+ if (
+ retAsAny.decorations.decorations !== outerDeco ||
+ retAsAny.decorations.innerDecorations !== innerDeco
+ ) {
+ decorations = {
+ decorations: outerDeco,
+ innerDecorations: innerDeco,
+ };
+ }
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ return oldUpdated(node, decorations, undefined as any);
+ };
+ return ret;
+ };
+ };
+ return nodeView;
+}
+
+const propSchema = {
+ open: {
+ type: "boolean",
+ default: false,
+ },
+};
+
+const node = createStronglyTypedTiptapNode({
+ name: "latex",
+ inline: true,
+ group: "inline",
+ content: "inline*",
+ editable: true,
+ selectable: false,
+
+ addAttributes() {
+ return propsToAttributes(propSchema);
+ },
+
+ parseHTML() {
+ return [
+ {
+ tag: "latex",
+ priority: 200,
+ node: "latex",
+ },
+ ];
+ },
+
+ renderHTML({ HTMLAttributes }) {
+ return [
+ "latex",
+ mergeAttributes(HTMLAttributes, {
+ "data-content-type": this.name,
+ }),
+ 0,
+ ];
+ },
+
+ addNodeView: LaTexView(),
+});
+
+export const LaTex = createInternalInlineContentSpec(
+ {
+ content: "styled",
+ type: "latex",
+ propSchema: propSchema,
+ },
+ {
+ node,
+ }
+);
diff --git a/examples/03-ui-components/13-equation/README.md b/examples/03-ui-components/13-equation/README.md
new file mode 100644
index 0000000000..8c064c8b11
--- /dev/null
+++ b/examples/03-ui-components/13-equation/README.md
@@ -0,0 +1,11 @@
+# Alert Block
+
+In this example, we create a custom `Alert` block which is used to emphasize text. In addition, we create a Slash Menu item which inserts an `Alert` block.
+
+**Try it out:** Press the "/" key to open the Slash Menu and insert an `Alert` block!
+
+**Relevant Docs:**
+
+- [Custom Blocks](/docs/custom-schemas/custom-blocks)
+- [Changing Slash Menu Items](/docs/ui-components/suggestion-menus#changing-slash-menu-items)
+- [Editor Setup](/docs/editor-basics/setup)
\ No newline at end of file
diff --git a/examples/03-ui-components/13-equation/index.html b/examples/03-ui-components/13-equation/index.html
new file mode 100644
index 0000000000..6c6a363839
--- /dev/null
+++ b/examples/03-ui-components/13-equation/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+ Equation
+
+
+
+
+
+
diff --git a/examples/03-ui-components/13-equation/main.tsx b/examples/03-ui-components/13-equation/main.tsx
new file mode 100644
index 0000000000..f88b490fbd
--- /dev/null
+++ b/examples/03-ui-components/13-equation/main.tsx
@@ -0,0 +1,11 @@
+// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY
+import React from "react";
+import { createRoot } from "react-dom/client";
+import App from "./App";
+
+const root = createRoot(document.getElementById("root")!);
+root.render(
+
+
+
+);
diff --git a/examples/03-ui-components/13-equation/package.json b/examples/03-ui-components/13-equation/package.json
new file mode 100644
index 0000000000..f1428967c0
--- /dev/null
+++ b/examples/03-ui-components/13-equation/package.json
@@ -0,0 +1,40 @@
+{
+ "name": "@blocknote/example-equation",
+ "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY",
+ "private": true,
+ "version": "0.12.4",
+ "scripts": {
+ "start": "vite",
+ "dev": "vite",
+ "build": "tsc && vite build",
+ "preview": "vite preview",
+ "lint": "eslint . --max-warnings 0"
+ },
+ "dependencies": {
+ "@blocknote/core": "latest",
+ "@blocknote/react": "latest",
+ "@blocknote/ariakit": "latest",
+ "@blocknote/mantine": "latest",
+ "@blocknote/shadcn": "latest",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "katex": "^0.16.11",
+ "@types/katex": "^0.16.7",
+ "react-icons": "^5.2.1"
+ },
+ "devDependencies": {
+ "@types/react": "^18.0.25",
+ "@types/react-dom": "^18.0.9",
+ "@vitejs/plugin-react": "^4.0.4",
+ "eslint": "^8.10.0",
+ "vite": "^4.4.8"
+ },
+ "eslintConfig": {
+ "extends": [
+ "../../../.eslintrc.js"
+ ]
+ },
+ "eslintIgnore": [
+ "dist"
+ ]
+}
\ No newline at end of file
diff --git a/examples/03-ui-components/13-equation/styles.css b/examples/03-ui-components/13-equation/styles.css
new file mode 100644
index 0000000000..7296b28dce
--- /dev/null
+++ b/examples/03-ui-components/13-equation/styles.css
@@ -0,0 +1,74 @@
+.alert {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex-grow: 1;
+ border-radius: 4px;
+ min-height: 48px;
+ padding: 4px;
+}
+
+.alert[data-alert-type="warning"] {
+ background-color: #fff6e6;
+}
+
+.alert[data-alert-type="error"] {
+ background-color: #ffe6e6;
+}
+
+.alert[data-alert-type="info"] {
+ background-color: #e6ebff;
+}
+
+.alert[data-alert-type="success"] {
+ background-color: #e6ffe6;
+}
+
+[data-color-scheme="dark"] .alert[data-alert-type="warning"] {
+ background-color: #805d20;
+}
+
+[data-color-scheme="dark"] .alert[data-alert-type="error"] {
+ background-color: #802020;
+}
+
+[data-color-scheme="dark"] .alert[data-alert-type="info"] {
+ background-color: #203380;
+}
+
+[data-color-scheme="dark"] .alert[data-alert-type="success"] {
+ background-color: #208020;
+}
+
+.alert-icon-wrapper {
+ border-radius: 16px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin-left: 12px;
+ margin-right: 12px;
+ height: 18px;
+ width: 18px;
+ user-select: none;
+ cursor: pointer;
+}
+
+.alert-icon[data-alert-icon-type="warning"] {
+ color: #e69819
+}
+
+.alert-icon[data-alert-icon-type="error"] {
+ color: #d80d0d
+}
+
+.alert-icon[data-alert-icon-type="info"] {
+ color: #507aff
+}
+
+.alert-icon[data-alert-icon-type="success"] {
+ color: #0bc10b
+}
+
+.inline-content {
+ flex-grow: 1;
+}
\ No newline at end of file
diff --git a/examples/03-ui-components/13-equation/tsconfig.json b/examples/03-ui-components/13-equation/tsconfig.json
new file mode 100644
index 0000000000..1bd8ab3c57
--- /dev/null
+++ b/examples/03-ui-components/13-equation/tsconfig.json
@@ -0,0 +1,36 @@
+{
+ "__comment": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY",
+ "compilerOptions": {
+ "target": "ESNext",
+ "useDefineForClassFields": true,
+ "lib": [
+ "DOM",
+ "DOM.Iterable",
+ "ESNext"
+ ],
+ "allowJs": false,
+ "skipLibCheck": true,
+ "esModuleInterop": false,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "module": "ESNext",
+ "moduleResolution": "Node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+ "composite": true
+ },
+ "include": [
+ "."
+ ],
+ "__ADD_FOR_LOCAL_DEV_references": [
+ {
+ "path": "../../../packages/core/"
+ },
+ {
+ "path": "../../../packages/react/"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/examples/03-ui-components/13-equation/vite.config.ts b/examples/03-ui-components/13-equation/vite.config.ts
new file mode 100644
index 0000000000..f62ab20bc2
--- /dev/null
+++ b/examples/03-ui-components/13-equation/vite.config.ts
@@ -0,0 +1,32 @@
+// AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY
+import react from "@vitejs/plugin-react";
+import * as fs from "fs";
+import * as path from "path";
+import { defineConfig } from "vite";
+// import eslintPlugin from "vite-plugin-eslint";
+// https://vitejs.dev/config/
+export default defineConfig((conf) => ({
+ plugins: [react()],
+ optimizeDeps: {},
+ build: {
+ sourcemap: true,
+ },
+ resolve: {
+ alias:
+ conf.command === "build" ||
+ !fs.existsSync(path.resolve(__dirname, "../../packages/core/src"))
+ ? {}
+ : ({
+ // Comment out the lines below to load a built version of blocknote
+ // or, keep as is to load live from sources with live reload working
+ "@blocknote/core": path.resolve(
+ __dirname,
+ "../../packages/core/src/"
+ ),
+ "@blocknote/react": path.resolve(
+ __dirname,
+ "../../packages/react/src/"
+ ),
+ } as any),
+ },
+}));
diff --git a/package-lock.json b/package-lock.json
index 14e278c208..49ebbd228a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -17951,13 +17951,14 @@
"dev": true
},
"node_modules/katex": {
- "version": "0.16.10",
- "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.10.tgz",
- "integrity": "sha512-ZiqaC04tp2O5utMsl2TEZTXxa6WSC4yo0fv5ML++D3QZv/vx2Mct0mTlRx3O+uUkjfuAgOkzsCmq5MiUEsDDdA==",
+ "version": "0.16.11",
+ "resolved": "https://registry.npmmirror.com/katex/-/katex-0.16.11.tgz",
+ "integrity": "sha512-RQrI8rlHY92OLf3rho/Ts8i/XvjgguEjOkO1BEXcU3N8BqPpSzBNwV/G0Ukr+P/l3ivvJUE/Fa/CwbS6HesGNQ==",
"funding": [
"https://opencollective.com/katex",
"https://github.com/sponsors/katex"
],
+ "license": "MIT",
"dependencies": {
"commander": "^8.3.0"
},
@@ -29383,6 +29384,7 @@
"@liveblocks/client": "^1.10.0",
"@liveblocks/yjs": "^1.10.0",
"@mantine/core": "^7.10.1",
+ "@types/katex": "^0.16.7",
"@uppy/core": "^3.13.1",
"@uppy/dashboard": "^3.9.1",
"@uppy/drag-drop": "^3.1.1",
@@ -29394,6 +29396,7 @@
"@uppy/status-bar": "^3.1.1",
"@uppy/webcam": "^3.4.2",
"@uppy/xhr-upload": "^3.4.0",
+ "katex": "^0.16.11",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-icons": "^5.2.1",
@@ -29402,6 +29405,7 @@
"yjs": "^13.6.15"
},
"devDependencies": {
+ "@types/katex": "^0.16.7",
"@types/react": "^18.0.25",
"@types/react-dom": "^18.0.9",
"@vitejs/plugin-react": "^4.0.4",
diff --git a/playground/package.json b/playground/package.json
index 0ee1d1f6c7..45b44a2aeb 100644
--- a/playground/package.json
+++ b/playground/package.json
@@ -21,6 +21,7 @@
"@liveblocks/client": "^1.10.0",
"@liveblocks/yjs": "^1.10.0",
"@mantine/core": "^7.10.1",
+ "@types/katex": "^0.16.7",
"@uppy/core": "^3.13.1",
"@uppy/dashboard": "^3.9.1",
"@uppy/drag-drop": "^3.1.1",
@@ -32,6 +33,7 @@
"@uppy/status-bar": "^3.1.1",
"@uppy/webcam": "^3.4.2",
"@uppy/xhr-upload": "^3.4.0",
+ "katex": "^0.16.11",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-icons": "^5.2.1",
@@ -40,6 +42,7 @@
"yjs": "^13.6.15"
},
"devDependencies": {
+ "@types/katex": "^0.16.7",
"@types/react": "^18.0.25",
"@types/react-dom": "^18.0.9",
"@vitejs/plugin-react": "^4.0.4",
diff --git a/playground/src/examples.gen.tsx b/playground/src/examples.gen.tsx
index eaac162755..deab6d212c 100644
--- a/playground/src/examples.gen.tsx
+++ b/playground/src/examples.gen.tsx
@@ -554,6 +554,33 @@
"slug": "ui-components"
}
},
+ {
+ "projectSlug": "equation",
+ "fullSlug": "ui-components/equation",
+ "pathFromRoot": "examples/03-ui-components/13-equation",
+ "config": {
+ "playground": true,
+ "docs": true,
+ "author": "matthewlipski",
+ "tags": [
+ "Intermediate",
+ "Blocks",
+ "Custom Schemas",
+ "Suggestion Menus",
+ "Slash Menu"
+ ],
+ "dependencies": {
+ "katex": "^0.16.11",
+ "@types/katex": "^0.16.11",
+ "react-icons": "^5.2.1"
+ } as any
+ },
+ "title": "Equation",
+ "group": {
+ "pathFromRoot": "examples/03-ui-components",
+ "slug": "ui-components"
+ }
+ },
{
"projectSlug": "link-toolbar-buttons",
"fullSlug": "ui-components/link-toolbar-buttons",
From 536f3fb0b7a800ce1b539a36ec2b0aa566269487 Mon Sep 17 00:00:00 2001
From: jkcs <1778768609@qq.com>
Date: Fri, 19 Jul 2024 14:36:08 +0800
Subject: [PATCH 2/5] Inline Equation
---
examples/03-ui-components/13-equation/App.tsx | 49 ++--
.../03-ui-components/13-equation/Equation.tsx | 245 +++++++++++++-----
.../03-ui-components/13-equation/styles.css | 106 ++++----
3 files changed, 250 insertions(+), 150 deletions(-)
diff --git a/examples/03-ui-components/13-equation/App.tsx b/examples/03-ui-components/13-equation/App.tsx
index 567bb52e9a..f7e55ed27f 100644
--- a/examples/03-ui-components/13-equation/App.tsx
+++ b/examples/03-ui-components/13-equation/App.tsx
@@ -2,7 +2,6 @@ import {
BlockNoteSchema,
defaultInlineContentSpecs,
filterSuggestionItems,
- insertOrUpdateBlock,
} from "@blocknote/core";
import "@blocknote/core/fonts/inter.css";
import {
@@ -11,39 +10,36 @@ import {
useCreateBlockNote,
} from "@blocknote/react";
import { BlockNoteView } from "@blocknote/mantine";
+import { PiTextSuperscript } from "react-icons/pi";
import "@blocknote/mantine/style.css";
-import { LaTex } from "./Equation";
+import { InlineEquation } from "./Equation";
// Our schema with block specs, which contain the configs and implementations for blocks
// that we want our editor to use.
const schema = BlockNoteSchema.create({
inlineContentSpecs: {
...defaultInlineContentSpecs,
- latex: LaTex,
+ inlineEquation: InlineEquation,
},
});
// Slash menu item to insert an Alert block
const insertLaTex = (editor: typeof schema.BlockNoteEditor) => ({
- title: "latex",
- key: "latex",
- subtext: "Used for a top-level heading",
- aliases: ["latex", "heading1", "h1"],
+ icon: PiTextSuperscript,
+ title: "Inline Equation",
+ key: "inlineEquation",
+ subtext: "Insert mathematical symbols in text.",
+ aliases: ["equation", "latex", "katex"],
group: "Other",
onItemClick: () => {
- insertOrUpdateBlock(editor, {
- type: "paragraph",
- content: [
- {
- type: "latex",
- props: {
- open: true,
- },
- content: "\\sqrt{a^2 + b^2}",
- },
- ],
- });
+ const view = editor._tiptapEditor.view;
+ const pos = editor._tiptapEditor.state.selection.from;
+ const tr = view.state.tr.insert(
+ pos,
+ view.state.schema.nodes.inlineEquation.create(""),
+ );
+ view.dispatch(tr);
},
});
@@ -55,25 +51,22 @@ export default function App() {
{
type: "paragraph",
content: [
- "latex text editor ",
+ "This is an example inline equation",
{
- type: "latex",
+ type: "inlineEquation",
content: "c = \\pm\\sqrt{a^2 + b^2}",
},
],
},
+ {
+ type: "paragraph",
+ content: "Press the '/' key to open the Slash Menu and add another",
+ },
{
type: "paragraph",
},
{
type: "paragraph",
- content: [
- {
- type: "latex",
- content:
- "\\int \\frac{1}{\\sqrt{1-x^{2}}}\\mathrm{d}x= \\arcsin x +C",
- },
- ],
},
{
type: "paragraph",
diff --git a/examples/03-ui-components/13-equation/Equation.tsx b/examples/03-ui-components/13-equation/Equation.tsx
index 8bd958ded7..90339f2237 100644
--- a/examples/03-ui-components/13-equation/Equation.tsx
+++ b/examples/03-ui-components/13-equation/Equation.tsx
@@ -1,6 +1,6 @@
-import { useComponentsContext } from "@blocknote/react";
+import { useComponentsContext,useEditorContentOrSelectionChange } from "@blocknote/react";
import { NodeView } from "prosemirror-view";
-import { BlockNoteEditor, propsToAttributes } from "@blocknote/core";
+import { BlockNoteEditor } from "@blocknote/core";
import {
NodeViewProps,
NodeViewRenderer,
@@ -12,58 +12,169 @@ import {
createInternalInlineContentSpec,
} from "@blocknote/core";
import { mergeAttributes } from "@tiptap/core";
-import { ChangeEvent, useEffect, useState, useRef } from "react";
+import {ChangeEvent, useEffect, useState, useRef, useCallback, forwardRef, MouseEvent as ReactMouseEvent} from "react";
import katex from "katex";
+import { AiOutlineEnter } from "react-icons/ai";
import "katex/dist/katex.min.css";
import "./styles.css";
-function LaTexView() {
+
+const TextareaView = forwardRef((props: any, ref: any) => {
+ const { autofocus, ...rest } = props;
+ useEffect(() => {
+ if (autofocus && ref.current) {
+ ref.current.setSelectionRange(0, ref.current.value.length);
+ ref.current.focus()
+ }
+ }, [autofocus, ref]);
+
+ return (
+
+ )
+});
+
+function InlineEquationView() {
const nodeView:
- | ((this: {
- name: string;
- options: any;
- storage: any;
- editor: any;
- type: any;
- parent: any;
+ | ((this: {
+ name: string;
+ options: any;
+ storage: any;
+ editor: any;
+ type: any;
+ parent: any;
}) => NodeViewRenderer)
| null = function () {
const BlockContent = (props: NodeViewProps & { selectionHack: any }) => {
/* eslint-disable react-hooks/rules-of-hooks */
const editor: BlockNoteEditor = this.options.editor;
const content = props.node.textContent;
- const open = props.node.attrs.open;
+ const nodeSize = props.node.nodeSize;
const textareaRef = useRef(null);
const contentRef = useRef(null);
+ const containerRef = useRef(null);
const [html, setHtml] = useState("");
- const [loading, setLoading] = useState(false);
+ const [focus, setFocus] = useState(!content);
+ const [curEdge, setCurEdge] = useState(!content);
const Components = useComponentsContext()!;
+ const getTextareaEdge = () => {
+ const $textarea = textareaRef.current;
+ if (!$textarea) {
+ return {};
+ }
+
+ return {
+ isLeftEdge: $textarea.selectionStart === 0 && $textarea.selectionEnd === 0,
+ isRightEdge: $textarea.selectionStart === $textarea.value.length && $textarea.selectionEnd === $textarea.value.length,
+ }
+ }
+
useEffect(() => {
- setLoading(true);
const html = katex.renderToString(content, {
throwOnError: false,
});
setHtml(html);
- setLoading(false);
}, [content]);
+ useEditorContentOrSelectionChange(() => {
+ const pos = props.getPos?.();
+ const courPos = editor._tiptapEditor.state.selection.from;
+ const selection = editor.getSelection();
+
+ setCurEdge(!selection && (courPos === pos + nodeSize || courPos === pos));
+ })
+
+
useEffect(() => {
- if (open) {
- if (contentRef.current) {
- contentRef.current.click();
+ if (focus) {
+ contentRef.current?.click();
+ }
+ }, [focus]);
+
+ const handleEnter = useCallback((event: ReactMouseEvent | KeyboardEvent) => {
+ const pos = props.getPos?.();
+ event.preventDefault();
+ if (!content) {
+ const node = props.node;
+ const view = editor._tiptapEditor.view;
+
+ const tr = view.state.tr.delete(
+ pos,
+ pos + node.nodeSize
+ );
+
+ view.dispatch(tr);
+ editor._tiptapEditor.commands.setTextSelection(pos);
+ } else {
+ editor._tiptapEditor.commands.setTextSelection(pos + nodeSize);
+ }
+ editor.focus();
+ setFocus(false);
+ setCurEdge(true);
+ }, [content, editor, nodeSize, props]);
+
+ const handleMenuNavigationKeys = useCallback((event: KeyboardEvent) => {
+ const textareaEdge = getTextareaEdge();
+ const pos = props.getPos?.();
+ const courPos = editor._tiptapEditor.state.selection.from;
+
+ if (event.key === "ArrowLeft") {
+ if (courPos === pos + nodeSize && !focus) {
+ setFocus(true);
}
- setTimeout(() => {
- if (textareaRef.current) {
- textareaRef.current?.focus();
- textareaRef.current?.setSelectionRange(
- textareaRef.current.value.length,
- textareaRef.current.value.length
- );
- }
- });
+ if (textareaEdge.isLeftEdge) {
+ event.preventDefault();
+ editor.focus();
+ editor._tiptapEditor.commands.setTextSelection(pos);
+ setFocus(false);
+ }
+ return true;
+ }
+
+ if (event.key === "ArrowRight") {
+ if (courPos === pos && !focus) {
+ setFocus(true);
+ }
+ if (textareaEdge.isRightEdge) {
+ event.preventDefault();
+ editor.focus();
+ editor._tiptapEditor.commands.setTextSelection(pos + nodeSize);
+ setFocus(false)
+ }
+ return true;
+ }
+
+ if (event.key === "Enter" && focus) {
+ handleEnter(event);
+ return true;
+ }
+
+ return false;
+ }, [editor, focus, handleEnter, nodeSize, props]);
+
+ useEffect(() => {
+ if (focus || curEdge) {
+ editor.domElement.addEventListener(
+ "keydown",
+ handleMenuNavigationKeys,
+ true
+ );
}
- }, [open]);
+
+ return () => {
+ editor.domElement.removeEventListener(
+ "keydown",
+ handleMenuNavigationKeys,
+ true
+ );
+ };
+ }, [editor.domElement, focus, handleMenuNavigationKeys, curEdge]);
const handleChange = (e: ChangeEvent) => {
const val = e.target.value;
@@ -74,7 +185,7 @@ function LaTexView() {
const tr = view.state.tr.replaceWith(
pos,
pos + node.nodeSize,
- view.state.schema.nodes.latex.create(
+ view.state.schema.nodes.inlineEquation.create(
{
...node.attrs,
},
@@ -83,31 +194,51 @@ function LaTexView() {
);
view.dispatch(tr);
+ setFocus(true);
};
+ useEffect(() => {
+ const handleClickOutside = (event: MouseEvent) => {
+ if (containerRef.current && !containerRef.current.contains(event.target as Node)) {
+ setFocus(false);
+ }
+ };
+
+ document.addEventListener('pointerup', handleClickOutside, true);
+ return () => {
+ document.removeEventListener('pointerup', handleClickOutside, true);
+ };
+ }, []);
+
return (
-
-
+
+
-
- {loading ? (
- latex loading...
+
+ {!content ? (
+ setFocus(true)} className={"equation-empty"}>
+ New Equation
+
) : (
setFocus(true)} className={"equation-content"}
dangerouslySetInnerHTML={{ __html: html }}>
)}
-
+ className={"bn-popover-content bn-form-popover"}
+ variant={"form-popover"}>
+
@@ -152,38 +283,27 @@ function LaTexView() {
return nodeView;
}
-const propSchema = {
- open: {
- type: "boolean",
- default: false,
- },
-};
-
const node = createStronglyTypedTiptapNode({
- name: "latex",
+ name: "inlineEquation",
inline: true,
group: "inline",
content: "inline*",
editable: true,
selectable: false,
-
- addAttributes() {
- return propsToAttributes(propSchema);
- },
-
parseHTML() {
return [
{
- tag: "latex",
+ tag: "inlineEquation",
priority: 200,
- node: "latex",
+ node: "inlineEquation",
},
];
},
+ // @ts-ignore
renderHTML({ HTMLAttributes }) {
return [
- "latex",
+ "inlineEquation",
mergeAttributes(HTMLAttributes, {
"data-content-type": this.name,
}),
@@ -191,14 +311,13 @@ const node = createStronglyTypedTiptapNode({
];
},
- addNodeView: LaTexView(),
+ addNodeView: InlineEquationView(),
});
-export const LaTex = createInternalInlineContentSpec(
+export const InlineEquation = createInternalInlineContentSpec(
{
content: "styled",
- type: "latex",
- propSchema: propSchema,
+ type: "inlineEquation",
},
{
node,
diff --git a/examples/03-ui-components/13-equation/styles.css b/examples/03-ui-components/13-equation/styles.css
index 7296b28dce..b1793b3e9c 100644
--- a/examples/03-ui-components/13-equation/styles.css
+++ b/examples/03-ui-components/13-equation/styles.css
@@ -1,74 +1,62 @@
-.alert {
- display: flex;
- justify-content: center;
- align-items: center;
- flex-grow: 1;
+.equation-content {
+ caret-color: rgb(55, 53, 47);
+ padding: 2px 2px;
border-radius: 4px;
- min-height: 48px;
- padding: 4px;
+ transform: translate3d(-4px, 0, 0);
+ margin-right: -4px;
+ white-space: pre;
}
-.alert[data-alert-type="warning"] {
- background-color: #fff6e6;
-}
-
-.alert[data-alert-type="error"] {
- background-color: #ffe6e6;
-}
-.alert[data-alert-type="info"] {
- background-color: #e6ebff;
+.equation.focus .equation-content {
+ background: rgba(37, 135, 231, 0.12);
}
-.alert[data-alert-type="success"] {
- background-color: #e6ffe6;
-}
-
-[data-color-scheme="dark"] .alert[data-alert-type="warning"] {
- background-color: #805d20;
-}
-
-[data-color-scheme="dark"] .alert[data-alert-type="error"] {
- background-color: #802020;
+.equation .equation-empty {
+ white-space: nowrap;
+ font-size: 12px;
+ background: rgb(207, 207, 207);
+ caret-color: rgb(55, 53, 47);
+ vertical-align: top;
+ padding: 2px 4px;
+ border-radius: 4px;
+ transform: translate3d(-4px, 0, 0);
+ margin-right: -8px;
}
-[data-color-scheme="dark"] .alert[data-alert-type="info"] {
- background-color: #203380;
+.equation-label {
+ display: flex;
+ align-items: flex-start;
+ justify-content: flex-start;
+ padding: 4px;
}
-[data-color-scheme="dark"] .alert[data-alert-type="success"] {
- background-color: #208020;
+.equation-textarea {
+ min-width: 200px;
+ margin-right: 10px;
+ outline: none;
+ border: none;
+ font-size: 14px;
}
-.alert-icon-wrapper {
- border-radius: 16px;
- display: flex;
- justify-content: center;
- align-items: center;
- margin-left: 12px;
- margin-right: 12px;
- height: 18px;
- width: 18px;
+.equation-enter {
user-select: none;
cursor: pointer;
-}
-
-.alert-icon[data-alert-icon-type="warning"] {
- color: #e69819
-}
-
-.alert-icon[data-alert-icon-type="error"] {
- color: #d80d0d
-}
-
-.alert-icon[data-alert-icon-type="info"] {
- color: #507aff
-}
-
-.alert-icon[data-alert-icon-type="success"] {
- color: #0bc10b
-}
-
-.inline-content {
- flex-grow: 1;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ white-space: nowrap;
+ height: 28px;
+ border-radius: 4px;
+ box-shadow: rgba(15, 15, 15, 0.1) 0 0 0 1px inset, rgba(15, 15, 15, 0.1) 0 1px 2px;
+ background: rgb(35, 131, 226);
+ color: white;
+ fill: white;
+ line-height: 1.2;
+ padding-left: 12px;
+ padding-right: 12px;
+ font-size: 14px;
+ font-weight: 500;
+ align-self: flex-start;
}
\ No newline at end of file
From aafcc4f5ec49c1e74ca21e62dfb614277032ca01 Mon Sep 17 00:00:00 2001
From: jkcs <1778768609@qq.com>
Date: Fri, 19 Jul 2024 14:49:12 +0800
Subject: [PATCH 3/5] Inline Equation
---
.../03-ui-components/13-equation/.bnexample.json | 2 +-
examples/03-ui-components/13-equation/App.tsx | 6 +++---
examples/03-ui-components/13-equation/index.html | 2 +-
playground/src/examples.gen.tsx | 12 ++++++------
4 files changed, 11 insertions(+), 11 deletions(-)
diff --git a/examples/03-ui-components/13-equation/.bnexample.json b/examples/03-ui-components/13-equation/.bnexample.json
index 1aadbc234b..1e4b6d5d3e 100644
--- a/examples/03-ui-components/13-equation/.bnexample.json
+++ b/examples/03-ui-components/13-equation/.bnexample.json
@@ -2,7 +2,7 @@
"playground": true,
"docs": false,
"author": "matthewlipski",
- "tags": ["Intermediate", "Blocks", "Custom Schemas", "Suggestion Menus", "Slash Menu"],
+ "tags": ["Equation", "Inline Equation", "Custom Schemas", "Latex", "Slash Menu"],
"dependencies": {
"katex": "^0.16.11",
"@types/katex": "^0.16.7",
diff --git a/examples/03-ui-components/13-equation/App.tsx b/examples/03-ui-components/13-equation/App.tsx
index f7e55ed27f..20df20adbf 100644
--- a/examples/03-ui-components/13-equation/App.tsx
+++ b/examples/03-ui-components/13-equation/App.tsx
@@ -10,7 +10,7 @@ import {
useCreateBlockNote,
} from "@blocknote/react";
import { BlockNoteView } from "@blocknote/mantine";
-import { PiTextSuperscript } from "react-icons/pi";
+import { RiFormula } from "react-icons/ri";
import "@blocknote/mantine/style.css";
import { InlineEquation } from "./Equation";
@@ -26,7 +26,7 @@ const schema = BlockNoteSchema.create({
// Slash menu item to insert an Alert block
const insertLaTex = (editor: typeof schema.BlockNoteEditor) => ({
- icon: PiTextSuperscript,
+ icon: ,
title: "Inline Equation",
key: "inlineEquation",
subtext: "Insert mathematical symbols in text.",
@@ -51,7 +51,7 @@ export default function App() {
{
type: "paragraph",
content: [
- "This is an example inline equation",
+ "This is an example inline equation ",
{
type: "inlineEquation",
content: "c = \\pm\\sqrt{a^2 + b^2}",
diff --git a/examples/03-ui-components/13-equation/index.html b/examples/03-ui-components/13-equation/index.html
index 6c6a363839..84532134d8 100644
--- a/examples/03-ui-components/13-equation/index.html
+++ b/examples/03-ui-components/13-equation/index.html
@@ -5,7 +5,7 @@
- Equation
+ Alert Block
diff --git a/playground/src/examples.gen.tsx b/playground/src/examples.gen.tsx
index deab6d212c..b7364ed1a9 100644
--- a/playground/src/examples.gen.tsx
+++ b/playground/src/examples.gen.tsx
@@ -560,22 +560,22 @@
"pathFromRoot": "examples/03-ui-components/13-equation",
"config": {
"playground": true,
- "docs": true,
+ "docs": false,
"author": "matthewlipski",
"tags": [
- "Intermediate",
- "Blocks",
+ "Equation",
+ "Inline Equation",
"Custom Schemas",
- "Suggestion Menus",
+ "Latex",
"Slash Menu"
],
"dependencies": {
"katex": "^0.16.11",
- "@types/katex": "^0.16.11",
+ "@types/katex": "^0.16.7",
"react-icons": "^5.2.1"
} as any
},
- "title": "Equation",
+ "title": "Alert Block",
"group": {
"pathFromRoot": "examples/03-ui-components",
"slug": "ui-components"
From ca63b7ea2361200cac4aa65bd99e8809d69b88c9 Mon Sep 17 00:00:00 2001
From: jkcs <1778768609@qq.com>
Date: Fri, 19 Jul 2024 15:00:12 +0800
Subject: [PATCH 4/5] fix: type error
---
examples/03-ui-components/13-equation/App.tsx | 2 +-
examples/03-ui-components/13-equation/Equation.tsx | 1 +
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/examples/03-ui-components/13-equation/App.tsx b/examples/03-ui-components/13-equation/App.tsx
index 20df20adbf..42c3fcc292 100644
--- a/examples/03-ui-components/13-equation/App.tsx
+++ b/examples/03-ui-components/13-equation/App.tsx
@@ -37,7 +37,7 @@ const insertLaTex = (editor: typeof schema.BlockNoteEditor) => ({
const pos = editor._tiptapEditor.state.selection.from;
const tr = view.state.tr.insert(
pos,
- view.state.schema.nodes.inlineEquation.create(""),
+ view.state.schema.nodes.inlineEquation.create(),
);
view.dispatch(tr);
},
diff --git a/examples/03-ui-components/13-equation/Equation.tsx b/examples/03-ui-components/13-equation/Equation.tsx
index 90339f2237..7eace7beb3 100644
--- a/examples/03-ui-components/13-equation/Equation.tsx
+++ b/examples/03-ui-components/13-equation/Equation.tsx
@@ -318,6 +318,7 @@ export const InlineEquation = createInternalInlineContentSpec(
{
content: "styled",
type: "inlineEquation",
+ propSchema: {},
},
{
node,
From 41948a5ca794afb48b750800e84935e1f4ac37f4 Mon Sep 17 00:00:00 2001
From: jkcs <1778768609@qq.com>
Date: Fri, 19 Jul 2024 15:17:18 +0800
Subject: [PATCH 5/5] fix: title error
---
examples/03-ui-components/13-equation/README.md | 6 +++---
examples/03-ui-components/13-equation/index.html | 2 +-
playground/src/examples.gen.tsx | 2 +-
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/examples/03-ui-components/13-equation/README.md b/examples/03-ui-components/13-equation/README.md
index 8c064c8b11..2e140b5666 100644
--- a/examples/03-ui-components/13-equation/README.md
+++ b/examples/03-ui-components/13-equation/README.md
@@ -1,8 +1,8 @@
-# Alert Block
+# Inline Equation
-In this example, we create a custom `Alert` block which is used to emphasize text. In addition, we create a Slash Menu item which inserts an `Alert` block.
+In this example, we create a custom `Inline Equation`
-**Try it out:** Press the "/" key to open the Slash Menu and insert an `Alert` block!
+**Try it out:** Press the "/" key to open the Slash Menu and insert an `Equation` block!
**Relevant Docs:**
diff --git a/examples/03-ui-components/13-equation/index.html b/examples/03-ui-components/13-equation/index.html
index 84532134d8..772555bb29 100644
--- a/examples/03-ui-components/13-equation/index.html
+++ b/examples/03-ui-components/13-equation/index.html
@@ -5,7 +5,7 @@
- Alert Block
+ Inline Equation
diff --git a/playground/src/examples.gen.tsx b/playground/src/examples.gen.tsx
index b7364ed1a9..e6e3d9961d 100644
--- a/playground/src/examples.gen.tsx
+++ b/playground/src/examples.gen.tsx
@@ -575,7 +575,7 @@
"react-icons": "^5.2.1"
} as any
},
- "title": "Alert Block",
+ "title": "Inline Equation",
"group": {
"pathFromRoot": "examples/03-ui-components",
"slug": "ui-components"