Browse Source

Merge branch 'dev' into mv3

Ahmad Kholid 2 years ago
parent
commit
6deaa3cbf5

+ 10 - 10
package.json

@@ -1,6 +1,6 @@
 {
   "name": "automa",
-  "version": "1.21.3",
+  "version": "1.21.6",
   "description": "An extension for automating your browser by connecting blocks",
   "repository": {
     "type": "git",
@@ -30,7 +30,7 @@
   "dependencies": {
     "@codemirror/autocomplete": "^6.1.0",
     "@codemirror/lang-css": "^6.0.0",
-    "@codemirror/lang-html": "^6.1.0",
+    "@codemirror/lang-html": "^6.1.2",
     "@codemirror/lang-javascript": "^6.0.2",
     "@codemirror/lang-json": "^6.0.0",
     "@codemirror/language": "^6.2.1",
@@ -50,7 +50,7 @@
     "@vueuse/rxjs": "^9.1.1",
     "@vuex-orm/core": "^0.36.4",
     "codemirror": "^6.0.1",
-    "compare-versions": "^4.1.2",
+    "compare-versions": "^5.0.1",
     "cron-parser": "^4.6.0",
     "cronstrue": "^2.11.0",
     "crypto-js": "^4.1.1",
@@ -71,7 +71,7 @@
     "object-path": "^0.11.8",
     "papaparse": "^5.3.1",
     "pinia": "^2.0.22",
-    "rxjs": "^7.5.5",
+    "rxjs": "^7.5.7",
     "tippy.js": "^6.3.1",
     "v-remixicon": "^0.1.1",
     "vue": "^3.2.37",
@@ -89,16 +89,16 @@
     "@babel/preset-env": "^7.18.2",
     "@intlify/vue-i18n-loader": "^4.2.0",
     "@tailwindcss/typography": "^0.5.1",
-    "@vue/compiler-sfc": "^3.2.39",
+    "@vue/compiler-sfc": "^3.2.41",
     "archiver": "^5.3.1",
-    "autoprefixer": "^10.4.7",
+    "autoprefixer": "^10.4.12",
     "babel-loader": "^8.2.2",
     "clean-webpack-plugin": "4.0.0",
     "copy-webpack-plugin": "^11.0.0",
     "core-js": "^3.25.0",
     "cross-env": "^7.0.3",
     "css-loader": "^6.7.1",
-    "eslint": "^8.23.0",
+    "eslint": "^8.25.0",
     "eslint-config-airbnb-base": "^15.0.0",
     "eslint-config-prettier": "^8.3.0",
     "eslint-friendly-formatter": "^4.0.1",
@@ -108,11 +108,11 @@
     "eslint-plugin-vue": "^9.4.0",
     "file-loader": "^6.2.0",
     "fs-extra": "^10.1.0",
-    "html-loader": "^4.1.0",
+    "html-loader": "^4.2.0",
     "html-webpack-plugin": "^5.5.0",
     "lint-staged": "^13.0.2",
     "mini-css-extract-plugin": "^2.3.0",
-    "postcss": "^8.4.16",
+    "postcss": "^8.4.18",
     "postcss-loader": "^7.0.0",
     "prettier": "^2.7.1",
     "simple-git-hooks": "^2.6.1",
@@ -123,6 +123,6 @@
     "web-worker": "^1.2.0",
     "webpack": "^5.73.0",
     "webpack-cli": "^4.10.0",
-    "webpack-dev-server": "^4.11.0"
+    "webpack-dev-server": "^4.11.1"
   }
 }

+ 16 - 2
src/components/newtab/workflow/edit/EditClipboard.vue

@@ -6,7 +6,7 @@
       :placeholder="t('common.description')"
       @change="updateData({ description: $event })"
     />
-    <template v-if="permission.has.clipboardRead">
+    <template v-if="hasAllPermissions">
       <ui-select
         :model-value="data.type"
         class="mt-4 w-full"
@@ -50,6 +50,7 @@
   </div>
 </template>
 <script setup>
+import { computed } from 'vue';
 import { useI18n } from 'vue-i18n';
 import { useHasPermissions } from '@/composable/hasPermissions';
 import InsertWorkflowData from './InsertWorkflowData.vue';
@@ -63,9 +64,22 @@ const props = defineProps({
 const emit = defineEmits(['update:data']);
 
 const types = ['get', 'insert'];
+const permissions = ['clipboardRead'];
+const isFirefox = BROWSER_TYPE === 'firefox';
+
+if (isFirefox) {
+  permissions.push('clipboardWrite');
+}
 
 const { t } = useI18n();
-const permission = useHasPermissions(['clipboardRead']);
+const permission = useHasPermissions(permissions);
+
+const hasAllPermissions = computed(() => {
+  if (isFirefox)
+    return permission.has.clipboardRead && permission.has.clipboardWrite;
+
+  return permission.has.clipboardRead;
+});
 
 function updateData(value) {
   emit('update:data', { ...props.data, ...value });

+ 1 - 1
src/components/newtab/workflow/edit/EditElementExists.vue

@@ -20,8 +20,8 @@
       <ui-input
         :model-value="data.selector"
         :label="t('workflow.blocks.element-exists.selector')"
+        :placeholder="data.findBy === 'xpath' ? '//element' : '.element'"
         autocomplete="off"
-        placeholder=".element"
         class="w-full"
         @change="updateData({ selector: $event })"
       />

+ 1 - 1
src/components/newtab/workflow/edit/EditInsertData.vue

@@ -11,7 +11,7 @@
       variant="accent"
       @click="showModal = !showModal"
     >
-      Insert data
+      Insert data ({{ dataList.length }})
     </ui-button>
     <ui-modal
       v-model="showModal"

+ 7 - 0
src/components/newtab/workflow/edit/EditLoopElements.vue

@@ -21,6 +21,13 @@
       class="w-full mt-3"
       @change="updateData({ maxLoop: $event })"
     />
+    <ui-checkbox
+      :model-value="data.reverseLoop"
+      class="mt-2"
+      @change="updateData({ reverseLoop: $event })"
+    >
+      {{ t('workflow.blocks.loop-data.reverse') }}
+    </ui-checkbox>
     <div class="mt-4 border-t pt-4 mb-8">
       <p class="text-sm text-gray-600 dark:text-gray-200">
         {{ t('workflow.blocks.loop-elements.loadMore') }}

+ 20 - 7
src/components/newtab/workflow/edit/EditWorkflowParameters.vue

@@ -92,13 +92,22 @@
                   <span>Options</span>
                 </template>
                 <div class="pl-[28px] mt-2 mb-4">
-                  <ui-textarea
-                    v-model="param.description"
-                    placeholder="Description"
-                    title="Description"
-                    class="mb-2 block"
-                    style="max-width: 400px"
-                  />
+                  <div class="flex mb-2 items-start">
+                    <ui-textarea
+                      v-model="param.description"
+                      placeholder="Description"
+                      title="Description"
+                      style="max-width: 400px"
+                    />
+                    <ui-checkbox
+                      v-if="['string', 'number'].includes(param.type)"
+                      :model-value="param.data?.required"
+                      class="ml-6"
+                      @change="param.data.required = $event"
+                    >
+                      Parameter required
+                    </ui-checkbox>
+                  </div>
                   <component
                     :is="paramTypes[param.type].options"
                     v-if="paramTypes[param.type].options"
@@ -144,6 +153,7 @@ const paramTypes = {
     valueComp: ParameterInputValue,
     data: {
       masks: [],
+      required: false,
       useMask: false,
       unmaskValue: false,
     },
@@ -151,6 +161,9 @@ const paramTypes = {
   number: {
     id: 'number',
     name: 'Input (number)',
+    data: {
+      required: false,
+    },
   },
   ...customParameters,
 };

+ 19 - 0
src/components/newtab/workflows/WorkflowsFolder.vue

@@ -50,6 +50,16 @@
             />
           </template>
           <ui-list class="w-36 space-y-1">
+            <ui-list-item
+              v-close-popover
+              class="cursor-pointer"
+              @click="exportFolderWorkflows(folder.id)"
+            >
+              <v-remixicon name="riDownloadLine" class="mr-2 -ml-1" />
+              <span>
+                {{ t('common.export') }}
+              </span>
+            </ui-list-item>
             <ui-list-item
               v-close-popover
               class="cursor-pointer"
@@ -83,6 +93,7 @@ import { useDialog } from '@/composable/dialog';
 import { parseJSON } from '@/utils/helper';
 import { useFolderStore } from '@/stores/folder';
 import { useWorkflowStore } from '@/stores/workflow';
+import { exportWorkflow } from '@/utils/workflowData';
 
 defineProps({
   modelValue: {
@@ -99,6 +110,14 @@ const workflowStore = useWorkflowStore();
 
 const folders = computed(() => folderStore.items);
 
+function exportFolderWorkflows(folderId) {
+  const workflows = workflowStore.getWorkflows.filter(
+    (item) => item.folderId === folderId
+  );
+  workflows.forEach((workflow) => {
+    exportWorkflow(workflow);
+  });
+}
 function onDragover(event, toggle) {
   const parent = event.target.closest('.ui-list-item');
   if (!parent) return;

+ 5 - 2
src/components/ui/UiModal.vue

@@ -7,10 +7,13 @@
       <transition name="modal" mode="out-in">
         <div
           v-if="show"
-          class="bg-black overflow-y-auto bg-opacity-20 dark:bg-opacity-60 modal-ui__content-container z-50 flex justify-center items-end md:items-center"
+          class="overflow-y-auto modal-ui__content-container z-50 flex justify-center items-center"
           :style="{ 'backdrop-filter': blur && 'blur(2px)' }"
-          @click.self="closeModal"
         >
+          <div
+            class="absolute h-full w-full bg-black bg-opacity-20 dark:bg-opacity-60"
+            @click="closeModal"
+          />
           <slot v-if="customContent"></slot>
           <ui-card
             v-else

+ 20 - 2
src/content/commandPalette/App.vue

@@ -185,7 +185,6 @@ import {
 } from 'vue';
 import browser from 'webextension-polyfill';
 import workflowParameters from '@business/parameters';
-import { getReadableShortcut } from '@/composable/shortcut';
 import { sendMessage } from '@/utils/message';
 import { debounce } from '@/utils/helper';
 import ParameterInputValue from '@/components/newtab/workflow/edit/Parameter/ParameterInputValue.vue';
@@ -198,6 +197,7 @@ const paramsList = {
   },
 };
 
+const os = navigator.appVersion.indexOf('Mac') !== -1 ? 'mac' : 'win';
 const logoUrl = browser.runtime.getURL('/icon-128.png');
 
 const inputRef = ref(null);
@@ -225,6 +225,24 @@ const workflows = computed(() =>
   )
 );
 
+function getReadableShortcut(str) {
+  const list = {
+    option: {
+      win: 'alt',
+      mac: 'option',
+    },
+    mod: {
+      win: 'ctrl',
+      mac: '⌘',
+    },
+  };
+  const regex = /option|mod/g;
+  const replacedStr = str.replace(regex, (match) => {
+    return list[match][os];
+  });
+
+  return replacedStr;
+}
 function clearParamsState() {
   Object.assign(paramsState, {
     items: [],
@@ -312,7 +330,7 @@ function onKeydown(event) {
   }
 
   const shortcuts = window._automaShortcuts;
-  if (shortcuts.length < 1) return;
+  if (!shortcuts || shortcuts.length < 1) return;
 
   const automaShortcut = shortcuts.every((shortcutKey) => {
     if (shortcutKey === 'mod') return ctrlKey || metaKey;

+ 60 - 25
src/content/index.js

@@ -1,9 +1,11 @@
 import browser from 'webextension-polyfill';
 import { nanoid } from 'nanoid';
+import cloneDeep from 'lodash.clonedeep';
 import findSelector from '@/lib/findSelector';
-import { toCamelCase } from '@/utils/helper';
 import { sendMessage } from '@/utils/message';
 import automa from '@business';
+import FindElement from '@/utils/FindElement';
+import { toCamelCase, isXPath } from '@/utils/helper';
 import handleSelector from './handleSelector';
 import blocksHandler from './blocksHandler';
 import showExecutedBlock from './showExecutedBlock';
@@ -45,7 +47,13 @@ async function executeBlock(data) {
   const removeExecutedBlock = showExecutedBlock(data, data.executedBlockOnWeb);
   if (data.data?.selector?.includes('|>')) {
     const [frameSelector, selector] = data.data.selector.split(/\|>(.+)/);
-    const frameElement = document.querySelector(frameSelector);
+
+    let findBy = data?.data?.findBy;
+    if (!findBy) {
+      findBy = isXPath(frameSelector) ? 'xpath' : 'cssSelector';
+    }
+
+    const frameElement = FindElement[findBy]({ selector: frameSelector });
     const frameError = (message) => {
       const error = new Error(message);
       error.data = { selector: frameSelector };
@@ -137,8 +145,48 @@ function messageListener({ data, source }) {
   initCommandPalette();
 
   let contextElement = null;
+  let $ctxLink = '';
+  let $ctxMediaUrl = '';
   let $ctxTextSelection = '';
 
+  window.isAutomaInjected = true;
+  window.addEventListener('message', messageListener);
+  window.addEventListener(
+    'contextmenu',
+    ({ target }) => {
+      contextElement = target;
+      $ctxTextSelection = window.getSelection().toString();
+
+      const tag = target.tagName;
+      if (tag === 'A') {
+        $ctxLink = target.href;
+      } else {
+        const closestUrl = target.closest('a');
+        if (closestUrl) $ctxLink = closestUrl.href;
+      }
+
+      const getMediaSrc = (element) => {
+        let mediaSrc = element.src || '';
+
+        if (!mediaSrc.src) {
+          const sourceEl = element.querySelector('source');
+          if (sourceEl) mediaSrc = sourceEl.src;
+        }
+
+        return mediaSrc;
+      };
+
+      const mediaTags = ['AUDIO', 'VIDEO', 'IMG'];
+      if (mediaTags.includes(tag)) {
+        $ctxMediaUrl = getMediaSrc(target);
+      } else {
+        const closestMedia = target.closest('audio,video,img');
+        if (closestMedia) $ctxMediaUrl = getMediaSrc(closestMedia);
+      }
+    },
+    true
+  );
+
   window.isAutomaInjected = true;
   window.addEventListener('message', messageListener);
   window.addEventListener('contextmenu', ({ target }) => {
@@ -198,42 +246,29 @@ function messageListener({ data, source }) {
             break;
           }
           case 'context-element': {
-            let $ctxLink = '';
-            let $ctxMediaUrl = '';
             let $ctxElSelector = '';
 
             if (contextElement) {
               $ctxElSelector = findSelector(contextElement);
-
-              const tag = contextElement.tagName;
-              if (tag === 'A') {
-                $ctxLink = contextElement.href;
-              }
-
-              const mediaTags = ['AUDIO', 'VIDEO', 'IMG'];
-              if (mediaTags.includes(tag)) {
-                let mediaSrc = contextElement.src || '';
-
-                if (!mediaSrc.src) {
-                  const sourceEl = contextElement.querySelector('source');
-                  if (sourceEl) mediaSrc = sourceEl.src;
-                }
-
-                $ctxMediaUrl = mediaSrc;
-              }
-
               contextElement = null;
             }
             if (!$ctxTextSelection) {
               $ctxTextSelection = window.getSelection().toString();
             }
 
-            resolve({
-              $ctxElSelector,
-              $ctxTextSelection,
+            const cloneContextData = cloneDeep({
               $ctxLink,
               $ctxMediaUrl,
+              $ctxElSelector,
+              $ctxTextSelection,
             });
+
+            $ctxLink = '';
+            $ctxMediaUrl = '';
+            $ctxElSelector = '';
+            $ctxTextSelection = '';
+
+            resolve(cloneContextData);
             break;
           }
           default:

+ 13 - 16
src/content/services/shortcutListener.js

@@ -64,18 +64,21 @@ function workflowShortcutsListener(findWorkflow, shortcutsObj) {
   });
 }
 async function getWorkflows() {
-  const { workflows, workflowHosts } = await browser.storage.local.get([
+  const {
+    workflows: localWorkflows,
+    workflowHosts,
+    teamWorkflows,
+  } = await browser.storage.local.get([
     'workflows',
     'workflowHosts',
+    'teamWorkflows',
   ]);
-  const localWorkflows = Array.isArray(workflows)
-    ? workflows
-    : Object.values(workflows);
-
-  return {
-    local: localWorkflows,
-    hosted: Object.values(workflowHosts || {}),
-  };
+
+  return [
+    ...Object.values(workflowHosts || {}),
+    ...Object.values(localWorkflows || {}),
+    ...Object.values(Object.values(teamWorkflows || {})[0] || {}),
+  ];
 }
 
 export default async function () {
@@ -84,7 +87,7 @@ export default async function () {
     let workflows = await getWorkflows();
 
     const findWorkflow = (id, publicId = false) => {
-      let workflow = workflows.local.find((item) => {
+      const workflow = workflows.find((item) => {
         if (publicId) {
           return item.settings.publicId === id;
         }
@@ -92,12 +95,6 @@ export default async function () {
         return item.id === id;
       });
 
-      if (!workflow) {
-        workflow = workflows.hosted.find(({ hostId }) => hostId === id);
-
-        if (workflow) workflow.id = workflow.hostId;
-      }
-
       return workflow;
     };
 

+ 7 - 2
src/content/utils.js

@@ -13,11 +13,16 @@ export function simulateClickElement(element) {
 
 export function generateLoopSelectors(
   elements,
-  { max, attrId, frameSelector, startIndex = 0 }
+  { max, attrId, frameSelector, reverseLoop, startIndex = 0 }
 ) {
   const selectors = [];
+  let elementsList = elements;
 
-  elements.forEach((el, index) => {
+  if (reverseLoop) {
+    elementsList = Array.from(elements).reverse();
+  }
+
+  elementsList.forEach((el, index) => {
     if (max > 0 && selectors.length - 1 > max) return;
 
     const attrName = 'automa-loop';

+ 1 - 1
src/manifest.firefox.json

@@ -37,7 +37,7 @@
       "all_frames": false
     }
   ],
-  "optional_permissions": ["clipboardRead", "downloads", "notifications", "cookies"],
+  "optional_permissions": ["clipboardRead", "clipboardWrite", "downloads", "notifications", "cookies"],
   "permissions": [
     "tabs",
     "proxy",

+ 1 - 0
src/newtab/index.js

@@ -19,6 +19,7 @@ createApp(App)
   .use(router)
   .use(compsUi)
   .use(pinia)
+  .use(head)
   .use(vueI18n)
   .use(vueToastification)
   .use(vRemixicon, icons)

+ 8 - 6
src/newtab/pages/workflows/[id].vue

@@ -300,8 +300,8 @@ import cloneDeep from 'lodash.clonedeep';
 import { useI18n } from 'vue-i18n';
 import { useRoute, useRouter, onBeforeRouteLeave } from 'vue-router';
 import { customAlphabet } from 'nanoid';
-import { useHead } from '@vueuse/head';
 import { useToast } from 'vue-toastification';
+import { useHead } from '@vueuse/head';
 import defu from 'defu';
 import dagre from 'dagre';
 import { useUserStore } from '@/stores/user';
@@ -739,10 +739,10 @@ function onClickEditor({ target }) {
     if (!connectionExist) {
       editor.value.addEdges([
         {
-          source: nodeid,
-          sourceHandle: handleid,
-          target: nodeTargetHandle.nodeId,
-          targetHandle: nodeTargetHandle.handleId,
+          target: nodeid,
+          targetHandle: handleid,
+          source: nodeTargetHandle.nodeId,
+          sourceHandle: nodeTargetHandle.handleId,
         },
       ]);
     }
@@ -1200,6 +1200,8 @@ function onEditorInit(instance) {
   // });
 
   instance.onNodeDragStop(({ nodes }) => {
+    if (!editorCommands?.state?.nodes) return;
+
     nodes.forEach((node) => {
       editorCommands.state.nodes[node.id] = node;
     });
@@ -1558,7 +1560,7 @@ function checkWorkflowUpdate() {
 }
 
 useHead({
-  title: () => `${workflow.value?.name} workflow` || 'Edit workflow',
+  title: () => `${workflow.value?.name} workflow - Automa` || 'Automa',
 });
 const shortcut = useShortcut([
   getShortcut('editor:toggle-sidebar', toggleSidebar),

+ 12 - 5
src/newtab/workflowEngine/blocksHandler/handlerGoogleSheets.js

@@ -64,16 +64,23 @@ async function updateSpreadsheetValues(
     if (keysAsFirstRow) {
       values = convertArrObjTo2DArr(columns);
     } else {
-      values = columns.map((item) =>
-        Object.values(item).map((value) =>
-          typeof value === 'object' ? JSON.stringify(value) : value
-        )
-      );
+      values = columns.map((item) => Object.values(item));
     }
   } else if (dataFrom === 'custom') {
     values = parseJSON(customData, customData);
   }
 
+  if (Array.isArray(values)) {
+    const validTypes = ['boolean', 'string', 'number'];
+    values.forEach((row, rowIndex) => {
+      row.forEach((column, colIndex) => {
+        if (column && validTypes.includes(typeof column)) return;
+
+        values[rowIndex][colIndex] = ' ';
+      });
+    });
+  }
+
   const queries = {
     valueInputOption: valueInputOption || 'RAW',
   };

+ 2 - 1
src/newtab/workflowEngine/blocksHandler/handlerLoopData.js

@@ -49,6 +49,7 @@ async function loopData({ data, id }, { refData }) {
               findBy,
               max: maxLoop,
               multiple: true,
+              reverseLoop: data.reverseLoop,
               selector: data.elementSelector,
               waitForSelector: data.waitForSelector ?? false,
               waitSelectorTimeout: data.waitSelectorTimeout ?? 5000,
@@ -81,7 +82,7 @@ async function loopData({ data, id }, { refData }) {
           index = data.startIndex;
         }
 
-        if (data.reverseLoop) {
+        if (data.reverseLoop && data.loopThrough !== 'elements') {
           currLoopData.reverse();
         }
       }

+ 13 - 3
src/params/App.vue

@@ -53,7 +53,7 @@
                 :is="paramsList[param.type].valueComp"
                 v-if="paramsList[param.type]"
                 v-model="param.value"
-                :label="param.name"
+                :label="param.name + (param.data?.required ? '*' : '')"
                 :param-data="param"
                 class="w-full"
               />
@@ -61,10 +61,9 @@
                 v-else
                 v-model="param.value"
                 :type="param.inputType"
-                :label="param.name"
+                :label="param.name + (param.data?.required ? '*' : '')"
                 :placeholder="param.placeholder"
                 class="w-full"
-                @keyup.enter="runWorkflow(index, workflow)"
               />
               <p
                 v-if="param.description"
@@ -83,6 +82,7 @@
             </ui-button>
             <ui-button
               v-if="workflow.type === 'block'"
+              :disabled="!isValidParams(workflow.params)"
               variant="accent"
               @click="continueWorkflow(index, workflow)"
             >
@@ -90,6 +90,7 @@
             </ui-button>
             <ui-button
               v-else
+              :disabled="!isValidParams(workflow.params)"
               variant="accent"
               @click="runWorkflow(index, workflow)"
             >
@@ -245,6 +246,15 @@ function continueWorkflow(index, { data, params }) {
       deleteWorkflow(index);
     });
 }
+function isValidParams(params) {
+  const isValid = params.every((param) => {
+    if (!param.data?.required) return true;
+
+    return param.value;
+  });
+
+  return isValid;
+}
 
 browser.runtime.onMessage.addListener(({ name, data }) => {
   if (name === 'workflow:params') {

+ 5 - 4
src/utils/shared.js

@@ -743,11 +743,12 @@ export const tasks = {
     data: {
       disableBlock: false,
       loopId: '',
+      selector: '',
       maxLoop: '0',
       description: '',
-      selector: '',
-      findBy: 'cssSelector',
+      reverseLoop: false,
       actionElSelector: '',
+      findBy: 'cssSelector',
       actionElMaxWaitTime: 5,
       actionPageMaxWaitTime: 10,
       loadMoreAction: 'none',
@@ -1242,7 +1243,7 @@ export const tasks = {
     outputs: 1,
     allowedInputs: true,
     maxConnection: 1,
-    refDataKeys: ['html', 'css'],
+    refDataKeys: ['html', 'css', 'selector'],
     data: {
       disableBlock: false,
       description: '',
@@ -1659,7 +1660,7 @@ export const conditionBuilder = {
       placeholder: 'name',
     },
     dataPath: {
-      label: 'variables.variableName',
+      label: 'variables@variableName',
       placeholder: '',
     },
   },

+ 9 - 1
src/utils/testConditions.js

@@ -43,7 +43,15 @@ export default async function (conditionsArr, workflowData) {
 
   async function getConditionItemValue({ type, data }) {
     if (type.startsWith('data')) {
-      return objectPath.has(workflowData.refData, data.dataPath);
+      let dataPath = data.dataPath.trim().replace('@', '.');
+      const isInsideBrackets =
+        dataPath.startsWith('{{') && dataPath.endsWith('}}');
+
+      if (isInsideBrackets) {
+        dataPath = dataPath.slice(2, -2).trim();
+      }
+
+      return objectPath.has(workflowData.refData, dataPath);
     }
 
     const copyData = cloneDeep(data);

+ 15 - 3
src/utils/workflowData.js

@@ -1,6 +1,12 @@
 import browser from 'webextension-polyfill';
 import { useWorkflowStore } from '@/stores/workflow';
-import { parseJSON, fileSaver, openFilePicker } from './helper';
+import { registerWorkflowTrigger } from './workflowTrigger';
+import {
+  parseJSON,
+  fileSaver,
+  openFilePicker,
+  findTriggerBlock,
+} from './helper';
 
 const contextMenuPermission =
   BROWSER_TYPE === 'firefox' ? 'menus' : 'contextMenus';
@@ -109,15 +115,21 @@ export function importWorkflow(attrs = {}) {
           workflow.table = workflow.table || workflow.dataColumns;
           delete workflow.dataColumns;
 
-          if (typeof workflow.drawflow === 'string')
+          if (typeof workflow.drawflow === 'string') {
             workflow.drawflow = parseJSON(workflow.drawflow, {});
+          }
 
           workflowStore
             .insert({
               ...workflow,
               createdAt: Date.now(),
             })
-            .then(resolve);
+            .then((result) => {
+              const triggerBlock = findTriggerBlock(result.drawflow);
+              registerWorkflowTrigger(result.id, triggerBlock);
+
+              resolve(result);
+            });
         };
 
         files.forEach((file) => {

+ 31 - 9
src/utils/workflowTrigger.js

@@ -3,7 +3,7 @@ import dayjs from 'dayjs';
 import cronParser from 'cron-parser';
 import { isObject } from './helper';
 
-export function registerContextMenu(workflowId, data) {
+export function registerContextMenu(triggerId, data) {
   return new Promise((resolve, reject) => {
     const documentUrlPatterns = ['https://*/*', 'http://*/*'];
     const contextTypes =
@@ -19,6 +19,10 @@ export function registerContextMenu(workflowId, data) {
       return;
     }
 
+    const workflowId = triggerId.includes(':')
+      ? triggerId.split(':')[1]
+      : triggerId;
+
     browserContext.create(
       {
         id: workflowId,
@@ -29,7 +33,6 @@ export function registerContextMenu(workflowId, data) {
       },
       () => {
         const error = browser.runtime.lastError;
-
         if (error) {
           if (error.message.includes('automaContextMenu')) {
             browserContext.create(
@@ -45,6 +48,13 @@ export function registerContextMenu(workflowId, data) {
                   .catch(reject);
               }
             );
+            resolve();
+            return;
+          }
+          if (error.message.includes('Duplicate id')) {
+            browserContext.remove(triggerId).then(() => {
+              registerContextMenu(workflowId, data).then(resolve).catch(reject);
+            });
             return;
           }
 
@@ -71,7 +81,7 @@ async function removeFromWorkflowQueue(workflowId) {
   await browser.storage.local.set({ workflowQueue });
 }
 
-export async function cleanWorkflowTriggers(workflowId) {
+export async function cleanWorkflowTriggers(workflowId, triggers) {
   try {
     const alarms = await browser.alarms.getAll();
     for (const alarm of alarms) {
@@ -109,17 +119,29 @@ export async function cleanWorkflowTriggers(workflowId) {
       visitWebTriggers: filteredVisitWebTriggers,
     });
 
+    const browserContextMenu =
+      BROWSER_TYPE === 'firefox' ? browser.menus : browser.contextMenus;
     const removeFromContextMenu = async () => {
       try {
-        await (BROWSER_TYPE === 'firefox'
-          ? browser.menus
-          : browser.contextMenus
-        )?.remove(workflowId);
+        let promises = [];
+
+        if (triggers) {
+          promises = triggers.map(async (trigger) => {
+            if (trigger.type !== 'context-menu') return;
+
+            const triggerId = `trigger:${workflowId}:${trigger.id}`;
+            await browserContextMenu.remove(triggerId);
+          });
+        }
+
+        promises.push(browserContextMenu.remove(workflowId));
+
+        await Promise.allSettled(promises);
       } catch (error) {
         // Do nothing
       }
     };
-    await removeFromContextMenu();
+    if (browserContextMenu) await removeFromContextMenu();
   } catch (error) {
     console.error(error);
   }
@@ -259,7 +281,7 @@ export const workflowTriggersMap = {
 
 export async function registerWorkflowTrigger(workflowId, { data }) {
   try {
-    await cleanWorkflowTriggers(workflowId);
+    await cleanWorkflowTriggers(workflowId, data && data?.triggers);
 
     if (data.triggers) {
       for (const trigger of data.triggers) {

+ 97 - 46
yarn.lock

@@ -942,7 +942,7 @@
     "@codemirror/state" "^6.0.0"
     "@lezer/css" "^1.0.0"
 
-"@codemirror/lang-html@^6.1.0":
+"@codemirror/lang-html@^6.1.2":
   version "6.1.2"
   resolved "https://registry.yarnpkg.com/@codemirror/lang-html/-/lang-html-6.1.2.tgz#64979e583c6120c70d3123e6ce3f8595b20abc52"
   integrity sha512-e8JAUWyOo7N26tmek+WK0+Zg+pZRe+dQi8TZq0OOVVygpLV+mNAT2n5b5JhknY+TVZIVGLjuhdsoizw1SDFfPg==
@@ -1051,10 +1051,10 @@
     minimatch "^3.1.2"
     strip-json-comments "^3.1.1"
 
-"@humanwhocodes/config-array@^0.10.5":
-  version "0.10.7"
-  resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.10.7.tgz#6d53769fd0c222767e6452e8ebda825c22e9f0dc"
-  integrity sha512-MDl6D6sBsaV452/QSdX+4CXIjZhIcI0PELsxUjk4U828yd58vk3bTIvk/6w5FY+4hIy9sLW0sfrV7K7Kc++j/w==
+"@humanwhocodes/config-array@^0.11.6":
+  version "0.11.6"
+  resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.6.tgz#6a51d603a3aaf8d4cf45b42b3f2ac9318a4adc4b"
+  integrity sha512-jJr+hPTJYKyDILJfhNSHsjiwXYf26Flsz8DvNndOsHs5pwSnpGUEy8yzF0JYhCEvTDdV2vuOK5tt8BVhwO5/hg==
   dependencies:
     "@humanwhocodes/object-schema" "^1.2.1"
     debug "^4.1.1"
@@ -1267,7 +1267,7 @@
   resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b"
   integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
 
-"@nodelib/fs.walk@^1.2.3":
+"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8":
   version "1.2.8"
   resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a"
   integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==
@@ -1696,6 +1696,16 @@
     estree-walker "^2.0.2"
     source-map "^0.6.1"
 
+"@vue/compiler-core@3.2.41":
+  version "3.2.41"
+  resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.41.tgz#fb5b25f23817400f44377d878a0cdead808453ef"
+  integrity sha512-oA4mH6SA78DT+96/nsi4p9DX97PHcNROxs51lYk7gb9Z4BPKQ3Mh+BLn6CQZBw857Iuhu28BfMSRHAlPvD4vlw==
+  dependencies:
+    "@babel/parser" "^7.16.4"
+    "@vue/shared" "3.2.41"
+    estree-walker "^2.0.2"
+    source-map "^0.6.1"
+
 "@vue/compiler-dom@3.2.40":
   version "3.2.40"
   resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.40.tgz#c225418773774db536174d30d3f25ba42a33e7e4"
@@ -1704,7 +1714,15 @@
     "@vue/compiler-core" "3.2.40"
     "@vue/shared" "3.2.40"
 
-"@vue/compiler-sfc@3.2.40", "@vue/compiler-sfc@^3.2.39":
+"@vue/compiler-dom@3.2.41":
+  version "3.2.41"
+  resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.41.tgz#dc63dcd3ce8ca8a8721f14009d498a7a54380299"
+  integrity sha512-xe5TbbIsonjENxJsYRbDJvthzqxLNk+tb3d/c47zgREDa/PCp6/Y4gC/skM4H6PIuX5DAxm7fFJdbjjUH2QTMw==
+  dependencies:
+    "@vue/compiler-core" "3.2.41"
+    "@vue/shared" "3.2.41"
+
+"@vue/compiler-sfc@3.2.40":
   version "3.2.40"
   resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.40.tgz#61823283efc84d25d9d2989458f305d32a2ed141"
   integrity sha512-tzqwniIN1fu1PDHC3CpqY/dPCfN/RN1thpBC+g69kJcrl7mbGiHKNwbA6kJ3XKKy8R6JLKqcpVugqN4HkeBFFg==
@@ -1720,6 +1738,22 @@
     postcss "^8.1.10"
     source-map "^0.6.1"
 
+"@vue/compiler-sfc@^3.2.41":
+  version "3.2.41"
+  resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.41.tgz#238fb8c48318408c856748f4116aff8cc1dc2a73"
+  integrity sha512-+1P2m5kxOeaxVmJNXnBskAn3BenbTmbxBxWOtBq3mQTCokIreuMULFantBUclP0+KnzNCMOvcnKinqQZmiOF8w==
+  dependencies:
+    "@babel/parser" "^7.16.4"
+    "@vue/compiler-core" "3.2.41"
+    "@vue/compiler-dom" "3.2.41"
+    "@vue/compiler-ssr" "3.2.41"
+    "@vue/reactivity-transform" "3.2.41"
+    "@vue/shared" "3.2.41"
+    estree-walker "^2.0.2"
+    magic-string "^0.25.7"
+    postcss "^8.1.10"
+    source-map "^0.6.1"
+
 "@vue/compiler-ssr@3.2.40":
   version "3.2.40"
   resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.40.tgz#67df95a096c63e9ec4b50b84cc6f05816793629c"
@@ -1728,6 +1762,14 @@
     "@vue/compiler-dom" "3.2.40"
     "@vue/shared" "3.2.40"
 
+"@vue/compiler-ssr@3.2.41":
+  version "3.2.41"
+  resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.41.tgz#344f564d68584b33367731c04ffc949784611fcb"
+  integrity sha512-Y5wPiNIiaMz/sps8+DmhaKfDm1xgj6GrH99z4gq2LQenfVQcYXmHIOBcs5qPwl7jaW3SUQWjkAPKMfQemEQZwQ==
+  dependencies:
+    "@vue/compiler-dom" "3.2.41"
+    "@vue/shared" "3.2.41"
+
 "@vue/devtools-api@^6.0.0-beta.11", "@vue/devtools-api@^6.1.4", "@vue/devtools-api@^6.2.1", "@vue/devtools-api@^6.4.4":
   version "6.4.4"
   resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.4.4.tgz#0b024fc8ca91bb4b6035abaf53c5aecc17119b3b"
@@ -1744,6 +1786,17 @@
     estree-walker "^2.0.2"
     magic-string "^0.25.7"
 
+"@vue/reactivity-transform@3.2.41":
+  version "3.2.41"
+  resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.41.tgz#9ff938877600c97f646e09ac1959b5150fb11a0c"
+  integrity sha512-mK5+BNMsL4hHi+IR3Ft/ho6Za+L3FA5j8WvreJ7XzHrqkPq8jtF/SMo7tuc9gHjLDwKZX1nP1JQOKo9IEAn54A==
+  dependencies:
+    "@babel/parser" "^7.16.4"
+    "@vue/compiler-core" "3.2.41"
+    "@vue/shared" "3.2.41"
+    estree-walker "^2.0.2"
+    magic-string "^0.25.7"
+
 "@vue/reactivity@3.2.40":
   version "3.2.40"
   resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.40.tgz#ae65496f5b364e4e481c426f391568ed7d133cca"
@@ -1781,6 +1834,11 @@
   resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.40.tgz#e57799da2a930b975321981fcee3d1e90ed257ae"
   integrity sha512-0PLQ6RUtZM0vO3teRfzGi4ltLUO5aO+kLgwh4Um3THSR03rpQWLTuRCkuO5A41ITzwdWeKdPHtSARuPkoo5pCQ==
 
+"@vue/shared@3.2.41":
+  version "3.2.41"
+  resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.41.tgz#fbc95422df654ea64e8428eced96ba6ad555d2bb"
+  integrity sha512-W9mfWLHmJhkfAmV+7gDjcHeAWALQtgGT3JErxULl0oz6R6+3ug91I7IErs93eCFhPCZPHBs4QJS7YWEV7A3sxw==
+
 "@vueuse/core@^9.3.0":
   version "9.3.0"
   resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-9.3.0.tgz#74d855bd19cb5eadd2edb30c871918fac881e8b8"
@@ -2216,11 +2274,6 @@ array-union@^1.0.1:
   dependencies:
     array-uniq "^1.0.1"
 
-array-union@^2.1.0:
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d"
-  integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
-
 array-uniq@^1.0.1:
   version "1.0.3"
   resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6"
@@ -2251,7 +2304,7 @@ atob@^2.1.2:
   resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
   integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
 
-autoprefixer@^10.4.7:
+autoprefixer@^10.4.12:
   version "10.4.12"
   resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.12.tgz#183f30bf0b0722af54ee5ef257f7d4320bb33129"
   integrity sha512-WrCGV9/b97Pa+jtwf5UGaRjgQIg7OK3D06GnoYoZNcG1Xb8Gt3EfuKjlhh9i/VtT16g6PYjZ69jdJ2g8FxSC4Q==
@@ -2653,10 +2706,10 @@ commondir@^1.0.1:
   resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
   integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==
 
-compare-versions@^4.1.2:
-  version "4.1.4"
-  resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-4.1.4.tgz#3571f4d610924d4414846a4183d386c8f3d51112"
-  integrity sha512-FemMreK9xNyL8gQevsdRMrvO4lFCkQP7qbuktn1q8ndcNk1+0mz7lgE7b/sNvbhVgY4w6tMN1FDp6aADjqw2rw==
+compare-versions@^5.0.1:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-5.0.1.tgz#14c6008436d994c3787aba38d4087fabe858555e"
+  integrity sha512-v8Au3l0b+Nwkp4G142JcgJFh1/TUhdxut7wzD1Nq1dyp5oa3tXaqb03EXOAB6jS4gMlalkjAUPZBMiAfKUixHQ==
 
 compress-commons@^4.1.0:
   version "4.1.1"
@@ -3458,14 +3511,15 @@ eslint-visitor-keys@^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@^8.23.0:
-  version "8.25.0"
-  resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.25.0.tgz#00eb962f50962165d0c4ee3327708315eaa8058b"
-  integrity sha512-DVlJOZ4Pn50zcKW5bYH7GQK/9MsoQG2d5eDH0ebEkE8PbgzTTmtt/VTH9GGJ4BfeZCpBLqFfvsjX35UacUL83A==
+eslint@^8.25.0:
+  version "8.26.0"
+  resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.26.0.tgz#2bcc8836e6c424c4ac26a5674a70d44d84f2181d"
+  integrity sha512-kzJkpaw1Bfwheq4VXUezFriD1GxszX6dUekM7Z3aC2o4hju+tsR/XyTC3RcoSD7jmy9VkPU3+N6YjVU2e96Oyg==
   dependencies:
     "@eslint/eslintrc" "^1.3.3"
-    "@humanwhocodes/config-array" "^0.10.5"
+    "@humanwhocodes/config-array" "^0.11.6"
     "@humanwhocodes/module-importer" "^1.0.1"
+    "@nodelib/fs.walk" "^1.2.8"
     ajv "^6.10.0"
     chalk "^4.0.0"
     cross-spawn "^7.0.2"
@@ -3481,14 +3535,14 @@ eslint@^8.23.0:
     fast-deep-equal "^3.1.3"
     file-entry-cache "^6.0.1"
     find-up "^5.0.0"
-    glob-parent "^6.0.1"
+    glob-parent "^6.0.2"
     globals "^13.15.0"
-    globby "^11.1.0"
     grapheme-splitter "^1.0.4"
     ignore "^5.2.0"
     import-fresh "^3.0.0"
     imurmurhash "^0.1.4"
     is-glob "^4.0.0"
+    is-path-inside "^3.0.3"
     js-sdsl "^4.1.4"
     js-yaml "^4.1.0"
     json-stable-stringify-without-jsonify "^1.0.1"
@@ -3661,7 +3715,7 @@ fast-diff@^1.1.2:
   resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03"
   integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==
 
-fast-glob@^3.2.11, fast-glob@^3.2.9:
+fast-glob@^3.2.11:
   version "3.2.12"
   resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80"
   integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==
@@ -3923,18 +3977,6 @@ globals@^13.15.0:
   dependencies:
     type-fest "^0.20.2"
 
-globby@^11.1.0:
-  version "11.1.0"
-  resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b"
-  integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==
-  dependencies:
-    array-union "^2.1.0"
-    dir-glob "^3.0.1"
-    fast-glob "^3.2.9"
-    ignore "^5.2.0"
-    merge2 "^1.4.1"
-    slash "^3.0.0"
-
 globby@^13.1.1:
   version "13.1.2"
   resolved "https://registry.yarnpkg.com/globby/-/globby-13.1.2.tgz#29047105582427ab6eca4f905200667b056da515"
@@ -4045,7 +4087,7 @@ html-entities@^2.3.2:
   resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.3.3.tgz#117d7626bece327fc8baace8868fa6f5ef856e46"
   integrity sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==
 
-html-loader@^4.1.0:
+html-loader@^4.2.0:
   version "4.2.0"
   resolved "https://registry.yarnpkg.com/html-loader/-/html-loader-4.2.0.tgz#20f69f9ec69244860c250ae6ee0046c8c5c4d348"
   integrity sha512-OxCHD3yt+qwqng2vvcaPApCEvbx+nXWu+v69TYHx1FO8bffHn/JjHtE3TTQZmHjwvnJe4xxzuecetDVBrQR1Zg==
@@ -4390,6 +4432,11 @@ is-path-inside@^2.1.0:
   dependencies:
     path-is-inside "^1.0.2"
 
+is-path-inside@^3.0.3:
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283"
+  integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==
+
 is-plain-obj@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-3.0.0.tgz#af6f2ea14ac5a646183a5bbdb5baabbc156ad9d7"
@@ -5414,7 +5461,7 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.1.0, postcss-value-parser@^
   resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
   integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
 
-postcss@^8.1.10, postcss@^8.4.14, postcss@^8.4.16, postcss@^8.4.7:
+postcss@^8.1.10, postcss@^8.4.14, postcss@^8.4.7:
   version "8.4.17"
   resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.17.tgz#f87863ec7cd353f81f7ab2dec5d67d861bbb1be5"
   integrity sha512-UNxNOLQydcOFi41yHNMcKRZ39NeXlr8AxGuZJsdub8vIb12fHzcq37DTU/QtbI6WLxNg2gF9Z+8qtRwTj1UI1Q==
@@ -5423,6 +5470,15 @@ postcss@^8.1.10, postcss@^8.4.14, postcss@^8.4.16, postcss@^8.4.7:
     picocolors "^1.0.0"
     source-map-js "^1.0.2"
 
+postcss@^8.4.18:
+  version "8.4.18"
+  resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.18.tgz#6d50046ea7d3d66a85e0e782074e7203bc7fbca2"
+  integrity sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==
+  dependencies:
+    nanoid "^3.3.4"
+    picocolors "^1.0.0"
+    source-map-js "^1.0.2"
+
 prelude-ls@^1.2.1:
   version "1.2.1"
   resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
@@ -5820,7 +5876,7 @@ run-parallel@^1.1.9:
   dependencies:
     queue-microtask "^1.2.2"
 
-rxjs@^7.5.5:
+rxjs@^7.5.5, rxjs@^7.5.7:
   version "7.5.7"
   resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.5.7.tgz#2ec0d57fdc89ece220d2e702730ae8f1e49def39"
   integrity sha512-z9MzKh/UcOqB3i20H6rtrlaE/CgjLOvheWK/9ILrbhROGTweAi1BaFsTT9FbwZi5Trr1qNRs+MXkhmR06awzQA==
@@ -6005,11 +6061,6 @@ simple-git-hooks@^2.6.1:
   resolved "https://registry.yarnpkg.com/simple-git-hooks/-/simple-git-hooks-2.8.0.tgz#291558785af6e17ca0c7a4f9d3d91e8635965a64"
   integrity sha512-ocmZQORwa6x9mxg+gVIAp5o4wXiWOHGXyrDBA0+UxGKIEKOyFtL4LWNKkP/2ornQPdlnlDGDteVeYP5FjhIoWA==
 
-slash@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
-  integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
-
 slash@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7"
@@ -6668,7 +6719,7 @@ webpack-dev-middleware@^5.3.1:
     range-parser "^1.2.1"
     schema-utils "^4.0.0"
 
-webpack-dev-server@^4.11.0:
+webpack-dev-server@^4.11.1:
   version "4.11.1"
   resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.11.1.tgz#ae07f0d71ca0438cf88446f09029b92ce81380b5"
   integrity sha512-lILVz9tAUy1zGFwieuaQtYiadImb5M3d+H+L1zDYalYoDl0cksAB1UNyuE5MMWJrG6zR1tXkCP2fitl7yoUJiw==