From b544928a6593fc293abe03e80d8106e319d66892 Mon Sep 17 00:00:00 2001 From: Julie Blanc Date: Mon, 9 Mar 2026 15:37:05 +0100 Subject: [PATCH] reload custom css --- public/assets/css/pagedjs-interface.css | 2 +- .../5_test-avec-geoformat/narrative.txt | 65 +++++++++++++++++-- src/App.vue | 4 ++ src/composables/useElementSettings.js | 49 +++++--------- src/composables/useProjectFonts.js | 14 ++++ src/stores/stylesheet.js | 6 ++ src/utils/css-parsing.js | 14 +++- 7 files changed, 115 insertions(+), 39 deletions(-) diff --git a/public/assets/css/pagedjs-interface.css b/public/assets/css/pagedjs-interface.css index c03eeb7..2361147 100644 --- a/public/assets/css/pagedjs-interface.css +++ b/public/assets/css/pagedjs-interface.css @@ -161,7 +161,7 @@ .element-selected { outline: 2px dashed #7136ff !important; - background-color: #7136ff1a !important; + /* background-color: #7136ff1a !important; */ } .element-hover-label { diff --git a/public/content/cohesion-des-mondes/5_test-avec-geoformat/narrative.txt b/public/content/cohesion-des-mondes/5_test-avec-geoformat/narrative.txt index 19c9834..9186dc5 100644 --- a/public/content/cohesion-des-mondes/5_test-avec-geoformat/narrative.txt +++ b/public/content/cohesion-des-mondes/5_test-avec-geoformat/narrative.txt @@ -31,21 +31,30 @@ body { } p { - font-size: 14px; + font-size: 22px; line-height: 18px; } h1 { - font-size: 38px; + font-size: 28px; line-height: 38px; - background: rgba(255, 255, 255, 0.521); + background: rgba(60, 143, 66, 0.52); padding-top: 10px; padding-right: 20px; padding-bottom: 10px; padding-left: 20px; margin-top: 60px; margin-bottom: 30px; - font-family: "Montserrat"; + font-family: "EB Garamond"; + font-weight: normal; + margin-right: 0mm; + margin-left: 0mm; + color: rgb(224, 9, 9); + border-style: solid; + border-color: #e01b1b; + border-width: 3px; + text-align: right; + font-style: italic; } em { @@ -68,6 +77,54 @@ a { color: #0000ee; text-decoration-line: underline; } +h5 { + font-weight: bold; +} + +h4 { + font-size: 20px; + background: #dedede; + font-weight: bold; + text-align: center; + margin-bottom: 30px; +} + +h3 { + font-size: 20px; + border-width: 2px; + font-weight: bold; + border-style: dotted; + border-color: #000000; + margin-top: 30px; + margin-bottom: 20px; +} + +h2 { + font-size: 38px; + border-width: 2px; + font-weight: bold; + border-style: solid; + border-color: #000000; + padding-top: 20px; + padding-right: 20px; + padding-bottom: 20px; + padding-left: 20px; + margin-top: 36px; +} + +p.author { + font-size: 24px; + background: rgba(255, 255, 255, 0.521); + font-weight: bold; + padding-top: 10px; + padding-right: 20px; + padding-bottom: 10px; + padding-left: 20px; + margin-top: 20px; + margin-right: 0px; + margin-bottom: 0px; + margin-left: 0px; +} ---- diff --git a/src/App.vue b/src/App.vue index 1cddf87..f84f12e 100644 --- a/src/App.vue +++ b/src/App.vue @@ -14,9 +14,11 @@ import { useKeyboardShortcuts } from './composables/useKeyboardShortcuts'; import { useIframeInteractions } from './composables/useIframeInteractions'; import { usePreviewRenderer } from './composables/usePreviewRenderer'; import { usePrintPreview } from './composables/usePrintPreview'; +import { useProjectFonts } from './composables/useProjectFonts'; const stylesheetStore = useStylesheetStore(); const narrativeStore = useNarrativeStore(); +const { loadFontsFromCss } = useProjectFonts(); const previewFrame1 = ref(null); const previewFrame2 = ref(null); @@ -86,6 +88,8 @@ onMounted(async () => { // Initialize stylesheet with custom CSS if (narrativeStore.data) { await stylesheetStore.initializeFromNarrative(narrativeStore.data); + // Pre-load any fonts referenced in the saved CSS so @font-face rules are ready for the preview + await loadFontsFromCss(stylesheetStore.customCss); } // Render preview after data is loaded diff --git a/src/composables/useElementSettings.js b/src/composables/useElementSettings.js index 6b24e9f..162256a 100644 --- a/src/composables/useElementSettings.js +++ b/src/composables/useElementSettings.js @@ -228,7 +228,9 @@ export function useElementSettings({ margin, padding, basePopup }) { removeProps(['color']); stylesheetStore.updateProperty('body', 'color', textDefaults.color); } else if (group === 'font') { - removeProps(['font-family', 'font-style', 'font-weight']); + removeProps(['font-family', 'font-style']); + // Write font-weight: normal explicitly so applyDefaultsCss() won't re-apply bold defaults on reload + stylesheetStore.updateProperty(selector.value, 'font-weight', 'normal'); const fontVal = textDefaults.fontFamily === 'sans-serif' ? 'sans-serif' : `"${textDefaults.fontFamily}"`; stylesheetStore.updateProperty('body', 'font-family', fontVal); } @@ -422,36 +424,14 @@ export function useElementSettings({ margin, padding, basePopup }) { const specialGroupDiffersFromDefault = (group, parsed) => { switch (group) { - case 'font': { - if ('font-family' in parsed) { - const val = parsed['font-family'].split(',')[0].trim().replace(/['"]/g, ''); - if (val !== textDefaults.fontFamily) return true; - } - if ('font-style' in parsed && parsed['font-style'].trim() === 'italic') return true; - if ('font-weight' in parsed) { - const v = parsed['font-weight'].trim(); - if (v === 'bold' || parseInt(v) >= 700) return true; - } - return false; - } - case 'fontSize': { - if ('font-size' in parsed) { - const m = parsed['font-size'].match(/([\d.]+)(px|rem|em|mm|cm|in)?/); - if (m) return parseFloat(m[1]) !== textDefaults.fontSize.value || (m[2] || 'px') !== textDefaults.fontSize.unit; - } - return false; - } - case 'lineHeight': { - if ('line-height' in parsed) { - const m = parsed['line-height'].match(/([\d.]+)(px|rem|em|mm|cm|in)?/); - if (m) return parseFloat(m[1]) !== textDefaults.lineHeight.value || (m[2] || 'px') !== textDefaults.lineHeight.unit; - } - return false; - } - case 'color': { - if ('color' in parsed) return parsed['color'].trim() !== textDefaults.color; - return false; - } + case 'font': + return 'font-family' in parsed || 'font-style' in parsed || 'font-weight' in parsed; + case 'fontSize': + return 'font-size' in parsed; + case 'lineHeight': + return 'line-height' in parsed; + case 'color': + return 'color' in parsed; } return false; }; @@ -734,6 +714,13 @@ export function useElementSettings({ margin, padding, basePopup }) { loadValuesFromStylesheet(true); + // If no session state, sync toggles from custom CSS block (handles page reload with saved CSS) + // Uses extractCustomBlock (not extractBlock) to avoid activating toggles for base stylesheet rules + if (!stored) { + const cssBlock = stylesheetStore.extractCustomBlock(selector.value); + if (cssBlock) syncRefsFromCss(cssBlock); + } + if (stored && stored.values) { if (settingEnabled.font) { fontFamily.value = stored.values.fontFamily; diff --git a/src/composables/useProjectFonts.js b/src/composables/useProjectFonts.js index 822a7a0..87abf4a 100644 --- a/src/composables/useProjectFonts.js +++ b/src/composables/useProjectFonts.js @@ -134,6 +134,19 @@ async function loadFont(fontName) { stylesheetStore.setFontFaceCss(loadedFontsCss.value); } +async function loadFontsFromCss(css) { + if (!css) return; + const matches = css.matchAll(/font-family:\s*["']?([^"';,\n]+)["']?/gi); + const names = new Set(); + for (const m of matches) { + const name = m[1].trim().replace(/['"]/g, ''); + if (name && name !== 'sans-serif' && name !== 'serif' && name !== 'monospace') { + names.add(name); + } + } + await Promise.all([...names].map((name) => loadFont(name))); +} + async function loadAllFontPreviews() { // Only loads fonts into document.fonts for select preview — does NOT update the stylesheet store const promises = fonts @@ -148,6 +161,7 @@ export function useProjectFonts() { fonts, loadFont, loadAllFontPreviews, + loadFontsFromCss, loadedFontsCss, }; } diff --git a/src/stores/stylesheet.js b/src/stores/stylesheet.js index b711f31..59082d1 100644 --- a/src/stores/stylesheet.js +++ b/src/stores/stylesheet.js @@ -116,6 +116,11 @@ export const useStylesheetStore = defineStore('stylesheet', () => { return cssParsingUtils.extractCssBlock(baseCss.value, selector); }; + // Extract block only from custom CSS (used for toggle activation on load) + const extractCustomBlock = (selector) => { + return cssParsingUtils.extractCssBlock(customCss.value, selector); + }; + // Replace a CSS block in custom CSS (handles blocks from base CSS too) const replaceBlock = (oldBlock, newBlock) => { // Check if the old block exists in custom CSS @@ -369,6 +374,7 @@ export const useStylesheetStore = defineStore('stylesheet', () => { updateProperty, extractValue, extractBlock, + extractCustomBlock, replaceBlock, replaceInCustomCss, setCustomCss, diff --git a/src/utils/css-parsing.js b/src/utils/css-parsing.js index 24a4299..638c703 100644 --- a/src/utils/css-parsing.js +++ b/src/utils/css-parsing.js @@ -6,12 +6,20 @@ const extractCssBlock = (css, selector) => { const extractCssValue = (css, selector, property) => { const escaped = selector.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); - const regex = new RegExp( + // Try numeric with unit first + const numericRegex = new RegExp( `${escaped}\\s*{[^}]*${property}:\\s*([\\d.]+)(px|rem|em|mm|cm|in)`, 'i' ); - const match = css.match(regex); - return match ? { value: parseFloat(match[1]), unit: match[2] } : null; + const numericMatch = css.match(numericRegex); + if (numericMatch) return { value: parseFloat(numericMatch[1]), unit: numericMatch[2] }; + // Fallback: any string value (colors, keywords, font names, etc.) + const stringRegex = new RegExp( + `${escaped}\\s*\\{[^}]*?${property}:\\s*([^;}\\n]+)`, + 'i' + ); + const stringMatch = css.match(stringRegex); + return stringMatch ? stringMatch[1].trim() : null; }; const updateCssValue = ({ css, selector, property, value, unit }) => {