From b417911957c6e26b9343c255e7158755e21e2826 Mon Sep 17 00:00:00 2001 From: Emir Hasan Date: Thu, 21 Nov 2024 07:51:15 +0100 Subject: [PATCH 1/4] Add linters to code editor --- package.json | 16 +- .../codemirror/CodeMirror.stories.tsx | 8 + src/extensions/codemirror/CodeMirror.tsx | 34 ++- src/extensions/codemirror/debouncedLinter.ts | 26 +++ .../hooks/useCodemirrorModeExtension.hooks.ts | 13 +- src/extensions/codemirror/linters/jsLinter.ts | 38 +++ .../codemirror/linters/turtleLinter.ts | 102 ++++++++ src/extensions/codemirror/types.ts | 13 ++ yarn.lock | 219 +++++++++++++++--- 9 files changed, 420 insertions(+), 49 deletions(-) create mode 100644 src/extensions/codemirror/debouncedLinter.ts create mode 100644 src/extensions/codemirror/linters/jsLinter.ts create mode 100644 src/extensions/codemirror/linters/turtleLinter.ts create mode 100644 src/extensions/codemirror/types.ts diff --git a/package.json b/package.json index 2ddd64a0..c61bb1e9 100644 --- a/package.json +++ b/package.json @@ -70,16 +70,17 @@ "@blueprintjs/select": "^5.2.2", "@carbon/icons": "^11.47.1", "@carbon/react": "^1.64.1", - "@mavrin/remark-typograf": "^2.2.0", - "codemirror": "^6.0.1", - "@codemirror/legacy-modes": "^6.4.1", - "@codemirror/lang-markdown": "^6.2.5", "@codemirror/lang-json": "^6.0.1", + "@codemirror/lang-markdown": "^6.2.5", "@codemirror/lang-xml": "^6.1.0", - "xml-formatter": "^3.6.3", + "@codemirror/legacy-modes": "^6.4.1", + "@mavrin/remark-typograf": "^2.2.0", + "codemirror": "^6.0.1", "color": "^4.2.3", "compute-scroll-into-view": "^3.1.0", + "jshint": "^2.13.6", "lodash": "^4.17.21", + "n3": "^1.23.0", "re-resizable": "6.9.9", "react": "^16.13.1", "react-dom": "^16.13.1", @@ -94,7 +95,8 @@ "remark-parse": "^10.0.2", "reset-css": "^5.0.2", "unified": "^11.0.5", - "wicg-inert": "^3.1.3" + "wicg-inert": "^3.1.3", + "xml-formatter": "^3.6.3" }, "devDependencies": { "@babel/core": "^7.25.2", @@ -120,7 +122,9 @@ "@types/codemirror": "^5.60.15", "@types/color": "^3.0.6", "@types/jest": "^29.5.12", + "@types/jshint": "^2.12.4", "@types/lodash": "^4.17.7", + "@types/n3": "^1.21.1", "@types/react-syntax-highlighter": "^15.5.13", "@typescript-eslint/eslint-plugin": "^5.62.0", "@typescript-eslint/parser": "^5.62.0", diff --git a/src/extensions/codemirror/CodeMirror.stories.tsx b/src/extensions/codemirror/CodeMirror.stories.tsx index 270c4008..f369c9f4 100644 --- a/src/extensions/codemirror/CodeMirror.stories.tsx +++ b/src/extensions/codemirror/CodeMirror.stories.tsx @@ -22,3 +22,11 @@ BasicExample.args = { mode: "markdown", defaultValue: "**test me**", }; + +export const LinterExample = TemplateFull.bind({}); +LinterExample.args = { + name: "codeinput", + defaultValue: "**test me**", + mode: "json", + useLinting: true, +}; diff --git a/src/extensions/codemirror/CodeMirror.tsx b/src/extensions/codemirror/CodeMirror.tsx index 102b3459..0fa5c2bd 100644 --- a/src/extensions/codemirror/CodeMirror.tsx +++ b/src/extensions/codemirror/CodeMirror.tsx @@ -1,6 +1,7 @@ -import React, { useRef } from "react"; +import React, { useMemo, useRef } from "react"; import { defaultKeymap, indentWithTab } from "@codemirror/commands"; import { foldKeymap } from "@codemirror/language"; +import { lintGutter } from "@codemirror/lint"; import { EditorState, Extension } from "@codemirror/state"; import { DOMEventHandlers, EditorView, KeyBinding, keymap, Rect, ViewUpdate } from "@codemirror/view"; import { minimalSetup } from "codemirror"; @@ -14,6 +15,8 @@ import { supportedCodeEditorModes, useCodeMirrorModeExtension, } from "./hooks/useCodemirrorModeExtension.hooks"; +import { jsLinter } from "./linters/jsLinter"; +import { turtleLinter } from "./linters/turtleLinter"; //adaptations import { adaptedCodeFolding, @@ -25,6 +28,7 @@ import { adaptedLineNumbers, adaptedPlaceholder, } from "./tests/codemirrorTestHelper"; +import { EditorMode, ExtensionCreator } from "./types"; export interface CodeEditorProps { // Is called with the editor instance that allows access via the CodeMirror API @@ -133,6 +137,10 @@ export interface CodeEditorProps { * If the key is enabled as normal input, i.e. it won't have the behavior of changing to the next input element, expected in a web app. */ enableTab?: boolean; + /** + * If the editor should use a linting feature. + */ + useLinting?: boolean; } const addExtensionsFor = (flag: boolean, ...extensions: Extension[]) => (flag ? [...extensions] : []); @@ -140,6 +148,11 @@ const addToKeyMapConfigFor = (flag: boolean, ...keys: any) => (flag ? [...keys] const addHandlersFor = (flag: boolean, handlerName: string, handler: any) => flag ? ({ [handlerName]: handler } as DOMEventHandlers) : {}; +const ModeLinterMap: ReadonlyMap> = new Map([ + [EditorMode.Turtle, [turtleLinter]], + [EditorMode.JavaScript, [jsLinter]], +]); + /** * Includes a code editor, currently we use CodeMirror library as base. */ @@ -170,9 +183,27 @@ export const CodeEditor = ({ tabForceSpaceForModes = ["python", "yaml"], enableTab = false, height, + useLinting = false, }: CodeEditorProps) => { const parent = useRef(undefined); + // console.log("outerDivAttributes", outerDivAttributes); + + const linters = useMemo(() => { + if (!useLinting || !mode) { + return []; + } + + const values = [lintGutter()]; + + const linters = ModeLinterMap.get(mode as EditorMode); + if (linters) { + values.push(...linters.map((linter) => linter())); + } + + return values; + }, [useLinting, mode]); + const onKeyDownHandler = (event: KeyboardEvent, view: EditorView) => { if (onKeyDown && !onKeyDown(event)) { if (event.key === "Enter") { @@ -250,6 +281,7 @@ export const CodeEditor = ({ addExtensionsFor(shouldHighlightActiveLine, adaptedHighlightActiveLine()), addExtensionsFor(wrapLines, EditorView?.lineWrapping), addExtensionsFor(supportCodeFolding, adaptedFoldGutter(), adaptedCodeFolding()), + addExtensionsFor(useLinting, ...linters), additionalExtensions, ]; diff --git a/src/extensions/codemirror/debouncedLinter.ts b/src/extensions/codemirror/debouncedLinter.ts new file mode 100644 index 00000000..bf080b1c --- /dev/null +++ b/src/extensions/codemirror/debouncedLinter.ts @@ -0,0 +1,26 @@ +import { Diagnostic } from "@codemirror/lint"; +import { EditorView } from "@codemirror/view"; +import { debounce } from "lodash"; + +import { Linter } from "./types"; + +const DEBOUNCE_TIME = 500; + +export const debouncedLinter = (lintFunction: Linter, time = DEBOUNCE_TIME) => { + const debouncedFn = debounce( + ( + view: EditorView, + resolve: (diagnostics: ReadonlyArray | Promise>) => void + ) => { + const diagnostics = lintFunction(view); + resolve(diagnostics); + }, + time + ); + + return (view: EditorView) => { + return new Promise>((resolve) => { + debouncedFn(view, resolve); + }); + }; +}; diff --git a/src/extensions/codemirror/hooks/useCodemirrorModeExtension.hooks.ts b/src/extensions/codemirror/hooks/useCodemirrorModeExtension.hooks.ts index ece0952c..8f22d1e3 100644 --- a/src/extensions/codemirror/hooks/useCodemirrorModeExtension.hooks.ts +++ b/src/extensions/codemirror/hooks/useCodemirrorModeExtension.hooks.ts @@ -1,18 +1,17 @@ -import { defaultHighlightStyle, StreamLanguage, StreamParser, LanguageSupport } from "@codemirror/language"; - +import { json } from "@codemirror/lang-json"; //modes imports import { markdown } from "@codemirror/lang-markdown"; -import { json } from "@codemirror/lang-json"; import { xml } from "@codemirror/lang-xml"; +import { defaultHighlightStyle, LanguageSupport, StreamLanguage, StreamParser } from "@codemirror/language"; import { javascript } from "@codemirror/legacy-modes/mode/javascript"; +import { jinja2 } from "@codemirror/legacy-modes/mode/jinja2"; +import { mathematica } from "@codemirror/legacy-modes/mode/mathematica"; +import { ntriples } from "@codemirror/legacy-modes/mode/ntriples"; import { python } from "@codemirror/legacy-modes/mode/python"; import { sparql } from "@codemirror/legacy-modes/mode/sparql"; import { sql } from "@codemirror/legacy-modes/mode/sql"; import { turtle } from "@codemirror/legacy-modes/mode/turtle"; -import { jinja2 } from "@codemirror/legacy-modes/mode/jinja2"; import { yaml } from "@codemirror/legacy-modes/mode/yaml"; -import { ntriples } from "@codemirror/legacy-modes/mode/ntriples"; -import { mathematica } from "@codemirror/legacy-modes/mode/mathematica"; //adaptations import { adaptedSyntaxHighlighting } from "../tests/codemirrorTestHelper"; @@ -39,6 +38,6 @@ export const useCodeMirrorModeExtension = (mode?: SupportedCodeEditorModes) => { return !mode ? adaptedSyntaxHighlighting(defaultHighlightStyle) : ["json", "markdown", "xml"].includes(mode) - ? ((typeof supportedModes[mode] === "function" ? supportedModes[mode] : () => {}) as () => LanguageSupport)() + ? ((typeof supportedModes[mode] === "function" ? supportedModes[mode] : () => null) as () => LanguageSupport)() : StreamLanguage?.define(supportedModes[mode] as StreamParser); }; diff --git a/src/extensions/codemirror/linters/jsLinter.ts b/src/extensions/codemirror/linters/jsLinter.ts new file mode 100644 index 00000000..bec78e4a --- /dev/null +++ b/src/extensions/codemirror/linters/jsLinter.ts @@ -0,0 +1,38 @@ +import { Diagnostic, linter } from "@codemirror/lint"; +import { JSHINT as jshint } from "jshint"; + +import { ExtensionCreator } from "../types"; + +const lintOptions = { + esversion: 11, + browser: true, +}; + +/** + * Sets up the javascript linter. Documentation: https://codemirror.net/examples/lint/ + */ +export const jsLinter: ExtensionCreator = () => { + return linter((view) => { + const diagnostics: Array = []; + const codeText = view.state.doc.toJSON(); + jshint(codeText, lintOptions); + const errors = jshint?.data()?.errors; + + if (errors && errors.length > 0) { + errors.forEach((error) => { + const selectedLine = view.state.doc.line(error.line); + + const diagnostic: Diagnostic = { + from: selectedLine.from, + to: selectedLine.to, + severity: "error", + message: error.reason, + }; + + diagnostics.push(diagnostic); + }); + } + + return diagnostics; + }); +}; diff --git a/src/extensions/codemirror/linters/turtleLinter.ts b/src/extensions/codemirror/linters/turtleLinter.ts new file mode 100644 index 00000000..b91388a6 --- /dev/null +++ b/src/extensions/codemirror/linters/turtleLinter.ts @@ -0,0 +1,102 @@ +import { Diagnostic, linter } from "@codemirror/lint"; +import { EditorView } from "@codemirror/view"; +import { Parser } from "n3"; + +import { debouncedLinter } from "../debouncedLinter"; +import { ExtensionCreator, Linter } from "../types"; + +const parser = new Parser(); + +const EMPTY_RESOURCE = "<>"; + +const getError = (message: string, view: EditorView) => { + const lineMatch = message.match(/(?<=line )\d{1,}/); + const valueMatch = message.match(/"([^"]*)"/); + + const lineNumber = lineMatch ? Number(lineMatch[0]) : 1; + // the [1] index is used to get the caputre group + const errorContent = valueMatch && valueMatch[1]; + + const line = view.state.doc.line(lineNumber); + const position = line.text.search(errorContent ?? /\S/); + + const from = line.from + position; + const errorLength = errorContent?.length; + + return { from, to: errorLength ? from + errorLength : line.to }; +}; + +const getQuadError = (view: EditorView) => { + const lines = view.state.doc.toJSON(); + + for (let i = 0; i < lines.length; i += 1) { + const input = lines[i].trim(); + + if (!input) { + continue; + } + + if (input.includes(EMPTY_RESOURCE)) { + // i + 1 is used here because the codemirror uses 1-indexes + const line = view.state.doc.line(i + 1); + const position = line.text.search(EMPTY_RESOURCE); + + const from = line.from + position; + + return { + from, + to: from + EMPTY_RESOURCE.length, + }; + } + } + + return { from: 0, to: view.state.doc.length }; +}; + +const n3Linter: Linter = (view) => { + const diagnostics: Array = []; + const value = view.state.doc.toString(); + + try { + const quads = parser.parse(value); + + quads.forEach((quad) => { + if (!quad.subject || !quad.predicate || !quad.object) { + const { from, to } = getQuadError(view); + + view.dispatch({ + scrollIntoView: true, + }); + + diagnostics.push({ + from, + to, + severity: "error", + message: `Invalid RDF quad:\n\nsubject: ${quad.subject}\npredicate: ${quad.predicate}\nobject: ${quad.object}`, + }); + } + }); + } catch (error) { + const { message } = error as Error; + + const { from, to } = getError(message, view); + + view.dispatch({ + scrollIntoView: true, + }); + + diagnostics.push({ + from, + to, + severity: "error", + message: (error as Error).message, + }); + } + + return diagnostics; +}; + +/** + * Sets up the turtle linter. Documentation: https://codemirror.net/examples/lint/ + */ +export const turtleLinter: ExtensionCreator = () => linter(debouncedLinter(n3Linter)); diff --git a/src/extensions/codemirror/types.ts b/src/extensions/codemirror/types.ts new file mode 100644 index 00000000..14e399ba --- /dev/null +++ b/src/extensions/codemirror/types.ts @@ -0,0 +1,13 @@ +import { Diagnostic } from "@codemirror/lint"; +import { Extension } from "@codemirror/state"; +import { EditorView } from "@codemirror/view"; + +export type Linter = (view: EditorView) => ReadonlyArray | Promise>; + +export enum EditorMode { + Turtle = "turtle", + SPARQL = "sparql", + JavaScript = "javascript", +} + +export type ExtensionCreator = (options?: T) => Extension; diff --git a/yarn.lock b/yarn.lock index 698aa61e..ecab37f9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2551,6 +2551,13 @@ resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f" integrity sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A== +"@rdfjs/types@^1.1.0": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@rdfjs/types/-/types-1.1.2.tgz#9c2f9848c44c26d383bed2a808571de3b93b808d" + integrity sha512-wqpOJK1QCbmsGNtyzYnojPU8gRDPid2JO0Q0kMtb4j65xhCK880cnKAfEOwC+dX85VJcCByQx5zOwyyfCjDJsg== + dependencies: + "@types/node" "*" + "@sinclair/typebox@^0.27.8": version "0.27.8" resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" @@ -3517,6 +3524,11 @@ "@types/tough-cookie" "*" parse5 "^7.0.0" +"@types/jshint@^2.12.4": + version "2.12.4" + resolved "https://registry.yarnpkg.com/@types/jshint/-/jshint-2.12.4.tgz#8ba3e1cc1d7e0bbe5979062c5dbfc05219f3f377" + integrity sha512-oglnnIxrE50jLiDjc96F26E8kU4U1t+WI8STeEDWMMQaRZQ1+V0yYxiqQ0WZefviVbMwCw0MYIrjdTDEvRXjMQ== + "@types/json-schema@*", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": version "7.0.15" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" @@ -3571,6 +3583,14 @@ resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197" integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA== +"@types/n3@^1.21.1": + version "1.21.1" + resolved "https://registry.yarnpkg.com/@types/n3/-/n3-1.21.1.tgz#5e9f93f50e7a77f8c40bffa3e66f67209633c235" + integrity sha512-9KxFlFj3etnpdI2nyQEp/jHry5DHxWT22z9Nc/y/hdHe0CHVc9rKu+NacWKUyN06dDLDh7ZnjCzY8yBJ9lmzdw== + dependencies: + "@rdfjs/types" "^1.1.0" + "@types/node" "*" + "@types/node@*": version "20.11.19" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.19.tgz#b466de054e9cb5b3831bee38938de64ac7f81195" @@ -4011,6 +4031,13 @@ abab@^2.0.6: resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291" integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA== +abort-controller@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" + integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== + dependencies: + event-target-shim "^5.0.0" + accepts@~1.3.8: version "1.3.8" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" @@ -4583,6 +4610,14 @@ buffer@^5.5.0: base64-js "^1.3.1" ieee754 "^1.1.13" +buffer@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" + integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.2.1" + bytes@3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" @@ -4887,6 +4922,14 @@ cli-truncate@^4.0.0: slice-ansi "^5.0.0" string-width "^7.0.0" +cli@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/cli/-/cli-1.0.1.tgz#22817534f24bfa4950c34d532d48ecbc621b8c14" + integrity sha512-41U72MB56TfUMGndAKK8vJ78eooOD4Z5NOL4xEfjc0c23s+6EYKXlXsmACBVclLP1yOfWCgEganVzddVrSNoTg== + dependencies: + exit "0.1.2" + glob "^7.1.1" + cliui@^8.0.1: version "8.0.1" resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" @@ -5055,6 +5098,13 @@ consola@^3.2.3: resolved "https://registry.yarnpkg.com/consola/-/consola-3.2.3.tgz#0741857aa88cfa0d6fd53f1cff0375136e98502f" integrity sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ== +console-browserify@1.1.x: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10" + integrity sha512-duS7VP5pvfsNLDvL1O4VOEbw37AI3A4ZUQYemvDlnpGrNu9tprR7BYWpDYwC0Xia0Zxz5ZupdiIrUp0GH1aXfg== + dependencies: + date-now "^0.1.4" + constant-case@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/constant-case/-/constant-case-2.0.0.tgz#4175764d389d3fa9c8ecd29186ed6005243b6a46" @@ -5128,6 +5178,11 @@ core-js@^3.19.2: resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.30.2.tgz#6528abfda65e5ad728143ea23f7a14f0dcf503fc" integrity sha512-uBJiDmwqsbJCWHAwjrx3cvjbMXP7xD72Dmsn5LOJpiRmE3WbBbN5rCqQ2Qh6Ek6/eOrjlWngEynBWo4VxerQhg== +core-util-is@~1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" + integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== + cosmiconfig@^7.0.0, cosmiconfig@^7.0.1: version "7.1.0" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6" @@ -5376,6 +5431,11 @@ data-view-byte-offset@^1.0.0: es-errors "^1.3.0" is-data-view "^1.0.1" +date-now@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" + integrity sha512-AsElvov3LoNB7tf5k37H2jYSB+ZZPMT5sG2QjJCcdlV5chIv6htBUBUui2IKRjgtKAKtCBN7Zbwa+MtwLjSeNw== + debug@2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" @@ -5592,6 +5652,14 @@ dom-helpers@^5.0.1: "@babel/runtime" "^7.8.7" csstype "^3.0.2" +dom-serializer@0: + version "0.2.2" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" + integrity sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g== + dependencies: + domelementtype "^2.0.1" + entities "^2.0.0" + dom-serializer@^1.0.1: version "1.4.1" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.4.1.tgz#de5d41b1aea290215dc45a6dae8adcf1d32e2d30" @@ -5601,6 +5669,11 @@ dom-serializer@^1.0.1: domhandler "^4.2.0" entities "^2.0.0" +domelementtype@1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" + integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== + domelementtype@^2.0.1, domelementtype@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" @@ -5613,6 +5686,13 @@ domexception@^4.0.0: dependencies: webidl-conversions "^7.0.0" +domhandler@2.3: + version "2.3.0" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-2.3.0.tgz#2de59a0822d5027fabff6f032c2b25a2a8abe738" + integrity sha512-q9bUwjfp7Eif8jWxxxPSykdRZAb6GkguBGSgvvCrhI9wB71W2K/Kvv4E61CF/mcCfnVJDeDWx/Vb/uAqbDj6UQ== + dependencies: + domelementtype "1" + domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c" @@ -5620,6 +5700,14 @@ domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1: dependencies: domelementtype "^2.2.0" +domutils@1.5: + version "1.5.1" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.5.1.tgz#dcd8488a26f563d61079e48c9f7b7e32373682cf" + integrity sha512-gSu5Oi/I+3wDENBsOWBiRK1eoGxcywYSqg3rR960/+EfY0CF4EX1VPkgHOZ3WiS/Jg2DtliF6BhWcHlfpYUcGw== + dependencies: + dom-serializer "0" + domelementtype "1" + domutils@^2.5.2, domutils@^2.8.0: version "2.8.0" resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135" @@ -5722,6 +5810,11 @@ enhanced-resolve@^5.15.0: graceful-fs "^4.2.4" tapable "^2.2.0" +entities@1.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-1.0.0.tgz#b2987aa3821347fcde642b24fdfc9e4fb712bf26" + integrity sha512-LbLqfXgJMmy81t+7c14mnulFHJ170cM6E+0vMXR9k/ZiZwgX8i5pNgjTCX3SO4VeUsFLV+8InixoretwU+MjBQ== + entities@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" @@ -6219,12 +6312,17 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== +event-target-shim@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" + integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== + eventemitter3@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== -events@^3.2.0: +events@^3.2.0, events@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== @@ -6264,7 +6362,7 @@ exenv@^1.2.2: resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d" integrity sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw== -exit@^0.1.2: +exit@0.1.2, exit@0.1.x, exit@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ== @@ -6773,7 +6871,7 @@ glob@^10.3.7: minipass "^5.0.0 || ^6.0.2 || ^7.0.0" path-scurry "^1.10.1" -glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: +glob@^7.1.1, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -7168,6 +7266,17 @@ html-webpack-plugin@^5.5.0: pretty-error "^4.0.0" tapable "^2.0.0" +htmlparser2@3.8.x: + version "3.8.3" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.8.3.tgz#996c28b191516a8be86501a7d79757e5c70c1068" + integrity sha512-hBxEg3CYXe+rPIua8ETe7tmG3XDn9B0edOE/e9wH2nLczxzgdu0m0aNHY+5wFZiviLWLdANPJTssa92dMcXQ5Q== + dependencies: + domelementtype "1" + domhandler "2.3" + domutils "1.5" + entities "1.0" + readable-stream "1.1" + htmlparser2@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7" @@ -7258,7 +7367,7 @@ identity-obj-proxy@^3.0.0: dependencies: harmony-reflect "^1.4.6" -ieee754@^1.1.13: +ieee754@^1.1.13, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== @@ -7317,7 +7426,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4: +inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -7718,6 +7827,11 @@ is-windows@^1.0.1: resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ== + isarray@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" @@ -8285,6 +8399,19 @@ jsesc@~0.5.0: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== +jshint@^2.13.6: + version "2.13.6" + resolved "https://registry.yarnpkg.com/jshint/-/jshint-2.13.6.tgz#3679a2687a3066fa9034ef85d8c305613a31eec6" + integrity sha512-IVdB4G0NTTeQZrBoM8C5JFVLjV2KtZ9APgybDA1MK73xb09qFs0jCXyQLnCOp1cSZZZbvhq/6mfXHUTaDkffuQ== + dependencies: + cli "~1.0.0" + console-browserify "1.1.x" + exit "0.1.x" + htmlparser2 "3.8.x" + lodash "~4.17.21" + minimatch "~3.0.2" + strip-json-comments "1.0.x" + json-buffer@3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" @@ -8501,7 +8628,7 @@ lodash.truncate@^4.4.2: resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw== -lodash@^4.17.20, lodash@^4.17.21: +lodash@^4.17.20, lodash@^4.17.21, lodash@~4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -9232,6 +9359,13 @@ minimatch@^9.0.1: dependencies: brace-expansion "^2.0.1" +minimatch@~3.0.2: + version "3.0.8" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.8.tgz#5e6a59bd11e2ab0de1cfb843eb2d82e546c321c1" + integrity sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q== + dependencies: + brace-expansion "^1.1.7" + minimist-options@4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619" @@ -9296,6 +9430,15 @@ ms@2.1.3: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +n3@^1.23.0: + version "1.23.0" + resolved "https://registry.yarnpkg.com/n3/-/n3-1.23.0.tgz#463d6da406ae03d2534d3404dae777c7773483f8" + integrity sha512-JEbhFlxFTNsHTHUAGykRoVNRG4Sqsu8Wn+SXq99l9pRQPgqJzvY4CwNp/yxRGwb4oni9BT5sH5kevnbVUIUvkQ== + dependencies: + buffer "^6.0.3" + queue-microtask "^1.1.2" + readable-stream "^4.0.0" + nanoid@^3.3.6, nanoid@^3.3.7: version "3.3.7" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" @@ -10177,7 +10320,7 @@ querystringify@^2.1.1: resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== -queue-microtask@^1.2.2: +queue-microtask@^1.1.2, queue-microtask@^1.2.2: version "1.2.3" resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== @@ -10474,6 +10617,16 @@ read-pkg@^6.0.0: parse-json "^5.2.0" type-fest "^1.0.1" +readable-stream@1.1: + version "1.1.13" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.13.tgz#f6eef764f514c89e2b9e23146a75ba106756d23e" + integrity sha512-E98tWzqShvKDGpR2MbjsDkDQWLW2TfWUC15H4tNQhIJ5Lsta84l8nUGL9/ybltGwe+wZzWPpc1Kmd2wQP4bdCA== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + readable-stream@^3.4.0: version "3.6.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" @@ -10483,6 +10636,17 @@ readable-stream@^3.4.0: string_decoder "^1.1.1" util-deprecate "^1.0.1" +readable-stream@^4.0.0: + version "4.5.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.5.2.tgz#9e7fc4c45099baeed934bff6eb97ba6cf2729e09" + integrity sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g== + dependencies: + abort-controller "^3.0.0" + buffer "^6.0.3" + events "^3.3.0" + process "^0.11.10" + string_decoder "^1.3.0" + readdirp@~3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" @@ -11334,16 +11498,7 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -11451,21 +11606,19 @@ string.prototype.trimstart@^1.0.8: define-properties "^1.2.1" es-object-atoms "^1.0.0" -string_decoder@^1.1.1: +string_decoder@^1.1.1, string_decoder@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== dependencies: safe-buffer "~5.2.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" +string_decoder@~0.10.x: + version "0.10.31" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" + integrity sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ== -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -11513,6 +11666,11 @@ strip-indent@^4.0.0: dependencies: min-indent "^1.0.1" +strip-json-comments@1.0.x: + version "1.0.4" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91" + integrity sha512-AOPG8EBc5wAikaG1/7uFCNFJwnKOuQwFTpYBdTW6OvWHeZBQBrAA/amefHGrEiOnCPcLFZK6FUPtWVKpQVIRgg== + strip-json-comments@^3.0.1, strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" @@ -12767,16 +12925,7 @@ word-wrap@~1.2.3: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== From 47a61220674c6bb0f791e3ae697af87c78266767 Mon Sep 17 00:00:00 2001 From: Emir Hasan Date: Wed, 27 Nov 2024 09:18:36 +0100 Subject: [PATCH 2/4] Focus the code editor on load when the "autoFocus" prop is set --- .../codemirror/CodeMirror.stories.tsx | 2 +- src/extensions/codemirror/CodeMirror.tsx | 21 +++++++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/extensions/codemirror/CodeMirror.stories.tsx b/src/extensions/codemirror/CodeMirror.stories.tsx index f369c9f4..bfec8407 100644 --- a/src/extensions/codemirror/CodeMirror.stories.tsx +++ b/src/extensions/codemirror/CodeMirror.stories.tsx @@ -27,6 +27,6 @@ export const LinterExample = TemplateFull.bind({}); LinterExample.args = { name: "codeinput", defaultValue: "**test me**", - mode: "json", + mode: "javascript", useLinting: true, }; diff --git a/src/extensions/codemirror/CodeMirror.tsx b/src/extensions/codemirror/CodeMirror.tsx index 0fa5c2bd..755338cd 100644 --- a/src/extensions/codemirror/CodeMirror.tsx +++ b/src/extensions/codemirror/CodeMirror.tsx @@ -138,9 +138,17 @@ export interface CodeEditorProps { */ enableTab?: boolean; /** - * If the editor should use a linting feature. + * Enables linting feature in the editor. */ useLinting?: boolean; + /** + * Id used for testing + */ + dataTestId?: string; + /** + * Autofocus the editor when it is rendered + */ + autoFocus?: boolean; } const addExtensionsFor = (flag: boolean, ...extensions: Extension[]) => (flag ? [...extensions] : []); @@ -184,13 +192,13 @@ export const CodeEditor = ({ enableTab = false, height, useLinting = false, + dataTestId, + autoFocus = false, }: CodeEditorProps) => { const parent = useRef(undefined); - // console.log("outerDivAttributes", outerDivAttributes); - const linters = useMemo(() => { - if (!useLinting || !mode) { + if (!mode) { return []; } @@ -297,6 +305,10 @@ export const CodeEditor = ({ view.dom.style.height = typeof height === "string" ? height : `${height}px`; } + if (autoFocus) { + view.focus(); + } + setEditorView && setEditorView(view); return () => { @@ -312,6 +324,7 @@ export const CodeEditor = ({ id={id ? id : name ? `codemirror-${name}` : undefined} ref={parent} data-test-id="codemirror-wrapper" + data-testid={dataTestId ? `${dataTestId}_codemirror}` : undefined} className={ `${eccgui}-codeeditor ${eccgui}-codeeditor--mode-${mode}` + (outerDivAttributes?.className ? ` ${outerDivAttributes?.className}` : "") From 233a7fba87e070fdd12e5ca42fce418010e2bafd Mon Sep 17 00:00:00 2001 From: Emir Hasan Date: Wed, 27 Nov 2024 09:30:56 +0100 Subject: [PATCH 3/4] Update changelog --- CHANGELOG.md | 4 ++++ src/extensions/codemirror/CodeMirror.tsx | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a1b437e..36d8f682 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - basic Storybook example for `` components - `$eccgui-selector-text-spot-highlight` config variable to specify selector that is used to create shortly highlighted spots - it is highlighted when the selector is also active local anchor target or if it has the `.eccgui-typography--spothighlight` class attached to it +- `` + - implemented support for linting which is enabled via `useLinting` prop + - `turtle` and `javascript` are currently supported languages for linting + - editor is focused on load if `autoFocus` prop is set to `true` ### Changed diff --git a/src/extensions/codemirror/CodeMirror.tsx b/src/extensions/codemirror/CodeMirror.tsx index 755338cd..02ed701f 100644 --- a/src/extensions/codemirror/CodeMirror.tsx +++ b/src/extensions/codemirror/CodeMirror.tsx @@ -210,7 +210,7 @@ export const CodeEditor = ({ } return values; - }, [useLinting, mode]); + }, [mode]); const onKeyDownHandler = (event: KeyboardEvent, view: EditorView) => { if (onKeyDown && !onKeyDown(event)) { From e93358452135d3c6d7162e114fab044f8ff4a1ea Mon Sep 17 00:00:00 2001 From: Emir Hasan Date: Mon, 2 Dec 2024 07:48:10 +0100 Subject: [PATCH 4/4] Implement support for disabled and intent props in code editor --- CHANGELOG.md | 2 + .../StickyNoteModal/StickyNoteModal.tsx | 13 +- .../AutoSuggestion/ExtendedCodeEditor.tsx | 13 ++ .../TextField/stories/TextField.stories.tsx | 3 +- src/configuration/_variables.scss | 1 + .../codemirror/CodeMirror.stories.tsx | 22 +++ src/extensions/codemirror/CodeMirror.tsx | 26 ++++ src/extensions/codemirror/_codemirror.scss | 137 ++++++++++++++++++ 8 files changed, 215 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36d8f682..5db3a740 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - implemented support for linting which is enabled via `useLinting` prop - `turtle` and `javascript` are currently supported languages for linting - editor is focused on load if `autoFocus` prop is set to `true` + - implemented support for `disabled` state in code editor + - implemented support for `intent` states in code editor ### Changed diff --git a/src/cmem/react-flow/StickyNoteModal/StickyNoteModal.tsx b/src/cmem/react-flow/StickyNoteModal/StickyNoteModal.tsx index 3311a6a6..1d80b970 100644 --- a/src/cmem/react-flow/StickyNoteModal/StickyNoteModal.tsx +++ b/src/cmem/react-flow/StickyNoteModal/StickyNoteModal.tsx @@ -1,5 +1,6 @@ import React from "react"; +import { IntentTypes } from "../../../common/Intent"; import getColorConfiguration from "../../../common/utils/getColorConfiguration"; import { CodeEditor } from "../../../extensions"; import { ReactFlowHotkeyContext } from "../extensions/ReactFlowHotkeyContext"; @@ -32,10 +33,18 @@ export interface StickyNoteModalProps { * Forward other properties to the `SimpleModal` element that is used for this dialog. */ simpleDialogProps?: Omit; + /** + * Disables the code editor + */ + disabledCodeEditor?: boolean; + /** + *Code editor intent + */ + codeEditorIntent?: IntentTypes | "edited" | "removed"; } export const StickyNoteModal: React.FC = React.memo( - ({ metaData, onClose, onSubmit, translate, simpleDialogProps }) => { + ({ metaData, onClose, onSubmit, translate, simpleDialogProps, disabledCodeEditor, codeEditorIntent }) => { const refNote = React.useRef(metaData?.note ?? ""); const [color, setSelectedColor] = React.useState(metaData?.color ?? ""); const noteColors: [string, string][] = Object.entries(getColorConfiguration("stickynotes")).map( @@ -123,6 +132,8 @@ export const StickyNoteModal: React.FC = React.memo( refNote.current = value; }} defaultValue={refNote.current} + disabled={disabledCodeEditor} + intent={codeEditorIntent} /> { const initialContent = React.useRef(multiline ? initialValue : initialValue.replace(/[\r\n]/g, " ")); const multilineExtensions = multiline @@ -88,6 +99,8 @@ export const ExtendedCodeEditor = ({ multiline ? "codeeditor" : `singlelinecodeeditor ${BlueprintClassNames.INPUT}` }`, }} + disabled={disabled} + intent={intent} /> ); }; diff --git a/src/components/TextField/stories/TextField.stories.tsx b/src/components/TextField/stories/TextField.stories.tsx index 2f57b61a..2a65bbe5 100644 --- a/src/components/TextField/stories/TextField.stories.tsx +++ b/src/components/TextField/stories/TextField.stories.tsx @@ -31,6 +31,7 @@ Default.args = { fullWidth: false, placeholder: "placeholder text", readOnly: false, + disabled: true, }; /** Text field with default value that contains a zero width/invisible character. @@ -46,7 +47,7 @@ const invisibleCharacterWarningProps: TextFieldProps = { const codePointsString = [...Array.from(codePoints)] .map((n) => { const info = characters.invisibleZeroWidthCharacters.codePointMap.get(n); - return info.fullLabel; + return info?.fullLabel; }) .join(", "); alert("Invisible character detected in input string. Code points: " + codePointsString); diff --git a/src/configuration/_variables.scss b/src/configuration/_variables.scss index 6f980db6..6b7fb526 100644 --- a/src/configuration/_variables.scss +++ b/src/configuration/_variables.scss @@ -33,6 +33,7 @@ $eccgui-color-applicationheader-text: #222 !default; $eccgui-color-applicationheader-background: #ddd !default; $eccgui-color-workspace-text: #444 !default; $eccgui-color-workspace-background: #f5f5f5 !default; +$ecgui-color-background-disabled: #c6c6c6 !default; $eccgui-color-application-text: $eccgui-color-workspace-text !default; // deprecated $eccgui-color-application-background: $eccgui-color-workspace-background !default; // deprecated diff --git a/src/extensions/codemirror/CodeMirror.stories.tsx b/src/extensions/codemirror/CodeMirror.stories.tsx index bfec8407..c5e0fefa 100644 --- a/src/extensions/codemirror/CodeMirror.stories.tsx +++ b/src/extensions/codemirror/CodeMirror.stories.tsx @@ -1,6 +1,8 @@ import React from "react"; import { Meta, StoryFn } from "@storybook/react"; +import { helpersArgTypes } from "../../../.storybook/helpers"; + import { CodeEditor } from "./CodeMirror"; export default { @@ -11,6 +13,9 @@ export default { onChange: { action: "value changed", }, + intent: { + ...helpersArgTypes.exampleIntent, + }, }, } as Meta; @@ -29,4 +34,21 @@ LinterExample.args = { defaultValue: "**test me**", mode: "javascript", useLinting: true, + autoFocus: true, +}; + +export const DisabledExample = TemplateFull.bind({}); +DisabledExample.args = { + name: "codeinput", + defaultValue: "**test me**", + mode: "javascript", + disabled: true, +}; + +export const IntentExample = TemplateFull.bind({}); +IntentExample.args = { + name: "codeinput", + defaultValue: "**test me**", + mode: "javascript", + intent: "warning", }; diff --git a/src/extensions/codemirror/CodeMirror.tsx b/src/extensions/codemirror/CodeMirror.tsx index 02ed701f..8a3b19f6 100644 --- a/src/extensions/codemirror/CodeMirror.tsx +++ b/src/extensions/codemirror/CodeMirror.tsx @@ -6,6 +6,7 @@ import { EditorState, Extension } from "@codemirror/state"; import { DOMEventHandlers, EditorView, KeyBinding, keymap, Rect, ViewUpdate } from "@codemirror/view"; import { minimalSetup } from "codemirror"; +import { IntentTypes } from "../../common/Intent"; import { markField } from "../../components/AutoSuggestion/extensions/markText"; import { CLASSPREFIX as eccgui } from "../../configuration/constants"; @@ -149,6 +150,14 @@ export interface CodeEditorProps { * Autofocus the editor when it is rendered */ autoFocus?: boolean; + /** + * Intent state of the code editor. + */ + intent?: IntentTypes | "edited" | "removed"; + /** + * Disables the editor. + */ + disabled?: boolean; } const addExtensionsFor = (flag: boolean, ...extensions: Extension[]) => (flag ? [...extensions] : []); @@ -194,6 +203,8 @@ export const CodeEditor = ({ useLinting = false, dataTestId, autoFocus = false, + disabled = false, + intent, }: CodeEditorProps) => { const parent = useRef(undefined); @@ -258,13 +269,20 @@ export const CodeEditor = ({ keymap?.of(keyMapConfigs), EditorState?.tabSize.of(tabIntentSize), EditorState?.readOnly.of(readOnly), + EditorView?.editable.of(!disabled), AdaptedEditorViewDomEventHandlers(domEventHandlers) as Extension, EditorView?.updateListener.of((v: ViewUpdate) => { + if (disabled) return; + onChange && onChange(v.state.doc.toString()); if (onSelection) onSelection(v.state.selection.ranges.filter((r) => !r.empty).map(({ from, to }) => ({ from, to }))); + if (onFocusChange) { + v.view.dom.className += ` ${eccgui}-intent--${intent}`; + } + if (onCursorChange) { const cursorPosition = v.state.selection.main.head ?? 0; const editorRect = v.view.dom.getBoundingClientRect(); @@ -305,6 +323,14 @@ export const CodeEditor = ({ view.dom.style.height = typeof height === "string" ? height : `${height}px`; } + if (disabled) { + view.dom.className += ` ${eccgui}-disabled`; + } + + if (intent) { + view.dom.className += ` ${eccgui}-intent--${intent}`; + } + if (autoFocus) { view.focus(); } diff --git a/src/extensions/codemirror/_codemirror.scss b/src/extensions/codemirror/_codemirror.scss index 0197c622..d3d55d14 100644 --- a/src/extensions/codemirror/_codemirror.scss +++ b/src/extensions/codemirror/_codemirror.scss @@ -1,5 +1,33 @@ +@use "sass:color"; + // own vars $eccgui-color-codeeditor-background: $eccgui-color-textfield-background !default; +$eccgui-map-intent-bgcolors: ( + "primary": $eccgui-color-info-background, + "success": $eccgui-color-success-background, + "warning": $eccgui-color-warning-background, + "danger": $eccgui-color-danger-background, +); + +@mixin intent-state-flash-animation($state, $bgcolor, $mixratio: 24%) { + @keyframes intent-state-flash-#{$state} { + 0% { + background-color: color.mix($bgcolor, $eccgui-color-textfield-background, $mixratio); + } + + 39% { + background-color: $bgcolor; + } + + 100% { + background-color: color.mix($bgcolor, $eccgui-color-textfield-background, $mixratio); + } + } +} + +@each $each-intent, $each-bgcolor in $eccgui-map-intent-bgcolors { + @include intent-state-flash-animation($each-intent, $each-bgcolor); +} // adjustments // stylelint-disable selector-class-pattern @@ -22,6 +50,68 @@ $eccgui-color-codeeditor-background: $eccgui-color-textfield-background !default // get them a "border" like input boxes from blueprintjs box-shadow: input-transition-shadow($input-shadow-color-focus), $pt-input-box-shadow; + &.#{eccgui}-disabled { + color: $eccgui-color-workspace-text; + background-color: $ecgui-color-background-disabled; + opacity: $eccgui-opacity-disabled; + } + + @each $each-intent, $each-bgcolor in $eccgui-map-intent-bgcolors { + &.#{eccgui}-intent--#{$each-intent} { + background-color: color.mix($each-bgcolor, $eccgui-color-textfield-background, 24%); + animation-name: intent-state-flash-#{$each-intent}; + animation-duration: 1s; + animation-delay: 0.5s; + } + } + + &.#{eccgui}-intent--warning { + box-shadow: input-transition-shadow($eccgui-color-warning-text, true), $input-box-shadow-focus; + } + + &.#{eccgui}-intent--success { + box-shadow: input-transition-shadow($eccgui-color-success-text, true), $input-box-shadow-focus; + } + + &.#{eccgui}-intent-danger { + box-shadow: input-transition-shadow($eccgui-color-danger-text, true), $input-box-shadow-focus; + } + + &.#{eccgui}-intent--primary { + box-shadow: input-transition-shadow($eccgui-color-info-text, true), $input-box-shadow-focus; + } + + &.#{eccgui}-intent--info { + box-shadow: input-transition-shadow($eccgui-color-info-text, true), $input-box-shadow-focus; + + @include pt-input-intent($eccgui-color-info-text); + } + + &.#{eccgui}-intent--accent { + @include pt-input-intent($eccgui-color-primary); + + box-shadow: input-transition-shadow($eccgui-color-warning-text, true), $input-box-shadow-focus; + } + + &.#{eccgui}-intent--neutral { + @include pt-input-intent($eccgui-color-workspace-text); + + box-shadow: input-transition-shadow($eccgui-color-workspace-text), $input-box-shadow-focus; + } + + &.#{eccgui}-intent--edited { + @include pt-input-intent($eccgui-color-info-text); + + box-shadow: input-transition-shadow($eccgui-color-info-text), $input-box-shadow-focus; + } + + &.#{eccgui}-intent--removed { + @include pt-input-intent($eccgui-color-danger-text); + + text-decoration: line-through $eccgui-color-danger-text 2px; + box-shadow: input-transition-shadow($eccgui-color-danger-text), $input-box-shadow-focus; + } + .cm-scroller { width: calc(100% - 2px); height: calc(100% - 2px); @@ -34,6 +124,53 @@ $eccgui-color-codeeditor-background: $eccgui-color-textfield-background !default &.cm-focused { outline: none; box-shadow: input-transition-shadow($input-shadow-color-focus, true), $input-box-shadow-focus; + + &.#{eccgui}-intent--warning { + box-shadow: input-transition-shadow($eccgui-color-warning-text, true), $input-box-shadow-focus; + } + + &.#{eccgui}-intent--success { + box-shadow: input-transition-shadow($eccgui-color-success-text, true), $input-box-shadow-focus; + } + + &.#{eccgui}-intent--danger { + box-shadow: input-transition-shadow($eccgui-color-danger-text, true), $input-box-shadow-focus; + } + + &.#{eccgui}-intent--primary { + box-shadow: input-transition-shadow($eccgui-color-info-text, true), $input-box-shadow-focus; + } + + &.#{eccgui}-intent--info { + box-shadow: input-transition-shadow($eccgui-color-info-text, true), $input-box-shadow-focus; + + @include pt-input-intent($eccgui-color-info-text); + } + + &.#{eccgui}-intent--accent { + @include pt-input-intent($eccgui-color-primary); + + box-shadow: input-transition-shadow($eccgui-color-warning-text, true), $input-box-shadow-focus; + } + + &.#{eccgui}-intent--neutral { + @include pt-input-intent($eccgui-color-workspace-text); + + box-shadow: input-transition-shadow($eccgui-color-workspace-text), $input-box-shadow-focus; + } + + &.#{eccgui}-intent--edited { + @include pt-input-intent($eccgui-color-info-text); + + box-shadow: input-transition-shadow($eccgui-color-info-text), $input-box-shadow-focus; + } + + &.#{eccgui}-intent--removed { + @include pt-input-intent($eccgui-color-danger-text); + + text-decoration: line-through $eccgui-color-danger-text 2px; + box-shadow: input-transition-shadow($eccgui-color-danger-text), $input-box-shadow-focus; + } } .CodeMirror-hscrollbar {