refactor: make EditorPanel and ElementPopup fully autonomous

- Move all popup logic into ElementPopup component (state, positioning, click handling)
- Make EditorPanel autonomous with direct store access
- Simplify App.vue by removing prop drilling and intermediary logic
- Update EditorPanel to control paragraph font-size instead of .about
- Fix CSS parsing: escape selectors in extractCssValue and updateCssValue
- Remove hardcoded .about references from PagedJsWrapper

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
isUnknown 2025-11-24 18:18:27 +01:00
parent 7bc0dad32b
commit ae8136a48e
6 changed files with 82 additions and 116 deletions

View file

@ -6,7 +6,7 @@
>
<div class="popup-header">
<span>{{ selector }}</span>
<button class="close-btn" @click="$emit('close')">×</button>
<button class="close-btn" @click="close">×</button>
</div>
<div class="popup-body">
<div class="popup-controls">
@ -30,7 +30,7 @@
</template>
<script setup>
import { computed } from 'vue';
import { ref, computed } from 'vue';
import { useStylesheetStore } from '../stores/stylesheet';
import hljs from 'highlight.js/lib/core';
import css from 'highlight.js/lib/languages/css';
@ -41,21 +41,57 @@ hljs.registerLanguage('css', css);
const stylesheetStore = useStylesheetStore();
const props = defineProps({
visible: Boolean,
position: Object,
selector: String
iframeRef: Object
});
defineEmits(['close']);
const visible = ref(false);
const position = ref({ x: 0, y: 0 });
const selector = ref('');
const getSelectorFromElement = (element) => {
return element.id
? `#${element.id}`
: `.${element.className.split(' ')[0]}`;
};
const calculatePosition = (element) => {
const rect = element.getBoundingClientRect();
const iframeRect = props.iframeRef.getBoundingClientRect();
return {
x: iframeRect.left + rect.left,
y: iframeRect.top + rect.bottom + 5,
};
};
const open = (element) => {
selector.value = getSelectorFromElement(element);
position.value = calculatePosition(element);
visible.value = true;
};
const close = () => {
visible.value = false;
};
const handleIframeClick = (event) => {
const element = event.target;
if (element.tagName === 'BODY' || element.tagName === 'HTML') {
close();
return;
}
open(element);
};
const elementCss = computed(() => {
if (!props.selector) return '';
return stylesheetStore.extractBlock(props.selector);
if (!selector.value) return '';
return stylesheetStore.extractBlock(selector.value);
});
const fontSizeData = computed(() => {
if (!props.selector) return null;
return stylesheetStore.extractValue(props.selector, 'font-size');
if (!selector.value) return null;
return stylesheetStore.extractValue(selector.value, 'font-size');
});
const highlightedCss = computed(() => {
@ -66,12 +102,14 @@ const highlightedCss = computed(() => {
const updateFontSize = (value) => {
if (!fontSizeData.value) return;
stylesheetStore.updateProperty(
props.selector,
selector.value,
'font-size',
value,
fontSizeData.value.unit
);
};
defineExpose({ handleIframeClick });
</script>
<style scoped>