mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-11-04 04:57:51 +00:00 
			
		
		
		
	feat: Add the ability for the agents to execute a macro (#5698)
Co-authored-by: Pranav Raj S <pranav@chatwoot.com>
This commit is contained in:
		@@ -5,9 +5,12 @@ import Button from './ui/WootButton';
 | 
				
			|||||||
import Code from './Code';
 | 
					import Code from './Code';
 | 
				
			||||||
import ColorPicker from './widgets/ColorPicker';
 | 
					import ColorPicker from './widgets/ColorPicker';
 | 
				
			||||||
import ConfirmDeleteModal from './widgets/modal/ConfirmDeleteModal.vue';
 | 
					import ConfirmDeleteModal from './widgets/modal/ConfirmDeleteModal.vue';
 | 
				
			||||||
 | 
					import ConfirmModal from './widgets/modal/ConfirmationModal.vue';
 | 
				
			||||||
 | 
					import ContextMenu from './ui/ContextMenu.vue';
 | 
				
			||||||
import DeleteModal from './widgets/modal/DeleteModal.vue';
 | 
					import DeleteModal from './widgets/modal/DeleteModal.vue';
 | 
				
			||||||
import DropdownItem from 'shared/components/ui/dropdown/DropdownItem';
 | 
					import DropdownItem from 'shared/components/ui/dropdown/DropdownItem';
 | 
				
			||||||
import DropdownMenu from 'shared/components/ui/dropdown/DropdownMenu';
 | 
					import DropdownMenu from 'shared/components/ui/dropdown/DropdownMenu';
 | 
				
			||||||
 | 
					import FeatureToggle from './widgets/FeatureToggle';
 | 
				
			||||||
import HorizontalBar from './widgets/chart/HorizontalBarChart';
 | 
					import HorizontalBar from './widgets/chart/HorizontalBarChart';
 | 
				
			||||||
import Input from './widgets/forms/Input.vue';
 | 
					import Input from './widgets/forms/Input.vue';
 | 
				
			||||||
import Label from './ui/Label';
 | 
					import Label from './ui/Label';
 | 
				
			||||||
@@ -21,8 +24,6 @@ import SubmitButton from './buttons/FormSubmitButton';
 | 
				
			|||||||
import Tabs from './ui/Tabs/Tabs';
 | 
					import Tabs from './ui/Tabs/Tabs';
 | 
				
			||||||
import TabsItem from './ui/Tabs/TabsItem';
 | 
					import TabsItem from './ui/Tabs/TabsItem';
 | 
				
			||||||
import Thumbnail from './widgets/Thumbnail.vue';
 | 
					import Thumbnail from './widgets/Thumbnail.vue';
 | 
				
			||||||
import ConfirmModal from './widgets/modal/ConfirmationModal.vue';
 | 
					 | 
				
			||||||
import ContextMenu from './ui/ContextMenu.vue';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const WootUIKit = {
 | 
					const WootUIKit = {
 | 
				
			||||||
  AvatarUploader,
 | 
					  AvatarUploader,
 | 
				
			||||||
@@ -31,9 +32,12 @@ const WootUIKit = {
 | 
				
			|||||||
  Code,
 | 
					  Code,
 | 
				
			||||||
  ColorPicker,
 | 
					  ColorPicker,
 | 
				
			||||||
  ConfirmDeleteModal,
 | 
					  ConfirmDeleteModal,
 | 
				
			||||||
 | 
					  ConfirmModal,
 | 
				
			||||||
 | 
					  ContextMenu,
 | 
				
			||||||
  DeleteModal,
 | 
					  DeleteModal,
 | 
				
			||||||
  DropdownItem,
 | 
					  DropdownItem,
 | 
				
			||||||
  DropdownMenu,
 | 
					  DropdownMenu,
 | 
				
			||||||
 | 
					  FeatureToggle,
 | 
				
			||||||
  HorizontalBar,
 | 
					  HorizontalBar,
 | 
				
			||||||
  Input,
 | 
					  Input,
 | 
				
			||||||
  Label,
 | 
					  Label,
 | 
				
			||||||
@@ -47,8 +51,6 @@ const WootUIKit = {
 | 
				
			|||||||
  Tabs,
 | 
					  Tabs,
 | 
				
			||||||
  TabsItem,
 | 
					  TabsItem,
 | 
				
			||||||
  Thumbnail,
 | 
					  Thumbnail,
 | 
				
			||||||
  ConfirmModal,
 | 
					 | 
				
			||||||
  ContextMenu,
 | 
					 | 
				
			||||||
  install(Vue) {
 | 
					  install(Vue) {
 | 
				
			||||||
    const keys = Object.keys(this);
 | 
					    const keys = Object.keys(this);
 | 
				
			||||||
    keys.pop(); // remove 'install' from keys
 | 
					    keys.pop(); // remove 'install' from keys
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					  <div v-if="isFeatureEnabled">
 | 
				
			||||||
 | 
					    <slot />
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					import { mapGetters } from 'vuex';
 | 
				
			||||||
 | 
					export default {
 | 
				
			||||||
 | 
					  props: {
 | 
				
			||||||
 | 
					    featureKey: {
 | 
				
			||||||
 | 
					      type: String,
 | 
				
			||||||
 | 
					      required: true,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  computed: {
 | 
				
			||||||
 | 
					    ...mapGetters({
 | 
				
			||||||
 | 
					      isFeatureEnabledonAccount: 'accounts/isFeatureEnabledonAccount',
 | 
				
			||||||
 | 
					      accountId: 'getCurrentAccountId',
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					    isFeatureEnabled() {
 | 
				
			||||||
 | 
					      return this.isFeatureEnabledonAccount(this.accountId, this.featureKey);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
@@ -1,4 +1,11 @@
 | 
				
			|||||||
import { emptyMacro } from '../../routes/dashboard/settings/macros/macroHelper';
 | 
					import {
 | 
				
			||||||
 | 
					  emptyMacro,
 | 
				
			||||||
 | 
					  resolveActionName,
 | 
				
			||||||
 | 
					  resolveLabels,
 | 
				
			||||||
 | 
					  resolveTeamIds,
 | 
				
			||||||
 | 
					} from '../../routes/dashboard/settings/macros/macroHelper';
 | 
				
			||||||
 | 
					import { MACRO_ACTION_TYPES } from '../../routes/dashboard/settings/macros/constants';
 | 
				
			||||||
 | 
					import { teams, labels } from './macrosFixtures';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('#emptyMacro', () => {
 | 
					describe('#emptyMacro', () => {
 | 
				
			||||||
  const defaultMacro = {
 | 
					  const defaultMacro = {
 | 
				
			||||||
@@ -15,3 +22,31 @@ describe('#emptyMacro', () => {
 | 
				
			|||||||
    expect(emptyMacro).toEqual(defaultMacro);
 | 
					    expect(emptyMacro).toEqual(defaultMacro);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('#resolveActionName', () => {
 | 
				
			||||||
 | 
					  it('resolve action name from key and return the correct label', () => {
 | 
				
			||||||
 | 
					    expect(resolveActionName(MACRO_ACTION_TYPES[0].key)).toEqual(
 | 
				
			||||||
 | 
					      MACRO_ACTION_TYPES[0].label
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    expect(resolveActionName(MACRO_ACTION_TYPES[1].key)).toEqual(
 | 
				
			||||||
 | 
					      MACRO_ACTION_TYPES[1].label
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    expect(resolveActionName(MACRO_ACTION_TYPES[1].key)).not.toEqual(
 | 
				
			||||||
 | 
					      MACRO_ACTION_TYPES[0].label
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('#resolveTeamIds', () => {
 | 
				
			||||||
 | 
					  it('resolves team names from ids, and returns a joined string', () => {
 | 
				
			||||||
 | 
					    const resolvedTeams = '⚙️ sales team, 🤷♂️ fayaz';
 | 
				
			||||||
 | 
					    expect(resolveTeamIds(teams, [1, 2])).toEqual(resolvedTeams);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('#resolveLabels', () => {
 | 
				
			||||||
 | 
					  it('resolves labels names from ids and returns a joined string', () => {
 | 
				
			||||||
 | 
					    const resolvedLabels = 'sales, billing';
 | 
				
			||||||
 | 
					    expect(resolveLabels(labels, ['sales', 'billing'])).toEqual(resolvedLabels);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -208,7 +208,8 @@
 | 
				
			|||||||
      "CONVERSATION_LABELS": "Conversation Labels",
 | 
					      "CONVERSATION_LABELS": "Conversation Labels",
 | 
				
			||||||
      "CONVERSATION_INFO": "Conversation Information",
 | 
					      "CONVERSATION_INFO": "Conversation Information",
 | 
				
			||||||
      "CONTACT_ATTRIBUTES": "Contact Attributes",
 | 
					      "CONTACT_ATTRIBUTES": "Contact Attributes",
 | 
				
			||||||
      "PREVIOUS_CONVERSATION": "Previous Conversations"
 | 
					      "PREVIOUS_CONVERSATION": "Previous Conversations",
 | 
				
			||||||
 | 
					      "MACROS": "Macros"
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "CONVERSATION_CUSTOM_ATTRIBUTES": {
 | 
					  "CONVERSATION_CUSTOM_ATTRIBUTES": {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,12 +24,7 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "LIST": {
 | 
					    "LIST": {
 | 
				
			||||||
      "TABLE_HEADER": [
 | 
					      "TABLE_HEADER": ["Name", "Created by", "Last updated by", "Visibility"],
 | 
				
			||||||
        "Name",
 | 
					 | 
				
			||||||
        "Created by",
 | 
					 | 
				
			||||||
        "Last updated by",
 | 
					 | 
				
			||||||
        "Visibility"
 | 
					 | 
				
			||||||
      ],
 | 
					 | 
				
			||||||
      "404": "No macros found"
 | 
					      "404": "No macros found"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "DELETE": {
 | 
					    "DELETE": {
 | 
				
			||||||
@@ -68,6 +63,11 @@
 | 
				
			|||||||
          "DESCRIPTION": "This macro will be private to you and not be available to others."
 | 
					          "DESCRIPTION": "This macro will be private to you and not be available to others."
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "EXECUTE": {
 | 
				
			||||||
 | 
					      "BUTTON_TOOLTIP": "Execute",
 | 
				
			||||||
 | 
					      "PREVIEW": "Preview Macro",
 | 
				
			||||||
 | 
					      "EXECUTED_SUCCESSFULLY": "Macro executed successfully"
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -119,6 +119,7 @@ describe('uiSettingsMixin', () => {
 | 
				
			|||||||
        { name: 'conversation_info' },
 | 
					        { name: 'conversation_info' },
 | 
				
			||||||
        { name: 'contact_attributes' },
 | 
					        { name: 'contact_attributes' },
 | 
				
			||||||
        { name: 'previous_conversation' },
 | 
					        { name: 'previous_conversation' },
 | 
				
			||||||
 | 
					        { name: 'macros' },
 | 
				
			||||||
      ]);
 | 
					      ]);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
import { mapGetters } from 'vuex';
 | 
					import { mapGetters } from 'vuex';
 | 
				
			||||||
export const DEFAULT_CONVERSATION_SIDEBAR_ITEMS_ORDER = [
 | 
					export const DEFAULT_CONVERSATION_SIDEBAR_ITEMS_ORDER = [
 | 
				
			||||||
  { name: 'conversation_actions' },
 | 
					  { name: 'conversation_actions' },
 | 
				
			||||||
 | 
					  { name: 'macros' },
 | 
				
			||||||
  { name: 'conversation_info' },
 | 
					  { name: 'conversation_info' },
 | 
				
			||||||
  { name: 'contact_attributes' },
 | 
					  { name: 'contact_attributes' },
 | 
				
			||||||
  { name: 'previous_conversation' },
 | 
					  { name: 'previous_conversation' },
 | 
				
			||||||
@@ -30,7 +31,17 @@ export default {
 | 
				
			|||||||
    ...mapGetters({ uiSettings: 'getUISettings' }),
 | 
					    ...mapGetters({ uiSettings: 'getUISettings' }),
 | 
				
			||||||
    conversationSidebarItemsOrder() {
 | 
					    conversationSidebarItemsOrder() {
 | 
				
			||||||
      const { conversation_sidebar_items_order: itemsOrder } = this.uiSettings;
 | 
					      const { conversation_sidebar_items_order: itemsOrder } = this.uiSettings;
 | 
				
			||||||
      return itemsOrder || DEFAULT_CONVERSATION_SIDEBAR_ITEMS_ORDER;
 | 
					      // If the sidebar order is not set, use the default order.
 | 
				
			||||||
 | 
					      if (!itemsOrder) {
 | 
				
			||||||
 | 
					        return DEFAULT_CONVERSATION_SIDEBAR_ITEMS_ORDER;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      // If the sidebar order doesn't have the new elements, then add them to the list.
 | 
				
			||||||
 | 
					      DEFAULT_CONVERSATION_SIDEBAR_ITEMS_ORDER.forEach(item => {
 | 
				
			||||||
 | 
					        if (!itemsOrder.find(i => i.name === item.name)) {
 | 
				
			||||||
 | 
					          itemsOrder.push(item);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					      return itemsOrder;
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    contactSidebarItemsOrder() {
 | 
					    contactSidebarItemsOrder() {
 | 
				
			||||||
      const { contact_sidebar_items_order: itemsOrder } = this.uiSettings;
 | 
					      const { contact_sidebar_items_order: itemsOrder } = this.uiSettings;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -93,6 +93,19 @@
 | 
				
			|||||||
              />
 | 
					              />
 | 
				
			||||||
            </accordion-item>
 | 
					            </accordion-item>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
 | 
					          <woot-feature-toggle
 | 
				
			||||||
 | 
					            v-else-if="element.name === 'macros'"
 | 
				
			||||||
 | 
					            feature-key="macros"
 | 
				
			||||||
 | 
					          >
 | 
				
			||||||
 | 
					            <accordion-item
 | 
				
			||||||
 | 
					              :title="$t('CONVERSATION_SIDEBAR.ACCORDION.MACROS')"
 | 
				
			||||||
 | 
					              :is-open="isContactSidebarItemOpen('is_macro_open')"
 | 
				
			||||||
 | 
					              compact
 | 
				
			||||||
 | 
					              @click="value => toggleSidebarUIState('is_macro_open', value)"
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					              <macros-list :conversation-id="conversationId" />
 | 
				
			||||||
 | 
					            </accordion-item>
 | 
				
			||||||
 | 
					          </woot-feature-toggle>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </transition-group>
 | 
					      </transition-group>
 | 
				
			||||||
    </draggable>
 | 
					    </draggable>
 | 
				
			||||||
@@ -112,6 +125,7 @@ import CustomAttributes from './customAttributes/CustomAttributes.vue';
 | 
				
			|||||||
import CustomAttributeSelector from './customAttributes/CustomAttributeSelector.vue';
 | 
					import CustomAttributeSelector from './customAttributes/CustomAttributeSelector.vue';
 | 
				
			||||||
import draggable from 'vuedraggable';
 | 
					import draggable from 'vuedraggable';
 | 
				
			||||||
import uiSettingsMixin from 'dashboard/mixins/uiSettings';
 | 
					import uiSettingsMixin from 'dashboard/mixins/uiSettings';
 | 
				
			||||||
 | 
					import MacrosList from './Macros/List';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default {
 | 
					export default {
 | 
				
			||||||
  components: {
 | 
					  components: {
 | 
				
			||||||
@@ -123,6 +137,7 @@ export default {
 | 
				
			|||||||
    CustomAttributeSelector,
 | 
					    CustomAttributeSelector,
 | 
				
			||||||
    ConversationAction,
 | 
					    ConversationAction,
 | 
				
			||||||
    draggable,
 | 
					    draggable,
 | 
				
			||||||
 | 
					    MacrosList,
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  mixins: [alertMixin, uiSettingsMixin],
 | 
					  mixins: [alertMixin, uiSettingsMixin],
 | 
				
			||||||
  props: {
 | 
					  props: {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,75 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					  <div>
 | 
				
			||||||
 | 
					    <div
 | 
				
			||||||
 | 
					      v-if="!uiFlags.isFetching && !macros.length"
 | 
				
			||||||
 | 
					      class="macros_list--empty-state"
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
 | 
					      <p class="no-items-error-message">
 | 
				
			||||||
 | 
					        {{ $t('MACROS.LIST.404') }}
 | 
				
			||||||
 | 
					      </p>
 | 
				
			||||||
 | 
					      <router-link :to="addAccountScoping('settings/macros')">
 | 
				
			||||||
 | 
					        <woot-button
 | 
				
			||||||
 | 
					          variant="smooth"
 | 
				
			||||||
 | 
					          icon="add"
 | 
				
			||||||
 | 
					          size="tiny"
 | 
				
			||||||
 | 
					          class="macros_add-button"
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          {{ $t('MACROS.HEADER_BTN_TXT') }}
 | 
				
			||||||
 | 
					        </woot-button>
 | 
				
			||||||
 | 
					      </router-link>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    <woot-loading-state
 | 
				
			||||||
 | 
					      v-if="uiFlags.isFetching"
 | 
				
			||||||
 | 
					      :message="$t('MACROS.LOADING')"
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					    <div v-if="!uiFlags.isFetching && macros.length" class="macros-list">
 | 
				
			||||||
 | 
					      <macro-item
 | 
				
			||||||
 | 
					        v-for="macro in macros"
 | 
				
			||||||
 | 
					        :key="macro.id"
 | 
				
			||||||
 | 
					        :macro="macro"
 | 
				
			||||||
 | 
					        :conversation-id="conversationId"
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					import { mapGetters } from 'vuex';
 | 
				
			||||||
 | 
					import MacroItem from './MacroItem';
 | 
				
			||||||
 | 
					import accountMixin from 'dashboard/mixins/account.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default {
 | 
				
			||||||
 | 
					  components: {
 | 
				
			||||||
 | 
					    MacroItem,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  mixins: [accountMixin],
 | 
				
			||||||
 | 
					  props: {
 | 
				
			||||||
 | 
					    conversationId: {
 | 
				
			||||||
 | 
					      type: [Number, String],
 | 
				
			||||||
 | 
					      required: true,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  computed: {
 | 
				
			||||||
 | 
					    ...mapGetters({
 | 
				
			||||||
 | 
					      macros: ['macros/getMacros'],
 | 
				
			||||||
 | 
					      uiFlags: 'macros/getUIFlags',
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  mounted() {
 | 
				
			||||||
 | 
					    this.$store.dispatch('macros/get');
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					<style scoped lang="scss">
 | 
				
			||||||
 | 
					.macros-list {
 | 
				
			||||||
 | 
					  padding: var(--space-smaller);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.macros_list--empty-state {
 | 
				
			||||||
 | 
					  padding: var(--space-slab);
 | 
				
			||||||
 | 
					  p {
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					.macros_add-button {
 | 
				
			||||||
 | 
					  margin: var(--space-small) auto 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
@@ -0,0 +1,111 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					  <div class="macro">
 | 
				
			||||||
 | 
					    <span class="text-truncate">{{ macro.name }}</span>
 | 
				
			||||||
 | 
					    <div class="macros-actions">
 | 
				
			||||||
 | 
					      <woot-button
 | 
				
			||||||
 | 
					        v-tooltip.left-start="$t('MACROS.EXECUTE.PREVIEW')"
 | 
				
			||||||
 | 
					        size="tiny"
 | 
				
			||||||
 | 
					        variant="smooth"
 | 
				
			||||||
 | 
					        color-scheme="secondary"
 | 
				
			||||||
 | 
					        icon="info"
 | 
				
			||||||
 | 
					        class="margin-right-smaller"
 | 
				
			||||||
 | 
					        @click="toggleMacroPreview(macro)"
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					      <woot-button
 | 
				
			||||||
 | 
					        v-tooltip.left-start="$t('MACROS.EXECUTE.BUTTON_TOOLTIP')"
 | 
				
			||||||
 | 
					        size="tiny"
 | 
				
			||||||
 | 
					        variant="smooth"
 | 
				
			||||||
 | 
					        color-scheme="secondary"
 | 
				
			||||||
 | 
					        icon="play-circle"
 | 
				
			||||||
 | 
					        :is-loading="isExecuting"
 | 
				
			||||||
 | 
					        @click="executeMacro(macro)"
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					    <transition name="menu-slide">
 | 
				
			||||||
 | 
					      <macro-preview
 | 
				
			||||||
 | 
					        v-if="showPreview"
 | 
				
			||||||
 | 
					        v-on-clickaway="closeMacroPreview"
 | 
				
			||||||
 | 
					        :macro="macro"
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					    </transition>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					import alertMixin from 'shared/mixins/alertMixin';
 | 
				
			||||||
 | 
					import { mixin as clickaway } from 'vue-clickaway';
 | 
				
			||||||
 | 
					import MacroPreview from './MacroPreview';
 | 
				
			||||||
 | 
					export default {
 | 
				
			||||||
 | 
					  components: {
 | 
				
			||||||
 | 
					    MacroPreview,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  mixins: [alertMixin, clickaway],
 | 
				
			||||||
 | 
					  props: {
 | 
				
			||||||
 | 
					    macro: {
 | 
				
			||||||
 | 
					      type: Object,
 | 
				
			||||||
 | 
					      required: true,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    conversationId: {
 | 
				
			||||||
 | 
					      type: [Number, String],
 | 
				
			||||||
 | 
					      required: true,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  data() {
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					      isExecuting: false,
 | 
				
			||||||
 | 
					      showPreview: false,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  methods: {
 | 
				
			||||||
 | 
					    async executeMacro(macro) {
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        this.isExecuting = true;
 | 
				
			||||||
 | 
					        await this.$store.dispatch('macros/execute', {
 | 
				
			||||||
 | 
					          macroId: macro.id,
 | 
				
			||||||
 | 
					          conversationIds: [this.conversationId],
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					        this.showAlert(this.$t('MACROS.EXECUTE.EXECUTED_SUCCESSFULLY'));
 | 
				
			||||||
 | 
					      } catch (error) {
 | 
				
			||||||
 | 
					        this.showAlert(this.$t('MACROS.ERROR'));
 | 
				
			||||||
 | 
					      } finally {
 | 
				
			||||||
 | 
					        this.isExecuting = false;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    toggleMacroPreview() {
 | 
				
			||||||
 | 
					      this.showPreview = !this.showPreview;
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    closeMacroPreview() {
 | 
				
			||||||
 | 
					      this.showPreview = false;
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style scoped lang="scss">
 | 
				
			||||||
 | 
					.macro {
 | 
				
			||||||
 | 
					  position: relative;
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  align-items: center;
 | 
				
			||||||
 | 
					  justify-content: space-between;
 | 
				
			||||||
 | 
					  margin: 0;
 | 
				
			||||||
 | 
					  padding: var(--space-small);
 | 
				
			||||||
 | 
					  font-weight: var(--font-weight-medium);
 | 
				
			||||||
 | 
					  border-radius: var(--border-radius-normal);
 | 
				
			||||||
 | 
					  color: var(--s-700);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  &:hover {
 | 
				
			||||||
 | 
					    background: var(--s-25);
 | 
				
			||||||
 | 
					    color: var(--s-600);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  &:focus {
 | 
				
			||||||
 | 
					    border-color: var(--w-300);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .macros-actions {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    align-items: center;
 | 
				
			||||||
 | 
					    justify-content: flex-end;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
@@ -0,0 +1,116 @@
 | 
				
			|||||||
 | 
					<template>
 | 
				
			||||||
 | 
					  <div class="macro-preview">
 | 
				
			||||||
 | 
					    <p class="macro-title">{{ macro.name }}</p>
 | 
				
			||||||
 | 
					    <div v-for="(action, i) in resolvedMacro" :key="i" class="macro-block">
 | 
				
			||||||
 | 
					      <div v-if="i !== macro.actions.length - 1" class="macro-block-border" />
 | 
				
			||||||
 | 
					      <div class="macro-block-dot" />
 | 
				
			||||||
 | 
					      <p class="macro-action-name">{{ action.actionName }}</p>
 | 
				
			||||||
 | 
					      <p class="macro-action-params">{{ action.actionValue }}</p>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					</template>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  resolveActionName,
 | 
				
			||||||
 | 
					  resolveTeamIds,
 | 
				
			||||||
 | 
					  resolveLabels,
 | 
				
			||||||
 | 
					} from 'dashboard/routes/dashboard/settings/macros/macroHelper';
 | 
				
			||||||
 | 
					import { mapGetters } from 'vuex';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default {
 | 
				
			||||||
 | 
					  props: {
 | 
				
			||||||
 | 
					    macro: {
 | 
				
			||||||
 | 
					      type: Object,
 | 
				
			||||||
 | 
					      required: true,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  computed: {
 | 
				
			||||||
 | 
					    resolvedMacro() {
 | 
				
			||||||
 | 
					      return this.macro.actions.map(action => {
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					          actionName: resolveActionName(action.action_name),
 | 
				
			||||||
 | 
					          actionValue: this.getActionValue(
 | 
				
			||||||
 | 
					            action.action_name,
 | 
				
			||||||
 | 
					            action.action_params
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    ...mapGetters({
 | 
				
			||||||
 | 
					      labels: 'labels/getLabels',
 | 
				
			||||||
 | 
					      teams: 'teams/getTeams',
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  methods: {
 | 
				
			||||||
 | 
					    getActionValue(key, params) {
 | 
				
			||||||
 | 
					      const actionsMap = {
 | 
				
			||||||
 | 
					        assign_team: resolveTeamIds(this.teams, params),
 | 
				
			||||||
 | 
					        add_label: resolveLabels(this.labels, params),
 | 
				
			||||||
 | 
					        mute_conversation: null,
 | 
				
			||||||
 | 
					        snooze_conversation: null,
 | 
				
			||||||
 | 
					        resolve_conversation: null,
 | 
				
			||||||
 | 
					        send_webhook_event: params[0],
 | 
				
			||||||
 | 
					        send_message: params[0],
 | 
				
			||||||
 | 
					        send_email_transcript: params[0],
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					      return actionsMap[key] || '';
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style lang="scss" scoped>
 | 
				
			||||||
 | 
					.macro-preview {
 | 
				
			||||||
 | 
					  position: absolute;
 | 
				
			||||||
 | 
					  max-height: calc(var(--space-giga) * 1.5);
 | 
				
			||||||
 | 
					  min-height: var(--space-jumbo);
 | 
				
			||||||
 | 
					  width: calc(var(--space-giga) + var(--space-large));
 | 
				
			||||||
 | 
					  border-radius: var(--border-radius-normal);
 | 
				
			||||||
 | 
					  background-color: var(--white);
 | 
				
			||||||
 | 
					  box-shadow: var(--shadow-dropdown-pane);
 | 
				
			||||||
 | 
					  bottom: calc(var(--space-three) + var(--space-half));
 | 
				
			||||||
 | 
					  right: calc(var(--space-three) + var(--space-half));
 | 
				
			||||||
 | 
					  overflow-y: auto;
 | 
				
			||||||
 | 
					  padding: var(--space-slab);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .macro-title {
 | 
				
			||||||
 | 
					    margin-bottom: var(--space-slab);
 | 
				
			||||||
 | 
					    color: var(--s-900);
 | 
				
			||||||
 | 
					    font-weight: var(--font-weight-bold);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .macro-block {
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					    padding-left: var(--space-slab);
 | 
				
			||||||
 | 
					    &:not(:last-child) {
 | 
				
			||||||
 | 
					      padding-bottom: var(--space-slab);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .macro-block-border {
 | 
				
			||||||
 | 
					      top: 0.625rem;
 | 
				
			||||||
 | 
					      position: absolute;
 | 
				
			||||||
 | 
					      bottom: var(--space-minus-half);
 | 
				
			||||||
 | 
					      left: 0;
 | 
				
			||||||
 | 
					      width: 1px;
 | 
				
			||||||
 | 
					      background-color: var(--s-100);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .macro-block-dot {
 | 
				
			||||||
 | 
					      position: absolute;
 | 
				
			||||||
 | 
					      left: -0.35rem;
 | 
				
			||||||
 | 
					      height: var(--space-small);
 | 
				
			||||||
 | 
					      width: var(--space-small);
 | 
				
			||||||
 | 
					      border: 2px solid var(--s-100);
 | 
				
			||||||
 | 
					      background-color: var(--white);
 | 
				
			||||||
 | 
					      border-radius: var(--border-radius-full);
 | 
				
			||||||
 | 
					      top: 0.4375rem;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  .macro-action-name {
 | 
				
			||||||
 | 
					    font-size: var(--font-size-mini);
 | 
				
			||||||
 | 
					    color: var(--s-500);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
@@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import { MACRO_ACTION_TYPES as macroActionTypes } from 'dashboard/routes/dashboard/settings/macros/constants.js';
 | 
				
			||||||
export const emptyMacro = {
 | 
					export const emptyMacro = {
 | 
				
			||||||
  name: '',
 | 
					  name: '',
 | 
				
			||||||
  actions: [
 | 
					  actions: [
 | 
				
			||||||
@@ -8,3 +9,25 @@ export const emptyMacro = {
 | 
				
			|||||||
  ],
 | 
					  ],
 | 
				
			||||||
  visibility: 'global',
 | 
					  visibility: 'global',
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const resolveActionName = key => {
 | 
				
			||||||
 | 
					  return macroActionTypes.find(i => i.key === key).label;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const resolveTeamIds = (teams, ids) => {
 | 
				
			||||||
 | 
					  return ids
 | 
				
			||||||
 | 
					    .map(id => {
 | 
				
			||||||
 | 
					      const team = teams.find(i => i.id === id);
 | 
				
			||||||
 | 
					      return team ? team.name : '';
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .join(', ');
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const resolveLabels = (labels, ids) => {
 | 
				
			||||||
 | 
					  return ids
 | 
				
			||||||
 | 
					    .map(id => {
 | 
				
			||||||
 | 
					      const label = labels.find(i => i.title === id);
 | 
				
			||||||
 | 
					      return label ? label.title : '';
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .join(', ');
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,6 +19,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  --space-minus-micro: -0.2rem;
 | 
					  --space-minus-micro: -0.2rem;
 | 
				
			||||||
  --space-minus-smaller: -0.4rem;
 | 
					  --space-minus-smaller: -0.4rem;
 | 
				
			||||||
 | 
					  --space-minus-half: -0.5rem;
 | 
				
			||||||
  --space-minus-small: -0.8rem;
 | 
					  --space-minus-small: -0.8rem;
 | 
				
			||||||
  --space-minus-one: -1rem;
 | 
					  --space-minus-one: -1rem;
 | 
				
			||||||
  --space-minus-slab: -1.2rem;
 | 
					  --space-minus-slab: -1.2rem;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -119,6 +119,7 @@
 | 
				
			|||||||
  "person-add-outline": "M17.5 12a5.5 5.5 0 1 1 0 11a5.5 5.5 0 0 1 0-11zm-5.477 2a6.47 6.47 0 0 0-.709 1.5H4.253a.749.749 0 0 0-.75.75v.577c0 .535.192 1.053.54 1.46c1.253 1.469 3.22 2.214 5.957 2.214c.597 0 1.157-.035 1.68-.106c.246.495.553.954.912 1.367c-.795.16-1.66.24-2.592.24c-3.146 0-5.532-.906-7.098-2.74a3.75 3.75 0 0 1-.898-2.435v-.578A2.249 2.249 0 0 1 4.253 14h7.77zm5.477 0l-.09.008a.5.5 0 0 0-.402.402L17 14.5V17h-2.496l-.09.008a.5.5 0 0 0-.402.402l-.008.09l.008.09a.5.5 0 0 0 .402.402l.09.008H17L17 20.5l.008.09a.5.5 0 0 0 .402.402l.09.008l.09-.008a.5.5 0 0 0 .402-.402L18 20.5V18h2.504l.09-.008a.5.5 0 0 0 .402-.402l.008-.09l-.008-.09a.5.5 0 0 0-.402-.402l-.09-.008H18L18 14.5l-.008-.09a.5.5 0 0 0-.402-.402L17.5 14zM10 2.005a5 5 0 1 1 0 10a5 5 0 0 1 0-10zm0 1.5a3.5 3.5 0 1 0 0 7a3.5 3.5 0 0 0 0-7z",
 | 
					  "person-add-outline": "M17.5 12a5.5 5.5 0 1 1 0 11a5.5 5.5 0 0 1 0-11zm-5.477 2a6.47 6.47 0 0 0-.709 1.5H4.253a.749.749 0 0 0-.75.75v.577c0 .535.192 1.053.54 1.46c1.253 1.469 3.22 2.214 5.957 2.214c.597 0 1.157-.035 1.68-.106c.246.495.553.954.912 1.367c-.795.16-1.66.24-2.592.24c-3.146 0-5.532-.906-7.098-2.74a3.75 3.75 0 0 1-.898-2.435v-.578A2.249 2.249 0 0 1 4.253 14h7.77zm5.477 0l-.09.008a.5.5 0 0 0-.402.402L17 14.5V17h-2.496l-.09.008a.5.5 0 0 0-.402.402l-.008.09l.008.09a.5.5 0 0 0 .402.402l.09.008H17L17 20.5l.008.09a.5.5 0 0 0 .402.402l.09.008l.09-.008a.5.5 0 0 0 .402-.402L18 20.5V18h2.504l.09-.008a.5.5 0 0 0 .402-.402l.008-.09l-.008-.09a.5.5 0 0 0-.402-.402l-.09-.008H18L18 14.5l-.008-.09a.5.5 0 0 0-.402-.402L17.5 14zM10 2.005a5 5 0 1 1 0 10a5 5 0 0 1 0-10zm0 1.5a3.5 3.5 0 1 0 0 7a3.5 3.5 0 0 0 0-7z",
 | 
				
			||||||
  "person-assign-outline": "M11.313 15.5a6.471 6.471 0 0 1 .709-1.5h-7.77a2.249 2.249 0 0 0-2.249 2.25v.577c0 .892.319 1.756.899 2.435c1.566 1.834 3.952 2.74 7.098 2.74c.931 0 1.796-.08 2.592-.24a6.51 6.51 0 0 1-.913-1.366c-.524.07-1.083.105-1.68.105c-2.737 0-4.703-.745-5.957-2.213a2.25 2.25 0 0 1-.539-1.461v-.578a.75.75 0 0 1 .75-.749h7.06ZM10 2.005a5 5 0 1 1 0 10a5 5 0 0 1 0-10Zm0 1.5a3.5 3.5 0 1 0 0 7a3.5 3.5 0 0 0 0-7ZM23 17.5a5.5 5.5 0 1 1-11 0a5.5 5.5 0 0 1 11 0Zm-4.647-2.853a.5.5 0 0 0-.707.707L19.293 17H15a.5.5 0 1 0 0 1h4.293l-1.647 1.647a.5.5 0 0 0 .707.707l2.5-2.5a.497.497 0 0 0 .147-.345V17.5a.498.498 0 0 0-.15-.357l-2.497-2.496Z",
 | 
					  "person-assign-outline": "M11.313 15.5a6.471 6.471 0 0 1 .709-1.5h-7.77a2.249 2.249 0 0 0-2.249 2.25v.577c0 .892.319 1.756.899 2.435c1.566 1.834 3.952 2.74 7.098 2.74c.931 0 1.796-.08 2.592-.24a6.51 6.51 0 0 1-.913-1.366c-.524.07-1.083.105-1.68.105c-2.737 0-4.703-.745-5.957-2.213a2.25 2.25 0 0 1-.539-1.461v-.578a.75.75 0 0 1 .75-.749h7.06ZM10 2.005a5 5 0 1 1 0 10a5 5 0 0 1 0-10Zm0 1.5a3.5 3.5 0 1 0 0 7a3.5 3.5 0 0 0 0-7ZM23 17.5a5.5 5.5 0 1 1-11 0a5.5 5.5 0 0 1 11 0Zm-4.647-2.853a.5.5 0 0 0-.707.707L19.293 17H15a.5.5 0 1 0 0 1h4.293l-1.647 1.647a.5.5 0 0 0 .707.707l2.5-2.5a.497.497 0 0 0 .147-.345V17.5a.498.498 0 0 0-.15-.357l-2.497-2.496Z",
 | 
				
			||||||
  "person-outline": "M17.754 14a2.249 2.249 0 0 1 2.25 2.249v.575c0 .894-.32 1.76-.902 2.438-1.57 1.834-3.957 2.739-7.102 2.739-3.146 0-5.532-.905-7.098-2.74a3.75 3.75 0 0 1-.898-2.435v-.577a2.249 2.249 0 0 1 2.249-2.25h11.501Zm0 1.5H6.253a.749.749 0 0 0-.75.749v.577c0 .536.192 1.054.54 1.461 1.253 1.468 3.219 2.214 5.957 2.214s4.706-.746 5.962-2.214a2.25 2.25 0 0 0 .541-1.463v-.575a.749.749 0 0 0-.749-.75ZM12 2.004a5 5 0 1 1 0 10 5 5 0 0 1 0-10Zm0 1.5a3.5 3.5 0 1 0 0 7 3.5 3.5 0 0 0 0-7Z",
 | 
					  "person-outline": "M17.754 14a2.249 2.249 0 0 1 2.25 2.249v.575c0 .894-.32 1.76-.902 2.438-1.57 1.834-3.957 2.739-7.102 2.739-3.146 0-5.532-.905-7.098-2.74a3.75 3.75 0 0 1-.898-2.435v-.577a2.249 2.249 0 0 1 2.249-2.25h11.501Zm0 1.5H6.253a.749.749 0 0 0-.75.749v.577c0 .536.192 1.054.54 1.461 1.253 1.468 3.219 2.214 5.957 2.214s4.706-.746 5.962-2.214a2.25 2.25 0 0 0 .541-1.463v-.575a.749.749 0 0 0-.749-.75ZM12 2.004a5 5 0 1 1 0 10 5 5 0 0 1 0-10Zm0 1.5a3.5 3.5 0 1 0 0 7 3.5 3.5 0 0 0 0-7Z",
 | 
				
			||||||
 | 
					  "play-circle-outline": "M2 12C2 6.477 6.477 2 12 2s10 4.477 10 10s-4.477 10-10 10S2 17.523 2 12Zm8.856-3.845A1.25 1.25 0 0 0 9 9.248v5.504a1.25 1.25 0 0 0 1.856 1.093l5.757-3.189a.75.75 0 0 0 0-1.312l-5.757-3.189Z",
 | 
				
			||||||
  "power-outline": "M8.204 4.82a.75.75 0 0 1 .634 1.36A7.51 7.51 0 0 0 4.5 12.991c0 4.148 3.358 7.51 7.499 7.51s7.499-3.362 7.499-7.51a7.51 7.51 0 0 0-4.323-6.804.75.75 0 1 1 .637-1.358 9.01 9.01 0 0 1 5.186 8.162c0 4.976-4.029 9.01-9 9.01C7.029 22 3 17.966 3 12.99a9.01 9.01 0 0 1 5.204-8.17ZM12 2.496a.75.75 0 0 1 .743.648l.007.102v7.5a.75.75 0 0 1-1.493.102l-.007-.102v-7.5a.75.75 0 0 1 .75-.75Z",
 | 
					  "power-outline": "M8.204 4.82a.75.75 0 0 1 .634 1.36A7.51 7.51 0 0 0 4.5 12.991c0 4.148 3.358 7.51 7.499 7.51s7.499-3.362 7.499-7.51a7.51 7.51 0 0 0-4.323-6.804.75.75 0 1 1 .637-1.358 9.01 9.01 0 0 1 5.186 8.162c0 4.976-4.029 9.01-9 9.01C7.029 22 3 17.966 3 12.99a9.01 9.01 0 0 1 5.204-8.17ZM12 2.496a.75.75 0 0 1 .743.648l.007.102v7.5a.75.75 0 0 1-1.493.102l-.007-.102v-7.5a.75.75 0 0 1 .75-.75Z",
 | 
				
			||||||
  "quote-outline": "M7.5 6a2.5 2.5 0 0 1 2.495 2.336l.005.206c-.01 3.555-1.24 6.614-3.705 9.223a.75.75 0 1 1-1.09-1.03c1.64-1.737 2.66-3.674 3.077-5.859A2.5 2.5 0 1 1 7.5 6Zm9 0a2.5 2.5 0 0 1 2.495 2.336l.005.206c-.01 3.56-1.238 6.614-3.705 9.223a.75.75 0 1 1-1.09-1.03c1.643-1.738 2.662-3.672 3.078-5.859A2.5 2.5 0 1 1 16.5 6Zm-9 1.5a1 1 0 1 0 .993 1.117l.007-.124a1 1 0 0 0-1-.993Zm9 0a1 1 0 1 0 .993 1.117l.007-.124a1 1 0 0 0-1-.993Z",
 | 
					  "quote-outline": "M7.5 6a2.5 2.5 0 0 1 2.495 2.336l.005.206c-.01 3.555-1.24 6.614-3.705 9.223a.75.75 0 1 1-1.09-1.03c1.64-1.737 2.66-3.674 3.077-5.859A2.5 2.5 0 1 1 7.5 6Zm9 0a2.5 2.5 0 0 1 2.495 2.336l.005.206c-.01 3.56-1.238 6.614-3.705 9.223a.75.75 0 1 1-1.09-1.03c1.643-1.738 2.662-3.672 3.078-5.859A2.5 2.5 0 1 1 16.5 6Zm-9 1.5a1 1 0 1 0 .993 1.117l.007-.124a1 1 0 0 0-1-.993Zm9 0a1 1 0 1 0 .993 1.117l.007-.124a1 1 0 0 0-1-.993Z",
 | 
				
			||||||
  "resize-large-outline": "M6.25 4.5A1.75 1.75 0 0 0 4.5 6.25v1.5a.75.75 0 0 1-1.5 0v-1.5A3.25 3.25 0 0 1 6.25 3h1.5a.75.75 0 0 1 0 1.5h-1.5ZM19.5 6.25a1.75 1.75 0 0 0-1.75-1.75h-1.5a.75.75 0 0 1 0-1.5h1.5A3.25 3.25 0 0 1 21 6.25v1.5a.75.75 0 0 1-1.5 0v-1.5ZM19.5 17.75a1.75 1.75 0 0 1-1.75 1.75h-1.5a.75.75 0 0 0 0 1.5h1.5A3.25 3.25 0 0 0 21 17.75v-1.5a.75.75 0 0 0-1.5 0v1.5ZM4.5 17.75c0 .966.784 1.75 1.75 1.75h1.5a.75.75 0 0 1 0 1.5h-1.5A3.25 3.25 0 0 1 3 17.75v-1.5a.75.75 0 0 1 1.5 0v1.5ZM8.25 6A2.25 2.25 0 0 0 6 8.25v7.5A2.25 2.25 0 0 0 8.25 18h7.5A2.25 2.25 0 0 0 18 15.75v-7.5A2.25 2.25 0 0 0 15.75 6h-7.5ZM7.5 8.25a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 .75.75v7.5a.75.75 0 0 1-.75.75h-7.5a.75.75 0 0 1-.75-.75v-7.5Z",
 | 
					  "resize-large-outline": "M6.25 4.5A1.75 1.75 0 0 0 4.5 6.25v1.5a.75.75 0 0 1-1.5 0v-1.5A3.25 3.25 0 0 1 6.25 3h1.5a.75.75 0 0 1 0 1.5h-1.5ZM19.5 6.25a1.75 1.75 0 0 0-1.75-1.75h-1.5a.75.75 0 0 1 0-1.5h1.5A3.25 3.25 0 0 1 21 6.25v1.5a.75.75 0 0 1-1.5 0v-1.5ZM19.5 17.75a1.75 1.75 0 0 1-1.75 1.75h-1.5a.75.75 0 0 0 0 1.5h1.5A3.25 3.25 0 0 0 21 17.75v-1.5a.75.75 0 0 0-1.5 0v1.5ZM4.5 17.75c0 .966.784 1.75 1.75 1.75h1.5a.75.75 0 0 1 0 1.5h-1.5A3.25 3.25 0 0 1 3 17.75v-1.5a.75.75 0 0 1 1.5 0v1.5ZM8.25 6A2.25 2.25 0 0 0 6 8.25v7.5A2.25 2.25 0 0 0 8.25 18h7.5A2.25 2.25 0 0 0 18 15.75v-7.5A2.25 2.25 0 0 0 15.75 6h-7.5ZM7.5 8.25a.75.75 0 0 1 .75-.75h7.5a.75.75 0 0 1 .75.75v7.5a.75.75 0 0 1-.75.75h-7.5a.75.75 0 0 1-.75-.75v-7.5Z",
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user