This is an automated email from the ASF dual-hosted git repository.

andy pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/jena.git


The following commit(s) were added to refs/heads/main by this push:
     new a3bc7042b3 Update Fuseki Edit view CodeMirror usage * Create 
CodeMirror component for abstraction * Replace Edit.vue textarea to CodeMirror 
element * Update jena-fuseki-ui package.json to support native legacy mode 
languages * Add license header * Add VSCode .gitignore entry * Update 
jena-fuseki-ui yarn.lock
a3bc7042b3 is described below

commit a3bc7042b36df477abb8e047a4ae342247d73b2b
Author: AtesComp <[email protected]>
AuthorDate: Tue Dec 16 23:28:25 2025 -0500

    Update Fuseki Edit view CodeMirror usage
    * Create CodeMirror component for abstraction
    * Replace Edit.vue textarea to CodeMirror element
    * Update jena-fuseki-ui package.json to support native legacy mode languages
    * Add license header
    * Add VSCode .gitignore entry
    * Update jena-fuseki-ui yarn.lock
---
 .gitignore                                         |   3 +
 jena-fuseki2/jena-fuseki-ui/package.json           |   3 +-
 .../src/components/dataset/CodeMirror.vue          | 571 +++++++++++++++++++++
 .../jena-fuseki-ui/src/views/dataset/Edit.vue      |  92 +---
 jena-fuseki2/jena-fuseki-ui/yarn.lock              |  59 +--
 5 files changed, 626 insertions(+), 102 deletions(-)

diff --git a/.gitignore b/.gitignore
index b07be1b34a..e93f7bff2a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,6 +25,9 @@ maven-eclipse.xml
 *.iml
 .idea/
 
+# Visual Studio Code
+.vscode/
+
 # Caches
 .scala_dependencies
 
diff --git a/jena-fuseki2/jena-fuseki-ui/package.json 
b/jena-fuseki2/jena-fuseki-ui/package.json
index ca64d416df..3ee152793c 100644
--- a/jena-fuseki2/jena-fuseki-ui/package.json
+++ b/jena-fuseki2/jena-fuseki-ui/package.json
@@ -21,6 +21,8 @@
     "serve:offline": "cross-env FUSEKI_PORT=\"${FUSEKI_PORT:=3030}\" 
PORT=\"${PORT:=8080}\" concurrently --names 'SERVER,CLIENT' --prefix-colors 
'yellow,blue' --success 'first' --kill-others 'yarn run serve:fuseki' 'yarn 
wait-on http://localhost:${FUSEKI_PORT}/$/ping && yarn run dev'"
   },
   "dependencies": {
+    "@codemirror/language": "^6.11.3",
+    "@codemirror/legacy-modes": "^6.5.2",
     "@fortawesome/fontawesome-svg-core": "^7.0.0",
     "@fortawesome/free-solid-svg-icons": "^7.0.0",
     "@fortawesome/vue-fontawesome": "^3.0.3",
@@ -31,7 +33,6 @@
     "axios": "^1.6.1",
     "bootstrap": "^5.3.2",
     "codemirror": "6.0.2",
-    "codemirror-lang-turtle": "^0.0.2",
     "follow-redirects": "^1.15.4",
     "mitt": "^3.0.1",
     "qs": "6.14.0",
diff --git a/jena-fuseki2/jena-fuseki-ui/src/components/dataset/CodeMirror.vue 
b/jena-fuseki2/jena-fuseki-ui/src/components/dataset/CodeMirror.vue
new file mode 100644
index 0000000000..df7c324b3f
--- /dev/null
+++ b/jena-fuseki2/jena-fuseki-ui/src/components/dataset/CodeMirror.vue
@@ -0,0 +1,571 @@
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+
+<template>
+  <component
+    :is="tag"
+    ref="editor"
+    class="vue-codemirror font-fixed-width"
+  >
+    <aside
+      v-if="$slots.default"
+      style="display: none;"
+      aria-hidden
+    >
+      <slot name="default" />
+    </aside>
+  </component>
+</template>
+
+<script>
+// CodeMirror
+import { basicSetup, minimalSetup } from 'codemirror';
+import { EditorSelection, EditorState, StateEffect } from '@codemirror/state';
+import { EditorView, keymap, lineNumbers, showPanel } from "@codemirror/view";
+import { indentWithTab, history } from '@codemirror/commands';
+import { forceLinting, linter, lintGutter } from '@codemirror/lint';
+import { nextTick } from 'vue';
+
+//import useAppTheme from '@/lib/stateTheme';
+
+// Load Theme Manager...
+//const { isThemeDark } = useAppTheme();
+
+export default {
+  name: 'CodeMirror',
+
+  props: {
+    strValue: {
+      type: String,
+      required: true,
+    },
+    bAllowUpdate: {
+      type: Boolean,
+      required: true,
+    },
+    cmExtender: {
+      type: Object,
+      required: true,
+      /*
+        () => {
+          return {
+            theme: {},              // Object
+            dark: false,            // Boolean
+            basic: false,           // Boolean
+            minimal: false,         // Boolean
+            wrap: false,            // Boolean
+            tab: false,             // Boolean
+            tabSize: null,          // Number
+            lineSeparator: null,    // String
+            readonly: false,        // Boolean
+            disabled: false,        // Boolean
+            extensions: [],         // Array of Extensions
+            lang: undefined,        // Object
+            linter: undefined,      // Function
+            linterConfig: {},       // Object
+            lineNumbers: false,     // Boolean
+            lineNumbersConfig: {},  // Object
+            history: false,         // Boolean
+            historyConfig: null,    // Object
+            gutter: false,          // Boolean
+            gutterConfig: {},       // Object
+            panel: false,           // Boolean
+            panelFunc: null,        // Function
+          };
+        },
+    */
+    },
+    tag: {
+      type: String,
+      default: 'div',
+    },
+  },
+
+  //emits: ['ready', 'update-value', 'update-view', 'focus', 'change', 
'destroy'],
+  emits: ['ready', 'update-value', 'destroy'],
+
+  data() {
+    return {
+      isLoaded: false,
+      editor: this.$refs.editor,
+      // CodeMirror Editor View
+      view: null,
+      cmExtenderDefaults: {
+        theme: {},              // Object
+        dark: false, //isThemeDark(),    // Boolean
+        basic: false,           // Boolean
+        minimal: false,         // Boolean
+        wrap: false,            // Boolean
+        tab: false,             // Boolean
+        tabSize: null,          // Number
+        lineSeparator: null,    // String
+        readonly: false,        // Boolean
+        disabled: false,        // Boolean
+        lang: null,             // Object
+        linter: null,           // Function
+        linterConfig: null,     // Object
+        lineNumbers: false,     // Boolean
+        lineNumbersConfig: null,// Object
+        history: false,         // Boolean
+        historyConfig: null,    // Object
+        gutter: false,          // Boolean
+        gutterConfig: null,     // Object
+        panel: false,           // Boolean
+        panelFunc: null,        // Function
+        extensions: [],         // Array of Extensions
+      },
+      cmExtenderLocal: { ...this.cmExtenderDefaults, ...this.cmExtender },
+    }
+  },
+
+  computed: {
+    // Editor Selection
+    selection: {
+      get() { return (this.view?.state?.selection || null); },
+      set(value) { this.view.dispatch( { selection: value } ); }
+    },
+
+    // Editor State
+    state: {
+      get() { return (this.view?.state || null); },
+      set(value) { this.view.setState(value); }
+    },
+
+    // Cursor Position
+    cursor: {
+      get() { return (this.view?.state?.selection?.main?.head || 0); },
+      set(value) { this.view.dispatch( { selection: { anchor: value } } ); }
+    },
+
+    // Focus
+    focus: {
+      get() { return (this.view?.hasFocus || false) },
+      set(bFocus) {
+        if (bFocus) {
+          this.view.focus();
+        }
+      }
+    },
+
+    // Text length
+    length() { return (this.view?.state?.doc?.length || 0); },
+
+    // Get CodeMirror Extensions...
+    extAll() {
+      let exts = [];
+      if (this.cmExtender) {
+        let objExt = this.cmExtenderLocal;
+        /*
+        console.debug("CM: { " +
+          "basic: " + objExt.basic + ", " +
+          "dark: " + objExt.dark + ", " +
+          "wrap: " + objExt.wrap + ", " +
+          "tab: " + objExt.tab + ", " +
+          "tabSize: " + objExt.tabSize + ", " +
+          "lang: " + objExt.lang + ", " +
+          "linter: " + objExt.linter + ", " +
+          "lineNumbers: " + objExt.lineNumbers + ", " +
+          "gutter: " + objExt.gutter + ", " +
+          "panel: " + objExt.panel + ", " +
+          "panelFunc: " + objExt.panelFunc );
+        */
+        /**
+         * Extensions from props...
+         */
+        // Toggle basic setup...
+        if (objExt.basic) exts.push(basicSetup);
+        // Toggle minimal setup...
+        else if (objExt.minimal) exts.push(minimalSetup);
+        //// Set ViewUpdate event listener...
+        //exts.push(
+        //  EditorView.updateListener.of(
+        //    (viewUpdate) => this.$emit('update-view', viewUpdate)
+        //  )
+        //);
+        // Toggle light / dark mode...
+        exts.push(
+          EditorView.theme( objExt.theme, { dark: objExt.dark } ) );
+        // Toggle line wrapping...
+        if (objExt.wrap) exts.push(EditorView.lineWrapping);
+        // Set Indent with tab...
+        if (objExt.tab) exts.push( keymap.of([indentWithTab]) );
+        // Set Indent tab size...
+        if (objExt.tabSize) exts.push( EditorState.tabSize.of(objExt.tabSize) 
);
+        // Set Readonly option...
+        exts.push( EditorState.readOnly.of(objExt.readonly) );
+        // Set Editable option...
+        exts.push( EditorView.editable.of(!objExt.disabled) );
+        // Set Line Break char...
+        if (objExt.lineSeparator) exts.push( 
EditorState.lineSeparator.of(objExt.lineSeparator) );
+        // Set Language...
+        if (objExt.lang) exts.push(objExt.lang);
+        // Set Line Numbers Gutter...
+        if (objExt.lineNumbers) {
+          if (objExt.lineNumbersConfig) exts.push( 
lineNumbers(objExt.lineNumbersConfig) );
+          else exts.push( lineNumbers() );
+        }
+        if (objExt.history) {
+          if (objExt.historyConfig) exts.push( history(objExt.historyConfig) );
+          else exts.push( history() );
+        }
+        // Set Linter settings...
+        if (objExt.linter) {
+          if (objExt.linterConfig) exts.push( linter( objExt.linter(), 
objExt.linterConfig ) );
+          else exts.push( linter( objExt.linter() ) );
+        }
+        // Show 🔴 to error line when linter enabled...
+        if (objExt.linter && objExt.gutter) {
+          if (objExt.gutterConfig) exts.push( lintGutter(objExt.gutterConfig) 
);
+          else exts.push( lintGutter() );
+        }
+        // Set Panel Function...
+        if (objExt.panel) exts.push( showPanel.of(objExt.panelFunc) );
+
+        // Append other extensions...
+        // TODO: Ignore previous extension was not changed. Requires a diff.
+        if (  objExt.extensions && Array.isArray(objExt.extensions) && 
objExt.extensions.length > 0 )
+          exts.push( ...objExt.extensions );
+      }
+      return exts;
+    }
+  },
+
+  watch: {
+    strValue(valueNew, valueOld) {
+      if ( ! this.bAllowUpdate ) return;
+      if ( this.view.composing ) return;
+      if ( valueNew === valueOld ) return;
+
+      let trans = {
+        changes: [
+            { from: 0, to: this.view.state.doc.length },
+            { from: 0, insert: valueNew },
+          ],
+          selection: { anchor: 0 }, //this.view.state.selection
+          scrollIntoView: true
+      }
+      this.view.dispatch(trans);
+    },
+
+    cmExtender: {
+      deep: true,
+      handler(objCMExtender) {
+        this.cmExtenderLocal = { ...this.cmExtenderDefaults, 
...this.cmExtender };
+        if ( ! objCMExtender ) return;
+        if (this.isLoaded) {
+          this.isLoaded = false;
+          this.view.dispatch( { effects: 
StateEffect.reconfigure.of(this.extAll) } );
+          this.isLoaded = true;
+        }
+        else {
+          this.initialize();
+        }
+      }
+    },
+
+    //focus(isFocus) {
+    //  this.$emit('focus', isFocus);
+    //},
+  },
+
+  async mounted() {
+    if ( ! this.cmExtender ) return;
+    this.editor = this.$refs.editor;
+    await this.initialize();
+  },
+
+  unmounted() {
+    this.removeView();
+  },
+
+  methods: {
+    async initialize(){
+      if ( ! this.cmExtender ) return;
+      this.isLoaded = false;
+
+      let strValueText = this.strValue;
+      if (this.editor && this.editor.childNodes[0] && 
this.editor.childNodes[0].innerText) {
+        // Overwrite given value when an existing display value is present...
+        let strInner = String( this.editor.childNodes[0].innerText ).trim();
+        if (strInner) {
+          strValueText = strInner;
+        }
+      }
+
+      // Register CodeMirror...
+      this.removeView();
+      this.view = new EditorView(
+        { doc: strValueText,
+          selection: EditorSelection.cursor(0),
+          extensions: this.extAll,
+          parent: this.editor,
+          dispatchTransactions: this.dispatcher,
+          scrollIntoView: true,
+        }
+      );
+
+      await nextTick();
+      this.$emit('ready',
+        { view: this.view,
+          //state: this.view.state, // ...unused
+          //container: this.editor, // ...unused
+        }
+      );
+
+      this.isLoaded = true;
+    },
+
+    dispatcher(aTransacts, view) {
+      //this.view.disp.update([objTransact]);
+      //this.view.dispatch(objTransact);
+      view.update(aTransacts);
+
+      let bTest = aTransacts.some(
+        (objTransact) => {
+          // TODO: Emit lint error event
+          // console.debug("CodeMirror : dispatcher() : Transaction: ", 
objTransact);
+          return ( objTransact?.changes && objTransact.changes );
+        }
+      );
+      if (bTest) {
+          // console.debug( "CodeMirror : dispatcher() : Doc Text: ", 
view.state.doc.toString() );
+          // Pass text up to parent...
+          this.$emit( 'update-value', view.state.doc.toString() );
+          //this.$emit('change', view.state);
+      }
+    },
+
+    removeView() {
+      if (this.view) {
+        this.view.destroy();
+        this.$emit('destroy');
+      }
+      this.view = null;
+    },
+
+    // Forces any linters configured to run when the view is idle to run right 
away.
+    lint() {
+      if (this.cmExtenderLocal.linter) {
+        forceLinting(this.view);
+      }
+    },
+
+    // Force Reconfigure Extension
+    forceReconfigure() {
+      // Deconfigure all Extensions...
+      this.view.dispatch( { effects: StateEffect.reconfigure.of([]) } );
+      // Register extensions...
+      this.view.dispatch( { effects: StateEffect.appendConfig.of(this.extAll) 
} );
+    },
+
+    /**
+     * Get the text between the given points in the view
+     *
+     * @param from - start line number
+     * @param to - end line number
+     */
+    getRange(from, to) {
+      return this.view.state.sliceDoc(from, to);
+    },
+    /**
+     * Get the content of line
+     *
+     * @param number - line number
+     */
+    getLine(number) {
+      return this.view.state.doc.line(number + 1).text;
+    },
+    /**
+     * Get the number of lines in the view
+     */
+    lineCount() {
+      return this.view.state.doc.lines;
+    },
+    /**
+     * Get the cursor position.
+     */
+     getCursor() {
+      return this.cursor;
+    },
+    /**
+     * Retrieves a list of all current selections
+     */
+    listSelections() {
+      return this.view.state.selection.ranges;
+    },
+    /**
+     * Get the currently selected text
+     */
+    getSelection() {
+      return this.view.state.sliceDoc(
+        this.view.state.selection.main.from,
+        this.view.state.selection.main.to
+      );
+    },
+    /**
+     * The length of the given array should be the same as the number of 
active selections.
+     * Replaces the content of the selections with the strings in the array.
+     */
+    getSelections() {
+      return this.view.state.selection.ranges.map(
+        (range) => { return this.view.state.sliceDoc(range.from, range.to); }
+      );
+    },
+    /**
+     * Return true if any text is selected
+     */
+    isSelected() {
+      return this.view.state.selection.ranges.some(
+        (range) => { return !range.empty; }
+      );
+    },
+
+    /**
+     * Replace the part of the document between from and to with the given 
string.
+     *
+     * @param replacement - replacement text
+     * @param from - start string at position
+     * @param to -  insert the string at position
+     */
+    replaceRange(replacement, from, to) {
+      this.view.dispatch(
+        { changes: { from, to, insert: replacement } }
+      );
+    },
+    /**
+     * Replace the selection(s) with the given string.
+     * By default, the new selection ends up after the inserted text.
+     *
+     * @param replacement - replacement text
+     */
+    replaceSelection(replacement) {
+      this.view.dispatch( this.view.state.replaceSelection(replacement) );
+    },
+    /**
+     * Set the cursor position.
+     *
+     * @param position - position.
+     */
+    setCursor(position) {
+      this.cursor = position;
+      return this.cursor;
+    },
+    /**
+     * Set a single selection range.
+     *
+     * @param from - start position
+     * @param to - end position
+     */
+    setSelection(from, to) {
+      this.view.dispatch( { selection: { from, to } } );
+    },
+    /**
+     * Sets a new set of selections. There must be at least one selection in 
the given array.
+     *
+     * @param ranges - Selection range
+     * @param primary - Primary selection
+     */
+    setSelections(ranges, primary = undefined ) {
+      this.view.dispatch(
+        { selection: EditorSelection.create(ranges, primary) }
+      );
+    },
+    /**
+     * Applies the given function to all existing selections, and calls 
extendSelections on the result.
+     *
+     * @param func - Function
+     */
+    extendSelectionsBy(/** @type { Function } */ func) {
+      this.view.dispatch(
+        { selection:
+            EditorSelection.create(
+              this.selection.ranges.map(
+                (range) => range.extend( func(range) )
+              )
+            )
+        }
+      );
+    },
+  },
+
+  /**
+   * Render the template:
+   *
+   * <template>
+   *   <sometag ref="editor" class="vue-codemirror">
+   *     <aside
+   *       v-if="this.$slots.default"
+   *       style='display: none;'
+   *       aria-hidden
+   *     >
+   *       <slot />
+   *     </aside>
+   *   </sometag>
+   * </template>
+   *
+   * where <sometag> is dynamically determined from the parent;
+   *     defaults to <div>,
+   *//*
+   render() {
+    // function h ( Tag, Children ) | ( Tag, Data, Children )
+    let tagData = { ref: 'editor', class: 'vue-codemirror' };
+    let tagChild = undefined;
+    if ( this.$slots.default ) {
+      let asideData = { style: 'display: none;', 'aria-hidden': true };
+      let asideChild =
+        ( typeof this.$slots.default == 'function' )
+        ? this.$slots.default()
+        : this.$slots.default;
+      tagChild = h( 'aside', asideData, asideChild );
+      h()
+    }
+    return h( this.tag, tagData, tagChild );
+  },
+  */
+
+  /**
+   * Export component properties
+   */
+  expose: [
+    // props...
+    'editor',
+    // computed...
+    'selection',
+    'state',
+    'cursor',
+    'focus',
+    'length',
+    // methods...
+    'lint',
+    'forceReconfigure',
+    'getRange',
+    'getLine',
+    'lineCount',
+    'getCursor',
+    'listSelections',
+    'getSelection',
+    'getSelections',
+    'isSelected',
+    'replaceRange',
+    'replaceSelection',
+    'setCursor',
+    'setSelection',
+    'setSelections',
+    'extendSelectionsBy',
+  ]
+};
+</script>
\ No newline at end of file
diff --git a/jena-fuseki2/jena-fuseki-ui/src/views/dataset/Edit.vue 
b/jena-fuseki2/jena-fuseki-ui/src/views/dataset/Edit.vue
index d900d9d947..77ce9d42ab 100644
--- a/jena-fuseki2/jena-fuseki-ui/src/views/dataset/Edit.vue
+++ b/jena-fuseki2/jena-fuseki-ui/src/views/dataset/Edit.vue
@@ -88,12 +88,16 @@
                       disabled
                     />
                   </div>
-                  <textarea
+                  <CodeMirror
                     ref="graph-editor"
-                    :value="content"
-                    @update:modelValue="content = $event"
                     class="form-control"
-                  ></textarea>
+                    :str-value="content"
+                    :b-allow-update="bAllowUpdate"
+                    :cm-extender="cmOptions"
+                    @ready="(objReady) => { cmView = objReady.view; }"
+                    @update-value="(strNew) => { updateChanges(strNew); }"
+                    @destroy="() => { cmView = null; }"
+                  />
                   <div class="mt-2 text-right">
                     <button
                       @click="discardChanges()"
@@ -128,14 +132,10 @@
 <script>
 import Menu from '@/components/dataset/Menu.vue'
 import TableListing from '@/components/dataset/TableListing.vue'
-import { EditorView, keymap, lineNumbers } from '@codemirror/view'
-import { defaultKeymap, history, historyKeymap } from '@codemirror/commands'
-import { syntaxHighlighting, defaultHighlightStyle } from 
'@codemirror/language'
-import { turtle } from 'codemirror-lang-turtle'
-import {
-  faTimes,
-  faCheck
-} from '@fortawesome/free-solid-svg-icons'
+import CodeMirror from '@/components/dataset/CodeMirror.vue';
+import { StreamLanguage } from '@codemirror/language'
+import { turtle } from "@codemirror/legacy-modes/mode/turtle"
+import { faTimes, faCheck } from '@fortawesome/free-solid-svg-icons'
 import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
 import { library } from '@fortawesome/fontawesome-svg-core'
 import currentDatasetMixin from '@/mixins/current-dataset'
@@ -146,32 +146,13 @@ library.add(faTimes, faCheck)
 
 const MAX_EDITABLE_SIZE = 10000
 
-/**
- * In CodeMirror 5, the static CodeMirror.fromTextArea was used in Jena UI to
- * sync an area in the page to the CodeMirror editor. That was removed in the
- * 6.x release, https://codemirror.net/docs/migration/.
- *
- * @param textarea
- * @param extensions
- * @returns {EditorView}
- */
-function editorFromTextArea(textarea, extensions) {
-  const view = new EditorView({doc: textarea.value, extensions})
-  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
-}
-
-
 export default {
   name: 'DatasetEdit',
 
   components: {
     Menu,
     TableListing,
+    CodeMirror,
     FontAwesomeIcon
   },
 
@@ -201,15 +182,13 @@ export default {
         }
       ],
       selectedGraph: '',
-      content: '',
-      code: '',
+      bAllowUpdate: true,
+      content: '', // ...always comes from CodeMirror editor except on initial 
load
+      cmView: null, // TODO: Add error message display when CodeMirror view 
reports error
       cmOptions: {
+        basic: true,
         extensions: [
-          lineNumbers(),
-          history(),
-          turtle(),
-          syntaxHighlighting(defaultHighlightStyle),
-          keymap.of([...defaultKeymap, ...historyKeymap])
+          StreamLanguage.define(turtle)
         ]
       }
     }
@@ -233,21 +212,10 @@ export default {
     }
   },
 
-  created () {
-    this.codemirrorEditor = null
-  },
-
   watch: {
-    code (newVal) {
-      this.codeChanged(newVal)
-    },
     services (newVal) {
       if (newVal && newVal['gsp-rw'] && Object.keys(newVal['gsp-rw']).length > 
0) {
-        const element = this.$refs['graph-editor']
-        this.codemirrorEditor = editorFromTextArea(element, 
this.cmOptions.extensions)
-        EditorView.updateListener.of(v => {
-          this.content = v.getValue()
-        })
+        // ...nothing to do...
       }
     }
   },
@@ -257,19 +225,9 @@ export default {
   },
 
   methods: {
-    codeChanged (newVal) {
-      this.codemirrorEditor.dispatch({
-        changes: {
-          from: 0,
-          to: this.codemirrorEditor.state.doc.length,
-          insert: newVal
-        }
-      })
-    },
     listCurrentGraphs: async function () {
       this.loadingGraphs = true
       this.loadingGraph = true
-      this.code = ''
       this.selectedGraph = ''
       try {
         this.graphs = await 
this.$fusekiService.countGraphsTriples(this.datasetName, 
this.services.query['srv.endpoints'][0])
@@ -284,7 +242,7 @@ export default {
       const graph = Object.entries(this.graphs)
         .find(element => element[0] === graphName)
       if (parseInt(graph[1]) > MAX_EDITABLE_SIZE) {
-        alert('Sorry, that dataset is too large to load into the editor')
+        alert('The dataset is too large (> ' + MAX_EDITABLE_SIZE + ') for the 
editor')
         return
       }
       this.loadingGraph = true
@@ -294,7 +252,8 @@ export default {
           this.datasetName,
           this.services['gsp-rw']['srv.endpoints'],
           graphName)
-        this.code = result.data
+        this.bAllowUpdate = true;
+        this.content = result.data // ...reactive update to CodeMirror editor
       } catch (error) {
         displayError(this, error)
       } finally {
@@ -303,7 +262,12 @@ export default {
     },
     discardChanges: function () {
       this.selectedGraph = ''
-      this.code = ''
+      this.bAllowUpdate = true;
+      this.content = '' // ...reactive update to CodeMirror editor
+    },
+    updateChanges: function (strNew) {
+      this.bAllowUpdate = false; // ...since the update came from the editor, 
don't update itself
+      this.content = strNew; // ...reactive update does NOT change CodeMirror 
editor
     },
     saveGraph: async function () {
       if (!this.saveGraphDisabled) {
diff --git a/jena-fuseki2/jena-fuseki-ui/yarn.lock 
b/jena-fuseki2/jena-fuseki-ui/yarn.lock
index a276c70409..5a407c22ed 100644
--- a/jena-fuseki2/jena-fuseki-ui/yarn.lock
+++ b/jena-fuseki2/jena-fuseki-ui/yarn.lock
@@ -884,6 +884,25 @@
     "@lezer/lr" "^1.0.0"
     style-mod "^4.0.0"
 
+"@codemirror/language@^6.11.3":
+  version "6.11.3"
+  resolved 
"https://registry.yarnpkg.com/@codemirror/language/-/language-6.11.3.tgz#8e6632df566a7ed13a1bd307f9837765bb1abfdd";
+  integrity 
sha512-9HBM2XnwDj7fnu0551HkGdrUrrqmYq/WC5iv6nbY2WdicXdGbhR/gfbZOH73Aqj4351alY1+aoG9rCNfiwS1RA==
+  dependencies:
+    "@codemirror/state" "^6.0.0"
+    "@codemirror/view" "^6.23.0"
+    "@lezer/common" "^1.1.0"
+    "@lezer/highlight" "^1.0.0"
+    "@lezer/lr" "^1.0.0"
+    style-mod "^4.0.0"
+
+"@codemirror/legacy-modes@^6.5.2":
+  version "6.5.2"
+  resolved 
"https://registry.yarnpkg.com/@codemirror/legacy-modes/-/legacy-modes-6.5.2.tgz#7e2976c79007cd3fa9ed8a1d690892184a7f5ecf";
+  integrity 
sha512-/jJbwSTazlQEDOQw2FJ8LEEKVS72pU0lx6oM54kGpL8t/NJ2Jda3CZ4pcltiKTdqYSRk3ug1B3pil1gsjA6+8Q==
+  dependencies:
+    "@codemirror/language" "^6.0.0"
+
 "@codemirror/lint@^6.0.0":
   version "6.8.2"
   resolved 
"https://registry.yarnpkg.com/@codemirror/lint/-/lint-6.8.2.tgz#7864b03583e9efd18554cff1dd4504da10338ab1";
@@ -2846,15 +2865,6 @@ cliui@^8.0.1:
     strip-ansi "^6.0.1"
     wrap-ansi "^7.0.0"
 
-codemirror-lang-turtle@^0.0.2:
-  version "0.0.2"
-  resolved 
"https://registry.yarnpkg.com/codemirror-lang-turtle/-/codemirror-lang-turtle-0.0.2.tgz#2db4e56020b8defbbfa0788a85045404b494c957";
-  integrity 
sha512-YN9JzdxfUv2DKNHLZa8L7sG5StOri8ZHbMzY/zJhrq6pYR0peJaeN+GyQZaXl6fYmmq8hurf/BOqTYPh7Uzgcw==
-  dependencies:
-    "@codemirror/language" "^6.0.0"
-    "@lezer/highlight" "^1.0.0"
-    "@lezer/lr" "^1.0.0"
-
 [email protected]:
   version "6.0.2"
   resolved 
"https://registry.yarnpkg.com/codemirror/-/codemirror-6.0.2.tgz#4d3fea1ad60b6753f97ca835f2f48c6936a8946e";
@@ -6942,16 +6952,7 @@ string-hash@~1.1.3:
   resolved 
"https://registry.yarnpkg.com/string-hash/-/string-hash-1.1.3.tgz#e8aafc0ac1855b4666929ed7dd1275df5d6c811b";
   integrity 
sha512-kJUvRUFK49aub+a7T1nNE66EJbZBMnBgoC1UbCZ5n6bsZKBRga4KgBRTMn/pFkeCZSYtNeSyMxPDM0AXWELk2A==
 
-"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==
@@ -7008,14 +7009,7 @@ string_decoder@^1.3.0:
   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"
-
-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==
@@ -7787,7 +7781,7 @@ word-wrap@^1.2.5:
   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":
+"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==
@@ -7805,15 +7799,6 @@ wrap-ansi@^6.2.0:
     string-width "^4.1.0"
     strip-ansi "^6.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==
-  dependencies:
-    ansi-styles "^4.0.0"
-    string-width "^4.1.0"
-    strip-ansi "^6.0.0"
-
 wrap-ansi@^8.1.0:
   version "8.1.0"
   resolved 
"https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214";

Reply via email to