Skip to content

Commit

Permalink
json formatting in code-block plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
patrick-hertling committed Jan 31, 2025
1 parent 016b7ee commit 1383899
Show file tree
Hide file tree
Showing 9 changed files with 177 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { cn, withRef } from '@udecode/cn';
import { useCodeBlockElementState } from '@udecode/plate-code-block/react';

import { CodeBlockCombobox } from './code-block-combobox';
import { CodeBlockFormatButton } from './code-block-format-button';
import { PlateElement } from './plate-element';

import './code-block-element.css';
Expand All @@ -28,9 +29,10 @@ export const CodeBlockElement = withRef<typeof PlateElement>(

{state.syntax && (
<div
className="absolute right-2 top-2 z-10 select-none"
className="absolute right-2 top-2 z-10 flex select-none items-center gap-1"
contentEditable={false}
>
<CodeBlockFormatButton element={element} />
<CodeBlockCombobox />
</div>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
'use client';

import type { TElement } from '@udecode/plate';

import { useCodeBlockFormat } from '@udecode/plate-code-block/react';
import { BracesIcon } from 'lucide-react';

import { Button } from './button';

export function CodeBlockFormatButton({ element }: { element: TElement }) {
const { format, isSupported, validSyntax } = useCodeBlockFormat({
element,
});

if (!isSupported) {
return null;
}

return (
<Button
size="xs"
variant="ghost"
className="h-5 justify-between px-1 text-xs"
disabled={!validSyntax}
onClick={() => format()}
title="Format code"
>
<BracesIcon className="text-gray-500" />
</Button>
);
}
40 changes: 40 additions & 0 deletions packages/code-block/src/lib/formatter/formatter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { JsonFormatter } from './jsonFormatter';

export interface IFormatter {
format: (code: string) => string;
validSyntax: (code: string) => boolean;
}

const supportedLanguages = new Set(['json']);

export class Formatter {
format(code: string, lang?: string) {
if (!lang || !supportedLanguages.has(lang)) {
return '';
}

switch (lang) {
case 'json': {
return new JsonFormatter().format(code);
}
}

return code;
}

isLangSupported(lang?: string) {
return lang && supportedLanguages.has(lang);
}

validSyntax(code: string, lang?: string) {
if (!lang || !supportedLanguages.has(lang)) {
return false;
}

switch (lang) {
case 'json': {
return new JsonFormatter().validSyntax(code);
}
}
}
}
6 changes: 6 additions & 0 deletions packages/code-block/src/lib/formatter/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* @file Automatically generated by barrelsby.
*/

export * from './formatter';
export * from './jsonFormatter';
42 changes: 42 additions & 0 deletions packages/code-block/src/lib/formatter/jsonFormatter.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { createEditor } from '@udecode/plate';
import { createPlateEditor } from '@udecode/plate/react';

import { JsonFormatter } from './jsonFormatter';

describe('JsonFormatter', () => {
const formatter = new JsonFormatter();

it('should detect valid JSON', () => {
const json = '{ "name": "ChatGPT", "type": "AI" }';
const isValid = formatter.validSyntax(json);
expect(isValid).toBe(true);
});

it('should detect invalid JSON', () => {
const editor = createEditor();
const plateEditor = createPlateEditor({ editor });
const json = '{ name: "ChatGPT", type: AI }';
const isValid = formatter.validSyntax(json);
expect(isValid).toBe(false);
});

it('should format JSON', () => {
const editor = createEditor();
const plateEditor = createPlateEditor({ editor });
const json = '{"name":"ChatGPT","type":"AI"}';
const formattedJson = formatter.format(json);
const expected = `{
"name": "ChatGPT",
"type": "AI"
}`;
expect(formattedJson).toBe(expected);
});

it('should not format invalid JSON', () => {
const editor = createEditor();
const plateEditor = createPlateEditor({ editor });
const json = '{ name: "ChatGPT", type: AI }';
const formattedJson = formatter.format(json);
expect(formattedJson).toBe(json);
});
});
21 changes: 21 additions & 0 deletions packages/code-block/src/lib/formatter/jsonFormatter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { IFormatter } from './formatter';

export class JsonFormatter implements IFormatter {
format(code: string) {
try {
return JSON.stringify(JSON.parse(code), null, 2);
} catch (error) {
return code;
}
}

validSyntax(code: string) {
try {
JSON.parse(code);

return true;
} catch (error) {
return false;
}
}
}
1 change: 1 addition & 0 deletions packages/code-block/src/lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ export * from './withInsertDataCodeBlock';
export * from './withInsertFragmentCodeBlock';
export * from './withNormalizeCodeBlock';
export * from './deserializer/index';
export * from './formatter/index';
export * from './queries/index';
export * from './transforms/index';
1 change: 1 addition & 0 deletions packages/code-block/src/react/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@

export * from './useCodeBlockCombobox';
export * from './useCodeBlockElement';
export * from './useCodeBlockFormat';
export * from './useCodeSyntaxLeaf';
export * from './useToggleCodeBlockButton';
32 changes: 32 additions & 0 deletions packages/code-block/src/react/hooks/useCodeBlockFormat.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { useEditorRef } from '@udecode/plate/react';

import type { TCodeBlockElement } from '../../lib';

import { Formatter } from '../../lib/formatter/formatter';

export const useCodeBlockFormat = ({
element,
}: {
element: TCodeBlockElement;
}) => {
const editor = useEditorRef();

const { lang: language } = element;
const code = editor.api.string(element);

const formatter = new Formatter();
const isSupported = formatter.isLangSupported(language);

const format = () => {
const validSyntax = formatter.validSyntax(code, language);

if (validSyntax) {
const formattedCode = formatter.format(code, language);
editor.tf.insertText(formattedCode, { at: element });
}
};

const validSyntax = formatter.validSyntax(code, language);

return { format, isSupported, validSyntax };
};

0 comments on commit 1383899

Please sign in to comment.