reload custom css

This commit is contained in:
Julie Blanc 2026-03-09 15:37:05 +01:00
parent 9c58edb425
commit b544928a65
7 changed files with 115 additions and 39 deletions

View file

@ -161,7 +161,7 @@
.element-selected { .element-selected {
outline: 2px dashed #7136ff !important; outline: 2px dashed #7136ff !important;
background-color: #7136ff1a !important; /* background-color: #7136ff1a !important; */
} }
.element-hover-label { .element-hover-label {

View file

@ -31,21 +31,30 @@ body {
} }
p { p {
font-size: 14px; font-size: 22px;
line-height: 18px; line-height: 18px;
} }
h1 { h1 {
font-size: 38px; font-size: 28px;
line-height: 38px; line-height: 38px;
background: rgba(255, 255, 255, 0.521); background: rgba(60, 143, 66, 0.52);
padding-top: 10px; padding-top: 10px;
padding-right: 20px; padding-right: 20px;
padding-bottom: 10px; padding-bottom: 10px;
padding-left: 20px; padding-left: 20px;
margin-top: 60px; margin-top: 60px;
margin-bottom: 30px; 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 { em {
@ -68,6 +77,54 @@ a {
color: #0000ee; color: #0000ee;
text-decoration-line: underline; 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;
}
---- ----

View file

@ -14,9 +14,11 @@ import { useKeyboardShortcuts } from './composables/useKeyboardShortcuts';
import { useIframeInteractions } from './composables/useIframeInteractions'; import { useIframeInteractions } from './composables/useIframeInteractions';
import { usePreviewRenderer } from './composables/usePreviewRenderer'; import { usePreviewRenderer } from './composables/usePreviewRenderer';
import { usePrintPreview } from './composables/usePrintPreview'; import { usePrintPreview } from './composables/usePrintPreview';
import { useProjectFonts } from './composables/useProjectFonts';
const stylesheetStore = useStylesheetStore(); const stylesheetStore = useStylesheetStore();
const narrativeStore = useNarrativeStore(); const narrativeStore = useNarrativeStore();
const { loadFontsFromCss } = useProjectFonts();
const previewFrame1 = ref(null); const previewFrame1 = ref(null);
const previewFrame2 = ref(null); const previewFrame2 = ref(null);
@ -86,6 +88,8 @@ onMounted(async () => {
// Initialize stylesheet with custom CSS // Initialize stylesheet with custom CSS
if (narrativeStore.data) { if (narrativeStore.data) {
await stylesheetStore.initializeFromNarrative(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 // Render preview after data is loaded

View file

@ -228,7 +228,9 @@ export function useElementSettings({ margin, padding, basePopup }) {
removeProps(['color']); removeProps(['color']);
stylesheetStore.updateProperty('body', 'color', textDefaults.color); stylesheetStore.updateProperty('body', 'color', textDefaults.color);
} else if (group === 'font') { } 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}"`; const fontVal = textDefaults.fontFamily === 'sans-serif' ? 'sans-serif' : `"${textDefaults.fontFamily}"`;
stylesheetStore.updateProperty('body', 'font-family', fontVal); stylesheetStore.updateProperty('body', 'font-family', fontVal);
} }
@ -422,36 +424,14 @@ export function useElementSettings({ margin, padding, basePopup }) {
const specialGroupDiffersFromDefault = (group, parsed) => { const specialGroupDiffersFromDefault = (group, parsed) => {
switch (group) { switch (group) {
case 'font': { case 'font':
if ('font-family' in parsed) { return 'font-family' in parsed || 'font-style' in parsed || 'font-weight' in parsed;
const val = parsed['font-family'].split(',')[0].trim().replace(/['"]/g, ''); case 'fontSize':
if (val !== textDefaults.fontFamily) return true; return 'font-size' in parsed;
} case 'lineHeight':
if ('font-style' in parsed && parsed['font-style'].trim() === 'italic') return true; return 'line-height' in parsed;
if ('font-weight' in parsed) { case 'color':
const v = parsed['font-weight'].trim(); return 'color' in parsed;
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;
}
} }
return false; return false;
}; };
@ -734,6 +714,13 @@ export function useElementSettings({ margin, padding, basePopup }) {
loadValuesFromStylesheet(true); 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 (stored && stored.values) {
if (settingEnabled.font) { if (settingEnabled.font) {
fontFamily.value = stored.values.fontFamily; fontFamily.value = stored.values.fontFamily;

View file

@ -134,6 +134,19 @@ async function loadFont(fontName) {
stylesheetStore.setFontFaceCss(loadedFontsCss.value); 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() { async function loadAllFontPreviews() {
// Only loads fonts into document.fonts for select preview — does NOT update the stylesheet store // Only loads fonts into document.fonts for select preview — does NOT update the stylesheet store
const promises = fonts const promises = fonts
@ -148,6 +161,7 @@ export function useProjectFonts() {
fonts, fonts,
loadFont, loadFont,
loadAllFontPreviews, loadAllFontPreviews,
loadFontsFromCss,
loadedFontsCss, loadedFontsCss,
}; };
} }

View file

@ -116,6 +116,11 @@ export const useStylesheetStore = defineStore('stylesheet', () => {
return cssParsingUtils.extractCssBlock(baseCss.value, selector); 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) // Replace a CSS block in custom CSS (handles blocks from base CSS too)
const replaceBlock = (oldBlock, newBlock) => { const replaceBlock = (oldBlock, newBlock) => {
// Check if the old block exists in custom CSS // Check if the old block exists in custom CSS
@ -369,6 +374,7 @@ export const useStylesheetStore = defineStore('stylesheet', () => {
updateProperty, updateProperty,
extractValue, extractValue,
extractBlock, extractBlock,
extractCustomBlock,
replaceBlock, replaceBlock,
replaceInCustomCss, replaceInCustomCss,
setCustomCss, setCustomCss,

View file

@ -6,12 +6,20 @@ const extractCssBlock = (css, selector) => {
const extractCssValue = (css, selector, property) => { const extractCssValue = (css, selector, property) => {
const escaped = selector.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); 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)`, `${escaped}\\s*{[^}]*${property}:\\s*([\\d.]+)(px|rem|em|mm|cm|in)`,
'i' 'i'
); );
const match = css.match(regex); const numericMatch = css.match(numericRegex);
return match ? { value: parseFloat(match[1]), unit: match[2] } : null; 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 }) => { const updateCssValue = ({ css, selector, property, value, unit }) => {