Skip to content

Commit

Permalink
feat: add xpath in element selector
Browse files Browse the repository at this point in the history
  • Loading branch information
Kholid060 committed Feb 17, 2022
1 parent 531bccb commit fa4b202
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 36 deletions.
74 changes: 59 additions & 15 deletions src/content/element-selector/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
<app-selector
:selector="state.elSelector"
:selected-count="state.selectedElements.length"
:selector-type="state.selectorType"
@selector="state.selectorType = $event"
@child="selectChildElement"
@parent="selectParentElement"
@change="updateSelectedElements"
Expand All @@ -50,7 +52,7 @@
<ui-tab-panels
v-model="state.activeTab"
class="overflow-y-auto scroll"
style="max-height: calc(100vh - 15rem)"
style="max-height: calc(100vh - 17rem)"
>
<ui-tab-panel value="attributes">
<app-element-list
Expand All @@ -69,9 +71,12 @@
>
{{ attribute.name }}
</p>
<p title="Attribute value" class="text-overflow">
{{ attribute.value }}
</p>
<input
:value="attribute.value"
readonly
title="Attribute value"
class="bg-transparent w-full"
/>
</div>
</template>
</app-element-list>
Expand Down Expand Up @@ -151,6 +156,7 @@ import { debounce } from '@/utils/helper';
import AppBlocks from './AppBlocks.vue';
import AppSelector from './AppSelector.vue';
import AppElementList from './AppElementList.vue';
import findElement from '@/utils/find-element';
const selectedElement = {
path: [],
Expand All @@ -168,6 +174,7 @@ const state = reactive({
isDragging: false,
isExecuting: false,
selectElements: [],
selectorType: 'css',
selectedElements: [],
hide: window.self !== window.top,
});
Expand All @@ -184,6 +191,34 @@ const cardRect = reactive({
width: 0,
});
/* eslint-disable no-use-before-define */
const getElementSelector = (element) =>
state.selectorType === 'css' ? finder(element) : generateXPath(element);
function generateXPath(element) {
if (!element) return null;
if (element.id !== '') return `id("${element.id}")`;
if (element === document.body) return `//${element.tagName}`;
let ix = 0;
const siblings = element.parentNode.childNodes;
for (let index = 0; index < siblings.length; index += 1) {
const sibling = siblings[index];
if (sibling === element) {
return `${generateXPath(element.parentNode)}/${element.tagName}[${
ix + 1
}]`;
}
if (sibling.nodeType === 1 && sibling.tagName === element.tagName) {
ix += 1;
}
}
return null;
}
function toggleHighlightElement({ index, highlight }) {
state.selectedElements[index].highlight = highlight;
}
Expand All @@ -203,9 +238,14 @@ function updateSelectedElements(selector) {
state.elSelector = selector;
try {
const elements = document.querySelectorAll(selector);
const selectorType = state.selectorType === 'css' ? 'cssSelector' : 'xpath';
let elements = findElement[selectorType]({ selector, multiple: true });
const selectElements = [];
if (selectorType === 'xpath') {
elements = elements ? [elements] : [];
}
state.selectedElements = Array.from(elements).map((element, index) => {
const attributes = Array.from(element.attributes).map(
({ name, value }) => ({ name, value })
Expand Down Expand Up @@ -259,26 +299,30 @@ function handleMouseMove({ clientX, clientY, target }) {
Object.assign(hoverElementRect, getElementRect(target));
}
function handleClick(event) {
if (event.target === rootElement || state.hide || state.isExecuting) return;
const { target, path } = event;
if (target === rootElement || state.hide || state.isExecuting) return;
event.preventDefault();
event.stopPropagation();
const attributes = Array.from(event.target.attributes).map(
({ name, value }) => ({ name, value })
);
const attributes = Array.from(target.attributes).map(({ name, value }) => ({
name,
value,
}));
state.selectedElements = [
{
...getElementRect(event.target),
...getElementRect(target),
attributes,
element: event.target,
element: target,
highlight: false,
},
];
state.elSelector = finder(event.target);
state.elSelector = getElementSelector(target);
selectedElement.index = 0;
selectedElement.path = event.path;
selectedElement.path = path;
}
function selectChildElement() {
if (selectedElement.path.length === 0 || state.hide) return;
Expand All @@ -300,7 +344,7 @@ function selectChildElement() {
childElement = selectedElement.path[selectedElement.pathIndex];
}
updateSelectedElements(finder(childElement));
updateSelectedElements(getElementSelector(childElement));
}
function selectParentElement() {
if (selectedElement.path.length === 0 || state.hide) return;
Expand All @@ -311,7 +355,7 @@ function selectParentElement() {
selectedElement.pathIndex += 1;
updateSelectedElements(finder(parentElement));
updateSelectedElements(getElementSelector(parentElement));
}
function handleMouseUp() {
if (state.isDragging) state.isDragging = false;
Expand Down
56 changes: 37 additions & 19 deletions src/content/element-selector/AppSelector.vue
Original file line number Diff line number Diff line change
@@ -1,25 +1,39 @@
<template>
<div class="mt-4 flex items-center">
<ui-input
:model-value="selector"
placeholder="Element selector"
class="leading-normal flex-1 h-full element-selector"
@change="updateSelector"
<div class="mt-4">
<ui-select
:model-value="selectorType"
class="w-full"
@change="$emit('selector', $event)"
>
<template #prepend>
<button class="absolute ml-2 left-0" @click="copySelector">
<v-remixicon name="riFileCopyLine" />
<option value="css">CSS Selector</option>
<option value="xpath">XPath</option>
</ui-select>
<div class="mt-2 flex items-center">
<ui-input
:model-value="selector"
placeholder="Element selector"
class="leading-normal flex-1 h-full element-selector"
@change="updateSelector"
>
<template #prepend>
<button class="absolute ml-2 left-0" @click="copySelector">
<v-remixicon name="riFileCopyLine" />
</button>
</template>
</ui-input>
<template v-if="selectedCount === 1">
<button
class="mr-2 ml-4"
title="Parent element"
@click="$emit('parent')"
>
<v-remixicon rotate="90" name="riArrowLeftLine" />
</button>
<button title="Child element" @click="$emit('child')">
<v-remixicon rotate="-90" name="riArrowLeftLine" />
</button>
</template>
</ui-input>
<template v-if="selectedCount === 1">
<button class="mr-2 ml-4" title="Parent element" @click="$emit('parent')">
<v-remixicon rotate="90" name="riArrowLeftLine" />
</button>
<button title="Child element" @click="$emit('child')">
<v-remixicon rotate="-90" name="riArrowLeftLine" />
</button>
</template>
</div>
</div>
</template>
<script setup>
Expand All @@ -36,8 +50,12 @@ const props = defineProps({
type: Number,
default: 0,
},
selectorType: {
type: String,
default: '',
},
});
const emit = defineEmits(['change', 'parent', 'child']);
const emit = defineEmits(['change', 'parent', 'child', 'selector']);
const rootElement = inject('rootElement');
Expand Down
4 changes: 2 additions & 2 deletions src/utils/find-element.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
class FindElement {
static cssSelector(data, documentCtx) {
static cssSelector(data, documentCtx = document) {
const selector = data.markEl
? `${data.selector.trim()}:not([${data.blockIdAttr}])`
: data.selector;
Expand All @@ -15,7 +15,7 @@ class FindElement {
return documentCtx.querySelector(selector);
}

static xpath(data, documentCtx) {
static xpath(data, documentCtx = document) {
return documentCtx.evaluate(
data.selector,
documentCtx,
Expand Down

0 comments on commit fa4b202

Please sign in to comment.