Skip to content

Commit d0f701e

Browse files
authored
Merge pull request #40 from Kholid060/dev
Add HTML Section
2 parents 07422a8 + e649a7c commit d0f701e

File tree

7 files changed

+285
-79
lines changed

7 files changed

+285
-79
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "inspect-css",
3-
"version": "1.0.0",
3+
"version": "1.0.1",
44
"license": "MIT",
55
"scripts": {
66
"build": "vue-tsc --noEmit && vite build",
@@ -27,6 +27,7 @@
2727
"@codemirror/autocomplete": "^6.12.0",
2828
"@codemirror/commands": "^6.3.3",
2929
"@codemirror/lang-css": "^6.2.1",
30+
"@codemirror/lang-html": "^6.4.8",
3031
"@codemirror/language": "^6.10.0",
3132
"@codemirror/state": "^6.4.0",
3233
"@codemirror/view": "^6.23.0",
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<template>
2+
<PopoverRoot v-bind="forwarded">
3+
<PopoverTrigger as-child>
4+
<slot name="trigger" />
5+
</PopoverTrigger>
6+
<PopoverContent
7+
:class="[
8+
'z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
9+
props.class,
10+
]"
11+
>
12+
<slot />
13+
</PopoverContent>
14+
</PopoverRoot>
15+
</template>
16+
<script setup lang="ts">
17+
import {
18+
PopoverRoot,
19+
useForwardPropsEmits,
20+
PopoverTrigger,
21+
PopoverContent,
22+
} from 'radix-vue';
23+
import type { PopoverRootEmits, PopoverRootProps } from 'radix-vue';
24+
25+
const props = defineProps<PopoverRootProps & { class?: string }>();
26+
const emits = defineEmits<PopoverRootEmits>();
27+
28+
const forwarded = useForwardPropsEmits(props, emits);
29+
</script>

src/pages/content/ui/app/AppElementDetail.vue

Lines changed: 17 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,13 @@
55
class="fixed top-0 left-0 bg-background rounded-xl text-foreground border"
66
:style="{ zIndex: CONTENT_ZINDEX.content, width: CONTAINER_WIDTH + 'px' }"
77
>
8-
<div
9-
class="text-center py-1 bg-muted/25 cursor-move text-muted-foreground px-4 flex items-center transition-colors w-full rounded-t-lg border-b"
10-
@pointerdown.self="startDragging"
11-
>
12-
<GripHorizontalIcon class="h-5 w-5 text-muted-foreground opacity-50" />
13-
<div class="flex-grow"></div>
14-
<button
15-
class="hover p-1 rounded-sm hover:bg-muted/50 hover:text-foreground transition"
16-
@click="closeWindow"
17-
>
18-
<XIcon class="h-5 w-5" />
19-
</button>
20-
</div>
8+
<DetailHeader
9+
:container-el="containerRef"
10+
:last-position="lastPosition"
11+
:selected-el="selectedEl"
12+
@close-window="closeWindow"
13+
@update-window-pos="updateWindowPosition"
14+
/>
2115
<div class="px-4 pt-4">
2216
<UiElementSelector :selector="elProperties.selector" />
2317
<div class="flex items-center mt-0.5 gap-1 text-sm">
@@ -47,7 +41,7 @@
4741
</button>
4842
</div>
4943
<div style="max-height: calc(100vh - 200px)" class="overflow-auto p-4">
50-
<KeepAlive>
44+
<KeepAlive include="detail-style,DetailStyle">
5145
<DetailStyle
5246
v-if="state.activeTab === 'style' && elStyleData"
5347
:el-selector="elSelector"
@@ -60,6 +54,10 @@
6054
v-else-if="state.activeTab === 'attributes' && selectedEl"
6155
:element="selectedEl"
6256
/>
57+
<DetailElementHTML
58+
v-else-if="state.activeTab === 'html' && selectedEl"
59+
:element="selectedEl"
60+
/>
6361
</KeepAlive>
6462
</div>
6563
</div>
@@ -82,20 +80,17 @@ import {
8280
BrushIcon,
8381
CaseSensitiveIcon,
8482
PencilRuler,
85-
XIcon,
86-
GripHorizontalIcon,
83+
Code2Icon,
8784
} from 'lucide-vue-next';
88-
import {
89-
CONTENT_ZINDEX,
90-
EL_ATTR_NAME,
91-
SESSION_STORAGE_KEY,
92-
} from '@src/utils/constant';
85+
import { CONTENT_ZINDEX, SESSION_STORAGE_KEY } from '@src/utils/constant';
9386
import getElProperties, {
9487
ElementProperties,
9588
} from '@root/src/utils/getElProperties';
9689
import DetailStyle from './detail/DetailStyle.vue';
90+
import DetailElementHTML from './detail/DetailElementHTML.vue';
9791
import UiElementSelector from '@root/src/pages/components/ui/UiElementSelector.vue';
9892
import { finder } from '@medv/finder';
93+
import DetailHeader from './detail/DetailHeader.vue';
9994
import DetailAttributes from './detail/DetailAttributes.vue';
10095
import { debounce, parseJSON } from '@root/src/utils/helper';
10196
import { StyleDataItem, useAppProvider } from '../app-plugin';
@@ -117,6 +112,7 @@ const cssRulesUtils = new CSSRulesUtils();
117112
const tabItems = [
118113
{ name: 'Style', id: 'style', icon: BrushIcon },
119114
{ name: 'Attributes', id: 'attributes', icon: PencilRuler },
115+
{ name: 'HTML', id: 'html', icon: Code2Icon },
120116
];
121117
122118
const appProvider = useAppProvider();
@@ -177,45 +173,6 @@ function updateWindowPosition(x: number, y: number) {
177173
lastPosition = { x, y };
178174
containerRef.value.style.transform = `translate(${x}px, ${y}px)`;
179175
}
180-
function startDragging(pointerDownEvent: PointerEvent) {
181-
if (!containerRef.value) return;
182-
183-
const containerRect = containerRef.value.getBoundingClientRect();
184-
185-
const offsetY = pointerDownEvent.clientY - containerRect.top;
186-
const offsetX = pointerDownEvent.clientX - containerRect.left;
187-
188-
document.body.setAttribute(EL_ATTR_NAME.dragging, '');
189-
190-
function pointerMoveHandler({ clientX, clientY }: PointerEvent) {
191-
let x = clientX - offsetX;
192-
let y = clientY - offsetY;
193-
194-
if (x < 0) x = 0;
195-
else if (x + containerRect.width > window.innerWidth)
196-
x = window.innerWidth - containerRect.width;
197-
198-
if (y < 0) y = 0;
199-
else if (y + containerRect.height > window.innerHeight)
200-
y = window.innerHeight - containerRect.height;
201-
202-
updateWindowPosition(x, y);
203-
}
204-
205-
document.addEventListener('pointermove', pointerMoveHandler);
206-
document.addEventListener(
207-
'pointerup',
208-
() => {
209-
sessionStorage.setItem(
210-
SESSION_STORAGE_KEY.elPropsPosition,
211-
JSON.stringify(lastPosition),
212-
);
213-
document.body.removeAttribute(EL_ATTR_NAME.dragging);
214-
document.removeEventListener('pointermove', pointerMoveHandler);
215-
},
216-
{ once: true },
217-
);
218-
}
219176
function onSelectElement({
220177
el,
221178
properties,

src/pages/content/ui/app/detail/DetailAttributes.vue

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
<div
1414
v-for="(attr, index) in attrs"
1515
:key="index"
16-
class="bg-input/30 rounded-md text-sm focus-within:ring-primary focus-within:ring-2 highlight-white/5 relative"
16+
class="bg-input/30 rounded-md text-sm border focus-within:ring-primary focus-within:ring-2 highlight-white/5 relative"
1717
>
1818
<div
1919
class="bg-input/50 rounded-t-md border-b flex items-center gap-2 pr-3"
@@ -41,6 +41,9 @@
4141
<FolderOpenIcon class="h-5 w-5" />
4242
</UiButton>
4343
</UiTooltip>
44+
<UiTooltip v-if="attr.isInvalid" label="Invalid attribute name">
45+
<AlertTriangleIcon class="h-5 w-5 text-red-400 mr-1" />
46+
</UiTooltip>
4447
<button class="text-muted-foreground" @click="deleteAttribute(index)">
4548
<TrashIcon class="h-5 w-5" />
4649
</button>
@@ -72,12 +75,18 @@ import UiButton from '@root/src/pages/components/ui/UiButton.vue';
7275
import UiTooltip from '@root/src/pages/components/ui/UiTooltip.vue';
7376
import { EL_ATTR_NAME } from '@root/src/utils/constant';
7477
import { debounce } from '@root/src/utils/helper';
75-
import { PlusIcon, TrashIcon, FolderOpenIcon } from 'lucide-vue-next';
78+
import {
79+
PlusIcon,
80+
TrashIcon,
81+
FolderOpenIcon,
82+
AlertTriangleIcon,
83+
} from 'lucide-vue-next';
7684
import { ref, shallowRef, triggerRef, watch } from 'vue';
7785
7886
interface AttrItem {
7987
name: string;
8088
value: string;
89+
isInvalid: boolean;
8190
}
8291
8392
const props = defineProps<{
@@ -106,30 +115,42 @@ const updateAttribute = debounce(
106115
const attr = attrs.value[index];
107116
if (!attr) return;
108117
109-
if (type === 'name') {
110-
if (!value.trim()) return;
111-
112-
props.element.removeAttribute(attr.name);
113-
props.element.setAttribute(value, attr.value);
114-
} else {
115-
props.element.setAttribute(attr.name, value);
118+
try {
119+
attr[type] = value;
120+
121+
if (type === 'name') {
122+
if (!value.trim()) return;
123+
124+
props.element.removeAttribute(attr.name);
125+
props.element.setAttribute(value, attr.value);
126+
} else {
127+
props.element.setAttribute(attr.name, value);
128+
}
129+
130+
if (attr.isInvalid) {
131+
trigger = true;
132+
attr.isInvalid = false;
133+
}
134+
} catch (error) {
135+
trigger = true;
136+
attr.isInvalid = true;
137+
} finally {
138+
if (trigger) triggerRef(attrs);
116139
}
117-
118-
attr[type] = value;
119-
120-
if (trigger) triggerRef(attrs);
121140
},
122141
200,
123142
);
124143
function addAttribute() {
125-
attrs.value.push({ name: '', value: '' });
144+
attrs.value.push({ name: '', value: '', isInvalid: false });
145+
triggerRef(attrs);
126146
}
127147
function deleteAttribute(index: number) {
128148
const attr = attrs.value[index];
129149
if (!attr) return;
130150
131151
attrs.value.splice(index, 1);
132152
props.element.removeAttribute(attr.name);
153+
triggerRef(attrs);
133154
}
134155
function updateImgSrc() {
135156
const file = inputFile.value?.files?.item(0);
@@ -160,6 +181,7 @@ watch(
160181
161182
acc.push({
162183
name: curr.name,
184+
isInvalid: false,
163185
value: curr.value,
164186
});
165187
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<template>
2+
<p class="font-semibold">HTML</p>
3+
<div class="mt-2 rounded-md bg-muted/50 highlight-white/5">
4+
<UiCodemirror
5+
:key="editorKey"
6+
:extensions="[html(), EditorView.lineWrapping]"
7+
:model-value="innerHTML"
8+
:theme-options="{
9+
settings: {
10+
background: 'transparent',
11+
gutterBackground: 'transparent',
12+
},
13+
}"
14+
placeholder="HTML here"
15+
class="text-sm p-2"
16+
@change="updateInnerHTML"
17+
/>
18+
</div>
19+
</template>
20+
<script setup lang="ts">
21+
import { watch, shallowRef } from 'vue';
22+
import { html } from '@codemirror/lang-html';
23+
import UiCodemirror from '@root/src/pages/components/ui/UiCodemirror.vue';
24+
import { EditorView } from '@codemirror/view';
25+
26+
const props = defineProps<{
27+
element: Element;
28+
}>();
29+
30+
const innerHTML = shallowRef('');
31+
const editorKey = shallowRef(0);
32+
33+
function updateInnerHTML(html: string) {
34+
innerHTML.value = html;
35+
// eslint-disable-next-line vue/no-mutating-props
36+
props.element.innerHTML = html;
37+
}
38+
39+
watch(
40+
() => props.element,
41+
() => {
42+
innerHTML.value = props.element.innerHTML.trim();
43+
editorKey.value += 1;
44+
},
45+
{ immediate: true },
46+
);
47+
</script>

0 commit comments

Comments
 (0)