geoproject-app/src/components/editor/PageSettings.vue

566 lines
18 KiB
Vue
Raw Normal View History

<template>
<section
2026-03-07 19:59:01 +01:00
class="panel-settings__container"
2026-03-06 18:20:19 +01:00
id="settings__container_page"
data-color-type="page"
>
2026-03-07 19:59:01 +01:00
<div class="settings__header">
<div class="icon" v-html="bookIcon"></div>
<h2 class="title">Réglage des pages</h2>
</div>
<div class="container">
2026-03-07 19:59:01 +01:00
<!-- Format d'impression -->
<div class="setting__section" data-setting="format">
<div class="setting__header">
<label class="label-with-tooltip" data-css="size">Format d'impression</label>
</div>
<div class="setting__body">
<select id="page-format" v-model="pageFormat">
<option value="A4">A4</option>
<option value="A5">A5</option>
<option value="custom">Personnalisé</option>
</select>
</div>
</div>
2026-03-07 19:59:01 +01:00
<!-- Dimensions -->
<div class="setting__section" data-setting="dimensions" :class="{ 'setting-disabled': !isCustomFormat }">
<div class="setting__header">
<label class="label-with-tooltip" data-css="width / height">Dimensions</label>
</div>
<div class="setting__body">
<div class="field-size-page">
<label for="page-width" class="label-with-tooltip" data-css="width">Largeur</label>
<div class="input-with-unit">
<NumberInput
id="page-width"
:modelValue="customWidth"
@update:modelValue="(v) => (customWidth = v)"
:min="50"
:step="1"
:disabled="!isCustomFormat"
/>
<div class="unit-toggle">
<button type="button" class="active">mm</button>
</div>
</div>
</div>
2026-03-07 19:59:01 +01:00
<div class="field-size-page">
<label for="page-height" class="label-with-tooltip" data-css="height">Hauteur</label>
<div class="input-with-unit">
<NumberInput
id="page-height"
:modelValue="customHeight"
@update:modelValue="(v) => (customHeight = v)"
:min="50"
:step="1"
:disabled="!isCustomFormat"
/>
<div class="unit-toggle">
<button type="button" class="active">mm</button>
</div>
</div>
</div>
</div>
</div>
2026-03-07 19:59:01 +01:00
<!-- Marges -->
<div class="setting__section" data-setting="margin">
<div class="setting__header">
<label class="label-with-tooltip" data-css="margin">Marges</label>
</div>
<div class="setting__body">
<div class="field field-margin">
<label for="margin-top" class="label-with-tooltip" data-css="margin-top">Haut</label>
<div class="input-with-unit">
<NumberInput
id="margin-top"
:modelValue="margins.top.value"
:min="0"
:step="1"
@update:modelValue="(value) => (margins.top.value = value)"
/>
<div class="unit-toggle">
<button type="button" :class="{ active: margins.top.unit === 'mm' }" @click="updateMarginUnit('top', 'mm')">mm</button>
<button type="button" :class="{ active: margins.top.unit === 'px' }" @click="updateMarginUnit('top', 'px')">px</button>
</div>
</div>
</div>
2026-03-07 19:59:01 +01:00
<div class="field field-margin">
<label for="margin-bottom" class="label-with-tooltip" data-css="margin-bottom">Bas</label>
<div class="input-with-unit">
<NumberInput
id="margin-bottom"
:modelValue="margins.bottom.value"
:min="0"
:step="1"
@update:modelValue="(value) => (margins.bottom.value = value)"
/>
<div class="unit-toggle">
<button type="button" :class="{ active: margins.bottom.unit === 'mm' }" @click="updateMarginUnit('bottom', 'mm')">mm</button>
<button type="button" :class="{ active: margins.bottom.unit === 'px' }" @click="updateMarginUnit('bottom', 'px')">px</button>
</div>
</div>
</div>
2026-03-07 19:59:01 +01:00
<div class="field field-margin">
<label for="margin-left" class="label-with-tooltip" data-css="margin-left">Gauche</label>
<div class="input-with-unit">
<NumberInput
id="margin-left"
:modelValue="margins.left.value"
:min="0"
:step="1"
@update:modelValue="(value) => (margins.left.value = value)"
/>
<div class="unit-toggle">
<button type="button" :class="{ active: margins.left.unit === 'mm' }" @click="updateMarginUnit('left', 'mm')">mm</button>
<button type="button" :class="{ active: margins.left.unit === 'px' }" @click="updateMarginUnit('left', 'px')">px</button>
</div>
</div>
</div>
2026-03-07 19:59:01 +01:00
<div class="field field-margin">
<label for="margin-right" class="label-with-tooltip" data-css="margin-right">Droite</label>
<div class="input-with-unit">
<NumberInput
id="margin-right"
:modelValue="margins.right.value"
:min="0"
:step="1"
@update:modelValue="(value) => (margins.right.value = value)"
/>
<div class="unit-toggle">
<button type="button" :class="{ active: margins.right.unit === 'mm' }" @click="updateMarginUnit('right', 'mm')">mm</button>
<button type="button" :class="{ active: margins.right.unit === 'px' }" @click="updateMarginUnit('right', 'px')">px</button>
</div>
</div>
</div>
</div>
</div>
2026-03-07 19:59:01 +01:00
<!-- Arrière-plan -->
<div class="setting__section" data-setting="background">
<div class="setting__header">
<label class="label-with-tooltip" data-css="background">Arrière-plan</label>
</div>
<div class="setting__body">
<div class="input-with-color">
<input
ref="backgroundColorInput"
type="text"
id="background"
v-model="background.value"
data-coloris
/>
</div>
</div>
</div>
2026-03-07 19:59:01 +01:00
<!-- Motif -->
<div class="setting__section setting-disabled" data-setting="pattern" title="Fonctionnalité à venir">
<div class="setting__header">
<label class="label-with-tooltip" data-css="background-image">Motif</label>
</div>
<div class="setting__body">
<select id="pattern" v-model="pattern" disabled>
<option value="">Choisissez</option>
<option value="dots">Points</option>
<option value="lines">Lignes</option>
<option value="grid">Grille</option>
</select>
</div>
</div>
2026-03-07 19:59:01 +01:00
<!-- Options -->
<div class="setting__section" data-setting="options">
<div class="setting__header">
<label>Options</label>
</div>
2026-03-07 19:59:01 +01:00
<div class="setting__body">
<div class="field-checkbox">
<input id="page-numbers" type="checkbox" v-model="pageNumbers" />
<label for="page-numbers" class="label-with-tooltip" data-css="@bottom-left/right">Numéro de page</label>
</div>
<div class="field-checkbox">
<input id="running-title" type="checkbox" v-model="runningTitle" />
<label for="running-title" class="label-with-tooltip" data-css="string-set">Titre courant</label>
</div>
</div>
</div>
2026-03-07 19:59:01 +01:00
</div>
</section>
</template>
<script setup>
2026-03-08 08:51:22 +01:00
import { ref, computed, watch, onMounted, inject, nextTick } from 'vue';
2026-03-07 19:59:01 +01:00
import bookIcon from '/assets/svg/book.svg?raw';
import { useStylesheetStore } from '../../stores/stylesheet';
import { useDebounce } from '../../composables/useDebounce';
import { useCssSync } from '../../composables/useCssSync';
import { initColoris } from '../../composables/useColoris';
2025-12-09 17:08:40 +01:00
import NumberInput from '../ui/NumberInput.vue';
import { convertUnit } from '../../utils/unit-conversion';
const stylesheetStore = useStylesheetStore();
const { debouncedUpdate } = useDebounce(500);
const { extractSpacing } = useCssSync();
const backgroundColorInput = ref(null);
const activeTab = inject('activeTab', ref('document'));
let isUpdatingFromStore = false;
const pageFormat = ref('A4');
const pageFormats = {
A4: { width: 210, height: 297 },
A5: { width: 148, height: 210 },
// A3: { width: 297, height: 420 },
// letter: { width: 216, height: 279 },
// legal: { width: 216, height: 356 },
};
const customWidth = ref(210);
const customHeight = ref(297);
const isCustomFormat = computed(() => pageFormat.value === 'custom');
const margins = ref({
top: { value: 20, unit: 'mm' },
bottom: { value: 20, unit: 'mm' },
left: { value: 20, unit: 'mm' },
right: { value: 20, unit: 'mm' },
});
const updateMarginUnit = (side, newUnit) => {
const m = margins.value[side];
m.value = convertUnit(m.value, m.unit, newUnit);
m.unit = newUnit;
};
const background = ref({
value: '',
format: 'hex',
});
const pattern = ref('');
const pageNumbers = ref(false);
const runningTitle = ref(false);
const immediateUpdate = (callback) => {
callback();
};
watch(pageFormat, (newFormat) => {
if (isUpdatingFromStore) return;
if (newFormat === 'custom') {
immediateUpdate(() => {
stylesheetStore.updateProperty(
'@page',
'size',
`${customWidth.value}mm ${customHeight.value}mm`,
''
);
});
} else {
const format = pageFormats[newFormat];
if (format) {
customWidth.value = format.width;
customHeight.value = format.height;
}
immediateUpdate(() => {
stylesheetStore.updateProperty('@page', 'size', newFormat, '');
});
}
});
watch([customWidth, customHeight], () => {
if (isUpdatingFromStore || !isCustomFormat.value) return;
debouncedUpdate(() => {
stylesheetStore.updateProperty(
'@page',
'size',
`${customWidth.value}mm ${customHeight.value}mm`,
''
);
});
});
const updateMargins = () => {
const marginValue = `${margins.value.top.value}${margins.value.top.unit} ${margins.value.right.value}${margins.value.right.unit} ${margins.value.bottom.value}${margins.value.bottom.unit} ${margins.value.left.value}${margins.value.left.unit}`;
2026-03-07 20:05:48 +01:00
stylesheetStore.updateProperty('@page', 'margin', marginValue, '');
};
// Watch margin values (number inputs) with debounce
watch(
() => [
margins.value.top.value,
margins.value.bottom.value,
margins.value.left.value,
margins.value.right.value,
],
() => {
if (isUpdatingFromStore) return;
debouncedUpdate(updateMargins);
}
);
// Watch margin units (button clicks) without debounce
watch(
() => [
margins.value.top.unit,
margins.value.bottom.unit,
margins.value.left.unit,
margins.value.right.unit,
],
() => {
if (isUpdatingFromStore) return;
immediateUpdate(updateMargins);
}
);
const updateBackground = () => {
if (!background.value.value) return;
2026-03-07 20:05:48 +01:00
stylesheetStore.updateProperty('@page', 'background', background.value.value, '');
};
// Watch background value (text input) with debounce
watch(
() => background.value.value,
() => {
if (isUpdatingFromStore) return;
debouncedUpdate(updateBackground);
}
);
// Watch background format (button clicks) without debounce
watch(
() => background.value.format,
() => {
if (isUpdatingFromStore) return;
immediateUpdate(updateBackground);
}
);
watch(pattern, (newPattern) => {
if (!newPattern || isUpdatingFromStore) return;
immediateUpdate(() => {
// TODO: implement pattern application
});
});
watch(pageNumbers, (enabled) => {
if (isUpdatingFromStore) return;
immediateUpdate(() => {
feat: implement page numbers, running titles, and smooth iframe transitions Add functional page number and running title toggles with proper positioning for left/right pages. Implement smooth crossfade transitions between iframe reloads to eliminate visual flicker during PagedJS rendering. Page Numbers & Running Titles: - Page numbers toggle: adds counter(page) to @bottom-left (left pages) or @bottom-right (right pages) - Running title toggle: adds string(title) from h2 chapter titles - Combined positioning: both elements appear side-by-side in same margin box - Left pages: "1 Chapter Title" in @bottom-left - Right pages: "Chapter Title 2" in @bottom-right - Automatic CSS rule management: adds/removes @page:left, @page:right, and string-set rules based on checkbox state - Bidirectional sync: checkboxes reflect existing CSS state on load Smooth Iframe Transitions: - Dual iframe system: two iframes alternate as visible/hidden - Crossfade technique: hidden iframe loads new content while visible remains displayed, then smooth 300ms opacity transition - Scroll preservation: saves scroll percentage from visible iframe, restores to hidden iframe after PagedJS render - Collision prevention: isTransitioning flag prevents overlapping renders - Active frame tracking: computed property ensures ElementPopup always references the visible iframe Technical details: - Uses srcdoc to inject HTML with dynamic CSS - Z-index and opacity manipulation for layering - CSS transitions (opacity 0.3s ease-in-out) - Automatic frame swapping after transition completes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 15:27:23 +01:00
updatePageFooters();
});
});
watch(runningTitle, (enabled) => {
if (isUpdatingFromStore) return;
immediateUpdate(() => {
feat: implement page numbers, running titles, and smooth iframe transitions Add functional page number and running title toggles with proper positioning for left/right pages. Implement smooth crossfade transitions between iframe reloads to eliminate visual flicker during PagedJS rendering. Page Numbers & Running Titles: - Page numbers toggle: adds counter(page) to @bottom-left (left pages) or @bottom-right (right pages) - Running title toggle: adds string(title) from h2 chapter titles - Combined positioning: both elements appear side-by-side in same margin box - Left pages: "1 Chapter Title" in @bottom-left - Right pages: "Chapter Title 2" in @bottom-right - Automatic CSS rule management: adds/removes @page:left, @page:right, and string-set rules based on checkbox state - Bidirectional sync: checkboxes reflect existing CSS state on load Smooth Iframe Transitions: - Dual iframe system: two iframes alternate as visible/hidden - Crossfade technique: hidden iframe loads new content while visible remains displayed, then smooth 300ms opacity transition - Scroll preservation: saves scroll percentage from visible iframe, restores to hidden iframe after PagedJS render - Collision prevention: isTransitioning flag prevents overlapping renders - Active frame tracking: computed property ensures ElementPopup always references the visible iframe Technical details: - Uses srcdoc to inject HTML with dynamic CSS - Z-index and opacity manipulation for layering - CSS transitions (opacity 0.3s ease-in-out) - Automatic frame swapping after transition completes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 15:27:23 +01:00
updatePageFooters();
});
});
feat: implement page numbers, running titles, and smooth iframe transitions Add functional page number and running title toggles with proper positioning for left/right pages. Implement smooth crossfade transitions between iframe reloads to eliminate visual flicker during PagedJS rendering. Page Numbers & Running Titles: - Page numbers toggle: adds counter(page) to @bottom-left (left pages) or @bottom-right (right pages) - Running title toggle: adds string(title) from h2 chapter titles - Combined positioning: both elements appear side-by-side in same margin box - Left pages: "1 Chapter Title" in @bottom-left - Right pages: "Chapter Title 2" in @bottom-right - Automatic CSS rule management: adds/removes @page:left, @page:right, and string-set rules based on checkbox state - Bidirectional sync: checkboxes reflect existing CSS state on load Smooth Iframe Transitions: - Dual iframe system: two iframes alternate as visible/hidden - Crossfade technique: hidden iframe loads new content while visible remains displayed, then smooth 300ms opacity transition - Scroll preservation: saves scroll percentage from visible iframe, restores to hidden iframe after PagedJS render - Collision prevention: isTransitioning flag prevents overlapping renders - Active frame tracking: computed property ensures ElementPopup always references the visible iframe Technical details: - Uses srcdoc to inject HTML with dynamic CSS - Z-index and opacity manipulation for layering - CSS transitions (opacity 0.3s ease-in-out) - Automatic frame swapping after transition completes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 15:27:23 +01:00
const updatePageFooters = () => {
let currentCss = stylesheetStore.customCss;
feat: implement page numbers, running titles, and smooth iframe transitions Add functional page number and running title toggles with proper positioning for left/right pages. Implement smooth crossfade transitions between iframe reloads to eliminate visual flicker during PagedJS rendering. Page Numbers & Running Titles: - Page numbers toggle: adds counter(page) to @bottom-left (left pages) or @bottom-right (right pages) - Running title toggle: adds string(title) from h2 chapter titles - Combined positioning: both elements appear side-by-side in same margin box - Left pages: "1 Chapter Title" in @bottom-left - Right pages: "Chapter Title 2" in @bottom-right - Automatic CSS rule management: adds/removes @page:left, @page:right, and string-set rules based on checkbox state - Bidirectional sync: checkboxes reflect existing CSS state on load Smooth Iframe Transitions: - Dual iframe system: two iframes alternate as visible/hidden - Crossfade technique: hidden iframe loads new content while visible remains displayed, then smooth 300ms opacity transition - Scroll preservation: saves scroll percentage from visible iframe, restores to hidden iframe after PagedJS render - Collision prevention: isTransitioning flag prevents overlapping renders - Active frame tracking: computed property ensures ElementPopup always references the visible iframe Technical details: - Uses srcdoc to inject HTML with dynamic CSS - Z-index and opacity manipulation for layering - CSS transitions (opacity 0.3s ease-in-out) - Automatic frame swapping after transition completes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 15:27:23 +01:00
// Remove existing @page:left and @page:right rules
currentCss = currentCss.replace(/@page:left\s*\{[^}]*\}/g, '');
currentCss = currentCss.replace(/@page:right\s*\{[^}]*\}/g, '');
// Remove old @page @bottom-center rule if exists
currentCss = currentCss.replace(
/@page\s*\{[^}]*@bottom-center[^}]*\}/g,
(match) => {
return match.replace(/@bottom-center\s*\{[^}]*\}/g, '');
}
);
feat: implement page numbers, running titles, and smooth iframe transitions Add functional page number and running title toggles with proper positioning for left/right pages. Implement smooth crossfade transitions between iframe reloads to eliminate visual flicker during PagedJS rendering. Page Numbers & Running Titles: - Page numbers toggle: adds counter(page) to @bottom-left (left pages) or @bottom-right (right pages) - Running title toggle: adds string(title) from h2 chapter titles - Combined positioning: both elements appear side-by-side in same margin box - Left pages: "1 Chapter Title" in @bottom-left - Right pages: "Chapter Title 2" in @bottom-right - Automatic CSS rule management: adds/removes @page:left, @page:right, and string-set rules based on checkbox state - Bidirectional sync: checkboxes reflect existing CSS state on load Smooth Iframe Transitions: - Dual iframe system: two iframes alternate as visible/hidden - Crossfade technique: hidden iframe loads new content while visible remains displayed, then smooth 300ms opacity transition - Scroll preservation: saves scroll percentage from visible iframe, restores to hidden iframe after PagedJS render - Collision prevention: isTransitioning flag prevents overlapping renders - Active frame tracking: computed property ensures ElementPopup always references the visible iframe Technical details: - Uses srcdoc to inject HTML with dynamic CSS - Z-index and opacity manipulation for layering - CSS transitions (opacity 0.3s ease-in-out) - Automatic frame swapping after transition completes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 15:27:23 +01:00
// Remove string-set rule if running title is disabled
if (!runningTitle.value) {
currentCss = currentCss.replace(
/\.chapter\s*>\s*h2\s*\{[^}]*string-set:[^}]*\}\s*/g,
''
);
feat: implement page numbers, running titles, and smooth iframe transitions Add functional page number and running title toggles with proper positioning for left/right pages. Implement smooth crossfade transitions between iframe reloads to eliminate visual flicker during PagedJS rendering. Page Numbers & Running Titles: - Page numbers toggle: adds counter(page) to @bottom-left (left pages) or @bottom-right (right pages) - Running title toggle: adds string(title) from h2 chapter titles - Combined positioning: both elements appear side-by-side in same margin box - Left pages: "1 Chapter Title" in @bottom-left - Right pages: "Chapter Title 2" in @bottom-right - Automatic CSS rule management: adds/removes @page:left, @page:right, and string-set rules based on checkbox state - Bidirectional sync: checkboxes reflect existing CSS state on load Smooth Iframe Transitions: - Dual iframe system: two iframes alternate as visible/hidden - Crossfade technique: hidden iframe loads new content while visible remains displayed, then smooth 300ms opacity transition - Scroll preservation: saves scroll percentage from visible iframe, restores to hidden iframe after PagedJS render - Collision prevention: isTransitioning flag prevents overlapping renders - Active frame tracking: computed property ensures ElementPopup always references the visible iframe Technical details: - Uses srcdoc to inject HTML with dynamic CSS - Z-index and opacity manipulation for layering - CSS transitions (opacity 0.3s ease-in-out) - Automatic frame swapping after transition completes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 15:27:23 +01:00
} else if (!currentCss.includes('string-set: title')) {
// Add the string-set rule for h2 titles if running title is enabled
const stringSetRule =
'\n.chapter > h2 {\n string-set: title content(text);\n}\n';
feat: implement page numbers, running titles, and smooth iframe transitions Add functional page number and running title toggles with proper positioning for left/right pages. Implement smooth crossfade transitions between iframe reloads to eliminate visual flicker during PagedJS rendering. Page Numbers & Running Titles: - Page numbers toggle: adds counter(page) to @bottom-left (left pages) or @bottom-right (right pages) - Running title toggle: adds string(title) from h2 chapter titles - Combined positioning: both elements appear side-by-side in same margin box - Left pages: "1 Chapter Title" in @bottom-left - Right pages: "Chapter Title 2" in @bottom-right - Automatic CSS rule management: adds/removes @page:left, @page:right, and string-set rules based on checkbox state - Bidirectional sync: checkboxes reflect existing CSS state on load Smooth Iframe Transitions: - Dual iframe system: two iframes alternate as visible/hidden - Crossfade technique: hidden iframe loads new content while visible remains displayed, then smooth 300ms opacity transition - Scroll preservation: saves scroll percentage from visible iframe, restores to hidden iframe after PagedJS render - Collision prevention: isTransitioning flag prevents overlapping renders - Active frame tracking: computed property ensures ElementPopup always references the visible iframe Technical details: - Uses srcdoc to inject HTML with dynamic CSS - Z-index and opacity manipulation for layering - CSS transitions (opacity 0.3s ease-in-out) - Automatic frame swapping after transition completes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 15:27:23 +01:00
currentCss += stringSetRule;
}
// Build new rules based on checkboxes
let leftPageRule = '';
let rightPageRule = '';
if (pageNumbers.value || runningTitle.value) {
// Left pages: page number bottom-left, running title right next to it (bottom-left-corner)
let leftBottomLeft = '';
let leftBottomCenter = '';
if (pageNumbers.value && runningTitle.value) {
// Page number on the left, title right next to it
leftBottomLeft =
' @bottom-left {\n content: counter(page) " " string(title);\n }\n';
feat: implement page numbers, running titles, and smooth iframe transitions Add functional page number and running title toggles with proper positioning for left/right pages. Implement smooth crossfade transitions between iframe reloads to eliminate visual flicker during PagedJS rendering. Page Numbers & Running Titles: - Page numbers toggle: adds counter(page) to @bottom-left (left pages) or @bottom-right (right pages) - Running title toggle: adds string(title) from h2 chapter titles - Combined positioning: both elements appear side-by-side in same margin box - Left pages: "1 Chapter Title" in @bottom-left - Right pages: "Chapter Title 2" in @bottom-right - Automatic CSS rule management: adds/removes @page:left, @page:right, and string-set rules based on checkbox state - Bidirectional sync: checkboxes reflect existing CSS state on load Smooth Iframe Transitions: - Dual iframe system: two iframes alternate as visible/hidden - Crossfade technique: hidden iframe loads new content while visible remains displayed, then smooth 300ms opacity transition - Scroll preservation: saves scroll percentage from visible iframe, restores to hidden iframe after PagedJS render - Collision prevention: isTransitioning flag prevents overlapping renders - Active frame tracking: computed property ensures ElementPopup always references the visible iframe Technical details: - Uses srcdoc to inject HTML with dynamic CSS - Z-index and opacity manipulation for layering - CSS transitions (opacity 0.3s ease-in-out) - Automatic frame swapping after transition completes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 15:27:23 +01:00
} else if (pageNumbers.value) {
leftBottomLeft = ' @bottom-left {\n content: counter(page);\n }\n';
} else if (runningTitle.value) {
leftBottomLeft = ' @bottom-left {\n content: string(title);\n }\n';
}
if (leftBottomLeft || leftBottomCenter) {
leftPageRule = `@page:left {\n${leftBottomLeft}${leftBottomCenter}}\n\n`;
}
// Right pages: title on the left, page number on the right (next to it)
let rightBottomRight = '';
if (pageNumbers.value && runningTitle.value) {
// Title on the left of page number
rightBottomRight =
' @bottom-right {\n content: string(title) " " counter(page);\n }\n';
feat: implement page numbers, running titles, and smooth iframe transitions Add functional page number and running title toggles with proper positioning for left/right pages. Implement smooth crossfade transitions between iframe reloads to eliminate visual flicker during PagedJS rendering. Page Numbers & Running Titles: - Page numbers toggle: adds counter(page) to @bottom-left (left pages) or @bottom-right (right pages) - Running title toggle: adds string(title) from h2 chapter titles - Combined positioning: both elements appear side-by-side in same margin box - Left pages: "1 Chapter Title" in @bottom-left - Right pages: "Chapter Title 2" in @bottom-right - Automatic CSS rule management: adds/removes @page:left, @page:right, and string-set rules based on checkbox state - Bidirectional sync: checkboxes reflect existing CSS state on load Smooth Iframe Transitions: - Dual iframe system: two iframes alternate as visible/hidden - Crossfade technique: hidden iframe loads new content while visible remains displayed, then smooth 300ms opacity transition - Scroll preservation: saves scroll percentage from visible iframe, restores to hidden iframe after PagedJS render - Collision prevention: isTransitioning flag prevents overlapping renders - Active frame tracking: computed property ensures ElementPopup always references the visible iframe Technical details: - Uses srcdoc to inject HTML with dynamic CSS - Z-index and opacity manipulation for layering - CSS transitions (opacity 0.3s ease-in-out) - Automatic frame swapping after transition completes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 15:27:23 +01:00
} else if (pageNumbers.value) {
rightBottomRight =
' @bottom-right {\n content: counter(page);\n }\n';
feat: implement page numbers, running titles, and smooth iframe transitions Add functional page number and running title toggles with proper positioning for left/right pages. Implement smooth crossfade transitions between iframe reloads to eliminate visual flicker during PagedJS rendering. Page Numbers & Running Titles: - Page numbers toggle: adds counter(page) to @bottom-left (left pages) or @bottom-right (right pages) - Running title toggle: adds string(title) from h2 chapter titles - Combined positioning: both elements appear side-by-side in same margin box - Left pages: "1 Chapter Title" in @bottom-left - Right pages: "Chapter Title 2" in @bottom-right - Automatic CSS rule management: adds/removes @page:left, @page:right, and string-set rules based on checkbox state - Bidirectional sync: checkboxes reflect existing CSS state on load Smooth Iframe Transitions: - Dual iframe system: two iframes alternate as visible/hidden - Crossfade technique: hidden iframe loads new content while visible remains displayed, then smooth 300ms opacity transition - Scroll preservation: saves scroll percentage from visible iframe, restores to hidden iframe after PagedJS render - Collision prevention: isTransitioning flag prevents overlapping renders - Active frame tracking: computed property ensures ElementPopup always references the visible iframe Technical details: - Uses srcdoc to inject HTML with dynamic CSS - Z-index and opacity manipulation for layering - CSS transitions (opacity 0.3s ease-in-out) - Automatic frame swapping after transition completes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 15:27:23 +01:00
} else if (runningTitle.value) {
rightBottomRight =
' @bottom-right {\n content: string(title);\n }\n';
feat: implement page numbers, running titles, and smooth iframe transitions Add functional page number and running title toggles with proper positioning for left/right pages. Implement smooth crossfade transitions between iframe reloads to eliminate visual flicker during PagedJS rendering. Page Numbers & Running Titles: - Page numbers toggle: adds counter(page) to @bottom-left (left pages) or @bottom-right (right pages) - Running title toggle: adds string(title) from h2 chapter titles - Combined positioning: both elements appear side-by-side in same margin box - Left pages: "1 Chapter Title" in @bottom-left - Right pages: "Chapter Title 2" in @bottom-right - Automatic CSS rule management: adds/removes @page:left, @page:right, and string-set rules based on checkbox state - Bidirectional sync: checkboxes reflect existing CSS state on load Smooth Iframe Transitions: - Dual iframe system: two iframes alternate as visible/hidden - Crossfade technique: hidden iframe loads new content while visible remains displayed, then smooth 300ms opacity transition - Scroll preservation: saves scroll percentage from visible iframe, restores to hidden iframe after PagedJS render - Collision prevention: isTransitioning flag prevents overlapping renders - Active frame tracking: computed property ensures ElementPopup always references the visible iframe Technical details: - Uses srcdoc to inject HTML with dynamic CSS - Z-index and opacity manipulation for layering - CSS transitions (opacity 0.3s ease-in-out) - Automatic frame swapping after transition completes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 15:27:23 +01:00
}
if (rightBottomRight) {
rightPageRule = `@page:right {\n${rightBottomRight}}\n\n`;
}
}
// Insert the new rules after the main @page rule
const pageRuleMatch = currentCss.match(/@page\s*\{[^}]*\}/);
if (pageRuleMatch) {
const insertPosition = pageRuleMatch.index + pageRuleMatch[0].length;
currentCss =
currentCss.slice(0, insertPosition) +
'\n\n' +
leftPageRule +
rightPageRule +
currentCss.slice(insertPosition);
}
stylesheetStore.setCustomCss(currentCss);
feat: implement page numbers, running titles, and smooth iframe transitions Add functional page number and running title toggles with proper positioning for left/right pages. Implement smooth crossfade transitions between iframe reloads to eliminate visual flicker during PagedJS rendering. Page Numbers & Running Titles: - Page numbers toggle: adds counter(page) to @bottom-left (left pages) or @bottom-right (right pages) - Running title toggle: adds string(title) from h2 chapter titles - Combined positioning: both elements appear side-by-side in same margin box - Left pages: "1 Chapter Title" in @bottom-left - Right pages: "Chapter Title 2" in @bottom-right - Automatic CSS rule management: adds/removes @page:left, @page:right, and string-set rules based on checkbox state - Bidirectional sync: checkboxes reflect existing CSS state on load Smooth Iframe Transitions: - Dual iframe system: two iframes alternate as visible/hidden - Crossfade technique: hidden iframe loads new content while visible remains displayed, then smooth 300ms opacity transition - Scroll preservation: saves scroll percentage from visible iframe, restores to hidden iframe after PagedJS render - Collision prevention: isTransitioning flag prevents overlapping renders - Active frame tracking: computed property ensures ElementPopup always references the visible iframe Technical details: - Uses srcdoc to inject HTML with dynamic CSS - Z-index and opacity manipulation for layering - CSS transitions (opacity 0.3s ease-in-out) - Automatic frame swapping after transition completes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 15:27:23 +01:00
};
const syncFromStore = () => {
isUpdatingFromStore = true;
try {
const pageBlock = stylesheetStore.extractBlock('@page');
// Try named format first (A4, A5), then custom dimensions
const namedSizeMatch = pageBlock.match(/size:\s*(A4|A5)\b/);
if (namedSizeMatch) {
pageFormat.value = namedSizeMatch[1];
const format = pageFormats[namedSizeMatch[1]];
if (format) {
customWidth.value = format.width;
customHeight.value = format.height;
}
} else {
const customSizeMatch = pageBlock.match(
/size:\s*([0-9.]+)mm\s+([0-9.]+)mm/
);
if (customSizeMatch) {
customWidth.value = parseFloat(customSizeMatch[1]);
customHeight.value = parseFloat(customSizeMatch[2]);
pageFormat.value = 'custom';
}
}
const spacing = extractSpacing('@page', 'margin');
if (spacing?.detailed) {
margins.value.top = spacing.detailed.top;
margins.value.right = spacing.detailed.right;
margins.value.bottom = spacing.detailed.bottom;
margins.value.left = spacing.detailed.left;
} else if (spacing?.simple) {
margins.value.top = { ...spacing.simple };
margins.value.right = { ...spacing.simple };
margins.value.bottom = { ...spacing.simple };
margins.value.left = { ...spacing.simple };
}
const bgMatch = pageBlock.match(/background:\s*([^;]+)/);
if (bgMatch) {
background.value.value = bgMatch[1].trim();
}
feat: implement page numbers, running titles, and smooth iframe transitions Add functional page number and running title toggles with proper positioning for left/right pages. Implement smooth crossfade transitions between iframe reloads to eliminate visual flicker during PagedJS rendering. Page Numbers & Running Titles: - Page numbers toggle: adds counter(page) to @bottom-left (left pages) or @bottom-right (right pages) - Running title toggle: adds string(title) from h2 chapter titles - Combined positioning: both elements appear side-by-side in same margin box - Left pages: "1 Chapter Title" in @bottom-left - Right pages: "Chapter Title 2" in @bottom-right - Automatic CSS rule management: adds/removes @page:left, @page:right, and string-set rules based on checkbox state - Bidirectional sync: checkboxes reflect existing CSS state on load Smooth Iframe Transitions: - Dual iframe system: two iframes alternate as visible/hidden - Crossfade technique: hidden iframe loads new content while visible remains displayed, then smooth 300ms opacity transition - Scroll preservation: saves scroll percentage from visible iframe, restores to hidden iframe after PagedJS render - Collision prevention: isTransitioning flag prevents overlapping renders - Active frame tracking: computed property ensures ElementPopup always references the visible iframe Technical details: - Uses srcdoc to inject HTML with dynamic CSS - Z-index and opacity manipulation for layering - CSS transitions (opacity 0.3s ease-in-out) - Automatic frame swapping after transition completes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 15:27:23 +01:00
// Check for page numbers and running title in @page:left and @page:right
const leftPageMatch = stylesheetStore.content.match(
/@page:left\s*\{[^}]*\}/
);
const rightPageMatch = stylesheetStore.content.match(
/@page:right\s*\{[^}]*\}/
);
feat: implement page numbers, running titles, and smooth iframe transitions Add functional page number and running title toggles with proper positioning for left/right pages. Implement smooth crossfade transitions between iframe reloads to eliminate visual flicker during PagedJS rendering. Page Numbers & Running Titles: - Page numbers toggle: adds counter(page) to @bottom-left (left pages) or @bottom-right (right pages) - Running title toggle: adds string(title) from h2 chapter titles - Combined positioning: both elements appear side-by-side in same margin box - Left pages: "1 Chapter Title" in @bottom-left - Right pages: "Chapter Title 2" in @bottom-right - Automatic CSS rule management: adds/removes @page:left, @page:right, and string-set rules based on checkbox state - Bidirectional sync: checkboxes reflect existing CSS state on load Smooth Iframe Transitions: - Dual iframe system: two iframes alternate as visible/hidden - Crossfade technique: hidden iframe loads new content while visible remains displayed, then smooth 300ms opacity transition - Scroll preservation: saves scroll percentage from visible iframe, restores to hidden iframe after PagedJS render - Collision prevention: isTransitioning flag prevents overlapping renders - Active frame tracking: computed property ensures ElementPopup always references the visible iframe Technical details: - Uses srcdoc to inject HTML with dynamic CSS - Z-index and opacity manipulation for layering - CSS transitions (opacity 0.3s ease-in-out) - Automatic frame swapping after transition completes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 15:27:23 +01:00
// Check if page numbers exist (counter(page) in either left or right)
const hasPageNumbers =
(leftPageMatch && leftPageMatch[0].includes('counter(page)')) ||
(rightPageMatch && rightPageMatch[0].includes('counter(page)'));
pageNumbers.value = hasPageNumbers;
// Check if running title exists (string(title) in either left or right)
const hasRunningTitle =
(leftPageMatch && leftPageMatch[0].includes('string(title)')) ||
(rightPageMatch && rightPageMatch[0].includes('string(title)'));
runningTitle.value = hasRunningTitle;
} finally {
2026-03-08 08:51:22 +01:00
nextTick(() => { isUpdatingFromStore = false; });
}
};
watch(
() => stylesheetStore.content,
() => {
if (!isUpdatingFromStore) {
syncFromStore();
}
}
);
const updateColorisButton = () => {
const input = backgroundColorInput.value;
if (input && background.value.value) {
// Force Coloris to update by triggering a change event
const event = new Event('input', { bubbles: true });
input.dispatchEvent(event);
}
};
// Watch for when the user returns to the "document" tab
watch(activeTab, (newTab, oldTab) => {
if (
newTab === 'document' &&
oldTab !== 'document' &&
background.value.value
) {
// Small delay to ensure DOM is ready
setTimeout(updateColorisButton, 100);
}
});
onMounted(() => {
syncFromStore();
initColoris();
// Initialize button color if value exists
if (background.value.value) {
setTimeout(updateColorisButton, 100);
}
});
</script>