mirror of https://github.com/buster-so/buster.git
code bock nodes
This commit is contained in:
parent
9233af8b25
commit
4e31af9a9c
|
@ -54,7 +54,196 @@ const initialValue: Value = [
|
|||
{ text: ' text for emphasis!' }
|
||||
],
|
||||
type: 'p'
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{ text: 'Here is another paragraph with ' },
|
||||
{ italic: true, text: 'italic text' },
|
||||
{ text: ' and ' },
|
||||
{ underline: true, text: 'underlined text' },
|
||||
{ text: ' to demonstrate various formatting options.' }
|
||||
],
|
||||
type: 'p'
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{ text: 'This paragraph contains ' },
|
||||
{ code: true, text: 'inline code' },
|
||||
{ text: ' and ' },
|
||||
{ strikethrough: true, text: 'strikethrough text' },
|
||||
{ text: ' along with ' },
|
||||
{ highlight: true, text: 'highlighted text' },
|
||||
{ text: '.' }
|
||||
],
|
||||
type: 'p'
|
||||
},
|
||||
{
|
||||
children: [{ text: 'Code Block Example' }],
|
||||
type: 'h4'
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{ children: [{ text: 'function hello() {' }], type: 'code_line' },
|
||||
{
|
||||
children: [{ text: " console.info('Code blocks are supported!');" }],
|
||||
type: 'code_line'
|
||||
},
|
||||
{ children: [{ text: '}' }], type: 'code_line' }
|
||||
],
|
||||
lang: 'javascript',
|
||||
type: 'code_block'
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: `SELECT id, name
|
||||
FROM users
|
||||
WHERE active = true
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 5;`
|
||||
}
|
||||
],
|
||||
type: 'code_block',
|
||||
lang: 'sql'
|
||||
},
|
||||
{
|
||||
children: [
|
||||
{
|
||||
text: `function calculateTotal(items) {
|
||||
let total = 0;
|
||||
for (const item of items) {
|
||||
total += item.price;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
function calculateAverage(items) {
|
||||
if (items.length === 0) return 0;
|
||||
return calculateTotal(items) / items.length;
|
||||
}`
|
||||
}
|
||||
],
|
||||
type: 'code_block',
|
||||
lang: 'javascript'
|
||||
}
|
||||
// {
|
||||
// children: [{ text: 'Unordered List' }],
|
||||
// type: 'h4'
|
||||
// },
|
||||
// {
|
||||
// children: [{ text: 'Features of this editor:' }],
|
||||
// type: 'p'
|
||||
// },
|
||||
// {
|
||||
// children: [
|
||||
// {
|
||||
// children: [{ text: 'Rich text formatting (bold, italic, underline)' }],
|
||||
// type: 'li'
|
||||
// }
|
||||
// ],
|
||||
// type: 'ul'
|
||||
// },
|
||||
// {
|
||||
// children: [
|
||||
// {
|
||||
// children: [{ text: 'Code blocks with syntax highlighting' }],
|
||||
// type: 'li'
|
||||
// }
|
||||
// ],
|
||||
// type: 'ul'
|
||||
// },
|
||||
// {
|
||||
// children: [
|
||||
// {
|
||||
// children: [{ text: 'Multiple heading levels' }],
|
||||
// type: 'li'
|
||||
// }
|
||||
// ],
|
||||
// type: 'ul'
|
||||
// },
|
||||
// {
|
||||
// children: [
|
||||
// {
|
||||
// children: [{ text: 'Blockquotes and callouts' }],
|
||||
// type: 'li'
|
||||
// }
|
||||
// ],
|
||||
// type: 'ul'
|
||||
// },
|
||||
// {
|
||||
// children: [{ text: 'Ordered List' }],
|
||||
// type: 'h4'
|
||||
// },
|
||||
// {
|
||||
// children: [{ text: 'Steps to create a great report:' }],
|
||||
// type: 'p'
|
||||
// },
|
||||
// {
|
||||
// children: [
|
||||
// {
|
||||
// children: [{ text: 'Start with a clear title and introduction' }],
|
||||
// type: 'li'
|
||||
// }
|
||||
// ],
|
||||
// type: 'ol'
|
||||
// },
|
||||
// {
|
||||
// children: [
|
||||
// {
|
||||
// children: [{ text: 'Organize content with headings and subheadings' }],
|
||||
// type: 'li'
|
||||
// }
|
||||
// ],
|
||||
// type: 'ol'
|
||||
// },
|
||||
// {
|
||||
// children: [
|
||||
// {
|
||||
// children: [{ text: 'Use formatting to emphasize key points' }],
|
||||
// type: 'li'
|
||||
// }
|
||||
// ],
|
||||
// type: 'ol'
|
||||
// },
|
||||
// {
|
||||
// children: [
|
||||
// {
|
||||
// children: [{ text: 'Include code examples when relevant' }],
|
||||
// type: 'li'
|
||||
// }
|
||||
// ],
|
||||
// type: 'ol'
|
||||
// },
|
||||
// {
|
||||
// children: [
|
||||
// {
|
||||
// children: [{ text: 'Conclude with a summary or call to action' }],
|
||||
// type: 'li'
|
||||
// }
|
||||
// ],
|
||||
// type: 'ol'
|
||||
// },
|
||||
// {
|
||||
// children: [{ text: 'Important Note' }],
|
||||
// type: 'h4'
|
||||
// },
|
||||
// {
|
||||
// children: [
|
||||
// {
|
||||
// text: '💡 This is an informational callout that helps draw attention to important information. You can use callouts to highlight tips, warnings, or key insights throughout your report.'
|
||||
// }
|
||||
// ],
|
||||
// type: 'callout',
|
||||
// variant: 'info'
|
||||
// },
|
||||
// {
|
||||
// children: [
|
||||
// {
|
||||
// text: 'This is the final paragraph of our sample content. It demonstrates how all these different content types can work together to create a comprehensive and well-formatted document.'
|
||||
// }
|
||||
// ],
|
||||
// type: 'p'
|
||||
// }
|
||||
];
|
||||
|
||||
export const Default: Story = {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useImperativeHandle } from 'react';
|
||||
import type { Value, AnyPluginConfig } from 'platejs';
|
||||
import { Plate, type TPlateEditor } from 'platejs/react';
|
||||
import { Plate, useEditorMounted, type TPlateEditor } from 'platejs/react';
|
||||
import { EditorContainer } from './EditorContainer';
|
||||
import { EditorContent } from './EditorContent';
|
||||
import { useEditor } from './useEditor';
|
||||
|
@ -46,6 +46,8 @@ export const AppReport = React.memo(
|
|||
// Optionally expose the editor instance to the parent via ref
|
||||
useImperativeHandle(ref, () => ({ editor, onReset }), [editor]);
|
||||
|
||||
if (!editor) return null;
|
||||
|
||||
return (
|
||||
<Plate editor={editor} readOnly={readOnly}>
|
||||
{/*
|
||||
|
|
|
@ -29,37 +29,36 @@ This is used for code blocks.
|
|||
*/
|
||||
export function CodeBlockElement(props: PlateElementProps<TCodeBlockElement>) {
|
||||
const { editor, element } = props;
|
||||
|
||||
return (
|
||||
<PlateElement
|
||||
className="py-1 **:[.hljs-addition]:bg-[#f0fff4] **:[.hljs-addition]:text-[#22863a] dark:**:[.hljs-addition]:bg-[#3c5743] dark:**:[.hljs-addition]:text-[#ceead5] **:[.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable]:text-[#005cc5] dark:**:[.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable]:text-[#6596cf] **:[.hljs-built\\\\_in,.hljs-symbol]:text-[#e36209] dark:**:[.hljs-built\\\\_in,.hljs-symbol]:text-[#c3854e] **:[.hljs-bullet]:text-[#735c0f] **:[.hljs-comment,.hljs-code,.hljs-formula]:text-[#6a737d] dark:**:[.hljs-comment,.hljs-code,.hljs-formula]:text-[#6a737d] **:[.hljs-deletion]:bg-[#ffeef0] **:[.hljs-deletion]:text-[#b31d28] dark:**:[.hljs-deletion]:bg-[#473235] dark:**:[.hljs-deletion]:text-[#e7c7cb] **:[.hljs-emphasis]:italic **:[.hljs-keyword,.hljs-doctag,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language\\\\_]:text-[#d73a49] dark:**:[.hljs-keyword,.hljs-doctag,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language\\\\_]:text-[#ee6960] **:[.hljs-name,.hljs-quote,.hljs-selector-tag,.hljs-selector-pseudo]:text-[#22863a] dark:**:[.hljs-name,.hljs-quote,.hljs-selector-tag,.hljs-selector-pseudo]:text-[#36a84f] **:[.hljs-regexp,.hljs-string,.hljs-meta_.hljs-string]:text-[#032f62] dark:**:[.hljs-regexp,.hljs-string,.hljs-meta_.hljs-string]:text-[#3593ff] **:[.hljs-section]:font-bold **:[.hljs-section]:text-[#005cc5] dark:**:[.hljs-section]:text-[#61a5f2] **:[.hljs-strong]:font-bold **:[.hljs-title,.hljs-title.class\\\\_,.hljs-title.class\\\\_.inherited\\\\_\\\\_,.hljs-title.function\\\\_]:text-[#6f42c1] dark:**:[.hljs-title,.hljs-title.class\\\\_,.hljs-title.class\\\\_.inherited\\\\_\\\\_,.hljs-title.function\\\\_]:text-[#a77bfa]"
|
||||
className="py-1 **:[.hljs-addition]:bg-[#f0fff4] **:[.hljs-addition]:text-[#22863a] **:[.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable]:text-[#005cc5] **:[.hljs-built_in,.hljs-symbol]:text-[#e36209] **:[.hljs-bullet]:text-[#735c0f] **:[.hljs-comment,.hljs-code,.hljs-formula]:text-[#6a737d] **:[.hljs-deletion]:bg-[#ffeef0] **:[.hljs-deletion]:text-[#b31d28] **:[.hljs-emphasis]:italic **:[.hljs-keyword,.hljs-doctag,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_]:text-[#d73a49] **:[.hljs-name,.hljs-quote,.hljs-selector-tag,.hljs-selector-pseudo]:text-[#22863a] **:[.hljs-regexp,.hljs-string,.hljs-meta_.hljs-string]:text-[#032f62] **:[.hljs-section]:font-bold **:[.hljs-section]:text-[#005cc5] **:[.hljs-strong]:font-bold **:[.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_]:text-[#6f42c1]"
|
||||
{...props}>
|
||||
<div className="bg-muted/50 relative rounded-md">
|
||||
<pre className="overflow-x-auto p-8 pr-4 font-mono text-sm leading-[normal] [tab-size:2] print:break-inside-avoid">
|
||||
<code>{props.children}</code>
|
||||
</pre>
|
||||
|
||||
<div
|
||||
className="absolute top-1 right-1 z-10 flex gap-0.5 select-none"
|
||||
contentEditable={false}>
|
||||
{isLangSupported(element.lang) && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="size-6 text-xs"
|
||||
onClick={() => formatCodeBlock(editor, { element })}
|
||||
title="Format code"
|
||||
prefix={<BracketsCurly />}></Button>
|
||||
)}
|
||||
|
||||
<CodeBlockCombobox />
|
||||
<CopyButton
|
||||
variant="ghost"
|
||||
className="text-muted-foreground size-6 gap-1 text-xs"
|
||||
prefix={<Copy2 />}
|
||||
value={() => NodeApi.string(element)}
|
||||
/>
|
||||
|
||||
<CopyButton value={() => NodeApi.string(element)} />
|
||||
</div>
|
||||
</div>
|
||||
</PlateElement>
|
||||
);
|
||||
}
|
||||
|
||||
function CodeBlockCombobox() {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const readOnly = useReadOnly();
|
||||
|
@ -67,6 +66,7 @@ function CodeBlockCombobox() {
|
|||
const element = useElement<TCodeBlockElement>();
|
||||
const value = element.lang || 'plaintext';
|
||||
const [searchValue, setSearchValue] = React.useState('');
|
||||
|
||||
const items = React.useMemo(
|
||||
() =>
|
||||
languages.filter(
|
||||
|
@ -75,16 +75,13 @@ function CodeBlockCombobox() {
|
|||
),
|
||||
[searchValue]
|
||||
);
|
||||
|
||||
if (readOnly) return null;
|
||||
|
||||
return (
|
||||
<PopoverBase open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button
|
||||
size="small"
|
||||
variant="ghost"
|
||||
className="text-muted-foreground h-6 justify-between gap-1 px-2 text-xs select-none"
|
||||
aria-expanded={open}
|
||||
role="combobox">
|
||||
<Button aria-expanded={open} role="combobox">
|
||||
{languages.find((language) => language.value === value)?.label ?? 'Plain Text'}
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
|
@ -122,43 +119,41 @@ function CodeBlockCombobox() {
|
|||
</PopoverBase>
|
||||
);
|
||||
}
|
||||
|
||||
function CopyButton({
|
||||
value,
|
||||
...props
|
||||
}: { value: (() => string) | string } & Omit<React.ComponentProps<typeof Button>, 'value'>) {
|
||||
const [hasCopied, setHasCopied] = React.useState(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
setTimeout(() => {
|
||||
setHasCopied(false);
|
||||
}, 2000);
|
||||
}, [hasCopied]);
|
||||
|
||||
return (
|
||||
<Button
|
||||
prefix={hasCopied ? <Check /> : <Copy2 />}
|
||||
onClick={() => {
|
||||
void navigator.clipboard.writeText(typeof value === 'function' ? value() : value);
|
||||
setHasCopied(true);
|
||||
}}
|
||||
{...props}>
|
||||
<span className="sr-only">Copy</span>
|
||||
<div className="size-4">{hasCopied ? <Check /> : <Copy2 />}</div>
|
||||
}}>
|
||||
Copy
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
This is used for code lines.
|
||||
*/
|
||||
export function CodeLineElement(props: PlateElementProps) {
|
||||
return <PlateElement {...props} />;
|
||||
}
|
||||
|
||||
/*
|
||||
This is used for code syntax.
|
||||
*/
|
||||
export function CodeSyntaxLeaf(props: PlateLeafProps<TCodeSyntaxLeaf>) {
|
||||
const tokenClassName = props.leaf.className as string;
|
||||
|
||||
return <PlateLeaf className={tokenClassName} {...props} />;
|
||||
}
|
||||
|
||||
const languages: { label: string; value: string }[] = [
|
||||
{ label: 'Auto', value: 'auto' },
|
||||
{ label: 'Plain Text', value: 'plaintext' },
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import { CodeBlockPlugin, CodeLinePlugin, CodeSyntaxPlugin } from '@platejs/code-block/react';
|
||||
import { all, createLowlight } from 'lowlight';
|
||||
|
||||
import { CodeBlockElement, CodeLineElement, CodeSyntaxLeaf } from '../elements/CodeBlockNode';
|
||||
|
||||
const lowlight = createLowlight(all);
|
||||
|
|
Loading…
Reference in New Issue