From a79b907adc6fdca928b08ccd2eb670a8fb364aef Mon Sep 17 00:00:00 2001 From: Guillaume Date: Sat, 13 Jan 2024 11:07:16 +0100 Subject: [PATCH] Add tools --- components/footer.tsx | 4 +- components/nav.tsx | 8 + .../docs/latest/api-endpoints/crud-routes.md | 2 + .../response-configuration/xml-support.md | 4 +- .../tutorials/use-persisting-data-buckets.md | 2 + package-lock.json | 96 +++++++++- package.json | 5 +- pages/tools/index.tsx | 64 +++++++ pages/tools/json-validator.tsx | 50 ++--- pages/tools/xml-to-json.tsx | 173 ++++++++++++++++++ pages/tools/xml-validator.tsx | 169 +++++++++++++++++ public/images/illustrations/json-valid.svg | 32 ++++ public/images/illustrations/xml-to-json.svg | 29 +++ public/images/illustrations/xml-valid.svg | 25 +++ styles/_user.scss | 10 + utils/code-editor.ts | 47 +++++ 16 files changed, 673 insertions(+), 47 deletions(-) create mode 100644 pages/tools/index.tsx create mode 100644 pages/tools/xml-to-json.tsx create mode 100644 pages/tools/xml-validator.tsx create mode 100644 public/images/illustrations/json-valid.svg create mode 100644 public/images/illustrations/xml-to-json.svg create mode 100644 public/images/illustrations/xml-valid.svg create mode 100644 utils/code-editor.ts diff --git a/components/footer.tsx b/components/footer.tsx index dfa390da..0474ac96 100644 --- a/components/footer.tsx +++ b/components/footer.tsx @@ -245,8 +245,8 @@ const Footer: FunctionComponent<{
  • - - JSON validator + + Useful tools
  • diff --git a/components/nav.tsx b/components/nav.tsx index caf73053..7ed094e6 100644 --- a/components/nav.tsx +++ b/components/nav.tsx @@ -233,6 +233,14 @@ const Nav: FunctionComponent = function () { > Templates + + Useful tools + diff --git a/content/docs/latest/api-endpoints/crud-routes.md b/content/docs/latest/api-endpoints/crud-routes.md index 5ecf2e3f..e9d1e52f 100644 --- a/content/docs/latest/api-endpoints/crud-routes.md +++ b/content/docs/latest/api-endpoints/crud-routes.md @@ -38,6 +38,8 @@ After creating a CRUD endpoint, you need to link it to a data bucket: The CRUD route will work with any content stored in your data bucket: valid JSON in the form of an array of objects, an object, a primitive, etc., or any non-valid JSON. The route behaviors will vary depending on the content stored in the bucket (see table below). +> 🛠️ Use our [JSON validator](/tools/json-validator/) to check if your content is valid JSON. + ### Resetting the data bucket content The data bucket content is generated when the server starts, and its state persists between calls. However, its state will not be saved in the [data file](docs:mockoon-data-files/data-storage-location), and you can reset it to its initial state by restarting the mock API. diff --git a/content/docs/latest/response-configuration/xml-support.md b/content/docs/latest/response-configuration/xml-support.md index fc347358..43829c5f 100644 --- a/content/docs/latest/response-configuration/xml-support.md +++ b/content/docs/latest/response-configuration/xml-support.md @@ -46,4 +46,6 @@ JSON equivalent (compacted): } ``` -> Please refer to [xml-js documentation](https://www.npmjs.com/package/xml-js) for more detail on how the XML is parsed. +> 📘 Please refer to [xml-js documentation](https://www.npmjs.com/package/xml-js) for more detail on how the XML is parsed. + +> 🛠️ Use our [XML to JSON converter](/tools/xml-to-json/) to get a preview of how Mockoon will convert your XML to JSON. diff --git a/content/tutorials/use-persisting-data-buckets.md b/content/tutorials/use-persisting-data-buckets.md index 1e416c10..8a87106c 100644 --- a/content/tutorials/use-persisting-data-buckets.md +++ b/content/tutorials/use-persisting-data-buckets.md @@ -40,6 +40,8 @@ Mockoon will automatically attribute a new **unique ID** to your data bucket. Yo Data buckets can contain any type of text content. They also support all of Mockoon's [templating helpers](docs:templating/overview). If your data bucket contains valid JSON, Mockoon will parse it to let you access the JS object, array, primitives, etc., using the templating helpers like (`data`, `each`, `if`, etc.). +> 🛠️ Use our [JSON validator](/tools/json-validator/) to check if your content is valid JSON. + ## Data bucket generation time Mockoon usually generates data buckets when the server starts. Their state will persist until the next restart, so you can always expect the same content to be returned. diff --git a/package-lock.json b/package-lock.json index 339ec9b4..6428ce57 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,12 +10,14 @@ "license": "MIT", "dependencies": { "@codemirror/lang-json": "^6.0.1", + "@codemirror/lang-xml": "^6.0.2", "@docsearch/react": "^3.5.2", "@tanstack/react-query": "^5.8.3", "@uiw/codemirror-theme-nord": "^4.21.21", "@uiw/react-codemirror": "^4.21.21", "bootstrap": "^5.3.2", "eslint-config-next": "^14.0.2", + "fast-xml-parser": "^4.3.3", "firebase": "^10.6.0", "github-slugger": "^2.0.0", "glob": "^10.3.10", @@ -33,7 +35,8 @@ "remark-gfm": "^4.0.0", "sass": "^1.69.5", "semver": "^7.5.4", - "typed.js": "^2.1.0" + "typed.js": "^2.1.0", + "xml-js": "^1.6.11" }, "devDependencies": { "@mockoon/cli": "^5.1.0", @@ -328,6 +331,18 @@ "@lezer/json": "^1.0.0" } }, + "node_modules/@codemirror/lang-xml": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-xml/-/lang-xml-6.0.2.tgz", + "integrity": "sha512-JQYZjHL2LAfpiZI2/qZ/qzDuSqmGKMwyApYmEUUCTxLM4MWS7sATUEfIguZQr9Zjx/7gcdnewb039smF6nC2zw==", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.4.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/xml": "^1.0.0" + } + }, "node_modules/@codemirror/language": { "version": "6.10.0", "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.0.tgz", @@ -1521,6 +1536,16 @@ "@lezer/common": "^1.0.0" } }, + "node_modules/@lezer/xml": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@lezer/xml/-/xml-1.0.4.tgz", + "integrity": "sha512-WmXKb5eX8+rRfZYSNRR5TPee/ZoDgBdVS/rj1VCJGDKa5gNldIctQYibCoFVyNhvZsyL/8nHbZJZPM4gnXN2Vw==", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, "node_modules/@mockoon/cli": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@mockoon/cli/-/cli-5.1.0.tgz", @@ -7109,6 +7134,27 @@ "punycode": "^1.3.2" } }, + "node_modules/fast-xml-parser": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.3.3.tgz", + "integrity": "sha512-coV/D1MhrShMvU6D0I+VAK3umz6hUaxxhL0yp/9RjfiYUfAv14rDhGQL+PLForhMdr0wq3PiV07WtkkNjJjNHg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/fastq": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", @@ -13709,8 +13755,7 @@ "node_modules/sax": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", - "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==", - "dev": true + "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==" }, "node_modules/scheduler": { "version": "0.23.0", @@ -14357,6 +14402,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" + }, "node_modules/style-mod": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.0.tgz", @@ -15815,7 +15865,6 @@ "version": "1.6.11", "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", - "dev": true, "dependencies": { "sax": "^1.2.4" }, @@ -16212,6 +16261,18 @@ "@lezer/json": "^1.0.0" } }, + "@codemirror/lang-xml": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-xml/-/lang-xml-6.0.2.tgz", + "integrity": "sha512-JQYZjHL2LAfpiZI2/qZ/qzDuSqmGKMwyApYmEUUCTxLM4MWS7sATUEfIguZQr9Zjx/7gcdnewb039smF6nC2zw==", + "requires": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.4.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/xml": "^1.0.0" + } + }, "@codemirror/language": { "version": "6.10.0", "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.0.tgz", @@ -17154,6 +17215,16 @@ "@lezer/common": "^1.0.0" } }, + "@lezer/xml": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@lezer/xml/-/xml-1.0.4.tgz", + "integrity": "sha512-WmXKb5eX8+rRfZYSNRR5TPee/ZoDgBdVS/rj1VCJGDKa5gNldIctQYibCoFVyNhvZsyL/8nHbZJZPM4gnXN2Vw==", + "requires": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, "@mockoon/cli": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@mockoon/cli/-/cli-5.1.0.tgz", @@ -21652,6 +21723,14 @@ "punycode": "^1.3.2" } }, + "fast-xml-parser": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.3.3.tgz", + "integrity": "sha512-coV/D1MhrShMvU6D0I+VAK3umz6hUaxxhL0yp/9RjfiYUfAv14rDhGQL+PLForhMdr0wq3PiV07WtkkNjJjNHg==", + "requires": { + "strnum": "^1.0.5" + } + }, "fastq": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", @@ -26532,8 +26611,7 @@ "sax": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz", - "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==", - "dev": true + "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==" }, "scheduler": { "version": "0.23.0", @@ -27045,6 +27123,11 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" }, + "strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" + }, "style-mod": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.0.tgz", @@ -28114,7 +28197,6 @@ "version": "1.6.11", "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", - "dev": true, "requires": { "sax": "^1.2.4" } diff --git a/package.json b/package.json index 12cb12c7..dc14de0d 100644 --- a/package.json +++ b/package.json @@ -36,12 +36,14 @@ }, "dependencies": { "@codemirror/lang-json": "^6.0.1", + "@codemirror/lang-xml": "^6.0.2", "@docsearch/react": "^3.5.2", "@tanstack/react-query": "^5.8.3", "@uiw/codemirror-theme-nord": "^4.21.21", "@uiw/react-codemirror": "^4.21.21", "bootstrap": "^5.3.2", "eslint-config-next": "^14.0.2", + "fast-xml-parser": "^4.3.3", "firebase": "^10.6.0", "github-slugger": "^2.0.0", "glob": "^10.3.10", @@ -59,7 +61,8 @@ "remark-gfm": "^4.0.0", "sass": "^1.69.5", "semver": "^7.5.4", - "typed.js": "^2.1.0" + "typed.js": "^2.1.0", + "xml-js": "^1.6.11" }, "devDependencies": { "@mockoon/cli": "^5.1.0", diff --git a/pages/tools/index.tsx b/pages/tools/index.tsx new file mode 100644 index 00000000..2bdb86b0 --- /dev/null +++ b/pages/tools/index.tsx @@ -0,0 +1,64 @@ +import { FunctionComponent } from 'react'; +import Card from '../../components/card'; +import Hero from '../../components/hero'; +import Meta from '../../components/meta'; +import Layout from '../../layout/layout'; + +const tools = [ + { + title: 'JSON validator', + description: 'Validate your JSON online and get detailed error messages', + links: [{ src: '/tools/json-validator/', text: 'Validate' }], + imageSrc: '/images/illustrations/json-valid.svg' + }, + { + title: 'XML validator', + description: 'Validate your XML online and get detailed error messages', + links: [{ src: '/tools/xml-validator/', text: 'Validate' }], + imageSrc: '/images/illustrations/xml-valid.svg' + }, + { + title: 'XML to JSON converter', + description: + 'Convert your XML data to a JSON object and verify its validity', + links: [{ src: '/tools/xml-to-json', text: 'Convert' }], + imageSrc: '/images/illustrations/xml-to-json.svg' + } +]; + +const Tools: FunctionComponent = function () { + return ( + + + + + +
    +
    +
    + {tools.map((tool, toolIndex) => ( +
    + +
    + ))} +
    +
    +
    +
    + ); +}; + +export default Tools; diff --git a/pages/tools/json-validator.tsx b/pages/tools/json-validator.tsx index afc30bd7..019ce587 100644 --- a/pages/tools/json-validator.tsx +++ b/pages/tools/json-validator.tsx @@ -1,12 +1,5 @@ import { json, jsonParseLinter } from '@codemirror/lang-json'; -import { - Diagnostic, - diagnosticCount, - forEachDiagnostic, - lintGutter, - linter -} from '@codemirror/lint'; -import { nordInit } from '@uiw/codemirror-theme-nord'; +import { Diagnostic, forEachDiagnostic, linter } from '@codemirror/lint'; import CodeMirror, { EditorSelection, ReactCodeMirrorRef @@ -15,11 +8,11 @@ import { FunctionComponent, useRef, useState } from 'react'; import Hero from '../../components/hero'; import Meta from '../../components/meta'; import Layout from '../../layout/layout'; +import { defaultCodeEditorConfig } from '../../utils/code-editor'; -const linterExtension = linter(jsonParseLinter(), { delay: 10 }); +const linterExtension = linter(jsonParseLinter(), { delay: 100 }); const JsonValidator: FunctionComponent = function () { const [error, setError] = useState(null); - const [errorCount, setErrorCount] = useState(0); const editor = useRef(); const scrollDocToView = () => { @@ -43,51 +36,36 @@ const JsonValidator: FunctionComponent = function () { title='JSON validator' subtitle='Validate your JSON online and get detailed error messages' /> -
    +
    -
    +
    { setError(null); - setErrorCount(diagnosticCount(view.state)); forEachDiagnostic(view.state, (error) => { setError(error); }); }} > - {error && (
    -
    - {error.message} - - {errorCount} error{errorCount > 1 ? 's' : ''} - -
    -
    )} @@ -101,7 +79,7 @@ const JsonValidator: FunctionComponent = function () {
    -
    +
    diff --git a/pages/tools/xml-to-json.tsx b/pages/tools/xml-to-json.tsx new file mode 100644 index 00000000..bbcf0369 --- /dev/null +++ b/pages/tools/xml-to-json.tsx @@ -0,0 +1,173 @@ +import { xml } from '@codemirror/lang-xml'; +import { Diagnostic, forEachDiagnostic } from '@codemirror/lint'; +import CodeMirror, { + EditorSelection, + ReactCodeMirrorRef +} from '@uiw/react-codemirror'; +import Link from 'next/link'; +import { FunctionComponent, useRef, useState } from 'react'; +import { xml2json } from 'xml-js'; +import Hero from '../../components/hero'; +import Meta from '../../components/meta'; +import Layout from '../../layout/layout'; +import { defaultCodeEditorConfig, xmlLinter } from '../../utils/code-editor'; + +const XmlToJson: FunctionComponent = function () { + const initialXml = + ' \n \n Paste your XML here\n'; + const [error, setError] = useState(null); + const xmlEditor = useRef(); + const [jsonContent, setJsonContent] = useState( + xml2json(initialXml, { + compact: true, + spaces: 2 + }) + ); + + const scrollDocToView = () => { + if (!xmlEditor?.current?.state?.doc) { + return; + } + + xmlEditor.current.view?.dispatch({ + selection: EditorSelection.single(error.from, error.to), + scrollIntoView: true + }); + }; + + return ( + + + +
    +
    +
    +
    + { + setError(null); + + forEachDiagnostic(view.state, (error) => { + setError(error); + }); + }} + onChange={(value) => { + try { + setJsonContent( + xml2json(value, { + compact: true, + spaces: 2 + }) + ); + } catch (error) {} + }} + > + + {error && ( + + )} + {!error && ( +
    +
    XML is valid!
    +
    + )} +
    + +
    + +
    +
    +
    +
    + +
    +
    +
    +
    +

    How do we convert XML to JSON?

    +

    + XML and JSON are popular formats to{' '} + store or exchange data between systems. Both + are text formats used to represent data in a + structured way. +

    +

    + Mockoon supports JSON in all its systems but is also capable of{' '} + + parsing the XML content + {' '} + from entering requests. Mockoon uses the{' '} + + xml-js NPM library + {' '} + to convert XML.
    + This library is also used in the tool on this page to help you + get a preview of the parsing result. It parses the XML in a way + that ensures no data is lost during the conversion as there are + some differences between XML and JSON. +

    + +

    + Differences between XML and JSON +

    +

    +
      +
    • + JSON uses mainly name/value pairs, while XML + uses a tree structure with nested elements. +
    • +
    • + XML supports comments, while JSON does not. +
    • +
    • + XML supports element attributes, while JSON + does not. +
    • +
    • + XML must have a single root element, while a + JSON root element could be an array or an object. +
    • +
    • + XML supports namespaces. Namespaces are a way + to avoid element name conflicts. +
    • +
    +
    +
    +
    +
    +
    + ); +}; + +export default XmlToJson; diff --git a/pages/tools/xml-validator.tsx b/pages/tools/xml-validator.tsx new file mode 100644 index 00000000..cd0a7b3a --- /dev/null +++ b/pages/tools/xml-validator.tsx @@ -0,0 +1,169 @@ +import { xml } from '@codemirror/lang-xml'; +import { Diagnostic, forEachDiagnostic } from '@codemirror/lint'; +import CodeMirror, { + EditorSelection, + ReactCodeMirrorRef +} from '@uiw/react-codemirror'; +import { FunctionComponent, useRef, useState } from 'react'; +import Hero from '../../components/hero'; +import Meta from '../../components/meta'; +import Layout from '../../layout/layout'; +import { defaultCodeEditorConfig, xmlLinter } from '../../utils/code-editor'; + +const XmlValidator: FunctionComponent = function () { + const [error, setError] = useState(null); + const xmlEditor = useRef(); + + const scrollDocToView = () => { + if (!xmlEditor?.current?.state?.doc) { + return; + } + + xmlEditor.current.view?.dispatch({ + selection: EditorSelection.single(error.from, error.to), + scrollIntoView: true + }); + }; + + return ( + + + + +
    +
    +
    +
    + \n \n Paste your XML here\n`} + lang='xml' + onUpdate={(view) => { + setError(null); + + forEachDiagnostic(view.state, (error) => { + setError(error); + }); + }} + > + + {error && ( + + )} + {!error && ( +
    +
    XML is valid!
    +
    + )} +
    +
    +
    +
    + +
    +
    +
    +
    +

    What is XML?

    +

    + XML stands for eXtensible Markup Language. It + is a markup language and{' '} + file format to store and transport data between + disparate systems. It is designed to be both human-readable and + machine-readable. It is very similar to HTML. +

    +

    + Several rules, defined by specifications, ensure that an XML + document is well-formed and understandable by the recipient. It + can be used to represent arbitrary data structures like lists or + records. +

    + +

    XML syntax

    +

    + XML is principally composed of a declaration,{' '} + elements, attributes, and{' '} + text or content. +

    +
      +
    • + Declaration: the first line of an XML + document is the declaration. It defines the XML version and + the encoding used. For example:{' '} + {``}. It is + mandatory for XML version 1.1 but optional for XML version + 1.0. +
    • +
    • + Elements: XML documents are composed of + elements. An element is defined by a start tag, an end tag, + and the content between them. For example:{' '} + {`content`} +
    • +
    • + Attributes: elements can have attributes that + are defined in the start tag. For example:{' '} + {`content`} +
    • +
    • + Text or content: the content of an element + can be text or other elements. For example:{' '} + {`content`} or{' '} + {`content`} +
    • +
    + +

    Common errors

    +

    + Here is a list of common errors you might encounter when writing + XML: +

    +
      +
    • + Missing closing tag: elements must have an + opening and a closing tag. For example:{' '} + {`content`} +
    • +
    • + Missing closing bracket: each element must + have an opening and a closing bracket. For example:{' '} + {`content`} +
    • +
    • + Missing quotes: attributes value must be + enclosed in quotes. For example:{' '} + {`content`} +
    • +
    +
    +
    +
    +
    +
    + ); +}; + +export default XmlValidator; diff --git a/public/images/illustrations/json-valid.svg b/public/images/illustrations/json-valid.svg new file mode 100644 index 00000000..152d2033 --- /dev/null +++ b/public/images/illustrations/json-valid.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/images/illustrations/xml-to-json.svg b/public/images/illustrations/xml-to-json.svg new file mode 100644 index 00000000..872117d3 --- /dev/null +++ b/public/images/illustrations/xml-to-json.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/images/illustrations/xml-valid.svg b/public/images/illustrations/xml-valid.svg new file mode 100644 index 00000000..d26b7f47 --- /dev/null +++ b/public/images/illustrations/xml-valid.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/styles/_user.scss b/styles/_user.scss index 304bac97..fcf551d3 100644 --- a/styles/_user.scss +++ b/styles/_user.scss @@ -41,6 +41,16 @@ pre .code { } } +.code-editor-container { + height: 80vh; +} + +@media screen and (min-width: 768px) { + .code-editor-container { + height: 60vh; + } +} + // disable some style for blocks of code pre code { background-color: transparent; diff --git a/utils/code-editor.ts b/utils/code-editor.ts new file mode 100644 index 00000000..a7b70623 --- /dev/null +++ b/utils/code-editor.ts @@ -0,0 +1,47 @@ +import { lintGutter, linter } from '@codemirror/lint'; +import { nordInit } from '@uiw/codemirror-theme-nord'; +import { + EditorView, + Extension, + ReactCodeMirrorProps +} from '@uiw/react-codemirror'; +import { XMLValidator } from 'fast-xml-parser'; + +export const xmlLinter = linter( + (view) => { + const validation = XMLValidator.validate(view.state.doc.toString()); + + if (validation !== true && validation.err) { + const startChar = + view.state.doc.line(validation.err.line).from + validation.err.col; + + return [ + { + from: startChar, + to: startChar, + severity: 'error', + message: validation.err.msg + } + ]; + } else { + return []; + } + }, + { delay: 100 } +); + +export const defaultCodeEditorConfig = ( + extensions: Extension[] = [] +): ReactCodeMirrorProps => ({ + theme: nordInit({ + settings: { + fontFamily: + '"Fira Code", Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace' + } + }), + extensions: [EditorView.lineWrapping, lintGutter(), ...extensions], + basicSetup: { lintKeymap: false }, + height: '100%', + style: { minHeight: '0' }, + className: 'h-100' +});