1
0
Эх сурвалжийг харах

feat: execute workflow using context menu

Ahmad Kholid 3 жил өмнө
parent
commit
59312804f3

+ 24 - 0
src/background/index.js

@@ -314,6 +314,30 @@ browser.alarms.onAlarm.addListener(async ({ name }) => {
   }
 });
 
+if (browser.contextMenus?.onClicked) {
+  browser.contextMenus.onClicked.addListener(
+    async ({ parentMenuItemId, menuItemId }, tab) => {
+      try {
+        if (parentMenuItemId !== 'automaContextMenu') return;
+
+        const message = await browser.tabs.sendMessage(tab.id, {
+          frameId: 0,
+          type: 'context-element',
+        });
+        const workflowData = await workflow.get(menuItemId);
+
+        workflow.execute(workflowData, {
+          data: {
+            variables: message,
+          },
+        });
+      } catch (error) {
+        console.error(error);
+      }
+    }
+  );
+}
+
 browser.runtime.onInstalled.addListener(async ({ reason }) => {
   try {
     if (reason === 'install') {

+ 2 - 0
src/components/newtab/workflow/edit/EditTrigger.vue

@@ -33,6 +33,7 @@ import { useI18n } from 'vue-i18n';
 import TriggerDate from './Trigger/TriggerDate.vue';
 import TriggerInterval from './Trigger/TriggerInterval.vue';
 import TriggerVisitWeb from './Trigger/TriggerVisitWeb.vue';
+import TriggerContextMenu from './Trigger/TriggerContextMenu.vue';
 import TriggerSpecificDay from './Trigger/TriggerSpecificDay.vue';
 import TriggerKeyboardShortcut from './Trigger/TriggerKeyboardShortcut.vue';
 
@@ -49,6 +50,7 @@ const { t } = useI18n();
 const triggers = {
   manual: null,
   interval: TriggerInterval,
+  'context-menu': TriggerContextMenu,
   date: TriggerDate,
   'specific-day': TriggerSpecificDay,
   'on-startup': null,

+ 101 - 0
src/components/newtab/workflow/edit/Trigger/TriggerContextMenu.vue

@@ -0,0 +1,101 @@
+<template>
+  <div class="mt-4">
+    <template v-if="!permission.has.contextMenus">
+      <p>
+        {{ t('workflow.blocks.trigger.contextMenus.noPermission') }}
+      </p>
+      <ui-button class="mt-2" @click="permission.request">
+        {{ t('workflow.blocks.trigger.contextMenus.grantPermission') }}
+      </ui-button>
+    </template>
+    <template v-else>
+      <ui-input
+        :label="t('workflow.blocks.trigger.contextMenus.contextName')"
+        :placeholder="workflow.data.value.name"
+        :model-value="data.contextMenuName"
+        class="w-full"
+        @change="$emit('update', { contextMenuName: $event })"
+      />
+      <ui-popover
+        :options="{ animation: null }"
+        trigger-width
+        class="w-full mt-2"
+        trigger-class="w-full"
+      >
+        <template #trigger>
+          <span class="text-sm ml-1 text-gray-600 dark:text-gray-200">
+            {{ t('workflow.blocks.trigger.contextMenus.appearIn') }}
+          </span>
+          <ui-button class="w-full">
+            <p class="text-left flex-1 text-overflow mr-2">
+              {{ data.contextTypes.join(', ') || 'All' }}
+            </p>
+            <v-remixicon
+              size="28"
+              name="riArrowDropDownLine"
+              class="text-gray-600 dark:text-gray-200 -mr-2"
+            />
+          </ui-button>
+        </template>
+        <div class="grid gap-2 grid-cols-2">
+          <ui-checkbox
+            v-for="type in types"
+            :key="type"
+            :model-value="data.contextTypes?.includes(type)"
+            @change="onSelectContextType($event, type)"
+          >
+            <span class="capitalize">{{ type }}</span>
+          </ui-checkbox>
+        </div>
+      </ui-popover>
+    </template>
+  </div>
+</template>
+<script setup>
+import { onMounted, inject } from 'vue';
+import { useI18n } from 'vue-i18n';
+import { useHasPermissions } from '@/composable/hasPermissions';
+
+const props = defineProps({
+  data: {
+    type: Object,
+    default: () => ({}),
+  },
+});
+const emit = defineEmits(['update']);
+
+const types = [
+  'audio',
+  'editable',
+  'image',
+  'link',
+  'page',
+  'password',
+  'selection',
+  'video',
+];
+
+const { t } = useI18n();
+const permission = useHasPermissions(['contextMenus']);
+
+const workflow = inject('workflow');
+
+function onSelectContextType(selected, type) {
+  const contextTypes = [...props.data.contextTypes];
+
+  if (selected) {
+    contextTypes.push(type);
+  } else {
+    const index = contextTypes.indexOf(type);
+    contextTypes.splice(index, 1);
+  }
+
+  emit('update', { contextTypes });
+}
+
+onMounted(() => {
+  if (props.data.contextMenuName.trim()) return;
+
+  emit('update', { contextMenuName: workflow.data.value.name });
+});
+</script>

+ 3 - 0
src/composable/hasPermissions.js

@@ -20,6 +20,9 @@ export function useHasPermissions(permissions) {
         reqPermissions.forEach((permission) => {
           handlePermission(permission, true);
         });
+      })
+      .catch((error) => {
+        console.error(error);
       });
   }
 

+ 18 - 1
src/content/index.js

@@ -1,4 +1,5 @@
 import browser from 'webextension-polyfill';
+import { finder } from '@medv/finder';
 import { toCamelCase } from '@/utils/helper';
 import blocksHandler from './blocksHandler';
 import showExecutedBlock from './showExecutedBlock';
@@ -133,7 +134,14 @@ function messageListener({ data, source }) {
   window.isAutomaInjected = true;
   window.addEventListener('message', messageListener);
 
-  if (isMainFrame) shortcutListener();
+  let contextElement = null;
+
+  if (isMainFrame) {
+    shortcutListener();
+    window.addEventListener('contextmenu', ({ target }) => {
+      contextElement = target;
+    });
+  }
 
   browser.runtime.onMessage.addListener((data) => {
     return new Promise((resolve, reject) => {
@@ -155,6 +163,15 @@ function messageListener({ data, source }) {
             resolve(selectorInstance);
             break;
           }
+          case 'context-element': {
+            let $ctxElSelector = '';
+            const $ctxTextSelection = window.getSelection().toString() || '';
+
+            if (contextElement) $ctxElSelector = finder(contextElement);
+
+            resolve({ $ctxElSelector, $ctxTextSelection });
+            break;
+          }
           default:
             resolve(null);
         }

+ 7 - 0
src/locales/en/blocks.json

@@ -173,6 +173,12 @@
         "selectDay": "Select day",
         "timeExist": "You alread add {time} on {day}",
         "fixedDelay": "Fixed delay",
+        "contextMenus": {
+          "noPermission": "This trigger requires \"contextMenus\" permission to be working",
+          "grantPermission": "Grant permission",
+          "appearIn": "Will appear in",
+          "contextName": "Workflow name in the context menu"
+        },
         "days": [
           "Sunday",
           "Monday",
@@ -203,6 +209,7 @@
           "manual": "Manually",
           "interval": "Interval",
           "date": "On a specific date",
+          "context-menu": "Context menu",
           "specific-day": "On a specific day",
           "visit-web": "When visit a website",
           "on-startup": "On browser startup",

+ 2 - 0
src/utils/shared.js

@@ -26,6 +26,8 @@ export const tasks = {
       activeInInput: false,
       isUrlRegex: false,
       days: [],
+      contextMenuName: '',
+      contextTypes: [],
     },
   },
   'execute-workflow': {

+ 48 - 0
src/utils/workflowTrigger.js

@@ -2,6 +2,51 @@ import browser from 'webextension-polyfill';
 import dayjs from 'dayjs';
 import { isObject } from './helper';
 
+function registerContextMenu(workflowId, data) {
+  return new Promise((resolve, reject) => {
+    const documentUrlPatterns = ['https://*/*', 'http://*/*'];
+    const contextTypes =
+      data.contextTypes.length === 0 ? ['all'] : data.contextTypes;
+
+    browser.contextMenus.create(
+      {
+        id: workflowId,
+        documentUrlPatterns,
+        contexts: contextTypes,
+        title: data.contextMenuName,
+        parentId: 'automaContextMenu',
+      },
+      () => {
+        const error = browser.runtime.lastError;
+
+        if (error) {
+          if (error.message.includes('automaContextMenu')) {
+            browser.contextMenus.create(
+              {
+                documentUrlPatterns,
+                contexts: ['all'],
+                id: 'automaContextMenu',
+                title: 'Run Automa workflow',
+              },
+              () => {
+                registerContextMenu(workflowId, data)
+                  .then(resolve)
+                  .catch(reject);
+              }
+            );
+            return;
+          }
+
+          reject(error.message);
+        } else {
+          if (browser.contextMenus.refresh) browser.contextMenus.refresh();
+          resolve();
+        }
+      }
+    );
+  });
+}
+
 async function removeFromWorkflowQueue(workflowId) {
   const { workflowQueue } = await browser.storage.local.get('workflowQueue');
   const queueIndex = (workflowQueue || []).indexOf(workflowId);
@@ -47,6 +92,8 @@ export async function cleanWorkflowTriggers(workflowId) {
       shortcuts: keyboardShortcuts,
       onStartupTriggers: startupTriggers,
     });
+
+    browser.contextMenus.remove(workflowId);
   } catch (error) {
     console.error(error);
   }
@@ -163,6 +210,7 @@ export async function registerWorkflowTrigger(workflowId, { data }) {
       'visit-web': registerVisitWeb,
       'on-startup': registerOnStartup,
       'specific-day': registerSpecificDay,
+      'context-menu': registerContextMenu,
       'keyboard-shortcut': registerKeyboardShortcut,
     };