suren-atoyan / monaco-react

Monaco Editor for React - use the monaco-editor in any React application without needing to use webpack (or rollup/parcel/etc) configuration files / plugins
https://monaco-react.surenatoyan.com/
MIT License
3.66k stars 262 forks source link

YAML validation for Monaco Editor #228

Open vrabota opened 3 years ago

vrabota commented 3 years ago

For YAML seems like we have indentation and code highlighting, but unfortunately, we can't validate the code (indentation format or naming, duplications of the fields).

I found a library (https://github.com/pengx17/monaco-yaml) that can maybe help, but I have issues to integrate with create-react-app.

I also tried this solution (https://github.com/suren-atoyan/monaco-react/issues/68#issuecomment-770205419) but unfortunately nothing works for create-react-app application.

Maybe someone integrated YAML validations and can share some knowledge.

suren-atoyan commented 3 years ago

this can be helpful. I'll try to find some time to make a working example

vrabota commented 3 years ago

It's hard to understand something in this example and how to integrate with monaco-react. Seems like all the code should be placed in public/index.html and we don't need monaco-react at all.

efreila commented 3 years ago

An update on this would be greatly appreciated

Chethannp commented 2 years ago

@suren-atoyan could you please provide an update on this.

Nivl commented 2 years ago

@suren-atoyan Did you manage to get some time to look into it? :)

suren-atoyan commented 2 years ago

@Nivl @Chethannp @efreila guys sorry, but this package isn't compatible with this yaml-monaco implmenetation. To avoid using webpack configuration in this package we download monaco sources from CDN. The mentioned yaml-monaco implementation heavily depends on the ESM version of monaco, which requires a specific webpack configuration

mohdashraf010897 commented 2 years ago

@suren-atoyan Is there a workaround that let us use https://github.com/remcohaszing/monaco-yaml with https://github.com/suren-atoyan/monaco-react ?

huruji commented 2 years ago

is there any example with monaco-yaml?

suren-atoyan commented 2 years ago

this might make it possible to use monaco-yaml with monaco-react

jseparovic commented 2 years ago

@suren-atoyan Cheers for the suggestion. I did manage to get it working using the webpack config from https://github.com/remcohaszing/monaco-yaml and the following component:

import React, {FC, useEffect} from 'react';

import * as monaco from "monaco-editor";
import Editor, { loader } from "@monaco-editor/react";
loader.config({ monaco });
import {setDiagnosticsOptions} from 'monaco-yaml';

// @ts-ignore
window.MonacoEnvironment = {
    getWorker(moduleId: any, label: string) {
        switch (label) {
            case 'editorWorkerService':
                // @ts-ignore
                return new Worker(new URL('monaco-editor/esm/vs/editor/editor.worker', import.meta.url));
            case 'css':
            case 'less':
            case 'scss':
                // @ts-ignore
                return new Worker(new URL('monaco-editor/esm/vs/language/css/css.worker', import.meta.url));
            case 'handlebars':
            case 'html':
            case 'razor':
                return new Worker(
                    // @ts-ignore
                    new URL('monaco-editor/esm/vs/language/html/html.worker', import.meta.url),
                );
            case 'json':
                return new Worker(
                    // @ts-ignore
                    new URL('monaco-editor/esm/vs/language/json/json.worker', import.meta.url),
                );
            case 'javascript':
            case 'typescript':
                return new Worker(
                    // @ts-ignore
                    new URL('monaco-editor/esm/vs/language/typescript/ts.worker', import.meta.url),
                );
            case 'yaml':
                // @ts-ignore
                return new Worker(new URL('monaco-yaml/yaml.worker', import.meta.url));
            default:
                throw new Error(`Unknown label ${label}`);
        }
    },
};

interface CodeEditorProps {
    language: string;
    value: any;
    disabled?: boolean;
    onChange(value: string|undefined): void;
    className?: string;
    width?: string;
    height?: string;
}

export const CodeEditor: FC<CodeEditorProps> = (props) => {
    const {language, value, disabled, onChange, className, width, height} = props;

    const handleOnChange = (value: string|undefined) => {
        onChange(value);
    }

    const handleEditorValidation = (markers: any) => {
        // model markers
        markers.forEach((marker: any) => console.log("onValidate:", marker.message));
    }

    useEffect(() => {
        setDiagnosticsOptions({
            // Have to set an empty Diagnostics options to get syntax checking
        });
    }, [])

    return (
        <div style={{border: "1px solid #ccc"}} className={className}>
            <Editor
                options={{
                    readOnly: disabled,
                    lineDecorationsWidth: 5,
                    lineNumbersMinChars: 0,
                    glyphMargin: false,
                    folding: false,
                    lineNumbers: 'off',
                    minimap: {
                        enabled: false
                    },
                    fontSize: 11,
                }}
                width={width}
                height={height}
                language={language}
                value={value}
                onValidate={handleEditorValidation}
                onChange={handleOnChange}
            />
        </div>
    );
}
josiext commented 1 year ago

@suren-atoyan Cheers for the suggestion. I did manage to get it working using the webpack config from https://github.com/remcohaszing/monaco-yaml and the following component:

import React, {FC, useEffect} from 'react';

import * as monaco from "monaco-editor";
import Editor, { loader } from "@monaco-editor/react";
loader.config({ monaco });
import {setDiagnosticsOptions} from 'monaco-yaml';

// @ts-ignore
window.MonacoEnvironment = {
    getWorker(moduleId: any, label: string) {
        switch (label) {
            case 'editorWorkerService':
                // @ts-ignore
                return new Worker(new URL('monaco-editor/esm/vs/editor/editor.worker', import.meta.url));
            case 'css':
            case 'less':
            case 'scss':
                // @ts-ignore
                return new Worker(new URL('monaco-editor/esm/vs/language/css/css.worker', import.meta.url));
            case 'handlebars':
            case 'html':
            case 'razor':
                return new Worker(
                    // @ts-ignore
                    new URL('monaco-editor/esm/vs/language/html/html.worker', import.meta.url),
                );
            case 'json':
                return new Worker(
                    // @ts-ignore
                    new URL('monaco-editor/esm/vs/language/json/json.worker', import.meta.url),
                );
            case 'javascript':
            case 'typescript':
                return new Worker(
                    // @ts-ignore
                    new URL('monaco-editor/esm/vs/language/typescript/ts.worker', import.meta.url),
                );
            case 'yaml':
                // @ts-ignore
                return new Worker(new URL('monaco-yaml/yaml.worker', import.meta.url));
            default:
                throw new Error(`Unknown label ${label}`);
        }
    },
};

interface CodeEditorProps {
    language: string;
    value: any;
    disabled?: boolean;
    onChange(value: string|undefined): void;
    className?: string;
    width?: string;
    height?: string;
}

export const CodeEditor: FC<CodeEditorProps> = (props) => {
    const {language, value, disabled, onChange, className, width, height} = props;

    const handleOnChange = (value: string|undefined) => {
        onChange(value);
    }

    const handleEditorValidation = (markers: any) => {
        // model markers
        markers.forEach((marker: any) => console.log("onValidate:", marker.message));
    }

    useEffect(() => {
        setDiagnosticsOptions({
            // Have to set an empty Diagnostics options to get syntax checking
        });
    }, [])

    return (
        <div style={{border: "1px solid #ccc"}} className={className}>
            <Editor
                options={{
                    readOnly: disabled,
                    lineDecorationsWidth: 5,
                    lineNumbersMinChars: 0,
                    glyphMargin: false,
                    folding: false,
                    lineNumbers: 'off',
                    minimap: {
                        enabled: false
                    },
                    fontSize: 11,
                }}
                width={width}
                height={height}
                language={language}
                value={value}
                onValidate={handleEditorValidation}
                onChange={handleOnChange}
            />
        </div>
    );
}

Throws:

Error: Target container is not a DOM element.

In a Next.js app :(

binhtran04 commented 3 months ago

I was able to configure @monaco-editor/react to work with monaco-yaml:

1. yaml.worker.ts

import "monaco-yaml/yaml.worker.js";

2. setupMonaco.ts

import { loader } from "@monaco-editor/react";
import * as monaco from "monaco-editor";
import editorWorker from "monaco-editor/esm/vs/editor/editor.worker?worker";
import jsonWorker from "monaco-editor/esm/vs/language/json/json.worker?worker";
import cssWorker from "monaco-editor/esm/vs/language/css/css.worker?worker";
import htmlWorker from "monaco-editor/esm/vs/language/html/html.worker?worker";
import tsWorker from "monaco-editor/esm/vs/language/typescript/ts.worker?worker";
import yamlWorker from "./yaml.worker.js?worker";

/** Configure Monaco editor for Vite */
self.MonacoEnvironment = {
  getWorker(_, label) {
    if (label === "json") {
      return new jsonWorker();
    }
    if (label === "css" || label === "scss" || label === "less") {
      return new cssWorker();
    }
    if (label === "html" || label === "handlebars" || label === "razor") {
      return new htmlWorker();
    }
    if (label === "typescript" || label === "javascript") {
      return new tsWorker();
    }
    if (label === "yaml") {
      return new yamlWorker();
    }
    return new editorWorker();
  },
};

loader.config({ monaco });
  1. import "./setupMonaco" to index.tsx
  2. YamlEditor.tsx
// In the docs, it is stated that there should only be one monaco yaml instance configured at a time
let monacoYamlInstance: MonacoYaml | undefined;

export function YamlEditor({
  schemaUri,
  path,
  ...props
}: YamlEditorProps) {
  useEffect(() => {
    if (schemaUri) {
      monacoYamlInstance?.update({
        enableSchemaRequest: true,
        schemas: [
          {
            fileMatch: [path ? `*/**${path}` : "*"],
            uri: schemaUri,
          },
        ],
      });
    }
  }, [path, schemaUri]);

  const handleEditorBeforeMount = (monaco: Monaco) => {
    if (!monacoYamlInstance) {
      monacoYamlInstance = configureMonacoYaml(monaco, {
        hover: true,
        completion: true,
        validate: true,
        format: true,
        enableSchemaRequest: true,
        schemas: schemaUri
          ? [
              {
                fileMatch: [path ? `*/**${path}` : "*"],
                uri: schemaUri,
              },
            ]
          : undefined,
      });
    }
  };

  return (
    <Editor
      defaultLanguage="yaml"
      path={path}
      beforeMount={handleEditorBeforeMount}
      {...props}
    />
  );
}