diff --git a/Build/Sources/TypeScript/t3editor/autocomplete/completion-result.ts b/Build/Sources/TypeScript/t3editor/autocomplete/completion-result.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2eb7118076ad0b8c84952d31682dbca15475c0e5
--- /dev/null
+++ b/Build/Sources/TypeScript/t3editor/autocomplete/completion-result.ts
@@ -0,0 +1,105 @@
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+import type { TsRef, TsRefType } from '@typo3/t3editor/autocomplete/ts-ref';
+import type { TreeNode } from '@typo3/t3editor/autocomplete/ts-parser';
+
+export type Proposal = {
+  type: string,
+  word?: string,
+  cssClass?: string,
+};
+
+export class CompletionResult {
+  private readonly tsRef: TsRef;
+  private readonly tsTreeNode: TreeNode;
+
+  constructor(tsRef: TsRef, tsTreeNode: TreeNode) {
+    this.tsRef = tsRef;
+    this.tsTreeNode = tsTreeNode;
+  }
+
+  /**
+   * returns the type of the currentTsTreeNode
+   */
+  public getType(): TsRefType | null {
+    const val = this.tsTreeNode.getValue();
+    if (this.tsRef.isType(val)) {
+      return this.tsRef.getType(val);
+    }
+    return null;
+  }
+
+  /**
+   * returns a list of possible path completions (proposals), which is:
+   * a list of the children of the current TsTreeNode (= userdefined properties)
+   * and a list of properties allowed for the current object in the TsRef
+   * remove all words from list that don't start with the string in filter
+   */
+  public getFilteredProposals(filter: string): Array<Proposal> {
+    const defined: Record<string, boolean> = {};
+    const propArr: Array<Proposal> = [];
+    const childNodes = this.tsTreeNode.getChildNodes();
+    const value = this.tsTreeNode.getValue();
+
+    // first get the childNodes of the Node (=properties defined by the user)
+    for (const key in childNodes) {
+      if (typeof childNodes[key].value !== 'undefined' && childNodes[key].value !== null) {
+        const propObj: Proposal = {} as Proposal;
+        propObj.word = key;
+        if (this.tsRef.typeHasProperty(value, childNodes[key].name)) {
+          this.tsRef.cssClass = 'definedTSREFProperty';
+          propObj.type = childNodes[key].value;
+        } else {
+          propObj.cssClass = 'userProperty';
+          if (this.tsRef.isType(childNodes[key].value)) {
+            propObj.type = childNodes[key].value;
+          } else {
+            propObj.type = '';
+          }
+        }
+        propArr.push(propObj);
+        defined[key] = true;
+      }
+    }
+
+    // then get the tsref properties
+    const props = this.tsRef.getPropertiesFromTypeId(this.tsTreeNode.getValue());
+    for (const key in props) {
+      // show just the TSREF properties - no properties of the array-prototype and no properties which have been defined by the user
+      if (typeof props[key].value !== 'undefined' && defined[key] !== true) {
+        const propObj = {
+          word: key,
+          cssClass: 'undefinedTSREFProperty',
+          type: props[key].value,
+        };
+        propArr.push(propObj);
+      }
+    }
+
+    const result: Array<Proposal> = [];
+    let wordBeginning = '';
+
+    for (let i = 0; i < propArr.length; i++) {
+      if (filter.length === 0) {
+        result.push(propArr[i]);
+        continue;
+      }
+      wordBeginning = propArr[i].word.substring(0, filter.length);
+      if (wordBeginning.toLowerCase() === filter.toLowerCase()) {
+        result.push(propArr[i]);
+      }
+    }
+    return result;
+  }
+}
diff --git a/Build/Sources/TypeScript/t3editor/autocomplete/ts-code-completion.ts b/Build/Sources/TypeScript/t3editor/autocomplete/ts-code-completion.ts
new file mode 100644
index 0000000000000000000000000000000000000000..346731bfdb68c5564367400bd94d433fabb5eb52
--- /dev/null
+++ b/Build/Sources/TypeScript/t3editor/autocomplete/ts-code-completion.ts
@@ -0,0 +1,142 @@
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+/**
+ * Module: @typo3/t3editor/autocomplete/ts-code-completion
+ * Contains the TsCodeCompletion class
+ */
+import AjaxRequest from '@typo3/core/ajax/ajax-request';
+import { TsRef } from '@typo3/t3editor/autocomplete/ts-ref';
+import { TsParser } from '@typo3/t3editor/autocomplete/ts-parser';
+import { CompletionResult, Proposal } from '@typo3/t3editor/autocomplete/completion-result';
+
+import type { CodeMirror5CompatibleCompletionState } from '@typo3/t3editor/language/typoscript';
+
+export type ContentObjectIdentifier = string;
+
+export type TsObjTree = {
+  c?: Record<string, TsObjTree>;
+  v?: ContentObjectIdentifier;
+}
+
+export class TsCodeCompletion {
+  public extTsObjTree: TsObjTree = {};
+  private readonly tsRef: TsRef;
+  private readonly parser: TsParser = null;
+  private proposals: Array<Proposal> = null;
+  private compResult: CompletionResult = null;
+
+  constructor(id: number) {
+    this.tsRef = new TsRef();
+    this.parser = new TsParser(this.tsRef, this.extTsObjTree);
+    this.tsRef.loadTsrefAsync();
+    this.loadExtTemplatesAsync(id);
+  }
+
+  /**
+   * Refreshes the code completion list based on the cursor's position
+   */
+  public refreshCodeCompletion(
+    completionState: CodeMirror5CompatibleCompletionState
+  ): Array<Proposal['word']> {
+    // the cursornode has to be stored cause inserted breaks have to be deleted after pressing enter if the codecompletion is active
+    const filter = this.getFilter(completionState);
+
+    // TODO: implement cases: operatorCompletion reference/copy path completion (formerly found in getCompletionResults())
+    const currentTsTreeNode = this.parser.buildTsObjTree(completionState);
+    this.compResult = new CompletionResult(
+      this.tsRef,
+      currentTsTreeNode
+    );
+
+    this.proposals = this.compResult.getFilteredProposals(filter);
+
+    const proposals: string[] = [];
+    for (let i = 0; i < this.proposals.length; i++) {
+      proposals[i] = this.proposals[i].word;
+    }
+
+    return proposals;
+  }
+
+  /**
+   * All external templates along the rootline have to be loaded,
+   * this function retrieves the JSON code by committing a AJAX request
+   */
+  private loadExtTemplatesAsync(id: number): void {
+    if (Number.isNaN(id) || id === 0) {
+      return null;
+    }
+    new AjaxRequest(TYPO3.settings.ajaxUrls.t3editor_codecompletion_loadtemplates)
+      .withQueryArguments({ pageId: id })
+      .get()
+      .then(async (response) => {
+        this.extTsObjTree.c = await response.resolve();
+        this.resolveExtReferencesRec(this.extTsObjTree.c);
+      });
+  }
+
+  /**
+   * Since the references are not resolved server side we have to do it client-side
+   * Benefit: less loading time due to less data which has to be transmitted
+   *
+   * @param {Array} childNodes
+   */
+  private resolveExtReferencesRec(childNodes: Record<string, TsObjTree>): void {
+    for (const key of Object.keys(childNodes)) {
+      let childNode;
+      // if the childnode has a value and there is a part of a reference operator ('<')
+      // and it does not look like a html tag ('>')
+      if (childNodes[key].v && childNodes[key].v[0] === '<' && childNodes[key].v.indexOf('>') === -1) {
+        const path = childNodes[key].v.replace(/</, '').trim();
+        // if there are still whitespaces it's no path
+        if (path.indexOf(' ') === -1) {
+          childNode = this.getExtChildNode(path);
+          // if the node was found - reference it
+          if (childNode !== null) {
+            childNodes[key] = childNode;
+          }
+        }
+      }
+      // if there was no reference-resolving then we go deeper into the tree
+      if (!childNode && childNodes[key].c) {
+        this.resolveExtReferencesRec(childNodes[key].c);
+      }
+    }
+  }
+
+  /**
+   * Get the child node of given path
+   */
+  private getExtChildNode(path: string): object {
+    let extTree = this.extTsObjTree;
+
+    const pathParts = path.split('.');
+    for (let i = 0; i < pathParts.length; i++) {
+      const pathSeg = pathParts[i];
+      if (typeof extTree.c === 'undefined' || typeof extTree.c[pathSeg] === 'undefined') {
+        return null;
+      }
+      extTree = extTree.c[pathSeg];
+    }
+    return extTree;
+  }
+
+  private getFilter(completionState: CodeMirror5CompatibleCompletionState): string {
+    if (completionState.completingAfterDot) {
+      return '';
+    }
+
+    return completionState.token.string.replace('.', '').replace(/\s/g, '');
+  }
+}
diff --git a/Build/Sources/TypeScript/t3editor/autocomplete/ts-parser.ts b/Build/Sources/TypeScript/t3editor/autocomplete/ts-parser.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c893bad5db998d28c05c4808c25e7c908c280bc2
--- /dev/null
+++ b/Build/Sources/TypeScript/t3editor/autocomplete/ts-parser.ts
@@ -0,0 +1,485 @@
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+import type { TsRef } from '@typo3/t3editor/autocomplete/ts-ref';
+import type { TsObjTree } from '@typo3/t3editor/autocomplete/ts-code-completion';
+import type { CodeMirror5CompatibleCompletionState } from '@typo3/t3editor/language/typoscript';
+
+export class TreeNode {
+  public value: string;
+  public childNodes: Record<string, TreeNode> = {};
+  public extPath = '';
+  public name: string;
+  public isExternal: boolean;
+
+  public global: boolean;
+  public parent: TreeNode = null;
+
+  private readonly tsParser: TsParser;
+
+  constructor(nodeName: string, tsParser: TsParser) {
+    this.name = nodeName;
+    this.childNodes = {};
+    this.extPath = '';
+    this.value = '';
+    this.isExternal = false;
+
+    this.tsParser = tsParser;
+  }
+
+  /**
+   * Returns local properties and the properties of the external templates
+   */
+  public getChildNodes(): Record<string, TreeNode> {
+    const node = this.getExtNode();
+    if (node !== null && typeof node.c === 'object') {
+      for (const key of Object.keys(node.c)) {
+        const tn = new TreeNode(key, this.tsParser);
+        tn.global = true;
+        tn.value = (node.c[key].v) ? node.c[key].v : '';
+        tn.isExternal = true;
+        this.childNodes[key] = tn;
+      }
+    }
+    return this.childNodes;
+  }
+
+  /**
+   * Returns the value of a node
+   */
+  public getValue(): string {
+    if (this.value) {
+      return this.value;
+    }
+    const node = this.getExtNode();
+    if (node && node.v) {
+      return node.v;
+    }
+
+    const type = this.getNodeTypeFromTsref();
+    if (type) {
+      return type;
+    }
+    return '';
+  }
+
+  /**
+   * This method will try to resolve the properties recursively from right
+   * to left. If the node's value property is not set, it will look for the
+   * value of its parent node, and if there is a matching childProperty
+   * (according to the TSREF) it will return the childProperties value.
+   * If there is no value in the parent node it will go one step further
+   * and look into the parent node of the parent node,...
+   */
+  private getNodeTypeFromTsref(): string {
+    const path = this.extPath.split('.'),
+      lastSeg = path.pop();
+
+    // attention: there will be recursive calls if necessary
+    const parentValue = this.parent.getValue();
+    if (parentValue) {
+      if (this.tsParser.tsRef.typeHasProperty(parentValue, lastSeg)) {
+        const type = this.tsParser.tsRef.getType(parentValue);
+        return type.properties[lastSeg].value;
+      }
+    }
+    return '';
+  }
+
+  /**
+   * Will look in the external ts-tree (static templates, templates on other pages)
+   * if there is a value or childproperties assigned to the current node.
+   * The method uses the extPath of the current node to navigate to the corresponding
+   * node in the external tree
+   */
+  private getExtNode(): TsObjTree {
+    let extTree = this.tsParser.extTsObjTree;
+
+    if (this.extPath === '') {
+      return extTree;
+    }
+    const path = this.extPath.split('.');
+
+    for (let i = 0; i < path.length; i++) {
+      const pathSeg = path[i];
+      if (typeof extTree.c === 'undefined' || typeof extTree.c[pathSeg] === 'undefined') {
+        return null;
+      }
+      extTree = extTree.c[pathSeg];
+    }
+    return extTree;
+  }
+}
+
+class Stack<T> extends Array<T> {
+  public lastElementEquals(el: T): boolean {
+    return this.length > 0 && this[this.length - 1] === el;
+  }
+
+  public popIfLastElementEquals(el: T): boolean {
+    if (this.lastElementEquals(el)) {
+      this.pop();
+      return true;
+    }
+    return false;
+  }
+}
+
+export class TsParser {
+  public readonly tsRef: TsRef;
+  public readonly extTsObjTree: TsObjTree;
+  private tsTree: TreeNode;
+
+  private clone: <T extends object | unknown>(myObj: T) => T;
+
+  constructor(tsRef: TsRef, extTsObjTree: TsObjTree) {
+    this.tsRef = tsRef;
+    this.extTsObjTree = extTsObjTree;
+    this.tsTree = new TreeNode('_L_', this);
+  }
+
+  /**
+   * Check if there is an operator in the line and return it
+   * if there is none, return -1
+   */
+  public getOperator(line: string): string | -1 {
+    const operators = [':=', '=<', '<', '>', '='];
+    for (let i = 0; i < operators.length; i++) {
+      const op = operators[i];
+      if (line.indexOf(op) !== -1) {
+        // check if there is some HTML in this line (simple check, however it's the only difference between a reference operator and HTML)
+        // we do this check only in case of the two operators "=<" and "<" since the delete operator would trigger our "HTML-finder"
+        if ((op === '=<' || op === '<') && line.indexOf('>') > -1) {
+          // if there is a ">" in the line suppose there's some HTML
+          return '=';
+        }
+        return op;
+      }
+    }
+    return -1;
+  }
+
+  /**
+   * Build the TypoScript object tree
+   */
+  public buildTsObjTree(completionState: CodeMirror5CompatibleCompletionState): TreeNode {
+    this.tsTree = new TreeNode('', this);
+    this.tsTree.value = 'TLO';
+
+    let currentLine = 1,
+      line = '',
+      ignoreLine = false,
+      insideCondition = false;
+    const stack = new Stack<string>();
+    const prefixes = [];
+    let path;
+
+    while (currentLine <= completionState.currentLineNumber) {
+      line = '';
+      const tokens = completionState.lineTokens[currentLine - 1];
+      for (let i = 0; i <= tokens.length; ++i) {
+        if (i < tokens.length && tokens[i].string.length > 0) {
+          const tokenValue = tokens[i].string;
+
+          if (tokenValue[0] === '#') {
+            stack.push('#');
+          } else if (tokenValue === '(') {
+            stack.push('(');
+          } else if (tokenValue[0] === '/' && tokenValue[1] === '*') {
+            stack.push('/*');
+          } else if (tokenValue === '{') {
+            // TODO: ignore whole block if wrong whitespaces in this line
+            if (this.getOperator(line) === -1) {
+              stack.push('{');
+              prefixes.push(line.trim());
+              ignoreLine = true;
+            }
+          }
+          // TODO: conditions
+          // if condition starts -> ignore everything until end of condition
+          if (tokenValue.search(/^\s*\[.*\]/) !== -1
+            && line.search(/\S/) === -1
+            && tokenValue.search(/^\s*\[(global|end|GLOBAL|END)\]/) === -1
+            && !stack.lastElementEquals('#')
+            && !stack.lastElementEquals('/*')
+            && !stack.lastElementEquals('{')
+            && !stack.lastElementEquals('(')
+          ) {
+            insideCondition = true;
+            ignoreLine = true;
+          }
+
+          // if end of condition reached
+          if (line.search(/\S/) === -1
+            && !stack.lastElementEquals('#')
+            && !stack.lastElementEquals('/*')
+            && !stack.lastElementEquals('(')
+            && (
+              (tokenValue.search(/^\s*\[(global|end|GLOBAL|END)\]/) !== -1
+                && !stack.lastElementEquals('{'))
+              || (tokenValue.search(/^\s*\[(global|GLOBAL)\]/) !== -1)
+            )
+          ) {
+            insideCondition = false;
+            ignoreLine = true;
+          }
+
+          if (tokenValue === ')') {
+            stack.popIfLastElementEquals('(');
+          }
+          if (tokenValue[0] === '*' && tokenValue[1] === '/') {
+            stack.popIfLastElementEquals('/*');
+            ignoreLine = true;
+          }
+          if (tokenValue === '}') {
+            //no characters except whitespace allowed before closing bracket
+            const trimmedLine = line.replace(/\s/g, '');
+            if (trimmedLine === '') {
+              stack.popIfLastElementEquals('{');
+              if (prefixes.length > 0) {
+                prefixes.pop();
+              }
+              ignoreLine = true;
+            }
+          }
+          if (!stack.lastElementEquals('#')) {
+            line += tokenValue;
+          }
+        } else {
+          // ignore comments, ...
+          if (!stack.lastElementEquals('/*') && !stack.lastElementEquals('(') && !ignoreLine && !insideCondition) {
+            line = line.trim();
+            // check if there is any operator in this line
+            const op = this.getOperator(line);
+            if (op !== -1) {
+              // figure out the position of the operator
+              const pos = line.indexOf(op);
+              // the target objectpath should be left to the operator
+              path = line.substring(0, pos);
+              // if we are in between curly brackets: add prefixes to object path
+              if (prefixes.length > 0) {
+                path = prefixes.join('.') + '.' + path;
+              }
+              // the type or value should be right to the operator
+              let str = line.substring(pos + op.length, line.length).trim();
+              path = path.trim();
+              switch (op) { // set a value or create a new object
+                case '=':
+                  //ignore if path is empty or contains whitespace
+                  if (path.search(/\s/g) === -1 && path.length > 0) {
+                    this.setTreeNodeValue(path, str);
+                  }
+                  break;
+                case '=<': // reference to another object in the tree
+                  // resolve relative path
+                  if (prefixes.length > 0 && str.substr(0, 1) === '.') {
+                    str = prefixes.join('.') + str;
+                  }
+                  //ignore if either path or str is empty or contains whitespace
+                  if (path.search(/\s/g) === -1
+                    && path.length > 0
+                    && str.search(/\s/g) === -1
+                    && str.length > 0
+                  ) {
+                    this.setReference(path, str);
+                  }
+                  break;
+                case '<': // copy from another object in the tree
+                  // resolve relative path
+                  if (prefixes.length > 0 && str.substr(0, 1) === '.') {
+                    str = prefixes.join('.') + str;
+                  }
+                  //ignore if either path or str is empty or contains whitespace
+                  if (path.search(/\s/g) === -1
+                    && path.length > 0
+                    && str.search(/\s/g) === -1
+                    && str.length > 0
+                  ) {
+                    this.setCopy(path, str);
+                  }
+                  break;
+                case '>': // delete object value and properties
+                  this.deleteTreeNodeValue(path);
+                  break;
+                case ':=': // function operator
+                  // TODO: function-operator
+                  break;
+                default:
+                  break;
+              }
+            }
+          }
+          stack.popIfLastElementEquals('#');
+          ignoreLine = false;
+        }
+      }
+      currentLine++;
+    }
+    // when node at cursorPos is reached:
+    // save currentLine, currentTsTreeNode and filter if necessary
+    // if there is a reference or copy operator ('<' or '=<')
+    // return the treeNode of the path right to the operator,
+    // else try to build a path from the whole line
+    if (!stack.lastElementEquals('/*') && !stack.lastElementEquals('(') && !ignoreLine) {
+      const i = line.indexOf('<');
+      if (i !== -1) {
+        path = line.substring(i + 1, line.length).trim();
+        if (prefixes.length > 0 && path.substr(0, 1) === '.') {
+          path = prefixes.join('.') + path;
+        }
+      } else {
+        path = line;
+        if (prefixes.length > 0) {
+          path = prefixes.join('.') + '.' + path;
+          path = path.replace(/\s/g, '');
+        }
+      }
+      const lastDot = path.lastIndexOf('.');
+      path = path.substring(0, lastDot);
+    }
+    return this.getTreeNode(path);
+  }
+
+  /**
+   * Iterates through the object tree, and creates treenodes
+   * along the path, if necessary
+   */
+  public getTreeNode(path: string): TreeNode | undefined {
+    path = path.trim();
+    if (path.length === 0) {
+      return this.tsTree;
+    }
+    const aPath = path.split('.');
+
+    let subTree = this.tsTree.childNodes,
+      pathSeg,
+      parent = this.tsTree;
+
+    // step through the path from left to right
+    for (let i = 0; i < aPath.length; i++) {
+      pathSeg = aPath[i];
+
+      // if there isn't already a treenode
+      if (typeof subTree[pathSeg] === 'undefined' || typeof subTree[pathSeg].childNodes === 'undefined') { // if this subpath is not defined in the code
+        // create a new treenode
+        subTree[pathSeg] = new TreeNode(pathSeg, this);
+        subTree[pathSeg].parent = parent;
+        // the extPath has to be set, so the TreeNode can retrieve the respecting node in the external templates
+        let extPath = parent.extPath;
+        if (extPath) {
+          extPath += '.';
+        }
+        extPath += pathSeg;
+        subTree[pathSeg].extPath = extPath;
+      }
+      if (i === aPath.length - 1) {
+        return subTree[pathSeg];
+      }
+      parent = subTree[pathSeg];
+      subTree = subTree[pathSeg].childNodes;
+    }
+    return undefined;
+  }
+
+  /**
+   * Navigates to the respecting treenode,
+   * create nodes in the path, if necessary, and sets the value
+   */
+  public setTreeNodeValue(path: string, value: string): void {
+    const treeNode = this.getTreeNode(path);
+    // if we are inside a GIFBUILDER Object
+    if (treeNode.parent !== null && treeNode.parent.value === 'GIFBUILDER' && value === 'TEXT') {
+      value = 'GB_TEXT';
+    }
+    if (treeNode.parent !== null && treeNode.parent.value === 'GIFBUILDER' && value === 'IMAGE') {
+      value = 'GB_IMAGE';
+    }
+
+    // just override if it is a real objecttype
+    if (this.tsRef.isType(value)) {
+      treeNode.value = value;
+    }
+  }
+
+  /**
+   * Navigates to the respecting treenode,
+   * creates nodes if necessary, empties the value and childNodes-Array
+   */
+  public deleteTreeNodeValue(path: string) {
+    const treeNode = this.getTreeNode(path);
+    // currently the node is not deleted really, it's just not displayed cause value == null
+    // deleting it would be a cleaner solution
+    treeNode.value = null;
+    treeNode.childNodes = {};
+  }
+
+  /**
+   * Copies a reference of the treeNode specified by path2
+   * to the location specified by path1
+   */
+  public setReference(path1: string, path2: string): void {
+    const path1arr = path1.split('.'),
+      lastNodeName = path1arr[path1arr.length - 1],
+      treeNode1 = this.getTreeNode(path1),
+      treeNode2 = this.getTreeNode(path2);
+
+    if (treeNode1.parent !== null) {
+      treeNode1.parent.childNodes[lastNodeName] = treeNode2;
+    } else {
+      this.tsTree.childNodes[lastNodeName] = treeNode2;
+    }
+  }
+
+  /**
+   * copies a treeNode specified by path2
+   * to the location specified by path1
+   */
+  public setCopy(path1: string, path2: string): void {
+    this.clone = <T extends object | unknown>(myObj: T): T => {
+      if (typeof myObj !== 'object') {
+        return myObj;
+      }
+
+      const myNewObj: Record<string, unknown> = {};
+      for (const i in myObj) {
+        if (i === 'tsParser') {
+          continue;
+        }
+        // disable recursive cloning for parent object -> copy by reference
+        if (i !== 'parent') {
+          if (typeof myObj[i] === 'object') {
+            myNewObj[i] = this.clone(myObj[i]);
+          } else {
+            myNewObj[i] = myObj[i];
+          }
+        } else {
+          if ('parent' in myObj) {
+            myNewObj.parent = myObj.parent;
+          }
+        }
+      }
+      return myNewObj as T;
+    };
+
+    const path1arr = path1.split('.'),
+      lastNodeName = path1arr[path1arr.length - 1],
+      treeNode1 = this.getTreeNode(path1),
+      treeNode2 = this.getTreeNode(path2);
+
+    if (treeNode1.parent !== null) {
+      treeNode1.parent.childNodes[lastNodeName] = this.clone(treeNode2);
+    } else {
+      this.tsTree.childNodes[lastNodeName] = this.clone(treeNode2);
+    }
+  }
+}
diff --git a/Build/Sources/TypeScript/t3editor/autocomplete/ts-ref.ts b/Build/Sources/TypeScript/t3editor/autocomplete/ts-ref.ts
new file mode 100644
index 0000000000000000000000000000000000000000..f9a47cb9ac0cd270b0ab3a9890c18258abad7a53
--- /dev/null
+++ b/Build/Sources/TypeScript/t3editor/autocomplete/ts-ref.ts
@@ -0,0 +1,164 @@
+/*
+ * This file is part of the TYPO3 CMS project.
+ *
+ * It is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License, either version 2
+ * of the License, or any later version.
+ *
+ * For the full copyright and license information, please read the
+ * LICENSE.txt file that was distributed with this source code.
+ *
+ * The TYPO3 project - inspiring people to share!
+ */
+
+import AjaxRequest from '@typo3/core/ajax/ajax-request';
+
+type TypeId = string;
+
+type PropertyName = string;
+
+type PlainDocData = Record<TypeId, {
+  extends: TypeId,
+  name: TypeId,
+  properties: Record<PropertyName, {
+    name: PropertyName,
+    type: string
+  }>,
+}>;
+
+export class TsRefType {
+  public readonly typeId: TypeId;
+  public extends: TypeId;
+  public properties: Record<PropertyName, TsRefProperty> & { clone?: () => void } = {};
+
+  constructor(
+    typeId: string,
+    extendsTypeId: string | null,
+    properties: Record<PropertyName, TsRefProperty>
+  ) {
+    this.typeId = typeId;
+    this.extends = extendsTypeId;
+    this.properties = properties;
+  }
+}
+
+export class TsRefProperty {
+  public readonly parentType: string;
+  public readonly name: PropertyName;
+  public readonly value: string;
+
+  constructor(parentType: string, name: string, value: string) {
+    this.parentType = parentType;
+    this.name = name;
+    this.value = value;
+  }
+}
+
+export class TsRef {
+  public typeTree: Record<TypeId, TsRefType> = {};
+  public doc: PlainDocData = null;
+  public cssClass: string;
+
+  /**
+   * Load available TypoScript reference
+   */
+  public async loadTsrefAsync(): Promise<void> {
+    const response = await new AjaxRequest(TYPO3.settings.ajaxUrls.t3editor_tsref).get();
+    this.doc = await response.resolve();
+    this.buildTree();
+  }
+
+  /**
+   * Build the TypoScript reference tree
+   */
+  public buildTree(): void {
+    for (const typeId of Object.keys(this.doc)) {
+      const arr = this.doc[typeId];
+      this.typeTree[typeId] = new TsRefType(
+        typeId,
+        arr.extends || undefined,
+        Object.fromEntries(
+          Object.entries(arr.properties).map(
+            ([propName, property]) => [propName, new TsRefProperty(typeId, propName, property.type)]
+          )
+        )
+      );
+    }
+    for (const typeId of Object.keys(this.typeTree)) {
+      if (typeof this.typeTree[typeId].extends !== 'undefined') {
+        this.addPropertiesToType(this.typeTree[typeId], this.typeTree[typeId].extends, 100);
+      }
+    }
+  }
+
+  /**
+   * Adds properties to TypoScript types
+   */
+  public addPropertiesToType(
+    addToType: TsRefType,
+    addFromTypeNames: string,
+    maxRecDepth: number
+  ): void {
+    if (maxRecDepth < 0) {
+      throw 'Maximum recursion depth exceeded while trying to resolve the extends in the TSREF!';
+    }
+    const exts = addFromTypeNames.split(',');
+    for (let i = 0; i < exts.length; i++) {
+      // "Type 'array' which is used to extend 'undefined', was not found in the TSREF!"
+      if (typeof this.typeTree[exts[i]] !== 'undefined') {
+        if (typeof this.typeTree[exts[i]].extends !== 'undefined') {
+          this.addPropertiesToType(this.typeTree[exts[i]], this.typeTree[exts[i]].extends, maxRecDepth - 1);
+        }
+        const properties = this.typeTree[exts[i]].properties;
+        for (const propName in properties) {
+          // only add this property if it was not already added by a supertype (subtypes override supertypes)
+          if (typeof addToType.properties[propName] === 'undefined') {
+            addToType.properties[propName] = properties[propName];
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Get properties from given TypoScript type id
+   */
+  public getPropertiesFromTypeId(tId: TypeId): Record<PropertyName, TsRefProperty> {
+    if (typeof this.typeTree[tId] !== 'undefined') {
+      // clone is needed to assure that nothing of the tsref is overwritten by user setup
+      this.typeTree[tId].properties.clone = function() {
+        const result = {} as Record<PropertyName, TsRefProperty>;
+        for (const key of Object.keys(this)) {
+          result[key] = new TsRefProperty(this[key].parentType, this[key].name, this[key].value);
+        }
+        return result;
+      }
+      return this.typeTree[tId].properties;
+    }
+    return {};
+  }
+
+  /**
+   * Check if a property of a type exists
+   */
+  public typeHasProperty(typeId: TypeId, propertyName: PropertyName): boolean {
+    return (
+      typeof this.typeTree[typeId] !== 'undefined' &&
+      typeof this.typeTree[typeId].properties[propertyName] !== 'undefined'
+    );
+  }
+
+  /**
+   * Get the type
+   */
+  public getType(typeId: TypeId): TsRefType {
+    return this.typeTree[typeId];
+  }
+
+  /**
+   * Check if type exists in the type tree
+   */
+  public isType(typeId: TypeId): boolean {
+    return typeof this.typeTree[typeId] !== 'undefined';
+  }
+}
diff --git a/Build/Sources/TypeScript/t3editor/language/typoscript.ts b/Build/Sources/TypeScript/t3editor/language/typoscript.ts
index 388b538f3a00035caf34c5e344aa9c17f056c086..2522a8a6c336f9d3181b9a9e8738f3f7ae0a7af2 100644
--- a/Build/Sources/TypeScript/t3editor/language/typoscript.ts
+++ b/Build/Sources/TypeScript/t3editor/language/typoscript.ts
@@ -1,7 +1,8 @@
+import DocumentService from '@typo3/core/document-service';
 import { StreamLanguage, LanguageSupport } from '@codemirror/language';
 import { CompletionContext, CompletionResult } from '@codemirror/autocomplete';
 import { typoScriptStreamParser } from '@typo3/t3editor/stream-parser/typoscript';
-import TsCodeCompletion from '@typo3/t3editor/autocomplete/ts-code-completion';
+import { TsCodeCompletion } from '@typo3/t3editor/autocomplete/ts-code-completion';
 import { syntaxTree } from '@codemirror/language';
 import type { SyntaxNodeRef } from '@lezer/common';
 
@@ -12,7 +13,7 @@ interface Token {
   end: number;
 }
 
-interface CodeMirror5CompatibleCompletionState {
+export interface CodeMirror5CompatibleCompletionState {
   lineTokens: Token[][];
   currentLineNumber: number;
   currentLine: string;
@@ -41,7 +42,13 @@ export function typoscript() {
   return new LanguageSupport(language, [completion]);
 }
 
-export function complete (context: CompletionContext): Promise<CompletionResult | null> | CompletionResult | null {
+const tsCodeCompletionInitializer = (async (): Promise<TsCodeCompletion> => {
+  await DocumentService.ready();
+  const effectivePid = parseInt((document.querySelector('input[name="effectivePid"]') as HTMLInputElement)?.value, 10);
+  return new TsCodeCompletion(effectivePid);
+})();
+
+export async function complete(context: CompletionContext): Promise<CompletionResult | null> {
   if (!context.explicit) {
     return null;
   }
@@ -70,7 +77,8 @@ export function complete (context: CompletionContext): Promise<CompletionResult
   }
   cm5state.token = tokenMetadata;
 
-  const keywords = TsCodeCompletion.refreshCodeCompletion(cm5state);
+  const tsCodeCompletion = await tsCodeCompletionInitializer;
+  const keywords = tsCodeCompletion.refreshCodeCompletion(cm5state);
 
   if ((token.name === 'string' || token.name === 'comment') && tokenIsSubStringOfKeywords(tokenValue, keywords)) {
     return null;
@@ -83,8 +91,6 @@ export function complete (context: CompletionContext): Promise<CompletionResult
       return { label: result, type: 'keyword' };
     })
   };
-
-  return null;
 }
 
 function parseCodeMirror5CompatibleCompletionState(context: CompletionContext): CodeMirror5CompatibleCompletionState {
diff --git a/Build/types/TYPO3/index.d.ts b/Build/types/TYPO3/index.d.ts
index 037badb0af5c87a22c3be2766524532840fd3a7d..0b5ecbdf91c38b59c6d151a232bb1a9580b4e8f6 100644
--- a/Build/types/TYPO3/index.d.ts
+++ b/Build/types/TYPO3/index.d.ts
@@ -92,7 +92,6 @@ declare module '@typo3/dashboard/contrib/chartjs';
 declare module '@typo3/backend/contrib/mark';
 
 declare module '@typo3/t3editor/stream-parser/typoscript';
-declare module '@typo3/t3editor/autocomplete/ts-code-completion';
 
 interface Taboverride {
   set(elems: HTMLElement|HTMLElement[], enable?: boolean): Taboverride
diff --git a/typo3/sysext/t3editor/Resources/Public/JavaScript/autocomplete/completion-result.js b/typo3/sysext/t3editor/Resources/Public/JavaScript/autocomplete/completion-result.js
index 74a1cfa4269895f3774a8ca543d6878da51ad1b8..f382ed1430db64e9d2ed5282598307633d6d29f4 100644
--- a/typo3/sysext/t3editor/Resources/Public/JavaScript/autocomplete/completion-result.js
+++ b/typo3/sysext/t3editor/Resources/Public/JavaScript/autocomplete/completion-result.js
@@ -10,112 +10,4 @@
  *
  * The TYPO3 project - inspiring people to share!
  */
-
-/**
- * Module: @typo3/t3editor/autocomplete/completion-result
- * Contains the CompletionResult class
- */
-
-export default (function() {
-  /**
-   *
-   * @type {{tsRef: null, tsTreeNode: null}}
-   * @exports @typo3/t3editor/addon/hint/completion-result
-   */
-  var CompletionResult = {
-    tsRef: null,
-    tsTreeNode: null
-  };
-
-  /**
-   *
-   * @param {Object} config
-   * @returns {{tsRef: null, tsTreeNode: null}}
-   */
-  CompletionResult.init = function(config) {
-    CompletionResult.tsRef = config.tsRef;
-    CompletionResult.tsTreeNode = config.tsTreeNode;
-
-    return CompletionResult;
-  };
-
-  /**
-   * returns the type of the currentTsTreeNode
-   *
-   * @returns {*}
-   */
-  CompletionResult.getType = function() {
-    var val = CompletionResult.tsTreeNode.getValue();
-    if (CompletionResult.tsRef.isType(val)) {
-      return CompletionResult.tsRef.getType(val);
-    }
-    return null;
-  };
-
-  /**
-   * returns a list of possible path completions (proposals), which is:
-   * a list of the children of the current TsTreeNode (= userdefined properties)
-   * and a list of properties allowed for the current object in the TsRef
-   * remove all words from list that don't start with the string in filter
-   *
-   * @param {String} filter beginning of the words contained in the proposal list
-   * @return {Array} an Array of Proposals
-   */
-  CompletionResult.getFilteredProposals = function(filter) {
-    var defined = [],
-      propArr = [],
-      childNodes = CompletionResult.tsTreeNode.getChildNodes(),
-      value = CompletionResult.tsTreeNode.getValue();
-
-    // first get the childNodes of the Node (=properties defined by the user)
-    for (var key in childNodes) {
-      if (typeof childNodes[key].value !== 'undefined' && childNodes[key].value !== null) {
-        var propObj = {};
-        propObj.word = key;
-        if (CompletionResult.tsRef.typeHasProperty(value, childNodes[key].name)) {
-          CompletionResult.tsRef.cssClass = 'definedTSREFProperty';
-          propObj.type = childNodes[key].value;
-        } else {
-          propObj.cssClass = 'userProperty';
-          if (CompletionResult.tsRef.isType(childNodes[key].value)) {
-            propObj.type = childNodes[key].value;
-          } else {
-            propObj.type = '';
-          }
-        }
-        propArr.push(propObj);
-        defined[key] = true;
-      }
-    }
-
-    // then get the tsref properties
-    var props = CompletionResult.tsRef.getPropertiesFromTypeId(CompletionResult.tsTreeNode.getValue());
-    for (var key in props) {
-      // show just the TSREF properties - no properties of the array-prototype and no properties which have been defined by the user
-      if (typeof props[key].value !== 'undefined' && defined[key] !== true) {
-        var propObj = {};
-        propObj.word = key;
-        propObj.cssClass = 'undefinedTSREFProperty';
-        propObj.type = props[key].value;
-        propArr.push(propObj);
-      }
-    }
-
-    var result = [],
-      wordBeginning = '';
-
-    for (var i = 0; i < propArr.length; i++) {
-      if (filter.length === 0) {
-        result.push(propArr[i]);
-        continue;
-      }
-      wordBeginning = propArr[i].word.substring(0, filter.length);
-      if (wordBeginning.toLowerCase() === filter.toLowerCase()) {
-        result.push(propArr[i]);
-      }
-    }
-    return result;
-  };
-
-  return CompletionResult;
-})();
+export class CompletionResult{constructor(e,t){this.tsRef=e,this.tsTreeNode=t}getType(){const e=this.tsTreeNode.getValue();return this.tsRef.isType(e)?this.tsRef.getType(e):null}getFilteredProposals(e){const t={},s=[],o=this.tsTreeNode.getChildNodes(),r=this.tsTreeNode.getValue();for(const e in o)if(void 0!==o[e].value&&null!==o[e].value){const l={};l.word=e,this.tsRef.typeHasProperty(r,o[e].name)?(this.tsRef.cssClass="definedTSREFProperty",l.type=o[e].value):(l.cssClass="userProperty",this.tsRef.isType(o[e].value)?l.type=o[e].value:l.type=""),s.push(l),t[e]=!0}const l=this.tsRef.getPropertiesFromTypeId(this.tsTreeNode.getValue());for(const e in l)if(void 0!==l[e].value&&!0!==t[e]){const t={word:e,cssClass:"undefinedTSREFProperty",type:l[e].value};s.push(t)}const i=[];let n="";for(let t=0;t<s.length;t++)0!==e.length?(n=s[t].word.substring(0,e.length),n.toLowerCase()===e.toLowerCase()&&i.push(s[t])):i.push(s[t]);return i}}
\ No newline at end of file
diff --git a/typo3/sysext/t3editor/Resources/Public/JavaScript/autocomplete/ts-code-completion.js b/typo3/sysext/t3editor/Resources/Public/JavaScript/autocomplete/ts-code-completion.js
index a53954182b73978f54c8432b16ca0f3a5a5ae4e7..d32b4018abaab72afbc2d07e5b1ce796c3bcf472 100644
--- a/typo3/sysext/t3editor/Resources/Public/JavaScript/autocomplete/ts-code-completion.js
+++ b/typo3/sysext/t3editor/Resources/Public/JavaScript/autocomplete/ts-code-completion.js
@@ -10,151 +10,4 @@
  *
  * The TYPO3 project - inspiring people to share!
  */
-
-/**
- * Module: @typo3/t3editor/autocomplete/ts-code-completion
- * Contains the TsCodeCompletion class
- */
-import AjaxRequest from '@typo3/core/ajax/ajax-request.js';
-import DocumentService from '@typo3/core/document-service.js';
-import TsRef from '@typo3/t3editor/autocomplete/ts-ref.js';
-import TsParser from '@typo3/t3editor/autocomplete/ts-parser.js';
-import CompletionResult from '@typo3/t3editor/autocomplete/completion-result.js';
-
-export default (function() {
-  /**
-   *
-   * @type {{tsRef: *, proposals: null, compResult: null, extTsObjTree: {}, parser: null, plugins: string[]}}
-   * @exports @typo3/t3editor/code-completion/ts-code-completion
-   */
-  var TsCodeCompletion = {
-    tsRef: TsRef,
-    proposals: null,
-    compResult: null,
-    extTsObjTree: {},
-    parser: null
-  };
-
-  /**
-   * All external templates along the rootline have to be loaded,
-   * this function retrieves the JSON code by committing a AJAX request
-   *
-   * @param {number} id
-   */
-  TsCodeCompletion.loadExtTemplatesAsync = function(id) {
-    // Ensure id is an integer
-    id *= 1;
-    if (Number.isNaN(id) || id === 0) {
-      return null;
-    }
-    new AjaxRequest(TYPO3.settings.ajaxUrls['t3editor_codecompletion_loadtemplates'])
-      .withQueryArguments({pageId: id})
-      .get()
-      .then(async function (response) {
-        TsCodeCompletion.extTsObjTree.c = await response.resolve();
-        TsCodeCompletion.resolveExtReferencesRec(TsCodeCompletion.extTsObjTree.c);
-      });
-  };
-
-  /**
-   * Since the references are not resolved server side we have to do it client-side
-   * Benefit: less loading time due to less data which has to be transmitted
-   *
-   * @param {Array} childNodes
-   */
-  TsCodeCompletion.resolveExtReferencesRec = function(childNodes) {
-    for (var key in childNodes) {
-      var childNode;
-      // if the childnode has a value and there is a part of a reference operator ('<')
-      // and it does not look like a html tag ('>')
-      if (childNodes[key].v && childNodes[key].v[0] === '<' && childNodes[key].v.indexOf('>') === -1) {
-        var path = childNodes[key].v.replace(/</, '').trim();
-        // if there are still whitespaces it's no path
-        if (path.indexOf(' ') === -1) {
-          childNode = TsCodeCompletion.getExtChildNode(path);
-          // if the node was found - reference it
-          if (childNode !== null) {
-            childNodes[key] = childNode;
-          }
-        }
-      }
-      // if there was no reference-resolving then we go deeper into the tree
-      if (!childNode && childNodes[key].c) {
-        TsCodeCompletion.resolveExtReferencesRec(childNodes[key].c);
-      }
-    }
-  };
-
-  /**
-   * Get the child node of given path
-   *
-   * @param {String} path
-   * @returns {Object}
-   */
-  TsCodeCompletion.getExtChildNode = function(path) {
-    var extTree = TsCodeCompletion.extTsObjTree,
-      path = path.split('.'),
-      pathSeg;
-
-    for (var i = 0; i < path.length; i++) {
-      pathSeg = path[i];
-      if (typeof extTree.c === 'undefined' || typeof extTree.c[pathSeg] === 'undefined') {
-        return null;
-      }
-      extTree = extTree.c[pathSeg];
-    }
-    return extTree;
-  };
-
-  /**
-   *
-   * @param {String} currentLine
-   * @returns {String}
-   */
-  TsCodeCompletion.getFilter = function(completionState) {
-    if (completionState.completingAfterDot) {
-      return '';
-    }
-
-    return completionState.token.string.replace('.', '').replace(/\s/g, '');
-  };
-
-  /**
-   * Refreshes the code completion list based on the cursor's position
-   */
-  TsCodeCompletion.refreshCodeCompletion = function(completionState) {
-    // the cursornode has to be stored cause inserted breaks have to be deleted after pressing enter if the codecompletion is active
-    var filter = TsCodeCompletion.getFilter(completionState);
-
-    // TODO: implement cases: operatorCompletion reference/copy path completion (formerly found in getCompletionResults())
-    var currentTsTreeNode = TsCodeCompletion.parser.buildTsObjTree(completionState);
-    TsCodeCompletion.compResult = CompletionResult.init({
-      tsRef: TsRef,
-      tsTreeNode: currentTsTreeNode
-    });
-
-    TsCodeCompletion.proposals = TsCodeCompletion.compResult.getFilteredProposals(filter);
-
-    var proposals = [];
-    for (var i = 0; i < TsCodeCompletion.proposals.length; i++) {
-      proposals[i] = TsCodeCompletion.proposals[i].word;
-    }
-
-    return proposals;
-  };
-
-  /**
-   * Resets the completion list
-   */
-  TsCodeCompletion.resetCompList = function() {
-    TsCodeCompletion.compResult = null;
-  };
-
-  DocumentService.ready().then(function () {
-    TsCodeCompletion.parser = TsParser.init(TsCodeCompletion.tsRef, TsCodeCompletion.extTsObjTree);
-    TsCodeCompletion.tsRef.loadTsrefAsync();
-    TsCodeCompletion.loadExtTemplatesAsync(document.querySelector('input[name="effectivePid"]')?.value);
-  });
-
-  return TsCodeCompletion;
-})();
+import AjaxRequest from"@typo3/core/ajax/ajax-request.js";import{TsRef}from"@typo3/t3editor/autocomplete/ts-ref.js";import{TsParser}from"@typo3/t3editor/autocomplete/ts-parser.js";import{CompletionResult}from"@typo3/t3editor/autocomplete/completion-result.js";export class TsCodeCompletion{constructor(e){this.extTsObjTree={},this.parser=null,this.proposals=null,this.compResult=null,this.tsRef=new TsRef,this.parser=new TsParser(this.tsRef,this.extTsObjTree),this.tsRef.loadTsrefAsync(),this.loadExtTemplatesAsync(e)}refreshCodeCompletion(e){const t=this.getFilter(e),s=this.parser.buildTsObjTree(e);this.compResult=new CompletionResult(this.tsRef,s),this.proposals=this.compResult.getFilteredProposals(t);const o=[];for(let e=0;e<this.proposals.length;e++)o[e]=this.proposals[e].word;return o}loadExtTemplatesAsync(e){if(Number.isNaN(e)||0===e)return null;new AjaxRequest(TYPO3.settings.ajaxUrls.t3editor_codecompletion_loadtemplates).withQueryArguments({pageId:e}).get().then((async e=>{this.extTsObjTree.c=await e.resolve(),this.resolveExtReferencesRec(this.extTsObjTree.c)}))}resolveExtReferencesRec(e){for(const t of Object.keys(e)){let s;if(e[t].v&&"<"===e[t].v[0]&&-1===e[t].v.indexOf(">")){const o=e[t].v.replace(/</,"").trim();-1===o.indexOf(" ")&&(s=this.getExtChildNode(o),null!==s&&(e[t]=s))}!s&&e[t].c&&this.resolveExtReferencesRec(e[t].c)}}getExtChildNode(e){let t=this.extTsObjTree;const s=e.split(".");for(let e=0;e<s.length;e++){const o=s[e];if(void 0===t.c||void 0===t.c[o])return null;t=t.c[o]}return t}getFilter(e){return e.completingAfterDot?"":e.token.string.replace(".","").replace(/\s/g,"")}}
\ No newline at end of file
diff --git a/typo3/sysext/t3editor/Resources/Public/JavaScript/autocomplete/ts-parser.js b/typo3/sysext/t3editor/Resources/Public/JavaScript/autocomplete/ts-parser.js
index df33284a4d76c513e04aac165b1b2c0b4363384c..35ffbc492a527fbb29c02434283c1a9674ec9f2c 100644
--- a/typo3/sysext/t3editor/Resources/Public/JavaScript/autocomplete/ts-parser.js
+++ b/typo3/sysext/t3editor/Resources/Public/JavaScript/autocomplete/ts-parser.js
@@ -10,508 +10,4 @@
  *
  * The TYPO3 project - inspiring people to share!
  */
-
-/**
- * Module: @typo3/t3editor/addon/hint/ts-parser
- * Contains the TsCodeCompletion class
- */
-
-import '@typo3/t3editor/autocomplete/ts-ref.js';
-
-export default (function() {
-
-  /**
-   *
-   * @type {{typeId: null, properties: null, typeTree: Array, doc: null, tsRef: null, extTsObjTree: Array, tsTree: null}}
-   * @exports @typo3/t3editor/addon/hint/ts-parser
-   */
-  var TsParser = {
-    typeId: null,
-    properties: null,
-    typeTree: [],
-    doc: null,
-    tsRef: null,
-    extTsObjTree: [],
-    tsTree: null
-  };
-
-  /**
-   *
-   * @param {Object} tsRef
-   * @param {Object} extTsObjTree
-   * @returns {{typeId: null, properties: null, typeTree: Array, doc: null, tsRef: null, extTsObjTree: Array, tsTree: null}}
-   */
-  TsParser.init = function(tsRef, extTsObjTree) {
-    TsParser.tsRef = tsRef;
-    TsParser.extTsObjTree = extTsObjTree;
-    TsParser.tsTree = new TsParser.treeNode('_L_');
-
-    return TsParser;
-  };
-
-  /**
-   *
-   * @param {String} nodeName
-   */
-  TsParser.treeNode = function(nodeName) {
-    this.name = nodeName;
-    this.childNodes = [];
-    this.extPath = '';
-    this.value = '';
-    this.isExternal = false;
-
-    /**
-     * Returns local properties and the properties of the external templates
-     *
-     * @return {Array}
-     */
-    this.getChildNodes = function() {
-      var node = this.getExtNode();
-      if (node !== null && typeof node.c === 'object') {
-        for (let key in node.c) {
-          var tn = new TsParser.treeNode(key, this.tsObjTree);
-          tn.global = true;
-          tn.value = (node.c[key].v) ? node.c[key].v : "";
-          tn.isExternal = true;
-          this.childNodes[key] = tn;
-        }
-      }
-      return this.childNodes;
-    };
-
-    /**
-     * Returns the value of a node
-     *
-     * @returns {String}
-     */
-    this.getValue = function() {
-      if (this.value) {
-        return this.value;
-      }
-      var node = this.getExtNode();
-      if (node && node.v) {
-        return node.v;
-      }
-
-      var type = this.getNodeTypeFromTsref();
-      if (type) {
-        return type;
-      }
-      return '';
-    };
-
-    /**
-     * This method will try to resolve the properties recursively from right
-     * to left. If the node's value property is not set, it will look for the
-     * value of its parent node, and if there is a matching childProperty
-     * (according to the TSREF) it will return the childProperties value.
-     * If there is no value in the parent node it will go one step further
-     * and look into the parent node of the parent node,...
-     *
-     * @return {String}
-     */
-    this.getNodeTypeFromTsref = function() {
-      var path = this.extPath.split('.'),
-        lastSeg = path.pop();
-
-      // attention: there will be recursive calls if necessary
-      var parentValue = this.parent.getValue();
-      if (parentValue) {
-        if (TsParser.tsRef.typeHasProperty(parentValue, lastSeg)) {
-          var type = TsParser.tsRef.getType(parentValue);
-          return type.properties[lastSeg].value;
-        }
-      }
-      return '';
-    };
-
-    /**
-     * Will look in the external ts-tree (static templates, templates on other pages)
-     * if there is a value or childproperties assigned to the current node.
-     * The method uses the extPath of the current node to navigate to the corresponding
-     * node in the external tree
-     *
-     * @return {Object}
-     */
-    this.getExtNode = function() {
-      var extTree = TsParser.extTsObjTree,
-        path,
-        pathSeg;
-
-      if (this.extPath === '') {
-        return extTree;
-      }
-      path = this.extPath.split('.');
-
-      for (var i = 0; i < path.length; i++) {
-        pathSeg = path[i];
-        if (typeof extTree.c === 'undefined' || typeof extTree.c[pathSeg] === 'undefined') {
-          return null;
-        }
-        extTree = extTree.c[pathSeg];
-      }
-      return extTree;
-    };
-  };
-
-  /**
-   * Check if there is an operator in the line and return it
-   * if there is none, return -1
-   *
-   * @return {(String|Number)}
-   */
-  TsParser.getOperator = function(line) {
-    var operators = [':=', '=<', '<', '>', '='];
-    for (var i = 0; i < operators.length; i++) {
-      var op = operators[i];
-      if (line.indexOf(op) !== -1) {
-        // check if there is some HTML in this line (simple check, however it's the only difference between a reference operator and HTML)
-        // we do this check only in case of the two operators "=<" and "<" since the delete operator would trigger our "HTML-finder"
-        if ((op === '=<' || op === '<') && line.indexOf('>') > -1) {
-          // if there is a ">" in the line suppose there's some HTML
-          return '=';
-        }
-        return op;
-      }
-    }
-    return -1;
-  };
-
-  /**
-   * Build the TypoScript object tree
-   */
-  TsParser.buildTsObjTree = function(completionState) {
-    TsParser.tsTree = new TsParser.treeNode('');
-    TsParser.tsTree.value = 'TLO';
-
-    function Stack() {
-    }
-
-    Stack.prototype = [];
-    Stack.prototype.lastElementEquals = function(str) {
-      return this.length > 0 && this[this.length - 1] === str;
-    };
-
-    Stack.prototype.popIfLastElementEquals = function(str) {
-      if (this.lastElementEquals(str)) {
-        this.pop();
-        return true;
-      }
-      return false;
-    };
-
-    var currentLine = 1,
-      line = '',
-      stack = new Stack(),
-      prefixes = [],
-      ignoreLine = false,
-      insideCondition = false;
-
-    while (currentLine <= completionState.currentLineNumber) {
-      line = '';
-      var tokens = completionState.lineTokens[currentLine - 1];
-      for (var i = 0; i <= tokens.length; ++i) {
-        if (i < tokens.length && tokens[i].string.length > 0) {
-          var tokenValue = tokens[i].string;
-
-          if (tokenValue[0] === '#') {
-            stack.push('#');
-          } else if (tokenValue === '(') {
-            stack.push('(');
-          } else if (tokenValue[0] === '/' && tokenValue[1] === '*') {
-            stack.push('/*');
-          } else if (tokenValue === '{') {
-            // TODO: ignore whole block if wrong whitespaces in this line
-            if (TsParser.getOperator(line) === -1) {
-              stack.push('{');
-              prefixes.push(line.trim());
-              ignoreLine = true;
-            }
-          }
-          // TODO: conditions
-          // if condition starts -> ignore everything until end of condition
-          if (tokenValue.search(/^\s*\[.*\]/) !== -1
-            && line.search(/\S/) === -1
-            && tokenValue.search(/^\s*\[(global|end|GLOBAL|END)\]/) === -1
-            && !stack.lastElementEquals('#')
-            && !stack.lastElementEquals('/*')
-            && !stack.lastElementEquals('{')
-            && !stack.lastElementEquals('(')
-          ) {
-            insideCondition = true;
-            ignoreLine = true;
-          }
-
-          // if end of condition reached
-          if (line.search(/\S/) === -1
-            && !stack.lastElementEquals('#')
-            && !stack.lastElementEquals('/*')
-            && !stack.lastElementEquals('(')
-            && (
-              (tokenValue.search(/^\s*\[(global|end|GLOBAL|END)\]/) !== -1
-                && !stack.lastElementEquals('{'))
-              || (tokenValue.search(/^\s*\[(global|GLOBAL)\]/) !== -1)
-            )
-          ) {
-            insideCondition = false;
-            ignoreLine = true;
-          }
-
-          if (tokenValue === ')') {
-            stack.popIfLastElementEquals('(');
-          }
-          if (tokenValue[0] === '*' && tokenValue[1] === '/') {
-            stack.popIfLastElementEquals('/*');
-            ignoreLine = true;
-          }
-          if (tokenValue === '}') {
-            //no characters except whitespace allowed before closing bracket
-            var trimmedLine = line.replace(/\s/g, '');
-            if (trimmedLine === '') {
-              stack.popIfLastElementEquals('{');
-              if (prefixes.length > 0) {
-                prefixes.pop();
-              }
-              ignoreLine = true;
-            }
-          }
-          if (!stack.lastElementEquals('#')) {
-            line += tokenValue;
-          }
-        } else {
-          // ignore comments, ...
-          if (!stack.lastElementEquals('/*') && !stack.lastElementEquals('(') && !ignoreLine && !insideCondition) {
-            line = line.trim();
-            // check if there is any operator in this line
-            var op = TsParser.getOperator(line);
-            if (op !== -1) {
-              // figure out the position of the operator
-              var pos = line.indexOf(op);
-              // the target objectpath should be left to the operator
-              var path = line.substring(0, pos);
-              // if we are in between curly brackets: add prefixes to object path
-              if (prefixes.length > 0) {
-                path = prefixes.join('.') + '.' + path;
-              }
-              // the type or value should be right to the operator
-              var str = line.substring(pos + op.length, line.length).trim();
-              path = path.trim();
-              switch (op) { // set a value or create a new object
-                case '=':
-                  //ignore if path is empty or contains whitespace
-                  if (path.search(/\s/g) === -1 && path.length > 0) {
-                    TsParser.setTreeNodeValue(path, str);
-                  }
-                  break;
-                case '=<': // reference to another object in the tree
-                  // resolve relative path
-                  if (prefixes.length > 0 && str.substr(0, 1) === '.') {
-                    str = prefixes.join('.') + str;
-                  }
-                  //ignore if either path or str is empty or contains whitespace
-                  if (path.search(/\s/g) === -1
-                    && path.length > 0
-                    && str.search(/\s/g) === -1
-                    && str.length > 0
-                  ) {
-                    TsParser.setReference(path, str);
-                  }
-                  break;
-                case '<': // copy from another object in the tree
-                  // resolve relative path
-                  if (prefixes.length > 0 && str.substr(0, 1) === '.') {
-                    str = prefixes.join('.') + str;
-                  }
-                  //ignore if either path or str is empty or contains whitespace
-                  if (path.search(/\s/g) === -1
-                    && path.length > 0
-                    && str.search(/\s/g) === -1
-                    && str.length > 0
-                  ) {
-                    TsParser.setCopy(path, str);
-                  }
-                  break;
-                case '>': // delete object value and properties
-                  TsParser.deleteTreeNodeValue(path);
-                  break;
-                case ':=': // function operator
-                  // TODO: function-operator
-                  break;
-              }
-            }
-          }
-          stack.popIfLastElementEquals('#');
-          ignoreLine = false;
-        }
-      }
-      currentLine++;
-    }
-    // when node at cursorPos is reached:
-    // save currentLine, currentTsTreeNode and filter if necessary
-    // if there is a reference or copy operator ('<' or '=<')
-    // return the treeNode of the path right to the operator,
-    // else try to build a path from the whole line
-    if (!stack.lastElementEquals('/*') && !stack.lastElementEquals('(') && !ignoreLine) {
-      var i = line.indexOf('<');
-
-      if (i !== -1) {
-        var path = line.substring(i + 1, line.length).trim();
-        if (prefixes.length > 0 && path.substr(0, 1) === '.') {
-          path = prefixes.join('.') + path;
-        }
-      } else {
-        var path = line;
-        if (prefixes.length > 0) {
-          path = prefixes.join('.') + '.' + path;
-          path = path.replace(/\s/g, '');
-        }
-      }
-      var lastDot = path.lastIndexOf('.');
-      path = path.substring(0, lastDot);
-    }
-    return TsParser.getTreeNode(path);
-  };
-
-  /**
-   * Iterates through the object tree, and creates treenodes
-   * along the path, if necessary
-   *
-   * @param {String} path
-   * @returns {Object}
-   */
-  TsParser.getTreeNode = function(path) {
-    path = path.trim();
-    if (path.length === 0) {
-      return TsParser.tsTree;
-    }
-    var aPath = path.split('.');
-
-    var subTree = TsParser.tsTree.childNodes,
-      pathSeg,
-      parent = TsParser.tsTree;
-
-    // step through the path from left to right
-    for (var i = 0; i < aPath.length; i++) {
-      pathSeg = aPath[i];
-
-      // if there isn't already a treenode
-      if (typeof subTree[pathSeg] === 'undefined' || typeof subTree[pathSeg].childNodes === 'undefined') { // if this subpath is not defined in the code
-        // create a new treenode
-        subTree[pathSeg] = new TsParser.treeNode(pathSeg);
-        subTree[pathSeg].parent = parent;
-        // the extPath has to be set, so the TreeNode can retrieve the respecting node in the external templates
-        var extPath = parent.extPath;
-        if (extPath) {
-          extPath += '.';
-        }
-        extPath += pathSeg;
-        subTree[pathSeg].extPath = extPath;
-      }
-      if (i === aPath.length - 1) {
-        return subTree[pathSeg];
-      }
-      parent = subTree[pathSeg];
-      subTree = subTree[pathSeg].childNodes;
-    }
-  };
-
-  /**
-   * Navigates to the respecting treenode,
-   * create nodes in the path, if necessary, and sets the value
-   *
-   * @param {String} path
-   * @param {String} value
-   */
-  TsParser.setTreeNodeValue = function(path, value) {
-    var treeNode = TsParser.getTreeNode(path);
-    // if we are inside a GIFBUILDER Object
-    if (treeNode.parent !== null && treeNode.parent.value === "GIFBUILDER" && value === "TEXT") {
-      value = 'GB_TEXT';
-    }
-    if (treeNode.parent !== null && treeNode.parent.value === "GIFBUILDER" && value === "IMAGE") {
-      value = 'GB_IMAGE';
-    }
-
-    // just override if it is a real objecttype
-    if (TsParser.tsRef.isType(value)) {
-      treeNode.value = value;
-    }
-  };
-
-  /**
-   * Navigates to the respecting treenode,
-   * creates nodes if necessary, empties the value and childNodes-Array
-   *
-   * @param {String} path
-   */
-  TsParser.deleteTreeNodeValue = function(path) {
-    var treeNode = TsParser.getTreeNode(path);
-    // currently the node is not deleted really, it's just not displayed cause value == null
-    // deleting it would be a cleaner solution
-    treeNode.value = null;
-    treeNode.childNodes = {};
-  };
-
-  /**
-   * Copies a reference of the treeNode specified by path2
-   * to the location specified by path1
-   *
-   * @param {String} path1
-   * @param {String} path2
-   */
-  TsParser.setReference = function(path1, path2) {
-    var path1arr = path1.split('.'),
-      lastNodeName = path1arr[path1arr.length - 1],
-      treeNode1 = TsParser.getTreeNode(path1),
-      treeNode2 = TsParser.getTreeNode(path2);
-
-    if (treeNode1.parent !== null) {
-      treeNode1.parent.childNodes[lastNodeName] = treeNode2;
-    } else {
-      TsParser.tsTree.childNodes[lastNodeName] = treeNode2;
-    }
-  };
-
-  /**
-   * copies a treeNode specified by path2
-   * to the location specified by path1
-   *
-   * @param {String} path1
-   * @param {String} path2
-   */
-  TsParser.setCopy = function(path1, path2) {
-    this.clone = function(myObj) {
-      if (typeof myObj !== 'object') {
-        return myObj;
-      }
-
-      var myNewObj = {};
-      for (var i in myObj) {
-        // disable recursive cloning for parent object -> copy by reference
-        if (i !== 'parent') {
-          if (typeof myObj[i] === 'object') {
-            myNewObj[i] = this.clone(myObj[i]);
-          } else {
-            myNewObj[i] = myObj[i];
-          }
-        } else {
-          myNewObj.parent = myObj.parent;
-        }
-      }
-      return myNewObj;
-    };
-
-    var path1arr = path1.split('.'),
-      lastNodeName = path1arr[path1arr.length - 1],
-      treeNode1 = TsParser.getTreeNode(path1),
-      treeNode2 = TsParser.getTreeNode(path2);
-
-    if (treeNode1.parent !== null) {
-      treeNode1.parent.childNodes[lastNodeName] = this.clone(treeNode2);
-    } else {
-      TsParser.tsTree.childNodes[lastNodeName] = this.clone(treeNode2);
-    }
-  };
-
-  return TsParser;
-})();
+export class TreeNode{constructor(e,t){this.childNodes={},this.extPath="",this.parent=null,this.name=e,this.childNodes={},this.extPath="",this.value="",this.isExternal=!1,this.tsParser=t}getChildNodes(){const e=this.getExtNode();if(null!==e&&"object"==typeof e.c)for(const t of Object.keys(e.c)){const s=new TreeNode(t,this.tsParser);s.global=!0,s.value=e.c[t].v?e.c[t].v:"",s.isExternal=!0,this.childNodes[t]=s}return this.childNodes}getValue(){if(this.value)return this.value;const e=this.getExtNode();if(e&&e.v)return e.v;const t=this.getNodeTypeFromTsref();return t||""}getNodeTypeFromTsref(){const e=this.extPath.split(".").pop(),t=this.parent.getValue();if(t&&this.tsParser.tsRef.typeHasProperty(t,e)){return this.tsParser.tsRef.getType(t).properties[e].value}return""}getExtNode(){let e=this.tsParser.extTsObjTree;if(""===this.extPath)return e;const t=this.extPath.split(".");for(let s=0;s<t.length;s++){const l=t[s];if(void 0===e.c||void 0===e.c[l])return null;e=e.c[l]}return e}}class Stack extends Array{lastElementEquals(e){return this.length>0&&this[this.length-1]===e}popIfLastElementEquals(e){return!!this.lastElementEquals(e)&&(this.pop(),!0)}}export class TsParser{constructor(e,t){this.tsRef=e,this.extTsObjTree=t,this.tsTree=new TreeNode("_L_",this)}getOperator(e){const t=[":=","=<","<",">","="];for(let s=0;s<t.length;s++){const l=t[s];if(-1!==e.indexOf(l))return("=<"===l||"<"===l)&&e.indexOf(">")>-1?"=":l}return-1}buildTsObjTree(e){this.tsTree=new TreeNode("",this),this.tsTree.value="TLO";let t=1,s="",l=!1,r=!1;const n=new Stack,i=[];let a;for(;t<=e.currentLineNumber;){s="";const h=e.lineTokens[t-1];for(let e=0;e<=h.length;++e)if(e<h.length&&h[e].string.length>0){const t=h[e].string;if("#"===t[0]?n.push("#"):"("===t?n.push("("):"/"===t[0]&&"*"===t[1]?n.push("/*"):"{"===t&&-1===this.getOperator(s)&&(n.push("{"),i.push(s.trim()),l=!0),-1===t.search(/^\s*\[.*\]/)||-1!==s.search(/\S/)||-1!==t.search(/^\s*\[(global|end|GLOBAL|END)\]/)||n.lastElementEquals("#")||n.lastElementEquals("/*")||n.lastElementEquals("{")||n.lastElementEquals("(")||(r=!0,l=!0),-1!==s.search(/\S/)||n.lastElementEquals("#")||n.lastElementEquals("/*")||n.lastElementEquals("(")||(-1===t.search(/^\s*\[(global|end|GLOBAL|END)\]/)||n.lastElementEquals("{"))&&-1===t.search(/^\s*\[(global|GLOBAL)\]/)||(r=!1,l=!0),")"===t&&n.popIfLastElementEquals("("),"*"===t[0]&&"/"===t[1]&&(n.popIfLastElementEquals("/*"),l=!0),"}"===t){""===s.replace(/\s/g,"")&&(n.popIfLastElementEquals("{"),i.length>0&&i.pop(),l=!0)}n.lastElementEquals("#")||(s+=t)}else{if(!(n.lastElementEquals("/*")||n.lastElementEquals("(")||l||r)){s=s.trim();const e=this.getOperator(s);if(-1!==e){const t=s.indexOf(e);a=s.substring(0,t),i.length>0&&(a=i.join(".")+"."+a);let l=s.substring(t+e.length,s.length).trim();switch(a=a.trim(),e){case"=":-1===a.search(/\s/g)&&a.length>0&&this.setTreeNodeValue(a,l);break;case"=<":i.length>0&&"."===l.substr(0,1)&&(l=i.join(".")+l),-1===a.search(/\s/g)&&a.length>0&&-1===l.search(/\s/g)&&l.length>0&&this.setReference(a,l);break;case"<":i.length>0&&"."===l.substr(0,1)&&(l=i.join(".")+l),-1===a.search(/\s/g)&&a.length>0&&-1===l.search(/\s/g)&&l.length>0&&this.setCopy(a,l);break;case">":this.deleteTreeNodeValue(a)}}}n.popIfLastElementEquals("#"),l=!1}t++}if(!n.lastElementEquals("/*")&&!n.lastElementEquals("(")&&!l){const e=s.indexOf("<");-1!==e?(a=s.substring(e+1,s.length).trim(),i.length>0&&"."===a.substr(0,1)&&(a=i.join(".")+a)):(a=s,i.length>0&&(a=i.join(".")+"."+a,a=a.replace(/\s/g,"")));const t=a.lastIndexOf(".");a=a.substring(0,t)}return this.getTreeNode(a)}getTreeNode(e){if(0===(e=e.trim()).length)return this.tsTree;const t=e.split(".");let s,l=this.tsTree.childNodes,r=this.tsTree;for(let e=0;e<t.length;e++){if(s=t[e],void 0===l[s]||void 0===l[s].childNodes){l[s]=new TreeNode(s,this),l[s].parent=r;let e=r.extPath;e&&(e+="."),e+=s,l[s].extPath=e}if(e===t.length-1)return l[s];r=l[s],l=l[s].childNodes}}setTreeNodeValue(e,t){const s=this.getTreeNode(e);null!==s.parent&&"GIFBUILDER"===s.parent.value&&"TEXT"===t&&(t="GB_TEXT"),null!==s.parent&&"GIFBUILDER"===s.parent.value&&"IMAGE"===t&&(t="GB_IMAGE"),this.tsRef.isType(t)&&(s.value=t)}deleteTreeNodeValue(e){const t=this.getTreeNode(e);t.value=null,t.childNodes={}}setReference(e,t){const s=e.split("."),l=s[s.length-1],r=this.getTreeNode(e),n=this.getTreeNode(t);null!==r.parent?r.parent.childNodes[l]=n:this.tsTree.childNodes[l]=n}setCopy(e,t){this.clone=e=>{if("object"!=typeof e)return e;const t={};for(const s in e)"tsParser"!==s&&("parent"!==s?"object"==typeof e[s]?t[s]=this.clone(e[s]):t[s]=e[s]:"parent"in e&&(t.parent=e.parent));return t};const s=e.split("."),l=s[s.length-1],r=this.getTreeNode(e),n=this.getTreeNode(t);null!==r.parent?r.parent.childNodes[l]=this.clone(n):this.tsTree.childNodes[l]=this.clone(n)}}
\ No newline at end of file
diff --git a/typo3/sysext/t3editor/Resources/Public/JavaScript/autocomplete/ts-ref.js b/typo3/sysext/t3editor/Resources/Public/JavaScript/autocomplete/ts-ref.js
index db15773c64e676339fc27dce215911588f065966..ed706c121ea6bfbf4d1547ae0e8ccb083bef6261 100644
--- a/typo3/sysext/t3editor/Resources/Public/JavaScript/autocomplete/ts-ref.js
+++ b/typo3/sysext/t3editor/Resources/Public/JavaScript/autocomplete/ts-ref.js
@@ -10,168 +10,4 @@
  *
  * The TYPO3 project - inspiring people to share!
  */
-
-import AjaxRequest from '@typo3/core/ajax/ajax-request.js';
-
-/**
- * Module: @typo3/t3editor/code-completion/ts-ref
- * Contains the TsCodeCompletion class
- */
-export default (function() {
-  /**
-   *
-   * @type {{typeId: null, properties: null, typeTree: Array, doc: null}}
-   * @exports @typo3/t3editor/code-completion/ts-ref
-   */
-  var TsRef = {
-    typeId: null,
-    properties: null,
-    typeTree: [],
-    doc: null
-  };
-
-  /**
-   * Prototypes a TS reference type object
-   *
-   * @param {String} typeId
-   */
-  TsRef.TsRefType = function(typeId) {
-    this.typeId = typeId;
-    this.properties = [];
-  };
-
-  /**
-   * Prototypes a TS reference property object
-   *
-   * @param {String} parentType
-   * @param {String} name
-   * @param {String} value
-   * @constructor
-   */
-  TsRef.TsRefProperty = function(parentType, name, value) {
-    this.parentType = parentType;
-    this.name = name;
-    this.value = value;
-  };
-
-  /**
-   * Load available TypoScript reference
-   */
-  TsRef.loadTsrefAsync = function() {
-    new AjaxRequest(TYPO3.settings.ajaxUrls['t3editor_tsref'])
-      .get()
-      .then(async function (response) {
-        TsRef.doc = await response.resolve();
-        TsRef.buildTree();
-      });
-  };
-
-  /**
-   * Build the TypoScript reference tree
-   */
-  TsRef.buildTree = function() {
-    for (var typeId in TsRef.doc) {
-      var arr = TsRef.doc[typeId];
-      TsRef.typeTree[typeId] = new TsRef.TsRefType(typeId);
-
-      if (typeof arr['extends'] !== 'undefined') {
-        TsRef.typeTree[typeId]['extends'] = arr['extends'];
-      }
-      for (var propName in arr.properties) {
-        var propType = arr.properties[propName].type;
-        TsRef.typeTree[typeId].properties[propName] = new TsRef.TsRefProperty(typeId, propName, propType);
-      }
-    }
-    for (var typeId in TsRef.typeTree) {
-      if (typeof TsRef.typeTree[typeId]['extends'] !== 'undefined') {
-        TsRef.addPropertiesToType(TsRef.typeTree[typeId], TsRef.typeTree[typeId]['extends'], 100);
-      }
-    }
-  };
-
-  /**
-   * Adds properties to TypoScript types
-   *
-   * @param {String} addToType
-   * @param {String} addFromTypeNames
-   * @param {Number} maxRecDepth
-   */
-  TsRef.addPropertiesToType = function(addToType, addFromTypeNames, maxRecDepth) {
-    if (maxRecDepth < 0) {
-      throw "Maximum recursion depth exceeded while trying to resolve the extends in the TSREF!";
-      return;
-    }
-    var exts = addFromTypeNames.split(','),
-      i;
-    for (i = 0; i < exts.length; i++) {
-      // "Type 'array' which is used to extend 'undefined', was not found in the TSREF!"
-      if (typeof TsRef.typeTree[exts[i]] !== 'undefined') {
-        if (typeof TsRef.typeTree[exts[i]]['extends'] !== 'undefined') {
-          TsRef.addPropertiesToType(TsRef.typeTree[exts[i]], TsRef.typeTree[exts[i]]['extends'], maxRecDepth - 1);
-        }
-        var properties = TsRef.typeTree[exts[i]].properties;
-        for (var propName in properties) {
-          // only add this property if it was not already added by a supertype (subtypes override supertypes)
-          if (typeof addToType.properties[propName] === 'undefined') {
-            addToType.properties[propName] = properties[propName];
-          }
-        }
-      }
-    }
-  };
-
-  /**
-   * Get properties from given TypoScript type id
-   *
-   * @param {String} tId
-   * @return {Array}
-   */
-  TsRef.getPropertiesFromTypeId = function(tId) {
-    if (typeof TsRef.typeTree[tId] !== 'undefined') {
-      // clone is needed to assure that nothing of the tsref is overwritten by user setup
-      TsRef.typeTree[tId].properties.clone = function() {
-        var result = [];
-        for (key in this) {
-          result[key] = new TsRef.TsRefProperty(this[key].parentType, this[key].name, this[key].value);
-        }
-        return result;
-      }
-      return TsRef.typeTree[tId].properties;
-    }
-    return [];
-  };
-
-  /**
-   * Check if a property of a type exists
-   *
-   * @param {String} typeId
-   * @param {String} propertyName
-   * @return {Boolean}
-   */
-  TsRef.typeHasProperty = function(typeId, propertyName) {
-    return typeof TsRef.typeTree[typeId] !== 'undefined'
-      && typeof TsRef.typeTree[typeId].properties[propertyName] !== 'undefined';
-  };
-
-  /**
-   * Get the type
-   *
-   * @param {String} typeId
-   * @return {Object}
-   */
-  TsRef.getType = function(typeId) {
-    return TsRef.typeTree[typeId];
-  };
-
-  /**
-   * Check if type exists in the type tree
-   *
-   * @param {String} typeId
-   * @return {Boolean}
-   */
-  TsRef.isType = function(typeId) {
-    return typeof TsRef.typeTree[typeId] !== 'undefined';
-  };
-
-  return TsRef;
-})();
+import AjaxRequest from"@typo3/core/ajax/ajax-request.js";export class TsRefType{constructor(e,t,s){this.properties={},this.typeId=e,this.extends=t,this.properties=s}}export class TsRefProperty{constructor(e,t,s){this.parentType=e,this.name=t,this.value=s}}export class TsRef{constructor(){this.typeTree={},this.doc=null}async loadTsrefAsync(){const e=await new AjaxRequest(TYPO3.settings.ajaxUrls.t3editor_tsref).get();this.doc=await e.resolve(),this.buildTree()}buildTree(){for(const e of Object.keys(this.doc)){const t=this.doc[e];this.typeTree[e]=new TsRefType(e,t.extends||void 0,Object.fromEntries(Object.entries(t.properties).map((([t,s])=>[t,new TsRefProperty(e,t,s.type)]))))}for(const e of Object.keys(this.typeTree))void 0!==this.typeTree[e].extends&&this.addPropertiesToType(this.typeTree[e],this.typeTree[e].extends,100)}addPropertiesToType(e,t,s){if(s<0)throw"Maximum recursion depth exceeded while trying to resolve the extends in the TSREF!";const r=t.split(",");for(let t=0;t<r.length;t++)if(void 0!==this.typeTree[r[t]]){void 0!==this.typeTree[r[t]].extends&&this.addPropertiesToType(this.typeTree[r[t]],this.typeTree[r[t]].extends,s-1);const i=this.typeTree[r[t]].properties;for(const t in i)void 0===e.properties[t]&&(e.properties[t]=i[t])}}getPropertiesFromTypeId(e){return void 0!==this.typeTree[e]?(this.typeTree[e].properties.clone=function(){const e={};for(const t of Object.keys(this))e[t]=new TsRefProperty(this[t].parentType,this[t].name,this[t].value);return e},this.typeTree[e].properties):{}}typeHasProperty(e,t){return void 0!==this.typeTree[e]&&void 0!==this.typeTree[e].properties[t]}getType(e){return this.typeTree[e]}isType(e){return void 0!==this.typeTree[e]}}
\ No newline at end of file
diff --git a/typo3/sysext/t3editor/Resources/Public/JavaScript/language/typoscript.js b/typo3/sysext/t3editor/Resources/Public/JavaScript/language/typoscript.js
index a38b5e2f351134b67eef71591be4461dc639f14c..5b50a8b2f80fde4bc431374c85c2189989c7290a 100644
--- a/typo3/sysext/t3editor/Resources/Public/JavaScript/language/typoscript.js
+++ b/typo3/sysext/t3editor/Resources/Public/JavaScript/language/typoscript.js
@@ -10,4 +10,4 @@
  *
  * The TYPO3 project - inspiring people to share!
  */
-import{StreamLanguage,LanguageSupport}from"@codemirror/language";import{typoScriptStreamParser}from"@typo3/t3editor/stream-parser/typoscript.js";import TsCodeCompletion from"@typo3/t3editor/autocomplete/ts-code-completion.js";import{syntaxTree}from"@codemirror/language";export function typoscript(){const t=StreamLanguage.define(typoScriptStreamParser),e=t.data.of({autocomplete:complete});return new LanguageSupport(t,[e])}export function complete(t){if(!t.explicit)return null;const e=parseCodeMirror5CompatibleCompletionState(t),o=t.pos-(e.completingAfterDot?1:0),r=syntaxTree(t.state).resolveInner(o,-1),n="Document"===r.name||e.completingAfterDot?"":t.state.sliceDoc(r.from,o),s="Document"===r.name||e.completingAfterDot?t.pos:r.from;let a={start:r.from,end:o,string:n,type:r.name};/^[\w$_]*$/.test(n)||(a={start:t.pos,end:t.pos,string:"",type:"."===n?"property":null}),e.token=a;const i=TsCodeCompletion.refreshCodeCompletion(e);if(("string"===r.name||"comment"===r.name)&&tokenIsSubStringOfKeywords(n,i))return null;return{from:s,options:getCompletions(n,i).map((t=>({label:t,type:"keyword"})))}}function parseCodeMirror5CompatibleCompletionState(t){const e=t.state.sliceDoc().split(t.state.lineBreak).length,o=t.state.sliceDoc(0,t.pos).split(t.state.lineBreak).length,r=t.state.sliceDoc().split(t.state.lineBreak)[o-1],n="."===t.state.sliceDoc(t.pos-1,t.pos);return{lineTokens:extractCodemirror5StyleLineTokens(e,t),currentLineNumber:o,currentLine:r,lineCount:e,completingAfterDot:n}}function extractCodemirror5StyleLineTokens(t,e){const o=Array(t).fill("").map((()=>[]));let r=0,n=1;return syntaxTree(e.state).cursor().iterate((s=>{const a=s.type.name||s.name;if("Document"===a)return;const i=s.from,l=s.to;r<i&&e.state.sliceDoc(r,i).split(e.state.lineBreak).forEach((e=>{e&&(o[Math.min(n-1,t-1)].push({type:null,string:e,start:r,end:r+e.length}),n++,r+=e.length)}));const p=e.state.sliceDoc(s.from,s.to);n=e.state.sliceDoc(0,s.from).split(e.state.lineBreak).length,o[n-1].push({type:a,string:p,start:i,end:l}),r=l})),r<e.state.doc.length&&o[n-1].push({type:null,string:e.state.sliceDoc(r),start:r,end:e.state.doc.length}),o}function tokenIsSubStringOfKeywords(t,e){const o=t.length;for(let r=0;r<e.length;++r)if(t===e[r].substr(o))return!0;return!1}function getCompletions(t,e){const o=new Set;for(let n=0,s=e.length;n<s;++n)0!==(r=e[n]).lastIndexOf(t,0)||o.has(r)||o.add(r);var r;const n=Array.from(o);return n.sort(),n}
\ No newline at end of file
+import DocumentService from"@typo3/core/document-service.js";import{StreamLanguage,LanguageSupport}from"@codemirror/language";import{typoScriptStreamParser}from"@typo3/t3editor/stream-parser/typoscript.js";import{TsCodeCompletion}from"@typo3/t3editor/autocomplete/ts-code-completion.js";import{syntaxTree}from"@codemirror/language";export function typoscript(){const t=StreamLanguage.define(typoScriptStreamParser),e=t.data.of({autocomplete:complete});return new LanguageSupport(t,[e])}const tsCodeCompletionInitializer=(async()=>{await DocumentService.ready();const t=parseInt(document.querySelector('input[name="effectivePid"]')?.value,10);return new TsCodeCompletion(t)})();export async function complete(t){if(!t.explicit)return null;const e=parseCodeMirror5CompatibleCompletionState(t),o=t.pos-(e.completingAfterDot?1:0),r=syntaxTree(t.state).resolveInner(o,-1),n="Document"===r.name||e.completingAfterDot?"":t.state.sliceDoc(r.from,o),s="Document"===r.name||e.completingAfterDot?t.pos:r.from;let i={start:r.from,end:o,string:n,type:r.name};/^[\w$_]*$/.test(n)||(i={start:t.pos,end:t.pos,string:"",type:"."===n?"property":null}),e.token=i;const a=(await tsCodeCompletionInitializer).refreshCodeCompletion(e);if(("string"===r.name||"comment"===r.name)&&tokenIsSubStringOfKeywords(n,a))return null;return{from:s,options:getCompletions(n,a).map((t=>({label:t,type:"keyword"})))}}function parseCodeMirror5CompatibleCompletionState(t){const e=t.state.sliceDoc().split(t.state.lineBreak).length,o=t.state.sliceDoc(0,t.pos).split(t.state.lineBreak).length,r=t.state.sliceDoc().split(t.state.lineBreak)[o-1],n="."===t.state.sliceDoc(t.pos-1,t.pos);return{lineTokens:extractCodemirror5StyleLineTokens(e,t),currentLineNumber:o,currentLine:r,lineCount:e,completingAfterDot:n}}function extractCodemirror5StyleLineTokens(t,e){const o=Array(t).fill("").map((()=>[]));let r=0,n=1;return syntaxTree(e.state).cursor().iterate((s=>{const i=s.type.name||s.name;if("Document"===i)return;const a=s.from,l=s.to;r<a&&e.state.sliceDoc(r,a).split(e.state.lineBreak).forEach((e=>{e&&(o[Math.min(n-1,t-1)].push({type:null,string:e,start:r,end:r+e.length}),n++,r+=e.length)}));const c=e.state.sliceDoc(s.from,s.to);n=e.state.sliceDoc(0,s.from).split(e.state.lineBreak).length,o[n-1].push({type:i,string:c,start:a,end:l}),r=l})),r<e.state.doc.length&&o[n-1].push({type:null,string:e.state.sliceDoc(r),start:r,end:e.state.doc.length}),o}function tokenIsSubStringOfKeywords(t,e){const o=t.length;for(let r=0;r<e.length;++r)if(t===e[r].substr(o))return!0;return!1}function getCompletions(t,e){const o=new Set;for(let n=0,s=e.length;n<s;++n)0!==(r=e[n]).lastIndexOf(t,0)||o.has(r)||o.add(r);var r;const n=Array.from(o);return n.sort(),n}
\ No newline at end of file