Skip to content

feat: example for code block #1099

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

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions examples/06-custom-schema/05-code-block/.bnexample.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"playground": true,
"docs": true,
"author": "JaeungJayJang",
"tags": [
"Intermediate",
"Blocks",
"Custom Schemas",
"Suggestion Menus",
"Slash Menu"
],
"dependencies": {
"@uiw/react-codemirror": "^4.23.3",
"@uiw/codemirror-extensions-langs": "^4.23.3",
"@uiw/codemirror-theme-material": "^4.23.3",
"lucide-react": "^0.407.0"
}
}
86 changes: 86 additions & 0 deletions examples/06-custom-schema/05-code-block/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import {
BlockNoteSchema,
defaultBlockSpecs,
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 { Code } from "./Code";
import { Code as CodeIcon } from "lucide-react";

// Our schema with block specs, which contain the configs and implementations for blocks
// that we want our editor to use.
const schema = BlockNoteSchema.create({
blockSpecs: {
// Adds all default blocks.
...defaultBlockSpecs,
// Adds the Code block.
codeBlock: Code,
},
});

// Slash menu item to insert an Code block
const insertCodeBlock = (editor: typeof schema.BlockNoteEditor) => ({
title: "Code",
onItemClick: () => {
insertOrUpdateBlock(editor, {
type: "codeBlock",
});
},
aliases: ["code"],
group: "Others",
icon: <CodeIcon size={18} />,
subtext: "Insert a code block.",
});

export default function App() {
// Creates a new editor instance.
const editor = useCreateBlockNote({
schema,
initialContent: [
{
type: "paragraph",
content: "Welcome to this demo!",
},
{
type: "codeBlock",
props: {
data: `// Your JavaScript code...
console.log('Hello, world!');`,
},
},
{
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}>
{/* Replaces the default Slash Menu. */}
<SuggestionMenuController
triggerCharacter={"/"}
getItems={async (query) =>
// Gets all default slash menu items and `insertCodeBlock` item.
filterSuggestionItems(
[...getDefaultReactSlashMenuItems(editor), insertCodeBlock(editor)],
query
)
}
/>
</BlockNoteView>
);
}
187 changes: 187 additions & 0 deletions examples/06-custom-schema/05-code-block/Code.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
"use client";

import { useState } from "react";
import { ReactCustomBlockRenderProps } from "@blocknote/react";
import {
defaultProps,
PropSchema,
BlockConfig,
CustomBlockConfig,
} from "@blocknote/core";
import { createReactBlockSpec } from "@blocknote/react";
import CodeMirror from "@uiw/react-codemirror";
import {
langs,
langNames,
LanguageName,
} from "@uiw/codemirror-extensions-langs";
import { material } from "@uiw/codemirror-theme-material";
import {
CheckIcon,
Group,
Combobox,
Input,
InputBase,
useCombobox,
} from "@mantine/core";
import "./styles.css";

// // Custom block configuration
type CustomCodeBlockConfig = {
type: string;
readonly propSchema: {
language: {
default: string;
};
data: {
default: string;
};
};
content: "none";
isSelectable?: boolean;
isFileBlock: false;
};

// Dropdown search component for selecting language
// export const SelectDropdownSearch = (props: any
export const SelectDropdownSearch = (
props: Omit<
ReactCustomBlockRenderProps<CustomCodeBlockConfig, any, any>,
"contentRef"
>
) => {
const [search, setSearch] = useState("");
const [value, setValue] = useState<string>(props.block.props.language);

const combobox = useCombobox({
onDropdownClose: () => {
combobox.resetSelectedOption();
combobox.focusTarget();
setSearch("");
},

onDropdownOpen: () => {
combobox.focusSearchInput();
},
});

// options for the dropdown from CodeMirror language names
const options = langNames
.filter((item) => item.toLowerCase().includes(search.toLowerCase().trim()))
.map((item) => (
<Combobox.Option value={item} key={item} active={item === value}>
<Group gap="xs">
{item === value && <CheckIcon size={12} />}
<span>{item}</span>
</Group>
</Combobox.Option>
));

return (
<Combobox
store={combobox}
withinPortal={false}
onOptionSubmit={(val) => {
setValue(val);
props.editor.updateBlock(props.block, {
props: {
...props.block.props,
language: val,
},
});
combobox.closeDropdown();
}}>
<Combobox.Target>
<InputBase
component="button"
type="button"
pointer
rightSection={<Combobox.Chevron />}
onClick={() => combobox.toggleDropdown()}
rightSectionPointerEvents="none">
{value || <Input.Placeholder>Pick language</Input.Placeholder>}
</InputBase>
</Combobox.Target>

<Combobox.Dropdown>
<Combobox.Search
value={search}
onChange={(event) => setSearch(event.currentTarget.value)}
placeholder="Search language..."
/>
<Combobox.Options mah={200} style={{ overflowY: "auto" }}>
{options.length > 0 ? (
options
) : (
<Combobox.Empty>No language found.</Combobox.Empty>
)}
</Combobox.Options>
</Combobox.Dropdown>
</Combobox>
);
};

export const Code = createReactBlockSpec(
{
type: "codeBlock",
content: "none",
propSchema: {
textAlignment: defaultProps.textAlignment,
textColor: defaultProps.textColor,
data: {
default: "",
},
language: {
default: "javascript",
},
},
},
{
render: (props) => {
const { data } = props.block.props;
const { language } = props.block.props;

// event handler for input change
const onInputChange = (val: string) => {
props.editor.updateBlock(props.block, {
props: {
...props.block.props,
data: val,
},
});
};

return (
// wrapper for Code block
<div className={"code-block"}>
{/* section to display currently selected language */}
<div className={"code-block-language-wrapper"}>
<SelectDropdownSearch
block={props.block}
editor={props.editor as any}
/>
</div>

{/* component to display code using Codemirror */}
<CodeMirror
id={props.block.id}
placeholder={"Write your code here..."}
style={{
width: "100%",
}}
extensions={[langs[language as LanguageName]()]}
value={data}
theme={material}
editable={props.editor.isEditable}
onChange={onInputChange}
basicSetup={{
lineNumbers: false,
foldGutter: false,
highlightActiveLine: false,
}}
/>
</div>
);
},
}
);
17 changes: 17 additions & 0 deletions examples/06-custom-schema/05-code-block/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Code Block

In this example, we create a custom CodeBlock component, which is used to display formatted code snippets. Additionally, we create a Slash Menu item that inserts a CodeBlock into the editor.

The example is based from below examples:

- [Alert Block](examples/06-custom-schema/01-alert-block)
- [BlockNote-code from detensestation](https://github.com/defensestation/blocknote-code)

**Try it out:** Press the "/" key to open the Slash Menu and insert an `Code` block!

**Relevant Docs:**

- [Custom Blocks](/docs/custom-schemas/custom-blocks)
- [Alert Block](examples/06-custom-schema/01-alert-block)
- [Changing Slash Menu Items](/docs/ui-components/suggestion-menus#changing-slash-menu-items)
- [Editor Setup](/docs/editor-basics/setup)
14 changes: 14 additions & 0 deletions examples/06-custom-schema/05-code-block/index.html
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>Code Block</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="./main.tsx"></script>
</body>
</html>
11 changes: 11 additions & 0 deletions examples/06-custom-schema/05-code-block/main.tsx
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>
);
Loading