Jelajahi Sumber

feat: add search block in the editor

Ahmad Kholid 3 tahun lalu
induk
melakukan
9ffb7de7a5

+ 1 - 1
.eslintrc.js

@@ -29,7 +29,7 @@ module.exports = {
   },
   // add your custom rules here
   rules: {
-    'no-undef': 'off',
+    camelcase: 'off',
     'no-await-in-loop': 'off',
     'no-console': ['warn', { allow: ['warn', 'error'] }],
     'no-underscore-dangle': 'off',

+ 1 - 1
package.json

@@ -88,7 +88,7 @@
     "eslint-import-resolver-webpack": "^0.13.2",
     "eslint-plugin-import": "^2.24.2",
     "eslint-plugin-prettier": "^4.0.0",
-    "eslint-plugin-vue": "7.18.0",
+    "eslint-plugin-vue": "^9.1.0",
     "file-loader": "^6.2.0",
     "fs-extra": "10.0.0",
     "html-loader": "2.1.2",

+ 1 - 1
src/background/index.js

@@ -315,7 +315,7 @@ browser.alarms.onAlarm.addListener(async ({ name }) => {
   }
 });
 
-if (browser.contextMenus?.onClicked) {
+if (browser.contextMenus && browser.contextMenus.onClicked) {
   browser.contextMenus.onClicked.addListener(
     async ({ parentMenuItemId, menuItemId }, tab) => {
       try {

+ 11 - 24
src/components/newtab/workflow/WorkflowBuilder.vue

@@ -34,6 +34,7 @@
             <v-remixicon name="riAddLine" />
           </button>
         </div>
+        <workflow-builder-search-blocks :editor="editor" />
       </div>
       <slot v-bind="{ editor }"></slot>
     </div>
@@ -91,8 +92,10 @@ import { tasks, excludeOnError } from '@/utils/shared';
 import { parseJSON } from '@/utils/helper';
 import { useGroupTooltip } from '@/composable/groupTooltip';
 import drawflow from '@/lib/drawflow';
+import WorkflowBuilderSearchBlocks from './WorkflowBuilderSearchBlocks.vue';
 
 export default {
+  components: { WorkflowBuilderSearchBlocks },
   props: {
     data: {
       type: [Object, String],
@@ -502,14 +505,6 @@ export default {
       editor.value.editor_mode = props.isShared ? 'fixed' : 'edit';
       editor.value.container.classList.toggle('is-shared', props.isShared);
     }
-    function refreshConnection() {
-      const nodes = document.querySelectorAll('#drawflow .drawflow-node');
-      nodes.forEach((node) => {
-        if (!node.id) return;
-
-        editor.value.updateConnectionNodes(node.id);
-      });
-    }
     function saveEditorState() {
       const editorStates =
         parseJSON(localStorage.getItem('editor-states'), {}) || {};
@@ -715,17 +710,6 @@ export default {
           ...store.state.settings.editor,
         },
       });
-
-      const editorStates =
-        parseJSON(localStorage.getItem('editor-states'), {}) || {};
-      const editorState = editorStates[workflowId];
-
-      if (editorState) {
-        editor.value.zoom = editorState.zoom;
-        editor.value.canvas_x = editorState.canvas_x;
-        editor.value.canvas_y = editorState.canvas_y;
-      }
-
       editor.value.start();
 
       emit('load', editor.value);
@@ -882,13 +866,16 @@ export default {
         }
       });
 
+      const editorStates =
+        parseJSON(localStorage.getItem('editor-states'), {}) || {};
+      const editorState = editorStates[workflowId];
+      if (editorState) {
+        const { canvas_x, canvas_y, zoom } = editorState;
+        editor.value.translate_to(canvas_x, canvas_y, zoom);
+      }
+
       checkWorkflowData();
       initSelectArea();
-
-      setTimeout(() => {
-        editor.value.zoom_refresh();
-        refreshConnection();
-      }, 500);
     });
     onBeforeUnmount(() => {
       const element = document.querySelector('#drawflow');

+ 159 - 0
src/components/newtab/workflow/WorkflowBuilderSearchBlocks.vue

@@ -0,0 +1,159 @@
+<template>
+  <div
+    class="bg-white dark:bg-gray-800 ml-2 inline-flex items-center rounded-lg"
+  >
+    <button
+      v-tooltip="
+        `${t('workflow.searchBlocks.title')} (${
+          shortcut['editor:search-blocks'].readable
+        })`
+      "
+      class="hoverable p-2 rounded-lg rounded-lg"
+      icon
+      @click="toggleActiveSearch"
+    >
+      <v-remixicon name="riSearch2Line" />
+    </button>
+    <ui-autocomplete
+      :model-value="state.query"
+      :items="state.autocompleteItems"
+      :custom-filter="searchNodes"
+      item-key="id"
+      item-label="name"
+      @select="onSelectItem"
+      @selected="onItemSelected"
+    >
+      <input
+        id="search-blocks"
+        v-model="state.query"
+        :placeholder="t('common.search')"
+        :style="{ width: state.active ? '250px' : '0px' }"
+        type="search"
+        class="py-2 focus:ring-0 rounded-lg bg-transparent"
+        @focus="extractBlocks"
+        @blur="clearState"
+      />
+      <template #item="{ item }">
+        <div class="flex-1 overflow-hidden">
+          <p class="text-overflow">
+            {{ item.name }}
+          </p>
+          <p
+            class="text-sm text-overflow leading-none text-gray-600 dark:text-gray-300"
+          >
+            {{ item.description }}
+          </p>
+        </div>
+      </template>
+    </ui-autocomplete>
+  </div>
+</template>
+<script setup>
+import { reactive } from 'vue';
+import { useI18n } from 'vue-i18n';
+import { useShortcut } from '@/composable/shortcut';
+
+const props = defineProps({
+  editor: {
+    type: Object,
+    default: () => ({}),
+  },
+});
+
+const { t } = useI18n();
+
+const initialState = {
+  zoom: 1,
+  rectX: 0,
+  rectY: 0,
+  canvasX: 0,
+  canvasY: 0,
+};
+const state = reactive({
+  query: '',
+  active: false,
+  selected: false,
+  autocompleteItems: [],
+});
+
+const shortcut = useShortcut('editor:search-blocks', () => {
+  state.active = true;
+  document.querySelector('#search-blocks')?.focus();
+});
+
+function searchNodes({ item, text }) {
+  const query = text.toLocaleLowerCase();
+
+  return (
+    item.name.toLocaleLowerCase().includes(query) ||
+    item.description.toLocaleLowerCase().includes(query)
+  );
+}
+function toggleActiveSearch() {
+  state.active = !state.active;
+
+  if (state.active) {
+    document.querySelector('#search-blocks')?.focus();
+  }
+}
+function extractBlocks() {
+  const { width, height } = props.editor.container.getBoundingClientRect();
+  initialState.rectX = width / 2;
+  initialState.rectY = height / 2;
+  initialState.zoom = props.editor.zoom;
+  initialState.canvasX = props.editor.canvas_x;
+  initialState.canvasY = props.editor.canvas_y;
+
+  const { drawflow } = props.editor.export();
+  state.autocompleteItems = Object.values(drawflow.Home.data).map(
+    ({ id, name, data, pos_x, pos_y }) => ({
+      id,
+      pos_x,
+      pos_y,
+      description: data.description || '',
+      name: t(`workflow.blocks.${name}.name`),
+    })
+  );
+}
+function clearState() {
+  if (!state.selected) {
+    const { canvasX, canvasY, zoom } = initialState;
+    props.editor.translate_to(canvasX, canvasY, zoom);
+  }
+
+  state.query = '';
+  state.active = false;
+  state.selected = false;
+
+  Object.assign(initialState, {
+    zoom: 1,
+    rectX: 0,
+    rectY: 0,
+    canvasX: 0,
+    canvasY: 0,
+  });
+}
+function onSelectItem({ item }) {
+  if (props.editor.zoom !== 1) {
+    /* eslint-disable-next-line */
+    props.editor.zoom = 1;
+    props.editor.zoom_refresh();
+  }
+
+  const { rectX, rectY } = initialState;
+  props.editor.translate_to(
+    -(item.pos_x - rectX),
+    -(item.pos_y - rectY),
+    props.editor.zoom
+  );
+}
+function onItemSelected(event) {
+  state.selected = true;
+  onSelectItem(event);
+}
+</script>
+<style scoped>
+input {
+  transition: width 250ms ease;
+}
+</style>

+ 31 - 6
src/components/ui/UiAutocomplete.vue

@@ -18,10 +18,10 @@
       <ui-list-item
         v-for="(item, index) in filteredItems"
         :id="`list-item-${index}`"
-        :key="getItem(item)"
+        :key="getItem(item, true)"
         :class="{ 'bg-box-transparent': state.activeIndex === index }"
         class="cursor-pointer"
-        @mousedown="selectItem(index)"
+        @mousedown="selectItem(index, true)"
         @mouseenter="state.activeIndex = index"
       >
         <slot name="item" :item="item">
@@ -56,6 +56,10 @@ const props = defineProps({
     type: String,
     default: '',
   },
+  itemLabel: {
+    type: String,
+    default: '',
+  },
   triggerChar: {
     type: Array,
     default: () => [],
@@ -72,7 +76,14 @@ const props = defineProps({
   disabled: Boolean,
   hideEmpty: Boolean,
 });
-const emit = defineEmits(['update:modelValue', 'change', 'search']);
+const emit = defineEmits([
+  'update:modelValue',
+  'change',
+  'search',
+  'select',
+  'cancel',
+  'selected',
+]);
 
 let input = null;
 const { t } = useI18n();
@@ -86,7 +97,8 @@ const state = shallowReactive({
   inputChanged: false,
 });
 
-const getItem = (item) => item[props.itemLabel] || item;
+const getItem = (item, key) =>
+  item[key ? props.itemKey : props.itemLabel] || item;
 
 const filteredItems = computed(() => {
   if (!state.showPopover) return [];
@@ -185,7 +197,7 @@ function updateValue(value) {
   input.value = value;
   input.dispatchEvent(new Event('input'));
 }
-function selectItem(itemIndex) {
+function selectItem(itemIndex, selected) {
   let selectedItem = filteredItems.value[itemIndex];
 
   if (!selectedItem) return;
@@ -226,6 +238,13 @@ function selectItem(itemIndex) {
 
   updateValue(selectedItem);
 
+  if (selected) {
+    emit('selected', {
+      index: itemIndex,
+      item: filteredItems.value[itemIndex],
+    });
+  }
+
   if (isTriggerChar) {
     input.selectionEnd = caretPosition;
     const isNotTextarea = input.tagName !== 'TEXTAREA';
@@ -250,10 +269,11 @@ function handleKeydown(event) {
 
     event.preventDefault();
   } else if (event.key === 'Enter' && state.showPopover) {
-    selectItem(state.activeIndex);
+    selectItem(state.activeIndex, true);
 
     event.preventDefault();
   } else if (event.key === 'Escape') {
+    emit('cancel');
     state.showPopover = false;
   }
 }
@@ -299,6 +319,11 @@ watch(
         behavior: 'smooth',
       });
     }
+
+    emit('select', {
+      index: activeIndex,
+      item: filteredItems.value[activeIndex],
+    });
   }, 100)
 );
 watch(

+ 4 - 0
src/composable/shortcut.js

@@ -36,6 +36,10 @@ const defaultShortcut = {
     id: 'editor:duplicate-block',
     combo: 'mod+option+d',
   },
+  'editor:search-blocks': {
+    id: 'editor:search-blocks',
+    combo: 'mod+b',
+  },
   'editor:save': {
     id: 'editor:save',
     combo: 'mod+shift+s',

+ 8 - 5
src/lib/drawflow.js

@@ -8,15 +8,16 @@ export default function (element, { context, options = {} }) {
   const editor = new Drawflow(element, { render, version: 3, h }, context);
 
   editor.useuuid = true;
-  editor.translate_to = function (x, y) {
+  editor.translate_to = function (x, y, zoom) {
+    if (typeof x !== 'number' || typeof y !== 'number') return;
+
     this.canvas_x = x;
     this.canvas_y = y;
 
-    const storedZoom = this.zoom;
-
     this.zoom = 1;
-    this.precanvas.style.transform = `translate("${this.canvas_x}"px, "${this.canvas_y}"px) scale("${this.zoom}")`;
-    this.zoom = storedZoom;
+
+    this.precanvas.style.transform = `translate(${this.canvas_x}px, ${this.canvas_y}px) scale(${this.zoom})`;
+    this.zoom = zoom;
     this.zoom_last_value = 1;
     this.zoom_refresh();
   };
@@ -111,5 +112,7 @@ export default function (element, { context, options = {} }) {
     editor.registerNode(name, blockComponents(key).default, { editor }, {});
   });
 
+  console.log(editor);
+
   return editor;
 }

+ 3 - 0
src/locales/en/newtab.json

@@ -106,6 +106,9 @@
     "clickToEnable": "Click to enable",
     "toggleSidebar": "Toggle sidebar",
     "cantEdit": "Can't edit shared workflow",
+    "searchBlocks": {
+      "title": "Search blocks in the editor"
+    },
     "conditionBuilder": {
       "title": "Condition builder",
       "add": "Add condition",

+ 68 - 19
yarn.lock

@@ -1983,7 +1983,7 @@ acorn-import-assertions@^1.7.6:
   resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz#ba2b5939ce62c238db6d93d81c9b111b29b855e9"
   integrity sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==
 
-acorn-jsx@^5.2.0, acorn-jsx@^5.3.1:
+acorn-jsx@^5.2.0, acorn-jsx@^5.3.1, acorn-jsx@^5.3.2:
   version "5.3.2"
   resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
   integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
@@ -2012,6 +2012,11 @@ acorn@^8.4.1, acorn@^8.5.0:
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf"
   integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==
 
+acorn@^8.7.1:
+  version "8.7.1"
+  resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30"
+  integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==
+
 aggregate-error@^3.0.0:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a"
@@ -3012,7 +3017,7 @@ debug@^3.1.1, debug@^3.2.7:
   dependencies:
     ms "^2.1.1"
 
-debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2:
+debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4:
   version "4.3.4"
   resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
   integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
@@ -3461,15 +3466,18 @@ eslint-plugin-prettier@^4.0.0:
   dependencies:
     prettier-linter-helpers "^1.0.0"
 
-eslint-plugin-vue@7.18.0:
-  version "7.18.0"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-7.18.0.tgz#02a452142330c7f27c242db21a1b9e25238540f6"
-  integrity sha512-ceDXlXYMMPMSXw7tdKUR42w9jlzthJGJ3Kvm3YrZ0zuQfvAySNxe8sm6VHuksBW0+060GzYXhHJG6IHVOfF83Q==
+eslint-plugin-vue@^9.1.0:
+  version "9.1.0"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-9.1.0.tgz#b528941325e26a24bc5d5c5030c0a8996c36659c"
+  integrity sha512-EPCeInPicQ/YyfOWJDr1yfEeSNoFCMzUus107lZyYi37xejdOolNzS5MXGXp8+9bkoKZMdv/1AcZzQebME6r+g==
   dependencies:
-    eslint-utils "^2.1.0"
+    eslint-utils "^3.0.0"
     natural-compare "^1.4.0"
-    semver "^6.3.0"
-    vue-eslint-parser "^7.10.0"
+    nth-check "^2.0.1"
+    postcss-selector-parser "^6.0.9"
+    semver "^7.3.5"
+    vue-eslint-parser "^9.0.1"
+    xml-name-validator "^4.0.0"
 
 eslint-scope@5.1.1, eslint-scope@^5.1.1:
   version "5.1.1"
@@ -3479,6 +3487,14 @@ eslint-scope@5.1.1, eslint-scope@^5.1.1:
     esrecurse "^4.3.0"
     estraverse "^4.1.1"
 
+eslint-scope@^7.1.1:
+  version "7.1.1"
+  resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.1.tgz#fff34894c2f65e5226d3041ac480b4513a163642"
+  integrity sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==
+  dependencies:
+    esrecurse "^4.3.0"
+    estraverse "^5.2.0"
+
 eslint-utils@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.1.0.tgz#d2de5e03424e707dc10c74068ddedae708741b27"
@@ -3486,6 +3502,13 @@ eslint-utils@^2.1.0:
   dependencies:
     eslint-visitor-keys "^1.1.0"
 
+eslint-utils@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672"
+  integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==
+  dependencies:
+    eslint-visitor-keys "^2.0.0"
+
 eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e"
@@ -3496,6 +3519,11 @@ eslint-visitor-keys@^2.0.0, eslint-visitor-keys@^2.1.0:
   resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303"
   integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==
 
+eslint-visitor-keys@^3.3.0:
+  version "3.3.0"
+  resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826"
+  integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==
+
 eslint@7.32.0:
   version "7.32.0"
   resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d"
@@ -3542,7 +3570,7 @@ eslint@7.32.0:
     text-table "^0.2.0"
     v8-compile-cache "^2.0.3"
 
-espree@^6.0.0, espree@^6.2.1:
+espree@^6.0.0:
   version "6.2.1"
   resolved "https://registry.yarnpkg.com/espree/-/espree-6.2.1.tgz#77fc72e1fd744a2052c20f38a5b575832e82734a"
   integrity sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==
@@ -3560,6 +3588,15 @@ espree@^7.3.0, espree@^7.3.1:
     acorn-jsx "^5.3.1"
     eslint-visitor-keys "^1.3.0"
 
+espree@^9.3.1:
+  version "9.3.2"
+  resolved "https://registry.yarnpkg.com/espree/-/espree-9.3.2.tgz#f58f77bd334731182801ced3380a8cc859091596"
+  integrity sha512-D211tC7ZwouTIuY5x9XnS0E9sWNChB7IYKX/Xp5eQj3nFXhqmiUDB9q27y76oFl8jTg3pXcQx/bpxMfs3CIZbA==
+  dependencies:
+    acorn "^8.7.1"
+    acorn-jsx "^5.3.2"
+    eslint-visitor-keys "^3.3.0"
+
 esprima@^4.0.0:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
@@ -6393,6 +6430,13 @@ semver@^7.2.1, semver@^7.3.5:
   dependencies:
     lru-cache "^6.0.0"
 
+semver@^7.3.6:
+  version "7.3.7"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f"
+  integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==
+  dependencies:
+    lru-cache "^6.0.0"
+
 send@0.17.2:
   version "0.17.2"
   resolved "https://registry.yarnpkg.com/send/-/send-0.17.2.tgz#926622f76601c41808012c8bf1688fe3906f7820"
@@ -7198,18 +7242,18 @@ vary@~1.1.2:
   resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
   integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
 
-vue-eslint-parser@^7.10.0:
-  version "7.11.0"
-  resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-7.11.0.tgz#214b5dea961007fcffb2ee65b8912307628d0daf"
-  integrity sha512-qh3VhDLeh773wjgNTl7ss0VejY9bMMa0GoDG2fQVyDzRFdiU3L7fw74tWZDHNQXdZqxO3EveQroa9ct39D2nqg==
+vue-eslint-parser@^9.0.1:
+  version "9.0.2"
+  resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-9.0.2.tgz#d2535516f3f55adb387939427fe741065eb7948a"
+  integrity sha512-uCPQwTGjOtAYrwnU+76pYxalhjsh7iFBsHwBqDHiOPTxtICDaraO4Szw54WFTNZTAEsgHHzqFOu1mmnBOBRzDA==
   dependencies:
-    debug "^4.1.1"
-    eslint-scope "^5.1.1"
-    eslint-visitor-keys "^1.1.0"
-    espree "^6.2.1"
+    debug "^4.3.4"
+    eslint-scope "^7.1.1"
+    eslint-visitor-keys "^3.3.0"
+    espree "^9.3.1"
     esquery "^1.4.0"
     lodash "^4.17.21"
-    semver "^6.3.0"
+    semver "^7.3.6"
 
 vue-i18n@^9.2.0-beta.29:
   version "9.2.0-beta.33"
@@ -7505,6 +7549,11 @@ ws@^6.2.1:
   dependencies:
     async-limiter "~1.0.0"
 
+xml-name-validator@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-4.0.0.tgz#79a006e2e63149a8600f15430f0a4725d1524835"
+  integrity sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==
+
 xtend@^4.0.1, xtend@^4.0.2:
   version "4.0.2"
   resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"