Skip to content

Commit

Permalink
Merge pull request #4043 from patrick-hertling/feature/code-block-jso…
Browse files Browse the repository at this point in the history
…n-format

json formatting in code-block plugin
  • Loading branch information
zbeyens authored Feb 1, 2025
2 parents 016b7ee + 56d3d21 commit 3eb6478
Show file tree
Hide file tree
Showing 8 changed files with 150 additions and 4 deletions.
5 changes: 5 additions & 0 deletions .changeset/flat-parrots-trade.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@udecode/plate-code-block': minor
---

Ability to format a valid JSON string in a code block.
32 changes: 31 additions & 1 deletion apps/www/src/registry/default/plate-ui/code-block-element.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@

import React from 'react';

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

import { cn, withRef } from '@udecode/cn';
import { formatCodeBlock, isLangSupported } from '@udecode/plate-code-block';
import { useCodeBlockElementState } from '@udecode/plate-code-block/react';
import { BracesIcon } from 'lucide-react';

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

Expand All @@ -28,13 +33,38 @@ 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 {...props} />
<CodeBlockCombobox />
</div>
)}
</PlateElement>
);
}
);

export function CodeBlockFormatButton({
editor,
element,
}: {
editor: Editor;
element: TElement;
}) {
if (!isLangSupported(element.lang as string)) {
return null;
}

return (
<Button
size="xs"
variant="ghost"
className="h-5 justify-between px-1 text-xs"
onClick={() => formatCodeBlock(editor, { element })}
title="Format code"
>
<BracesIcon className="text-gray-500" />
</Button>
);
}
58 changes: 58 additions & 0 deletions packages/code-block/src/lib/formatter/formatter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import type { Editor } from '@udecode/plate';

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

import { formatJson, isValidJson } from './jsonFormatter';

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

export const isLangSupported = (lang?: string): boolean =>
Boolean(lang && supportedLanguages.has(lang));

export const isValidSyntax = (code: string, lang?: string): boolean => {
if (!isLangSupported(lang)) {
return false;
}

switch (lang) {
case 'json': {
return isValidJson(code);
}
default: {
return false;
}
}
};

export const formatCodeBlock = (
editor: Editor,
{
element,
}: {
element: TCodeBlockElement;
}
) => {
const { lang } = element;

if (!lang || !isLangSupported(lang)) {
return;
}

const code = editor.api.string(element);

if (isValidSyntax(code, lang)) {
const formattedCode = formatCode(code, lang);
editor.tf.insertText(formattedCode, { at: element });
}
};

const formatCode = (code: string, lang?: string): string => {
switch (lang) {
case 'json': {
return formatJson(code);
}
default: {
return 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';
31 changes: 31 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,31 @@
import { formatJson, isValidJson } from './jsonFormatter';

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

it('should detect invalid JSON', () => {
const json = '{ name: "ChatGPT", type: AI }';
const isValid = isValidJson(json);
expect(isValid).toBe(false);
});

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

it('should not format invalid JSON', () => {
const json = '{ name: "ChatGPT", type: AI }';
const formattedJson = formatJson(json);
expect(formattedJson).toBe(json);
});
});
17 changes: 17 additions & 0 deletions packages/code-block/src/lib/formatter/jsonFormatter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export const formatJson = (code: string): string => {
try {
return JSON.stringify(JSON.parse(code), null, 2);
} catch (error) {
return code;
}
};

export const isValidJson = (code: string): boolean => {
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';
4 changes: 1 addition & 3 deletions packages/code-block/src/react/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
/**
* @file Automatically generated by barrelsby.
*/
/** @file Automatically generated by barrelsby. */

export * from './useCodeBlockCombobox';
export * from './useCodeBlockElement';
Expand Down

0 comments on commit 3eb6478

Please sign in to comment.