-
-
Notifications
You must be signed in to change notification settings - Fork 491
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
simple latex block #818
Closed
Closed
simple latex block #818
Changes from 1 commit
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
61f3ec7
fix: node conversion
jkcs fa20a9e
fix \n
jkcs 2399d24
test node conversions
jkcs 3d8e4ee
fix: command `BNUpdateBlock`
jkcs d4f9fcf
Add a test for customBlock with line breaks.
jkcs 23d1c5b
add test for BNUpdateBlock with line break
jkcs 27a04fb
latex demo
jkcs d11317d
latex
jkcs 385bb12
fix: gen in windows
jkcs 9a8871b
Merge branch 'main' into feature/latex
jkcs 0717c9a
change import sort
jkcs e586e07
fix MR error
jkcs 1809a21
change latex to inline content
jkcs b3309f4
fix error
jkcs File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
latex demo
- Loading branch information
commit 27a04fb7599a9e16fbec565f71f3026379b1cba4
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ | ||
"playground": true, | ||
"docs": true, | ||
"author": "matthewlipski", | ||
"tags": ["Intermediate", "Blocks", "Custom Schemas"], | ||
"dependencies": { | ||
"@mantine/core": "^7.7.1", | ||
"react-icons": "^5.2.1" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import { | ||
BlockNoteSchema, | ||
defaultBlockSpecs, | ||
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 "./LaTex"; | ||
|
||
// 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: { | ||
latex: "", | ||
open: true, | ||
}, | ||
content: 'c = \\pm\\sqrt{a^2 + b^2}' | ||
}, | ||
], | ||
}); | ||
}, | ||
}); | ||
|
||
export default function App() { | ||
// Creates a new editor instance. | ||
const editor = useCreateBlockNote({ | ||
schema, | ||
initialContent: [ | ||
{ | ||
type: "paragraph", | ||
content: [ | ||
"I enjoy working with ", | ||
{ | ||
type: "latex", | ||
content: "c = \\pm\\sqrt{a^2 + b^2}", | ||
}, | ||
], | ||
}, | ||
{ | ||
type: "paragraph", | ||
content: "Press the '/' key to open the Slash Menu and add another", | ||
}, | ||
{ | ||
type: "paragraph", | ||
}, | ||
], | ||
}); | ||
|
||
// Renders the editor instance. | ||
return ( | ||
<BlockNoteView editor={editor} slashMenu={false}> | ||
<SuggestionMenuController | ||
triggerCharacter={"/"} | ||
getItems={async (query) => | ||
filterSuggestionItems( | ||
[...getDefaultReactSlashMenuItems(editor), insertLaTex(editor)], | ||
query | ||
) | ||
} | ||
/> | ||
</BlockNoteView> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,216 @@ | ||
import { | ||
useComponentsContext, | ||
} from "@blocknote/react"; | ||
import { NodeView } from "prosemirror-view"; | ||
import { BlockNoteEditor } from "@blocknote/core"; | ||
import { | ||
NodeViewProps, | ||
NodeViewRenderer, | ||
NodeViewWrapper, | ||
ReactNodeViewRenderer, | ||
} from "@tiptap/react"; | ||
import "./styles.css"; | ||
import { createStronglyTypedTiptapNode, createInternalBlockSpec } from "@blocknote/core"; | ||
import { mergeAttributes } from "@tiptap/core"; | ||
import { ChangeEvent, useEffect, useState } from "react"; | ||
|
||
function loadKaTex(callback: () => void) { | ||
const katexScript = document.getElementById("katex-script"); | ||
const katexLink = document.getElementById("katex-link"); | ||
let scriptLoaded = !!katexScript; | ||
let linkLoaded = !!katexLink; | ||
|
||
const handleCallback = () => { | ||
if (scriptLoaded && linkLoaded) { | ||
setTimeout(callback); | ||
} | ||
}; | ||
|
||
if (!katexScript) { | ||
const script = document.createElement("script"); | ||
script.src = "https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/katex.min.js"; | ||
script.integrity = | ||
"sha384-hIoBPJpTUs74ddyc4bFZSM1TVlQDA60VBbJS0oA934VSz82sBx1X7kSx2ATBDIyd"; | ||
script.crossOrigin = "anonymous"; | ||
script.id = "katex-script"; | ||
// script.defer = true; | ||
script.onload = () => { | ||
scriptLoaded = true; | ||
handleCallback(); | ||
}; | ||
document.head.appendChild(script); | ||
} | ||
|
||
if (!katexLink) { | ||
const link = document.createElement("link"); | ||
link.rel = "stylesheet"; | ||
link.href = "https://cdn.jsdelivr.net/npm/katex@0.16.10/dist/katex.min.css"; | ||
link.integrity = | ||
"sha384-wcIxkf4k558AjM3Yz3BBFQUbk/zgIYC2R0QpeeYb+TwlBVMrlgLqwRjRtGZiK7ww"; | ||
link.crossOrigin = "anonymous"; | ||
link.id = "katex-link"; | ||
link.onload = () => { | ||
linkLoaded = true; | ||
handleCallback(); | ||
}; | ||
document.head.appendChild(link); | ||
} | ||
|
||
if (scriptLoaded && linkLoaded) { | ||
setTimeout(callback); | ||
} | ||
} | ||
|
||
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<any> = this.options.editor; | ||
|
||
const content = props.node.textContent; | ||
const [html, setHtml] = useState(""); | ||
const [loading, setLoading] = useState(false); | ||
const Components = useComponentsContext()!; | ||
|
||
useEffect(() => { | ||
setLoading(true); | ||
loadKaTex(() => { | ||
const html = window.katex.renderToString(content, { | ||
throwOnError: false, | ||
}); | ||
setHtml(html); | ||
setLoading(false); | ||
}); | ||
}, [content]); | ||
|
||
const handleChange = (e: ChangeEvent<HTMLTextAreaElement>) => { | ||
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, | ||
}, | ||
view.state.schema.text(val) | ||
)); | ||
|
||
view.dispatch(tr); | ||
} | ||
|
||
|
||
return ( | ||
<NodeViewWrapper as={'span'}> | ||
<Components.Generic.Popover.Root> | ||
<Components.Generic.Popover.Trigger> | ||
<span className={"latex"}> | ||
{loading ? ( | ||
<span className={"latex-loading"}>latex loading...</span> | ||
) : ( | ||
<span className={"latex-content"} dangerouslySetInnerHTML={{ __html: html }}></span> | ||
)} | ||
</span> | ||
</Components.Generic.Popover.Trigger> | ||
<Components.Generic.Popover.Content | ||
className={"bn-popover-content bn-form-popover"} | ||
variant={"form-popover"}> | ||
<textarea className={"latex-textarea"} value={content} onChange={handleChange} /> | ||
</Components.Generic.Popover.Content> | ||
</Components.Generic.Popover.Root> | ||
</NodeViewWrapper> | ||
); | ||
}; | ||
|
||
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 node = createStronglyTypedTiptapNode({ | ||
name: "latex", | ||
inline: true, | ||
group: "inline", | ||
content: "inline*", | ||
editable: true, | ||
selectable: false, | ||
parseHTML() { | ||
return [ | ||
{ | ||
tag: "latex", | ||
priority: 200, | ||
node: "latex", | ||
}, | ||
]; | ||
}, | ||
|
||
renderHTML() { | ||
return ["latex"]; | ||
}, | ||
|
||
addNodeView: LaTexView(), | ||
}); | ||
|
||
export const LaTex = createInternalBlockSpec( | ||
{ | ||
content: "inline", | ||
type: "latex", | ||
propSchema: { | ||
language: { | ||
type: "string", | ||
default: "typescript", | ||
}, | ||
storage: { | ||
type: "string", | ||
default: "", | ||
}, | ||
}, | ||
}, | ||
{ | ||
node, | ||
toExternalHTML: undefined as any, | ||
toInternalHTML: undefined as any, | ||
}, | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# LaTex Block | ||
|
||
In this example, we create a custom `LaTex` block. | ||
|
||
**Try it out:** Press the "/" key to open the Slash Menu and insert an `LaTex` 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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
<html lang="en"> | ||
<head> | ||
<script> | ||
<!-- AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY --> | ||
</script> | ||
<meta charset="UTF-8" /> | ||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
<title>Latex Block</title> | ||
</head> | ||
<body> | ||
<div id="root"></div> | ||
<script type="module" src="./main.tsx"></script> | ||
</body> | ||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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( | ||
<React.StrictMode> | ||
<App /> | ||
</React.StrictMode> | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
{ | ||
"name": "@blocknote/example-latex-block", | ||
"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": "^0.13.0", | ||
"@blocknote/react": "^0.13.0", | ||
"@blocknote/ariakit": "^0.13.0", | ||
"@blocknote/mantine": "^0.13.0", | ||
"@blocknote/shadcn": "^0.13.0", | ||
"react": "^18.2.0", | ||
"react-dom": "^18.2.0", | ||
"@mantine/core": "^7.7.1", | ||
"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" | ||
] | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer to just have this as a dependency of the package, that makes the code a lot cleaner imo