From 22ee9c7f2b2e23695bf897bb709e3a5e563152e9 Mon Sep 17 00:00:00 2001
From: Jan Faracik <43062514+janfaracik@users.noreply.github.com>
Date: Fri, 18 Aug 2023 21:19:50 +0100
Subject: [PATCH] Init
---
.../jenkins/model/Jenkins/_script.jelly | 2 +-
.../resources/lib/hudson/scriptConsole.jelly | 5 +-
war/package.json | 6 +
war/src/main/js/app.js | 2 +
war/src/main/js/components/textarea/index.js | 28 +++
war/src/main/js/components/textarea/theme.js | 140 +++++++++++++++
war/src/main/scss/form/codemirror.scss | 60 -------
.../main/webapp/scripts/hudson-behavior.js | 43 -----
war/yarn.lock | 159 ++++++++++++++++++
9 files changed, 338 insertions(+), 107 deletions(-)
create mode 100644 war/src/main/js/components/textarea/index.js
create mode 100644 war/src/main/js/components/textarea/theme.js
diff --git a/core/src/main/resources/jenkins/model/Jenkins/_script.jelly b/core/src/main/resources/jenkins/model/Jenkins/_script.jelly
index 89495f20ea35..2c9acd6d982a 100644
--- a/core/src/main/resources/jenkins/model/Jenkins/_script.jelly
+++ b/core/src/main/resources/jenkins/model/Jenkins/_script.jelly
@@ -30,4 +30,4 @@ THE SOFTWARE.
println(Jenkins.instance.pluginManager.plugins)
-
\ No newline at end of file
+
diff --git a/core/src/main/resources/lib/hudson/scriptConsole.jelly b/core/src/main/resources/lib/hudson/scriptConsole.jelly
index f6d21d6fcda9..9e6a9a962918 100644
--- a/core/src/main/resources/lib/hudson/scriptConsole.jelly
+++ b/core/src/main/resources/lib/hudson/scriptConsole.jelly
@@ -46,13 +46,12 @@ THE SOFTWARE.
-
-
+
${%Result}
diff --git a/war/package.json b/war/package.json
index 363ea601b452..2d846c9018dd 100644
--- a/war/package.json
+++ b/war/package.json
@@ -50,6 +50,12 @@
"webpack-remove-empty-scripts": "1.0.3"
},
"dependencies": {
+ "@codemirror/language": "^6.9.0",
+ "@codemirror/legacy-modes": "^6.3.3",
+ "@codemirror/state": "^6.2.1",
+ "@codemirror/view": "^6.16.0",
+ "@lezer/highlight": "^1.1.6",
+ "codemirror": "^6.0.1",
"handlebars": "4.7.8",
"hotkeys-js": "3.12.0",
"jquery": "3.7.0",
diff --git a/war/src/main/js/app.js b/war/src/main/js/app.js
index 63a35226d7d4..46b38a769b39 100644
--- a/war/src/main/js/app.js
+++ b/war/src/main/js/app.js
@@ -5,6 +5,7 @@ import Tooltips from "@/components/tooltips";
import StopButtonLink from "@/components/stop-button-link";
import ConfirmationLink from "@/components/confirmation-link";
import Dialogs from "@/components/dialogs";
+import Textarea from "@/components/textarea";
Dropdowns.init();
Notifications.init();
@@ -13,3 +14,4 @@ Tooltips.init();
StopButtonLink.init();
ConfirmationLink.init();
Dialogs.init();
+Textarea.init();
diff --git a/war/src/main/js/components/textarea/index.js b/war/src/main/js/components/textarea/index.js
new file mode 100644
index 000000000000..78053901b03b
--- /dev/null
+++ b/war/src/main/js/components/textarea/index.js
@@ -0,0 +1,28 @@
+import {EditorView, basicSetup } from "codemirror"
+import {EditorState} from "@codemirror/state"
+import {groovy} from "@codemirror/legacy-modes/mode/groovy"
+import {StreamLanguage} from "@codemirror/language"
+import { oneDark } from "@/components/textarea/theme";
+
+function editorFromTextArea(textarea, extensions) {
+ let view = new EditorView({
+ state: EditorState.create({doc: textarea.value, extensions: [basicSetup, StreamLanguage.define(groovy), oneDark]}),
+ });
+ textarea.parentNode.insertBefore(view.dom, textarea)
+ textarea.style.display = "none"
+ if (textarea.form) {
+ textarea.form.addEventListener("submit", () => {
+ textarea.value = view.state.doc.toString()
+ })
+ }
+ return view
+ }
+
+function init() {
+ const textareas = document.querySelectorAll(".script");
+ textareas.forEach((textarea) => {
+ const view = editorFromTextArea(textarea)
+ });
+}
+
+export default { init };
diff --git a/war/src/main/js/components/textarea/theme.js b/war/src/main/js/components/textarea/theme.js
new file mode 100644
index 000000000000..6587b26419e6
--- /dev/null
+++ b/war/src/main/js/components/textarea/theme.js
@@ -0,0 +1,140 @@
+import {EditorView} from "@codemirror/view"
+import {HighlightStyle, syntaxHighlighting} from "@codemirror/language"
+import {tags as t} from "@lezer/highlight"
+
+const chalky = "#e5c07b",
+ coral = "var(--red)",
+ cyan = "var(--cyan)",
+ invalid = "#ffffff",
+ ivory = "#abb2bf",
+ stone = "var(--text-color-secondary)",
+ malibu = "var(--blue)",
+ sage = "var(--green)",
+ whiskey = "var(--orange)",
+ violet = "var(--purple)",
+ darkBackground = "#21252b",
+ highlightBackground = "#2c313a",
+ background = "var(--input-color)",
+ tooltipBackground = "#353a42",
+ selection = "var(--selection-color)",
+ cursor = "var(--text-color)"
+
+/// The editor theme styles for One Dark.
+export const oneDarkTheme = EditorView.theme({
+ "&": {
+ color: ivory,
+ backgroundColor: background,
+ borderRadius: "10px",
+ border: "2px solid var(--input-border)",
+ padding: "6px",
+ boxShadow: 'var(--form-input-glow)',
+ transition: 'var(--standard-transition)',
+ },
+ "&:hover": {
+ borderColor: 'var(--input-border-hover)',
+ },
+ "&:focus-within": {
+ borderColor: 'var(--focus-input-border)',
+ boxShadow: 'var(--form-input-glow--focus)'
+ },
+ ".cm-content": {
+ caretColor: cursor
+ },
+ ".cm-cursor, .cm-dropCursor": { borderLeftColor: cursor },
+ "&.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection": { backgroundColor: selection },
+ ".cm-panels": { backgroundColor: darkBackground, color: ivory },
+ ".cm-panels.cm-panels-top": { borderBottom: "2px solid black" },
+ ".cm-panels.cm-panels-bottom": { borderTop: "2px solid black" },
+ ".cm-searchMatch": {
+ backgroundColor: "#72a1ff59",
+ outline: "1px solid #457dff"
+ },
+ ".cm-searchMatch.cm-searchMatch-selected": {
+ backgroundColor: "#6199ff2f"
+ },
+ ".cm-scroller": {
+ fontFamily: 'var(--font-family-mono)'
+ },
+ ".cm-activeLine": { backgroundColor: "#6699ff0b" },
+ ".cm-selectionMatch": { backgroundColor: "#aafe661a" },
+ "&.cm-focused .cm-matchingBracket, &.cm-focused .cm-nonmatchingBracket": {
+ backgroundColor: "#bad0f847"
+ },
+ "&.cm-focused": {
+ outline: "none",
+ },
+ ".cm-gutters": {
+ backgroundColor: "transparent",
+ color: stone,
+ border: "none"
+ },
+ ".cm-activeLineGutter": {
+ backgroundColor: "transparent"
+ },
+ ".cm-line": {
+ backgroundColor: "transparent",
+ },
+ ".cm-foldPlaceholder": {
+ backgroundColor: "transparent",
+ border: "none",
+ color: "#ddd"
+ },
+ ".cm-tooltip": {
+ border: "none",
+ backgroundColor: tooltipBackground
+ },
+ ".cm-tooltip .cm-tooltip-arrow:before": {
+ borderTopColor: "transparent",
+ borderBottomColor: "transparent"
+ },
+ ".cm-tooltip .cm-tooltip-arrow:after": {
+ borderTopColor: tooltipBackground,
+ borderBottomColor: tooltipBackground
+ },
+ ".cm-tooltip-autocomplete": {
+ "& > ul > li[aria-selected]": {
+ backgroundColor: highlightBackground,
+ color: ivory
+ }
+ }
+})
+
+/// The highlighting style for code in the One Dark theme.
+export const oneDarkHighlightStyle = HighlightStyle.define([
+ {tag: t.keyword,
+ color: violet},
+ {tag: [t.name, t.deleted, t.character, t.propertyName, t.macroName],
+ color: coral},
+ {tag: [t.function(t.variableName), t.labelName],
+ color: malibu},
+ {tag: [t.color, t.constant(t.name), t.standard(t.name)],
+ color: whiskey},
+ {tag: [t.definition(t.name), t.separator],
+ color: ivory},
+ {tag: [t.typeName, t.className, t.number, t.changed, t.annotation, t.modifier, t.self, t.namespace],
+ color: chalky},
+ {tag: [t.operator, t.operatorKeyword, t.url, t.escape, t.regexp, t.link, t.special(t.string)],
+ color: cyan},
+ {tag: [t.meta, t.comment],
+ color: stone},
+ {tag: t.strong,
+ fontWeight: "bold"},
+ {tag: t.emphasis,
+ fontStyle: "italic"},
+ {tag: t.strikethrough,
+ textDecoration: "line-through"},
+ {tag: t.link,
+ color: stone,
+ textDecoration: "underline"},
+ {tag: t.heading,
+ fontWeight: "bold",
+ color: coral},
+ {tag: [t.atom, t.bool, t.special(t.variableName)],
+ color: whiskey },
+ {tag: [t.processingInstruction, t.string, t.inserted],
+ color: sage},
+ {tag: t.invalid,
+ color: invalid},
+])
+
+export const oneDark = [oneDarkTheme, syntaxHighlighting(oneDarkHighlightStyle)]
diff --git a/war/src/main/scss/form/codemirror.scss b/war/src/main/scss/form/codemirror.scss
index 67520192c0e5..e69de29bb2d1 100644
--- a/war/src/main/scss/form/codemirror.scss
+++ b/war/src/main/scss/form/codemirror.scss
@@ -1,60 +0,0 @@
-.CodeMirror {
- display: block;
- background: var(--input-color);
- border: 2px solid var(--input-border);
- border-radius: var(--form-input-border-radius);
- width: 100%;
- box-shadow: var(--form-input-glow);
- transition:
- var(--standard-transition),
- height 0s;
- cursor: text;
- margin-bottom: var(--section-padding);
-
- // Override the defaults of codemirror.css
- pre {
- font-family: var(--font-family-mono) !important;
- font-weight: 500 !important;
- line-height: 1.66 !important;
- }
-
- &:hover {
- border-color: var(--input-border-hover);
- }
-
- &:active,
- &:focus-within {
- outline: none;
- border-color: var(--focus-input-border);
- box-shadow: var(--form-input-glow--focus);
- }
-
- textarea {
- background: transparent;
- border: none;
- outline: none;
- }
-
- .cm-variable {
- color: var(--text-color) !important;
- }
-
- .CodeMirror-selected {
- background-color: var(--selection-color) !important;
- }
-}
-
-.jenkins-codemirror-resizer {
- position: relative;
- width: 10px;
- height: 10px;
- margin: calc(-2rem - 11px) 3px 2rem auto;
- z-index: 10;
- cursor: ns-resize;
- background: currentColor;
- mask-image: url("data:image/svg+xml,%3C%3Fxml version='1.0' encoding='UTF-8'%3F%3E%3Csvg width='20px' height='20px' viewBox='0 0 20 20' version='1.1' xmlns='http://www.w3.org/2000/svg'%3E%3Cg id='Page-1' stroke='none' stroke-width='1' fill='none' fill-rule='evenodd'%3E%3Cline x1='19' y1='12' x2='12' y2='19' id='Path' stroke='%23979797' stroke-width='2'%3E%3C/line%3E%3Cline x1='1' y1='18' x2='17.9705627' y2='1.02943725' id='Path-2' stroke='%23979797' stroke-width='2'%3E%3C/line%3E%3C/g%3E%3C/svg%3E");
- mask-size: 7px 7px;
- mask-position: 3px 3px;
- mask-repeat: no-repeat;
- opacity: 0.75;
-}
diff --git a/war/src/main/webapp/scripts/hudson-behavior.js b/war/src/main/webapp/scripts/hudson-behavior.js
index 4197fa903207..89f40c48090c 100644
--- a/war/src/main/webapp/scripts/hudson-behavior.js
+++ b/war/src/main/webapp/scripts/hudson-behavior.js
@@ -1390,49 +1390,6 @@ function rowvgStartEachRow(recursive, f) {
e.tabIndex = 9999; // make help link unnavigable from keyboard
});
- // Script Console : settings and shortcut key
- Behaviour.specify("TEXTAREA.script", "textarea-script", ++p, function (e) {
- (function () {
- var cmdKeyDown = false;
- var mode = e.getAttribute("script-mode") || "text/x-groovy";
-
- // eslint-disable-next-line no-unused-vars
- var w = CodeMirror.fromTextArea(e, {
- mode: mode,
- lineNumbers: true,
- matchBrackets: true,
- onKeyEvent: function (editor, event) {
- function saveAndSubmit() {
- editor.save();
- getParentForm(e).submit();
- event.stop();
- }
-
- // Mac (Command + Enter)
- if (navigator.userAgent.indexOf("Mac") > -1) {
- if (event.type == "keydown" && isCommandKey(event)) {
- cmdKeyDown = true;
- }
- if (event.type == "keyup" && isCommandKey(event)) {
- cmdKeyDown = false;
- }
- if (cmdKeyDown && isReturnKeyDown()) {
- saveAndSubmit();
- return true;
- }
-
- // Windows, Linux (Ctrl + Enter)
- } else {
- if (event.ctrlKey && isReturnKeyDown()) {
- saveAndSubmit();
- return true;
- }
- }
- },
- }).getWrapperElement();
- })();
- });
-
// deferred client-side clickable map.
// this is useful where the generation of