From b2fd7b5d05773da507dcb4e68f1aee5718d26fb5 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Fri, 5 Apr 2024 12:01:52 +0200 Subject: [PATCH 1/3] Frontend fixes (#231) * Temp * Fixes editors. * Reuse hooks. --- backend/src/Notifo.Domain/Liquid/LiquidApp.cs | 2 +- .../Liquid/LiquidNotificationBase.cs | 13 +- .../Liquid/LiquidPropertiesProvider.cs | 2 + .../Notifo.Domain/Liquid/LiquidProperty.cs | 5 +- .../src/Notifo.Domain/Liquid/LiquidUser.cs | 6 +- .../Utils/UtilsServiceExtensions.cs | 4 + .../ChannelTemplatesController.cs | 25 +- .../Dtos/TemplatePropertyDto.cs | 34 ++ .../EmailTemplatesController.cs | 5 +- .../MessagingTemplatesController.cs | 5 +- .../SmsTemplatesController.cs | 5 +- frontend/package-lock.json | 28 +- frontend/package.json | 4 +- frontend/src/app/framework/index.ts | 2 +- frontend/src/app/framework/react/Code.tsx | 2 + .../framework/react/CodeEditor.stories.tsx | 28 ++ .../src/app/framework/react/CodeEditor.tsx | 136 ++++++ .../src/app/framework/react/Icon.stories.tsx | 2 +- frontend/src/app/framework/react/Icon.tsx | 2 +- .../src/app/framework/react/ListSearch.tsx | 18 +- frontend/src/app/framework/react/Marker.tsx | 49 --- frontend/src/app/framework/react/hooks.ts | 69 +++- frontend/src/app/pages/TopNav.tsx | 6 +- .../pages/email-templates/EmailTemplate.tsx | 7 +- .../email-templates/EmailTemplatePage.tsx | 41 +- .../editor/EmailHtmlTextEditor.tsx | 143 ++----- .../editor/EmailTextEditor.tsx | 31 +- .../pages/email-templates/editor/helpers.ts | 62 ++- .../MessagingTemplatePage.tsx | 91 ++-- .../src/app/pages/publish/PublishDialog.tsx | 2 +- .../pages/sms-templates/SmsTemplatePage.tsx | 88 ++-- .../src/app/pages/templates/TemplateForm.tsx | 2 +- frontend/src/app/service/service.ts | 173 +++++++- frontend/src/app/shared/components/Forms.tsx | 45 +- .../shared/components/TemplateProperties.tsx | 35 ++ frontend/src/app/shared/components/index.ts | 1 + frontend/src/app/style/_common.scss | 32 +- frontend/src/app/style/_emails.scss | 21 +- frontend/src/app/style/_messaging.scss | 2 +- frontend/src/app/style/_mixins.scss | 23 -- frontend/src/app/style/_sms.scss | 2 +- frontend/src/app/style/icons/Read Me.txt | 2 +- frontend/src/app/style/icons/demo.html | 16 +- .../src/app/style/icons/fonts/icomoon.eot | Bin 8176 -> 8620 bytes .../src/app/style/icons/fonts/icomoon.svg | 1 + .../src/app/style/icons/fonts/icomoon.ttf | Bin 8012 -> 8456 bytes .../src/app/style/icons/fonts/icomoon.woff | Bin 8088 -> 8532 bytes frontend/src/app/style/icons/selection.json | 2 +- frontend/src/app/style/icons/style.css | 13 +- frontend/src/app/texts/en.ts | 1 + frontend/src/app/texts/index.ts | 11 +- frontend/src/app/texts/tr.ts | 45 +- frontend/src/sdk/ui/components/Loader.tsx | 4 +- tools/sdk/Notifo.SDK/Generated.cs | 388 +++++++++++++++++- 54 files changed, 1323 insertions(+), 413 deletions(-) create mode 100644 backend/src/Notifo/Areas/Api/Controllers/ChannelTemplates/Dtos/TemplatePropertyDto.cs create mode 100644 frontend/src/app/framework/react/CodeEditor.stories.tsx create mode 100644 frontend/src/app/framework/react/CodeEditor.tsx delete mode 100644 frontend/src/app/framework/react/Marker.tsx create mode 100644 frontend/src/app/shared/components/TemplateProperties.tsx diff --git a/backend/src/Notifo.Domain/Liquid/LiquidApp.cs b/backend/src/Notifo.Domain/Liquid/LiquidApp.cs index cbaf726e..cfb8c92c 100644 --- a/backend/src/Notifo.Domain/Liquid/LiquidApp.cs +++ b/backend/src/Notifo.Domain/Liquid/LiquidApp.cs @@ -19,6 +19,6 @@ public sealed class LiquidApp(App app) public static void Describe(LiquidProperties properties) { properties.AddString("name", - "The name of the app."); + "The name of the app. Cannot be null or undefined."); } } diff --git a/backend/src/Notifo.Domain/Liquid/LiquidNotificationBase.cs b/backend/src/Notifo.Domain/Liquid/LiquidNotificationBase.cs index 7e3e2ef6..01ed1ce0 100644 --- a/backend/src/Notifo.Domain/Liquid/LiquidNotificationBase.cs +++ b/backend/src/Notifo.Domain/Liquid/LiquidNotificationBase.cs @@ -52,18 +52,21 @@ protected LiquidNotificationBase( protected static void DescribeBase(LiquidProperties properties) { properties.AddString("subject", - "The notification subject."); + "The notification subject. Cannot be null or undefined."); properties.AddString("body", - "The notification body. Can be null."); + "The notification body. Can be null or undefined."); properties.AddString("linkUrl", - "The link URL. Can be null."); + "The link URL. Can be null or undefined."); + + properties.AddString("linkText", + "The link text that can be set when a linkUrl is set. Can be null or undefined."); properties.AddString("imageSmall", - "The URL to the small image. Optimized for the current use case (e.g. emails). Can be null."); + "The URL to the small image. Optimized for the current use case (e.g. emails). Can be null or undefined."); properties.AddString("imageLarge", - "The URL to the large image. Optimized for the current use case (e.g. emails). Can be null."); + "The URL to the large image. Optimized for the current use case (e.g. emails). Can be null or undefined."); } } diff --git a/backend/src/Notifo.Domain/Liquid/LiquidPropertiesProvider.cs b/backend/src/Notifo.Domain/Liquid/LiquidPropertiesProvider.cs index e8b3cd94..e699ed58 100644 --- a/backend/src/Notifo.Domain/Liquid/LiquidPropertiesProvider.cs +++ b/backend/src/Notifo.Domain/Liquid/LiquidPropertiesProvider.cs @@ -5,6 +5,8 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +#pragma warning disable CA1822 // Mark members as static + namespace Notifo.Domain.Liquid; public sealed class LiquidPropertiesProvider diff --git a/backend/src/Notifo.Domain/Liquid/LiquidProperty.cs b/backend/src/Notifo.Domain/Liquid/LiquidProperty.cs index c9728579..e2711cf3 100644 --- a/backend/src/Notifo.Domain/Liquid/LiquidProperty.cs +++ b/backend/src/Notifo.Domain/Liquid/LiquidProperty.cs @@ -36,11 +36,10 @@ public void AddBoolean(string path, string? description = null) public void AddObject(string path, Action inner, string? description = null) { - pathStack.Push(path); - Add(new LiquidProperty(FullPath(path), LiquidPropertyType.Object, description)); - inner(); + pathStack.Push(path); + inner(); pathStack.Pop(); } diff --git a/backend/src/Notifo.Domain/Liquid/LiquidUser.cs b/backend/src/Notifo.Domain/Liquid/LiquidUser.cs index c23eeeee..eb3a94a8 100644 --- a/backend/src/Notifo.Domain/Liquid/LiquidUser.cs +++ b/backend/src/Notifo.Domain/Liquid/LiquidUser.cs @@ -23,12 +23,12 @@ public sealed class LiquidUser(User user) public static void Describe(LiquidProperties properties) { properties.AddString("fullName", - "The full name of the user."); + "The full name of the user. Can be null or undefined."); properties.AddString("emailAddress", - "The email address of the user."); + "The email address of the user. Can be null or undefined."); properties.AddString("phoneNumber", - "The phone number of the user."); + "The phone number of the user. Can be null or undefined."); } } diff --git a/backend/src/Notifo.Domain/Utils/UtilsServiceExtensions.cs b/backend/src/Notifo.Domain/Utils/UtilsServiceExtensions.cs index ac1c5985..1d77e198 100644 --- a/backend/src/Notifo.Domain/Utils/UtilsServiceExtensions.cs +++ b/backend/src/Notifo.Domain/Utils/UtilsServiceExtensions.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Notifo.Domain.Liquid; using Notifo.Domain.Utils; namespace Microsoft.Extensions.DependencyInjection; @@ -15,5 +16,8 @@ public static void AddMyUtils(this IServiceCollection services) { services.AddSingletonAs() .As(); + + services.AddSingletonAs() + .AsSelf(); } } diff --git a/backend/src/Notifo/Areas/Api/Controllers/ChannelTemplates/ChannelTemplatesController.cs b/backend/src/Notifo/Areas/Api/Controllers/ChannelTemplates/ChannelTemplatesController.cs index 31577099..96951e18 100644 --- a/backend/src/Notifo/Areas/Api/Controllers/ChannelTemplates/ChannelTemplatesController.cs +++ b/backend/src/Notifo/Areas/Api/Controllers/ChannelTemplates/ChannelTemplatesController.cs @@ -9,6 +9,7 @@ using Notifo.Areas.Api.Controllers.ChannelTemplates.Dtos; using Notifo.Domain.ChannelTemplates; using Notifo.Domain.Identity; +using Notifo.Domain.Liquid; using Notifo.Infrastructure; using Notifo.Infrastructure.Reflection; using Notifo.Pipeline; @@ -20,10 +21,12 @@ namespace Notifo.Areas.Api.Controllers.ChannelTemplates; public abstract class ChannelTemplatesController : BaseController where T : class, new() where TDto : class, new() { private readonly IChannelTemplateStore channelTemplateStore; + private readonly LiquidPropertiesProvider propertiesProvider; - protected ChannelTemplatesController(IChannelTemplateStore channelTemplateStore) + protected ChannelTemplatesController(IChannelTemplateStore channelTemplateStore, LiquidPropertiesProvider propertiesProvider) { this.channelTemplateStore = channelTemplateStore; + this.propertiesProvider = propertiesProvider; } /// @@ -47,6 +50,26 @@ public async Task> GetTemplates(string appId return response; } + /// + /// Get the template properties. + /// + /// The id of the app where the templates belong to. + /// Channel templates properties returned.. + /// App not found. + [HttpGet("properties")] + [AppPermission(NotifoRoles.AppAdmin)] + public ListResponseDto GetProperties(string appId) + { + var properties = propertiesProvider.GetProperties(); + + var response = new ListResponseDto(); + + response.Items.AddRange(properties.OrderBy(x => x.Path).Select(TemplatePropertyDto.FromDomainObject)); + response.Total = 0; + + return response; + } + /// /// Get the channel template by id. /// diff --git a/backend/src/Notifo/Areas/Api/Controllers/ChannelTemplates/Dtos/TemplatePropertyDto.cs b/backend/src/Notifo/Areas/Api/Controllers/ChannelTemplates/Dtos/TemplatePropertyDto.cs new file mode 100644 index 00000000..9296033a --- /dev/null +++ b/backend/src/Notifo/Areas/Api/Controllers/ChannelTemplates/Dtos/TemplatePropertyDto.cs @@ -0,0 +1,34 @@ +// ========================================================================== +// Notifo.io +// ========================================================================== +// Copyright (c) Sebastian Stehle +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Notifo.Domain.Liquid; +using Notifo.Infrastructure.Reflection; + +namespace Notifo.Areas.Api.Controllers.ChannelTemplates.Dtos; + +public sealed class TemplatePropertyDto +{ + /// + /// The property path. + /// + public string Path { get; set; } + + /// + /// The data ty. + /// + public LiquidPropertyType Type { get; set; } + + /// + /// The optional description. + /// + public string? Description { get; set; } + + public static TemplatePropertyDto FromDomainObject(LiquidProperty source) + { + return SimpleMapper.Map(source, new TemplatePropertyDto()); + } +} diff --git a/backend/src/Notifo/Areas/Api/Controllers/ChannelTemplates/EmailTemplatesController.cs b/backend/src/Notifo/Areas/Api/Controllers/ChannelTemplates/EmailTemplatesController.cs index 44d088dc..38a95034 100644 --- a/backend/src/Notifo/Areas/Api/Controllers/ChannelTemplates/EmailTemplatesController.cs +++ b/backend/src/Notifo/Areas/Api/Controllers/ChannelTemplates/EmailTemplatesController.cs @@ -9,6 +9,7 @@ using Notifo.Areas.Api.Controllers.ChannelTemplates.Dtos; using Notifo.Domain.Channels.Email; using Notifo.Domain.ChannelTemplates; +using Notifo.Domain.Liquid; namespace Notifo.Areas.Api.Controllers.ChannelTemplates; @@ -16,8 +17,8 @@ namespace Notifo.Areas.Api.Controllers.ChannelTemplates; [ApiExplorerSettings(GroupName = "EmailTemplates")] public sealed class EmailTemplatesController : ChannelTemplatesController { - public EmailTemplatesController(IChannelTemplateStore channelTemplateStore) - : base(channelTemplateStore) + public EmailTemplatesController(IChannelTemplateStore channelTemplateStore, LiquidPropertiesProvider propertiesProvider) + : base(channelTemplateStore, propertiesProvider) { } } diff --git a/backend/src/Notifo/Areas/Api/Controllers/ChannelTemplates/MessagingTemplatesController.cs b/backend/src/Notifo/Areas/Api/Controllers/ChannelTemplates/MessagingTemplatesController.cs index 4f716d34..974ff53a 100644 --- a/backend/src/Notifo/Areas/Api/Controllers/ChannelTemplates/MessagingTemplatesController.cs +++ b/backend/src/Notifo/Areas/Api/Controllers/ChannelTemplates/MessagingTemplatesController.cs @@ -9,6 +9,7 @@ using Notifo.Areas.Api.Controllers.ChannelTemplates.Dtos; using Notifo.Domain.Channels.Messaging; using Notifo.Domain.ChannelTemplates; +using Notifo.Domain.Liquid; namespace Notifo.Areas.Api.Controllers.ChannelTemplates; @@ -16,8 +17,8 @@ namespace Notifo.Areas.Api.Controllers.ChannelTemplates; [ApiExplorerSettings(GroupName = "MessagingTemplates")] public sealed class MessagingTemplatesController : ChannelTemplatesController { - public MessagingTemplatesController(IChannelTemplateStore channelTemplateStore) - : base(channelTemplateStore) + public MessagingTemplatesController(IChannelTemplateStore channelTemplateStore, LiquidPropertiesProvider propertiesProvider) + : base(channelTemplateStore, propertiesProvider) { } } diff --git a/backend/src/Notifo/Areas/Api/Controllers/ChannelTemplates/SmsTemplatesController.cs b/backend/src/Notifo/Areas/Api/Controllers/ChannelTemplates/SmsTemplatesController.cs index a74b1c1b..216b07c1 100644 --- a/backend/src/Notifo/Areas/Api/Controllers/ChannelTemplates/SmsTemplatesController.cs +++ b/backend/src/Notifo/Areas/Api/Controllers/ChannelTemplates/SmsTemplatesController.cs @@ -9,6 +9,7 @@ using Notifo.Areas.Api.Controllers.ChannelTemplates.Dtos; using Notifo.Domain.Channels.Sms; using Notifo.Domain.ChannelTemplates; +using Notifo.Domain.Liquid; namespace Notifo.Areas.Api.Controllers.ChannelTemplates; @@ -16,8 +17,8 @@ namespace Notifo.Areas.Api.Controllers.ChannelTemplates; [ApiExplorerSettings(GroupName = "SmsTemplates")] public sealed class SmsTemplatesController : ChannelTemplatesController { - public SmsTemplatesController(IChannelTemplateStore channelTemplateStore) - : base(channelTemplateStore) + public SmsTemplatesController(IChannelTemplateStore channelTemplateStore, LiquidPropertiesProvider propertiesProvider) + : base(channelTemplateStore, propertiesProvider) { } } diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 2d255bfa..dac33636 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -37,8 +37,8 @@ "react-popper": "2.3.0", "react-push-preview": "^0.1.9", "react-redux": "8.1.3", - "react-router": "6.19.0", - "react-router-dom": "6.19.0", + "react-router": "6.22.3", + "react-router-dom": "6.22.3", "react-router-redux": "4.0.8", "react-split": "2.0.14", "react-toastify": "^9.1.3", @@ -5681,9 +5681,9 @@ } }, "node_modules/@remix-run/router": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.12.0.tgz", - "integrity": "sha512-2hXv036Bux90e1GXTWSMfNzfDDK8LA8JYEWfyHxzvwdp6GyoWEovKc9cotb3KCKmkdwsIBuFGX7ScTWyiHv7Eg==", + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.3.tgz", + "integrity": "sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w==", "engines": { "node": ">=14.0.0" } @@ -20403,11 +20403,11 @@ } }, "node_modules/react-router": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.19.0.tgz", - "integrity": "sha512-0W63PKCZ7+OuQd7Tm+RbkI8kCLmn4GPjDbX61tWljPxWgqTKlEpeQUwPkT1DRjYhF8KSihK0hQpmhU4uxVMcdw==", + "version": "6.22.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.3.tgz", + "integrity": "sha512-dr2eb3Mj5zK2YISHK++foM9w4eBnO23eKnZEDs7c880P6oKbrjz/Svg9+nxqtHQK+oMW4OtjZca0RqPglXxguQ==", "dependencies": { - "@remix-run/router": "1.12.0" + "@remix-run/router": "1.15.3" }, "engines": { "node": ">=14.0.0" @@ -20417,12 +20417,12 @@ } }, "node_modules/react-router-dom": { - "version": "6.19.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.19.0.tgz", - "integrity": "sha512-N6dWlcgL2w0U5HZUUqU2wlmOrSb3ighJmtQ438SWbhB1yuLTXQ8yyTBMK3BSvVjp7gBtKurT554nCtMOgxCZmQ==", + "version": "6.22.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.3.tgz", + "integrity": "sha512-7ZILI7HjcE+p31oQvwbokjk6OA/bnFxrhJ19n82Ex9Ph8fNAq+Hm/7KchpMGlTgWhUxRHMMCut+vEtNpWpowKw==", "dependencies": { - "@remix-run/router": "1.12.0", - "react-router": "6.19.0" + "@remix-run/router": "1.15.3", + "react-router": "6.22.3" }, "engines": { "node": ">=14.0.0" diff --git a/frontend/package.json b/frontend/package.json index f1564bd7..afdbd153 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -32,8 +32,8 @@ "react-popper": "2.3.0", "react-push-preview": "^0.1.9", "react-redux": "8.1.3", - "react-router": "6.19.0", - "react-router-dom": "6.19.0", + "react-router": "6.22.3", + "react-router-dom": "6.22.3", "react-router-redux": "4.0.8", "react-split": "2.0.14", "react-toastify": "^9.1.3", diff --git a/frontend/src/app/framework/index.ts b/frontend/src/app/framework/index.ts index c600591f..7e0dad65 100644 --- a/frontend/src/app/framework/index.ts +++ b/frontend/src/app/framework/index.ts @@ -11,6 +11,7 @@ export * from './model'; export * from './react/ApiValue'; export * from './react/ClickOutside'; export * from './react/Code'; +export * from './react/CodeEditor'; export * from './react/Confirm'; export * from './react/DropZone'; export * from './react/ErrorBoundary'; @@ -20,7 +21,6 @@ export * from './react/FormControlError'; export * from './react/FormError'; export * from './react/Gist'; export * from './react/hooks'; -export * from './react/Marker'; export * from './react/Icon'; export * from './react/IFrame'; export * from './react/LanguageSelector'; diff --git a/frontend/src/app/framework/react/Code.tsx b/frontend/src/app/framework/react/Code.tsx index f4e7f821..be3fee9d 100644 --- a/frontend/src/app/framework/react/Code.tsx +++ b/frontend/src/app/framework/react/Code.tsx @@ -40,6 +40,7 @@ export const Code = (props: CodeProps) => { const editor = CodeMirror.fromTextArea(textarea, { foldGutter: true, gutters: [ + 'CodeMirror-lint-markers', 'CodeMirror-linenumbers', 'CodeMirror-foldgutter', ], @@ -48,6 +49,7 @@ export const Code = (props: CodeProps) => { lineNumbers: true, lineSeparator: undefined, lineWrapping: false, + theme: 'material', readOnly: true, tabindex: 0, tabSize: 2, diff --git a/frontend/src/app/framework/react/CodeEditor.stories.tsx b/frontend/src/app/framework/react/CodeEditor.stories.tsx new file mode 100644 index 00000000..16458732 --- /dev/null +++ b/frontend/src/app/framework/react/CodeEditor.stories.tsx @@ -0,0 +1,28 @@ +/* + * Notifo.io + * + * @license + * Copyright (c) Sebastian Stehle. All rights reserved. + */ + +import type { Meta, StoryObj } from '@storybook/react'; +import * as React from 'react'; +import { CodeEditor } from './CodeEditor'; + +const meta: Meta = { + component: CodeEditor, + render: args => { + // eslint-disable-next-line react-hooks/rules-of-hooks + const [value, setValue] = React.useState('Code'); + + return ( + + ); + + }, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; \ No newline at end of file diff --git a/frontend/src/app/framework/react/CodeEditor.tsx b/frontend/src/app/framework/react/CodeEditor.tsx new file mode 100644 index 00000000..5ab90e64 --- /dev/null +++ b/frontend/src/app/framework/react/CodeEditor.tsx @@ -0,0 +1,136 @@ +/* + * Notifo.io + * + * @license + * Copyright (c) Sebastian Stehle. All rights reserved. + */ + +import classNames from 'classnames'; +import * as CodeMirror from 'codemirror'; +import 'codemirror/addon/fold/brace-fold'; +import 'codemirror/addon/fold/foldcode'; +import 'codemirror/addon/fold/foldgutter'; +import 'codemirror/addon/fold/indent-fold'; +import * as React from 'react'; +import { Types } from './../utils'; +import { useEventCallback } from './hooks'; + +export interface CodeEditorProps { + // The actual value. + value?: any; + + // The class name. + className?: string; + + // The additional options. + initialOptions?: CodeMirror.EditorConfiguration; + + // The additional options. + options?: CodeMirror.EditorConfiguration; + + // Invoked when the value has been changed. + onChange?: (value: string) => void; + + // Invoked when blurred. + onBlur?: () => void; +} + +export const CodeEditor = (props: CodeEditorProps) => { + const { + className, + initialOptions, + onBlur, + onChange, + options, + value, + } = props; + + const [editor, setEditor] = React.useState(); + const onBlurRef = React.useRef(onBlur); + const onChangeRef = React.useRef(onChange); + const valueRef = React.useRef(''); + const ignoreNext = React.useRef(false); + + onBlurRef.current = onBlur; + onChangeRef.current = onChange; + + const doInit = useEventCallback((textarea: HTMLTextAreaElement) => { + if (!textarea) { + return; + } + + const editor = CodeMirror.fromTextArea(textarea, { + foldGutter: true, + gutters: [ + 'CodeMirror-lint-markers', + 'CodeMirror-linenumbers', + 'CodeMirror-foldgutter', + ], + indentUnit: 2, + indentWithTabs: false, + lineNumbers: true, + lineSeparator: undefined, + lineWrapping: false, + theme: 'material', + tabSize: 2, + ...initialOptions || {}, + }); + + editor.on('change', editor => { + const newValue = editor.getValue(); + + if (valueRef.current !== newValue) { + valueRef.current = newValue; + + ignoreNext.current = true; + try { + onChangeRef.current?.(newValue); + } finally { + ignoreNext.current = false; + } + } + }); + + editor.on('blur', () => { + onBlurRef.current?.(); + }); + + setEditor(editor); + }); + + React.useLayoutEffect(() => { + if (!editor) { + return; + } + + let actualText: string; + + if (Types.isString(value)) { + actualText = value; + } else { + actualText = JSON.stringify(value || {}, undefined, 2); + } + + + if (valueRef.current !== value) { + valueRef.current = value; + editor.setValue(actualText); + } + }, [editor, value]); + + React.useLayoutEffect(() => { + if (!editor || !options) { + return; + } + + for (const [key, value] of Object.entries(options)) { + editor.setOption(key as any, value); + } + }, [editor, options]); + + return ( +
+