refactor: archive disabled "page template styling" feature #14

The PagePopup / page-edge @page styling feature was DISABLED (commented) across
App.vue, useIframeInteractions and useKeyboardShortcuts. Move PagePopup.vue to
archive/page-template-styling/ with a self-contained REINSTALL.md, and strip all
the dead commented wiring from the live files (useIframeInteractions 545→401).

No behavior change (the feature was already inert). ARCHITECTURE.md updated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
isUnknown 2026-06-21 13:28:56 +02:00
parent 56c8d540db
commit 3f2c9f68b0
6 changed files with 163 additions and 179 deletions

View file

@ -44,7 +44,7 @@ Central hub = **⊙stylesheet** (everything CSS funnels through it). Selection/v
- `useElementStyleState.js` (77) — reactive MODEL: one ref per CSS prop + settingEnabled + settingCache + styleProps/unitProps (from descriptors). No logic.
### Preview / iframe / interaction
- `useIframeInteractions.js` (545) — hover labels + click routing inside iframe → element/image/carte popups. getSelectorFromElement, getContentElement, getImageFigure, getCarteFigure, handleIframeMouseMove/Click, popup-close handlers. ⚠ ~40% is commented-out `DISABLED: page template styling feature` (page hover/select/label). ☠ that dead block + pagePopup param.
- `useIframeInteractions.js` (401) — hover labels + click routing inside iframe → element/image/carte popups. getSelectorFromElement, getContentElement, getImageFigure, getCarteFigure, handleIframeMouseMove/Click, popup-close handlers. (Page-template dead code removed → archive/page-template-styling/.)
- `usePreviewRenderer.js` (171) — double-buffered iframe render w/ crossfade + scroll-% persistence. Builds srcdoc. watches ⊙stylesheet.content & ⊙narrative.data → renderPreview. setKeyboardShortcutHandler. → Coloris.close on iframe click.
- `usePrintPreview.js` (70) — collect iframe styles, swap into document, window.print(), reload.
- `useKeyboardShortcuts.js` (83) — Cmd/Ctrl+S(save)/+P(print), Esc(close popup), `\`(toggle panel). Listens on document + attachToIframe. ⚠ has DISABLED pagePopup branch.
@ -52,17 +52,15 @@ Central hub = **⊙stylesheet** (everything CSS funnels through it). Selection/v
### Fonts / spacing
- `useProjectFonts.js` (167) — SINGLETON. PROJECT_FONTS list, @font-face gen, loadFont(name)→⊙stylesheet.setFontFaceCss, loadAllFontPreviews (select preview), loadFontsFromCss. loadedFontsCss computed.
- `useSpacingEditor.js` (79) — margin/padding reactive (4 sides) + lock + unit convert. Used by ElementPopup (margin/padding passed into useElementSettings).
- `useLinkedSpacing.js` (146) ☠ — linked 4-side spacing w/ onUpdate. **No live importer.** (companion of dead MarginEditor.vue.)
## components/ (TODO: full map on demand)
Tree: App.vue → PagedJsWrapper(content-source), EditorPanel→{PageSettings,TextSettings,ImageSettings,CarteSettings,StylesheetViewer,ContentPanel→ContentTreeItem}, ElementPopup/ImagePopup/CartePopup(+BasePopup), PreviewLoader, SaveButton, PrintButton, ZoomControls. ui/: BasePopup, NumberInput, InputWithUnit, UnitToggle, MarginEditor, ZoomControls, CssFileImport. blocks/: per content-block render (Text/Heading/Image/Video/Audio/Map/Quote/List) via blocks/index.js.
Popups expose imperatively (defineExpose: open/close/visible/handleXClick); App wires refs into useIframeInteractions.
## ☠ DEAD-CODE / REFACTOR BACKLOG (verify before deleting)
- `PagePopup.vue` (541) — feature DISABLED (commented `<PagePopup>` in App.vue, commented import). Entire "page template styling" feature. Drag-along dead code in useIframeInteractions + useKeyboardShortcuts + App.
- `useLinkedSpacing.js` (146) — no importer.
- `components/ui/MarginEditor.vue` (167) — no importer.
- `components/SidePanel.vue` (22) — no importer.
## REFACTOR BACKLOG
- DUP: `ImagePopup.vue`(490) ≈ `CartePopup.vue`(480) — near-identical width/height/defaults logic. Strong de-dup target.
- DUP: `ImageSettings.vue`(147) ≈ `CarteSettings.vue`(137) — same.
- Commented `DISABLED: page template styling feature` blocks scattered (useIframeInteractions heaviest).
- Big files to split clean-code style: `PageSettings.vue`(507), `ElementPopup.vue`(493), `StylesheetViewer.vue`(369), `ContentTreeItem.vue`(338).
## ARCHIVED (removed from live tree, restorable)
- `archive/page-template-styling/` — "page template styling" feature (PagePopup + its wiring). See its REINSTALL.md. Removed orphans (no importer): useLinkedSpacing.js, ui/MarginEditor.vue, SidePanel.vue (git history only).

View file

@ -0,0 +1,156 @@
# Archived feature — "page template styling" (PagePopup)
Lets the user click near a **page edge** in the preview to style `@page` rules
(margins, etc.) for all pages sharing the same template, via a dedicated popup.
It was kept `DISABLED` (commented) in the codebase for a long time; archived here
to declutter the live code. Everything needed to bring it back is below.
`PagePopup.vue` (the component) sits next to this file, unchanged.
## Reinstall — step by step
### 1. Move the component back
```
git mv archive/page-template-styling/PagePopup.vue src/components/PagePopup.vue
```
### 2. `src/App.vue`
Add the import:
```js
import PagePopup from './components/PagePopup.vue';
```
Add the ref (next to the other popup refs):
```js
const pagePopup = ref(null);
```
Destructure the page handlers from `useIframeInteractions` (and pass `pagePopup` in):
```js
const {
hoveredPage,
selectedPages,
hoveredElement,
selectedElement,
handleIframeMouseMove,
handleIframeClick,
handlePagePopupClose,
handleElementPopupClose,
handleImagePopupClose,
handleCartePopupClose,
} = useIframeInteractions({ elementPopup, imagePopup, cartePopup, pagePopup });
```
Pass `pagePopup` to `useKeyboardShortcuts({ stylesheetStore, elementPopup, pagePopup, activeTab, printPreview })`.
Add to the template (after `<CartePopup>`):
```html
<PagePopup ref="pagePopup" :iframeRef="activeFrame" @close="handlePagePopupClose" />
```
Re-add `#page-popup,` to the `@media print { ... display:none }` selector list.
### 3. `src/composables/useKeyboardShortcuts.js`
Add `pagePopup` to the destructured options, and in the Escape handler:
```js
if (pagePopup.value?.visible) { pagePopup.value.close(); return; }
```
### 4. `src/composables/useIframeInteractions.js`
Accept `pagePopup` in the options. Restore the page-edge logic (was inline-commented).
Full reference code below — splice each piece back into its section.
```js
// --- state (top of composable) ---
const hoveredPage = ref(null);
const selectedPages = ref([]); // Pages with active border (when popup is open)
const EDGE_THRESHOLD = 30; // px from edge to trigger hover
// --- helpers ---
const isNearPageEdge = (pageElement, mouseX, mouseY) => {
const rect = pageElement.getBoundingClientRect();
const nearLeft = mouseX >= rect.left && mouseX <= rect.left + EDGE_THRESHOLD;
const nearRight = mouseX >= rect.right - EDGE_THRESHOLD && mouseX <= rect.right;
const nearTop = mouseY >= rect.top && mouseY <= rect.top + EDGE_THRESHOLD;
const nearBottom = mouseY >= rect.bottom - EDGE_THRESHOLD && mouseY <= rect.bottom;
const inHorizontalRange = mouseY >= rect.top && mouseY <= rect.bottom;
const inVerticalRange = mouseX >= rect.left && mouseX <= rect.right;
return (
(nearLeft && inHorizontalRange) || (nearRight && inHorizontalRange) ||
(nearTop && inVerticalRange) || (nearBottom && inVerticalRange)
);
};
const getPagesWithSameTemplate = (page, doc) => {
const pageType = page.getAttribute('data-page-type') || 'default';
const allPages = doc.querySelectorAll('.pagedjs_page');
return Array.from(allPages).filter(
(p) => (p.getAttribute('data-page-type') || 'default') === pageType
);
};
const createPageLabel = (page) => {
const doc = page.ownerDocument;
const existingLabel = doc.querySelector('.page-hover-label');
if (existingLabel) existingLabel.remove();
const rect = page.getBoundingClientRect();
const scrollTop = doc.documentElement.scrollTop || doc.body.scrollTop;
const scrollLeft = doc.documentElement.scrollLeft || doc.body.scrollLeft;
const templateName = page.getAttribute('data-page-type') || 'default';
const label = doc.createElement('div');
label.className = 'page-hover-label';
label.textContent = `@page ${templateName}`;
label.style.top = `${rect.top + scrollTop - 32}px`;
label.style.left = `${rect.left + scrollLeft}px`;
doc.body.appendChild(label);
return label;
};
const removePageLabel = (doc) => {
const label = doc.querySelector('.page-hover-label');
if (label) label.remove();
};
const clearSelectedPages = () => {
selectedPages.value.forEach((page) => page.classList.remove('page-selected'));
selectedPages.value = [];
};
// --- inside handleIframeMouseMove (BEFORE the image/carte/content checks) ---
const pages = event.target.ownerDocument.querySelectorAll('.pagedjs_page');
let foundPage = null;
for (const page of pages) {
if (isNearPageEdge(page, event.clientX, event.clientY)) { foundPage = page; break; }
}
if (foundPage !== hoveredPage.value) {
if (hoveredPage.value && !selectedPages.value.includes(hoveredPage.value)) {
hoveredPage.value.classList.remove('page-hovered');
}
removePageLabel(event.target.ownerDocument);
if (foundPage && !selectedPages.value.includes(foundPage)) {
foundPage.classList.add('page-hovered');
createPageLabel(foundPage);
}
hoveredPage.value = foundPage;
}
// --- inside handleIframeClick (FIRST thing, before link/element checks) ---
if (hoveredPage.value) {
event.stopPropagation();
clearSelectedPages();
clearSelectedElement();
const doc = event.target.ownerDocument;
const sameTemplatePages = getPagesWithSameTemplate(hoveredPage.value, doc);
sameTemplatePages.forEach((page) => page.classList.add('page-selected'));
selectedPages.value = sameTemplatePages;
removePageLabel(doc);
removeElementLabel(doc);
pagePopup.value.open(hoveredPage.value, event, sameTemplatePages.length);
elementPopup.value.close();
return;
}
// --- close handler + return additions ---
const handlePagePopupClose = () => { clearSelectedPages(); };
// return { ...hoveredPage, selectedPages, handlePagePopupClose, clearSelectedPages }
```
## Notes
- The exact pre-removal source (with the original `DISABLED:` comments) lives in git history — `git log --follow` this folder; the archival commit's parent has it.
- PagePopup.vue depends only on `ui/NumberInput.vue`, `ui/BasePopup.vue` and `⊙stylesheet` (`extractSpacing`, etc.), all still present.
- Relevant CSS classes (`.page-hovered`, `.page-selected`, `.page-hover-label`) live in `public/assets/css/pagedjs-interface.css`.

View file

@ -4,7 +4,6 @@ import EditorPanel from './components/editor/EditorPanel.vue';
import ElementPopup from './components/ElementPopup.vue';
import ImagePopup from './components/ImagePopup.vue';
import CartePopup from './components/CartePopup.vue';
// import PagePopup from './components/PagePopup.vue'; // DISABLED: page template styling feature
import PreviewLoader from './components/PreviewLoader.vue';
import SaveButton from './components/SaveButton.vue';
import PrintButton from './components/PrintButton.vue';
@ -27,20 +26,16 @@ const previewFrame2 = ref(null);
const elementPopup = ref(null);
const imagePopup = ref(null);
const cartePopup = ref(null);
// const pagePopup = ref(null); // DISABLED: page template styling feature
const activeTab = ref('');
provide('activeTab', activeTab);
// Setup iframe interactions (hover, click, labels)
const {
// hoveredPage, // DISABLED: page template styling feature
// selectedPages, // DISABLED: page template styling feature
hoveredElement,
selectedElement,
handleIframeMouseMove,
handleIframeClick,
// handlePagePopupClose, // DISABLED: page template styling feature
handleElementPopupClose,
handleImagePopupClose,
handleCartePopupClose,
@ -77,7 +72,6 @@ const { printPreview } = usePrintPreview(activeFrame);
const { handleKeyboardShortcut, isMac } = useKeyboardShortcuts({
stylesheetStore,
elementPopup,
// pagePopup, // DISABLED: page template styling feature
activeTab,
printPreview,
});
@ -145,14 +139,6 @@ onMounted(async () => {
ref="cartePopup"
@close="handleCartePopupClose"
/>
<!-- DISABLED: page template styling feature
<PagePopup
ref="pagePopup"
:iframeRef="activeFrame"
@close="handlePagePopupClose"
/>
-->
<ZoomControls ref="zoomControls" />
@ -196,7 +182,6 @@ onMounted(async () => {
@media print {
#editor-panel,
#element-popup,
#page-popup,
#actions-btn,
.preview-loader,
.print-btn {

View file

@ -4,17 +4,13 @@ import { ref } from 'vue';
* Composable for managing interactions with pages and elements in the iframe
* Handles hover effects, labels, and click events for both pages and content elements
*/
export function useIframeInteractions({ elementPopup, imagePopup, cartePopup /*, pagePopup // DISABLED: page template styling feature */ }) {
// DISABLED: page template styling feature
// const hoveredPage = ref(null);
// const selectedPages = ref([]); // Pages with active border (when popup is open)
export function useIframeInteractions({ elementPopup, imagePopup, cartePopup }) {
const hoveredElement = ref(null);
const hoveredFigure = ref(null); // block-image or geoformat-cover-image
const hoveredCarte = ref(null); // block-carte
const selectedElement = ref(null);
const selectedFigure = ref(null); // selected block-image
const selectedCarte = ref(null); // selected block-carte
// const EDGE_THRESHOLD = 30; // px from edge to trigger hover // DISABLED: page template styling feature
// Text elements that can trigger ElementPopup (excluding containers, images, etc.)
const CONTENT_ELEMENTS = [
@ -38,39 +34,6 @@ export function useIframeInteractions({ elementPopup, imagePopup, cartePopup /*,
'FIGCAPTION',
];
/* DISABLED: page template styling feature
// Check if mouse position is near the edges of a page element
const isNearPageEdge = (pageElement, mouseX, mouseY) => {
const rect = pageElement.getBoundingClientRect();
const nearLeft = mouseX >= rect.left && mouseX <= rect.left + EDGE_THRESHOLD;
const nearRight =
mouseX >= rect.right - EDGE_THRESHOLD && mouseX <= rect.right;
const nearTop = mouseY >= rect.top && mouseY <= rect.top + EDGE_THRESHOLD;
const nearBottom =
mouseY >= rect.bottom - EDGE_THRESHOLD && mouseY <= rect.bottom;
const inHorizontalRange = mouseY >= rect.top && mouseY <= rect.bottom;
const inVerticalRange = mouseX >= rect.left && mouseX <= rect.right;
return (
(nearLeft && inHorizontalRange) ||
(nearRight && inHorizontalRange) ||
(nearTop && inVerticalRange) ||
(nearBottom && inVerticalRange)
);
};
// Get all pages using the same template as the given page
const getPagesWithSameTemplate = (page, doc) => {
const pageType = page.getAttribute('data-page-type') || 'default';
const allPages = doc.querySelectorAll('.pagedjs_page');
return Array.from(allPages).filter(
(p) => (p.getAttribute('data-page-type') || 'default') === pageType
);
};
*/
// Get selector for element (same logic as ElementPopup)
const getSelectorFromElement = (element) => {
if (element.id) {
@ -152,40 +115,6 @@ export function useIframeInteractions({ elementPopup, imagePopup, cartePopup /*,
if (label) label.remove();
};
/* DISABLED: page template styling feature
// Create and position page label on hover
const createPageLabel = (page) => {
const doc = page.ownerDocument;
const existingLabel = doc.querySelector('.page-hover-label');
if (existingLabel) {
existingLabel.remove();
}
const rect = page.getBoundingClientRect();
const scrollTop = doc.documentElement.scrollTop || doc.body.scrollTop;
const scrollLeft = doc.documentElement.scrollLeft || doc.body.scrollLeft;
const templateName = page.getAttribute('data-page-type') || 'default';
const label = doc.createElement('div');
label.className = 'page-hover-label';
label.textContent = `@page ${templateName}`;
label.style.top = `${rect.top + scrollTop - 32}px`;
label.style.left = `${rect.left + scrollLeft}px`;
doc.body.appendChild(label);
return label;
};
// Remove page label
const removePageLabel = (doc) => {
const label = doc.querySelector('.page-hover-label');
if (label) {
label.remove();
}
};
*/
// Find closest image figure ancestor (block-image or geoformat-cover-image)
const getImageFigure = (element) => {
let current = element;
@ -247,16 +176,6 @@ export function useIframeInteractions({ elementPopup, imagePopup, cartePopup /*,
return null;
};
/* DISABLED: page template styling feature
// Clear selection highlight from all selected pages
const clearSelectedPages = () => {
selectedPages.value.forEach((page) => {
page.classList.remove('page-selected');
});
selectedPages.value = [];
};
*/
// Clear selected element highlight
const clearSelectedElement = () => {
if (selectedElement.value) {
@ -276,37 +195,6 @@ export function useIframeInteractions({ elementPopup, imagePopup, cartePopup /*,
// Handle mouse movement in iframe
const handleIframeMouseMove = (event) => {
/* DISABLED: page template styling feature
const pages = event.target.ownerDocument.querySelectorAll('.pagedjs_page');
let foundPage = null;
// Check if hovering near page edge
for (const page of pages) {
if (isNearPageEdge(page, event.clientX, event.clientY)) {
foundPage = page;
break;
}
}
// Update page hover state
if (foundPage !== hoveredPage.value) {
// Remove highlight from previous page (only if not in selectedPages)
if (hoveredPage.value && !selectedPages.value.includes(hoveredPage.value)) {
hoveredPage.value.classList.remove('page-hovered');
}
// Remove previous page label
removePageLabel(event.target.ownerDocument);
// Add highlight to new page (only if not already selected)
if (foundPage && !selectedPages.value.includes(foundPage)) {
foundPage.classList.add('page-hovered');
createPageLabel(foundPage);
}
hoveredPage.value = foundPage;
}
*/
// Check for block-image figure hover
const imageFigure = getImageFigure(event.target);
@ -366,33 +254,6 @@ export function useIframeInteractions({ elementPopup, imagePopup, cartePopup /*,
const handleIframeClick = (event) => {
const element = event.target;
/* DISABLED: page template styling feature
// Check if clicking near a page edge
if (hoveredPage.value) {
event.stopPropagation();
// Clear previous selections
clearSelectedPages();
clearSelectedElement();
// Get all pages with same template and highlight them
const doc = event.target.ownerDocument;
const sameTemplatePages = getPagesWithSameTemplate(hoveredPage.value, doc);
sameTemplatePages.forEach((page) => {
page.classList.add('page-selected');
});
selectedPages.value = sameTemplatePages;
// Remove labels when opening popup
removePageLabel(doc);
removeElementLabel(doc);
pagePopup.value.open(hoveredPage.value, event, sameTemplatePages.length);
elementPopup.value.close();
return;
}
*/
// Prevent link navigation inside the preview
if (element.closest('a')) {
event.preventDefault();
@ -501,11 +362,6 @@ export function useIframeInteractions({ elementPopup, imagePopup, cartePopup /*,
};
// Handlers for popup close events
/* DISABLED: page template styling feature
const handlePagePopupClose = () => {
clearSelectedPages();
};
*/
const handleElementPopupClose = () => {
clearSelectedElement();
@ -527,19 +383,15 @@ export function useIframeInteractions({ elementPopup, imagePopup, cartePopup /*,
return {
// State
// hoveredPage, // DISABLED: page template styling feature
// selectedPages, // DISABLED: page template styling feature
hoveredElement,
selectedElement,
// Handlers
handleIframeMouseMove,
handleIframeClick,
// handlePagePopupClose, // DISABLED: page template styling feature
handleElementPopupClose,
handleImagePopupClose,
handleCartePopupClose,
// Utilities
// clearSelectedPages, // DISABLED: page template styling feature
clearSelectedElement,
};
}

View file

@ -7,7 +7,6 @@ import { onMounted, onUnmounted } from 'vue';
export function useKeyboardShortcuts({
stylesheetStore,
elementPopup,
// pagePopup, // DISABLED: page template styling feature
activeTab,
printPreview
}) {
@ -22,12 +21,6 @@ export function useKeyboardShortcuts({
elementPopup.value.close();
return;
}
/* DISABLED: page template styling feature
if (pagePopup.value?.visible) {
pagePopup.value.close();
return;
}
*/
}
// Backslash key - toggle editor panel