Skip to content
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

code block fixes #200

Merged
merged 1 commit into from
Jun 28, 2024
Merged
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
30 changes: 27 additions & 3 deletions src/views/edit/editor/elements/CodeBlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
import { CODE_BLOCK_LANGUAGES } from "@udecode/plate-code-block";
import { Transforms } from "slate";

const languages: { label: string; value: string }[] = [
const knownLangOptions: { label: string; value: string }[] = [
{ label: "Plain Text", value: "text" },
...Object.entries({
// doesn't include python? But does SVG and GraphQL? WTF?
Expand All @@ -23,6 +23,28 @@ const languages: { label: string; value: string }[] = [
})),
];

// Set initial selected language, with some defaults.
function defaultLang(lang: string | null) {
if (!lang) return "text";
if (lang === "js") return "javascript";
if (lang === "ts") return "typescript";

// NOTE: If lang is a language not found in Prism / the menus above, it will default to "Plain text"
// in the dropdown.
return lang;
}

// On first render, if the language is not in the known options, add it to the list.
// This is to support unknown languages that are in the underlying markdown, but not known
// to Prism / language list above; syntax highlighting will not work.
function amendLanguageOptions(lang: string) {
if (!knownLangOptions.find((l) => l.value === lang)) {
return [{ label: lang, value: lang }, ...knownLangOptions];
}

return knownLangOptions;
}

// className -> slate-code_block
// state -> { className: '', syntax: true }
// props -> attributes, editor, element
Expand All @@ -33,7 +55,7 @@ export const CodeBlockElement = withRef<typeof PlateElement>(
({ children, className, ...props }, ref) => {
const { element } = props;
const editor = useEditorRef();
const [lang, setLang] = React.useState((element.lang as string) || "plain");
const [lang, setLang] = React.useState(defaultLang(element.lang as string));

// When the language changes, update S|Plate nodes.
React.useEffect(() => {
Expand Down Expand Up @@ -80,12 +102,14 @@ function LanguageSelect({ lang, setLang }: LangSelectProps) {
}

const languageOptions = React.useMemo(() => {
const languages = amendLanguageOptions(lang);

return languages.map((language) => (
<option key={language.value} value={language.value}>
{language.label}
</option>
));
}, [languages]);
}, []);

return (
<select value={lang} onChange={onChange} className="bg-muted">
Expand Down
26 changes: 12 additions & 14 deletions src/views/edit/editor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -342,20 +342,18 @@ export default observer(
);

return (
<>
<Plate
initialValue={value as any}
onChange={setValue}
plugins={plugins}
readOnly={saving}
>
<EditorToolbar
selectedEditorMode={selectedEditorMode}
setSelectedEditorMode={setSelectedEditorMode}
/>
<PlateContent placeholder="Type..." />
</Plate>
</>
<Plate
initialValue={value as any}
onChange={setValue}
plugins={plugins}
readOnly={saving}
>
<EditorToolbar
selectedEditorMode={selectedEditorMode}
setSelectedEditorMode={setSelectedEditorMode}
/>
<PlateContent placeholder="Type..." />
</Plate>
);
},
);
Original file line number Diff line number Diff line change
Expand Up @@ -15,60 +15,43 @@ export const createCodeBlockNormalizationPlugin = createPluginFactory({
// and they do `Editor as any` which seems wrong.
// Also, `node as any` -- Slate's methods aren't expecting `type` on Node but its foundational
// to custom elements.
const { normalizeNode, insertData } = editor;

editor.normalizeNode = ([node, path]) => {
if (node.type === ELEMENT_CODE_BLOCK) {
const blockEntries = Array.from(
Editor.nodes(editor as Editor, {
at: path,
match: (n) => (n as any).type === ELEMENT_CODE_LINE,
}),
);

if (blockEntries.length > 1) {
const [firstCodeLine, firstCodeLinePath] = blockEntries[0];
blockEntries.slice(1).forEach(([, codeLinePath]) => {
Transforms.mergeNodes(editor as Editor, { at: codeLinePath });
});
Transforms.setNodes(
editor as Editor,
{ type: ELEMENT_CODE_BLOCK } as any,
{ at: firstCodeLinePath },
);
}
}
normalizeNode([node, path]);
};
const { insertData } = editor;

editor.insertData = (data: DataTransfer) => {
const text = data.getData("text/plain");
if (
text &&
Editor.above(editor as Editor, {
!text ||
!Editor.above(editor as Editor, {
match: (n: any) => n.type === ELEMENT_CODE_BLOCK,
})
) {
const lines = text.split("\n");
const { selection } = editor;
if (selection && Range.isCollapsed(selection)) {
Transforms.insertText(editor as Editor, lines.join("\n"), {
at: selection,
});
} else {
lines.forEach((line) => {
Transforms.insertText(editor as Editor, line);
Transforms.insertNodes(
editor as Editor,
{
type: ELEMENT_CODE_LINE,
children: [{ text: "" }],
} as any,
);
});
}
insertData(data);
return;
}

const lines = text.split("\n");
const { selection } = editor;
if (selection && Range.isCollapsed(selection)) {
// When pasting at cursor (no text / blocks selected), insert as a single block.
Transforms.insertText(editor as Editor, lines.join("\n"), {
at: selection,
});
} else {
// Pasting when selecting multiple lines; if its within a code block, it ends up as one block.
// I think Plates createCodeBlockPlugin (from createBasicPlugins) is normalizing this to a single block...
// unclear if this code makes sense.
lines.forEach((line) => {
Transforms.insertText(editor as Editor, line);
Transforms.insertNodes(
editor as Editor,
{
type: ELEMENT_CODE_LINE,
children: [{ text: "" }],
} as any,
);
});
}

insertData(data);
};

Expand Down
3 changes: 2 additions & 1 deletion src/views/edit/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,8 @@ const DocumentEditView = observer((props: DocumentEditProps) => {
// flexGrows are needed so save / edit buttons are at bottom on both empty documents
// and scrollable documents
return (
<Pane flexGrow={1} display="flex" flexDirection="column">
// NOTE: width: 100% prevents child code_blocks (and maybe others) from overflowing
<Pane flexGrow={1} display="flex" flexDirection="column" width="100%">
<div style={{ marginBottom: "24px" }}>
<a
onClick={goBack}
Expand Down
Loading