general mechanism for default css

This commit is contained in:
Julie Blanc 2026-03-08 10:38:59 +01:00
parent ec1b23d67a
commit fc227bf519
4 changed files with 126 additions and 40 deletions

View file

@ -31,13 +31,18 @@ figure, img{
} }
h1{ /* h1{
font-size: 38px; font-size: 38px;
background: rgba(255, 255, 255, 0.521); background: rgba(255, 255, 255, 0.521);
padding: 10px 20px; padding-top: 10px;
margin: 0; padding-left: 20px;
padding-bottom: 10px;
padding-right: 20px;
margin-left: 0px;
margin-right: 0px;
margin-bottom: 0px;
margin-top: 60px; margin-top: 60px;
} } */
p.author{ p.author{
font-size: 24px; font-size: 24px;

View file

@ -267,71 +267,96 @@ export function useElementSettings({ margin, padding, basePopup }) {
const displayedCssOrder = [ const displayedCssOrder = [
{ css: 'font-family', group: 'font', special: true, { css: 'font-family', group: 'font', special: true,
getValue: () => { const val = settingEnabled.font ? fontFamily.value : textDefaults.fontFamily; return val === 'sans-serif' ? 'sans-serif' : `"${val}"`; } }, getValue: () => { const val = fontFamily.value; return val === 'sans-serif' ? 'sans-serif' : `"${val}"`; } },
{ css: 'font-style', group: 'font', { css: 'font-style', group: 'font',
getValue: () => italic.value ? 'italic' : (INLINE_DEFAULTS[currentTag.value]?.fontStyle || null), getValue: () => italic.value ? 'italic' : (INLINE_DEFAULTS[currentTag.value]?.fontStyle || null),
skip: () => !italic.value && !INLINE_DEFAULTS[currentTag.value]?.fontStyle }, skip: () => !italic.value && !INLINE_DEFAULTS[currentTag.value]?.fontStyle && !hasInCss('font-style') },
{ css: 'font-weight', group: 'font', { css: 'font-weight', group: 'font',
getValue: () => bold.value ? 'bold' : (INLINE_DEFAULTS[currentTag.value]?.fontWeight || null), getValue: () => bold.value ? 'bold' : (INLINE_DEFAULTS[currentTag.value]?.fontWeight || null),
skip: () => !bold.value && !INLINE_DEFAULTS[currentTag.value]?.fontWeight }, skip: () => !bold.value && !INLINE_DEFAULTS[currentTag.value]?.fontWeight && !hasInCss('font-weight') },
{ css: 'font-size', group: 'fontSize', special: true, { css: 'font-size', group: 'fontSize', special: true,
getValue: () => `${(settingEnabled.fontSize ? fontSize : textDefaults.fontSize).value}${(settingEnabled.fontSize ? fontSize : textDefaults.fontSize).unit}` }, getValue: () => `${fontSize.value}${fontSize.unit}` },
{ css: 'line-height', group: 'lineHeight', special: true, { css: 'line-height', group: 'lineHeight', special: true,
getValue: () => `${(settingEnabled.lineHeight ? lineHeight : textDefaults.lineHeight).value}${(settingEnabled.lineHeight ? lineHeight : textDefaults.lineHeight).unit}` }, getValue: () => `${lineHeight.value}${lineHeight.unit}` },
{ css: 'text-align', group: 'textAlign', { css: 'text-align', group: 'textAlign',
getValue: () => textAlign.value, getValue: () => textAlign.value,
skip: () => !settingEnabled.textAlign }, skip: () => !settingEnabled.textAlign && !hasInCss('text-align') },
{ css: 'color', group: 'color', special: true, { css: 'color', group: 'color', special: true,
getValue: () => settingEnabled.color ? color.value : textDefaults.color }, getValue: () => color.value },
{ css: 'background', group: 'background', { css: 'background', group: 'background',
getValue: () => background.value, getValue: () => background.value,
skip: () => !settingEnabled.background }, skip: () => !settingEnabled.background && !hasInCss('background') },
{ css: 'border-width', group: 'border', { css: 'border-width', group: 'border',
getValue: () => `${borderWidth.value}${borderWidth.unit}`, getValue: () => `${borderWidth.value}${borderWidth.unit}`,
skip: () => !settingEnabled.border }, skip: () => !settingEnabled.border && !hasInCss('border-width') },
{ css: 'border-style', group: 'border', { css: 'border-style', group: 'border',
getValue: () => borderStyle.value, getValue: () => borderStyle.value,
skip: () => !settingEnabled.border || borderWidth.value === 0 }, skip: () => (!settingEnabled.border && !hasInCss('border-style')) || borderWidth.value === 0 },
{ css: 'border-color', group: 'border', { css: 'border-color', group: 'border',
getValue: () => borderColor.value, getValue: () => borderColor.value,
skip: () => !settingEnabled.border || borderWidth.value === 0 }, skip: () => (!settingEnabled.border && !hasInCss('border-color')) || borderWidth.value === 0 },
{ css: 'text-decoration-line', group: 'textDecoration', { css: 'text-decoration-line', group: 'textDecoration',
getValue: () => textDecorationLine.value, getValue: () => textDecorationLine.value || (INLINE_DEFAULTS[currentTag.value]?.textDecorationLine || null),
skip: () => !settingEnabled.textDecoration }, skip: () => !settingEnabled.textDecoration && !INLINE_DEFAULTS[currentTag.value]?.textDecorationLine && !hasInCss('text-decoration-line') },
{ css: 'text-decoration-style', group: 'textDecoration', { css: 'text-decoration-style', group: 'textDecoration',
getValue: () => textDecorationStyle.value, getValue: () => textDecorationStyle.value,
skip: () => !settingEnabled.textDecoration }, skip: () => !settingEnabled.textDecoration && !hasInCss('text-decoration-style') },
{ css: 'text-decoration-thickness', group: 'textDecoration', { css: 'text-decoration-thickness', group: 'textDecoration',
getValue: () => `${textDecorationThickness.value}${textDecorationThickness.unit}`, getValue: () => `${textDecorationThickness.value}${textDecorationThickness.unit}`,
skip: () => !settingEnabled.textDecoration }, skip: () => !settingEnabled.textDecoration && !hasInCss('text-decoration-thickness') },
{ css: 'text-decoration-color', group: 'textDecoration', { css: 'text-decoration-color', group: 'textDecoration',
getValue: () => textDecorationColor.value, getValue: () => textDecorationColor.value,
skip: () => !settingEnabled.textDecoration }, skip: () => !settingEnabled.textDecoration && !hasInCss('text-decoration-color') },
{ css: 'text-underline-offset', group: 'textDecoration', { css: 'text-underline-offset', group: 'textDecoration',
getValue: () => `${textUnderlineOffset.value}${textUnderlineOffset.unit}`, getValue: () => `${textUnderlineOffset.value}${textUnderlineOffset.unit}`,
skip: () => !settingEnabled.textDecoration }, skip: () => !settingEnabled.textDecoration && !hasInCss('text-underline-offset') },
]; ];
const isInlineElement = computed(() => !!INLINE_DEFAULTS[currentTag.value]); const isInlineElement = computed(() => !!INLINE_DEFAULTS[currentTag.value]);
// Check if the current selector has a CSS property in the store
const hasInCss = (cssProp) => {
if (!selector.value) return false;
return !!stylesheetStore.extractValue(selector.value, cssProp);
};
const displayedCss = computed(() => { const displayedCss = computed(() => {
if (!selector.value) return ''; if (!selector.value) return '';
const lines = []; const lines = [];
for (const entry of displayedCssOrder) { for (const entry of displayedCssOrder) {
if (entry.skip && entry.skip()) continue; if (entry.skip && entry.skip()) continue;
// For inline elements, skip special groups (TextSettings defaults) when toggle is OFF // For inline elements, skip special groups (TextSettings defaults) when toggle is OFF
if (entry.special && isInlineElement.value && !settingEnabled[entry.group]) continue; // Exception: keep if the current tag has an inline default for this CSS property
if (entry.special && isInlineElement.value && !settingEnabled[entry.group]) {
const tagDefaults = INLINE_DEFAULTS[currentTag.value];
const cssToDefaultKey = { 'color': 'color', 'font-family': 'fontFamily' };
const defaultKey = cssToDefaultKey[entry.css];
if (!defaultKey || !tagDefaults?.[defaultKey]) continue;
}
const val = entry.getValue(); const val = entry.getValue();
if (val === null || val === undefined) continue; if (val === null || val === undefined) continue;
const comment = (entry.special && !settingEnabled[entry.group]) ? ' /* valeur par défaut */' : ''; // Show "valeur par défaut" only if the value actually matches the TextSettings default
let isTextDefault = false;
if (entry.special && !settingEnabled[entry.group]) {
const textDefaultValues = {
'font-family': textDefaults.fontFamily === 'sans-serif' ? 'sans-serif' : `"${textDefaults.fontFamily}"`,
'font-size': `${textDefaults.fontSize.value}${textDefaults.fontSize.unit}`,
'line-height': `${textDefaults.lineHeight.value}${textDefaults.lineHeight.unit}`,
'color': textDefaults.color,
};
isTextDefault = val === textDefaultValues[entry.css];
}
const comment = isTextDefault ? ' /* valeur par défaut */' : '';
lines.push(` ${entry.css}: ${val};${comment}`); lines.push(` ${entry.css}: ${val};${comment}`);
} }
if (settingEnabled.margin) { const showMargin = settingEnabled.margin || ['top', 'right', 'bottom', 'left'].some(s => hasInCss(`margin-${s}`));
if (showMargin) {
for (const side of ['top', 'right', 'bottom', 'left']) { for (const side of ['top', 'right', 'bottom', 'left']) {
lines.push(` margin-${side}: ${margin[side].value}${margin[side].unit};`); lines.push(` margin-${side}: ${margin[side].value}${margin[side].unit};`);
} }
} }
if (settingEnabled.padding) { const showPadding = settingEnabled.padding || ['top', 'right', 'bottom', 'left'].some(s => hasInCss(`padding-${s}`));
if (showPadding) {
for (const side of ['top', 'right', 'bottom', 'left']) { for (const side of ['top', 'right', 'bottom', 'left']) {
lines.push(` padding-${side}: ${padding[side].value}${padding[side].unit};`); lines.push(` padding-${side}: ${padding[side].value}${padding[side].unit};`);
} }
@ -588,25 +613,38 @@ export function useElementSettings({ margin, padding, basePopup }) {
} }
} }
// Fallback to TextSettings defaults only if the selector doesn't already have the value
if (!settingEnabled.font && !settingCache.font) { if (!settingEnabled.font && !settingCache.font) {
const ff = stylesheetStore.extractValue('body', 'font-family'); if (!stylesheetStore.extractValue(selector.value, 'font-family')) {
if (ff) fontFamily.value = (typeof ff === 'string' ? ff : ff.value).replace(/['"]/g, ''); const ff = stylesheetStore.extractValue('body', 'font-family');
const fs = stylesheetStore.extractValue('p', 'font-style'); if (ff) fontFamily.value = (typeof ff === 'string' ? ff : ff.value).replace(/['"]/g, '');
if (fs) italic.value = (typeof fs === 'string' ? fs : fs.value) === 'italic'; }
const fw = stylesheetStore.extractValue('p', 'font-weight'); if (!stylesheetStore.extractValue(selector.value, 'font-style')) {
if (fw) { const v = typeof fw === 'string' ? fw : fw.value; bold.value = v === 'bold' || parseInt(v) >= 700; } const fs = stylesheetStore.extractValue('p', 'font-style');
if (fs) italic.value = (typeof fs === 'string' ? fs : fs.value) === 'italic';
}
if (!stylesheetStore.extractValue(selector.value, 'font-weight')) {
const fw = stylesheetStore.extractValue('p', 'font-weight');
if (fw) { const v = typeof fw === 'string' ? fw : fw.value; bold.value = v === 'bold' || parseInt(v) >= 700; }
}
} }
if (!settingEnabled.fontSize && !settingCache.fontSize) { if (!settingEnabled.fontSize && !settingCache.fontSize) {
const data = stylesheetStore.extractValue('p', 'font-size'); if (!stylesheetStore.extractValue(selector.value, 'font-size')) {
if (data && data.value !== undefined) { fontSize.value = data.value; fontSize.unit = data.unit; } const data = stylesheetStore.extractValue('p', 'font-size');
if (data && data.value !== undefined) { fontSize.value = data.value; fontSize.unit = data.unit; }
}
} }
if (!settingEnabled.lineHeight && !settingCache.lineHeight) { if (!settingEnabled.lineHeight && !settingCache.lineHeight) {
const data = stylesheetStore.extractValue('p', 'line-height'); if (!stylesheetStore.extractValue(selector.value, 'line-height')) {
if (data && data.value !== undefined) { lineHeight.value = data.value; lineHeight.unit = data.unit; } const data = stylesheetStore.extractValue('p', 'line-height');
if (data && data.value !== undefined) { lineHeight.value = data.value; lineHeight.unit = data.unit; }
}
} }
if (!settingEnabled.color && settingCache.color === null) { if (!settingEnabled.color && settingCache.color === null) {
const c = stylesheetStore.extractValue('body', 'color'); if (!stylesheetStore.extractValue(selector.value, 'color')) {
if (c) color.value = typeof c === 'string' ? c : c.value; const c = stylesheetStore.extractValue('body', 'color');
if (c) color.value = typeof c === 'string' ? c : c.value;
}
} }
} catch (error) { } catch (error) {
console.error('Error loading values from stylesheet:', error); console.error('Error loading values from stylesheet:', error);
@ -639,12 +677,19 @@ export function useElementSettings({ margin, padding, basePopup }) {
settingCache.color = null; settingCache.color = null;
} }
// Apply tag-based defaults (em/i → italic, strong/b → bold) // Reset inline-specific values, then apply tag-based defaults
italic.value = ELEMENT_DEFAULTS.italic;
bold.value = ELEMENT_DEFAULTS.bold;
color.value = ELEMENT_DEFAULTS.color;
textDecorationLine.value = ELEMENT_DEFAULTS.textDecorationLine;
const tag = element.tagName.toLowerCase(); const tag = element.tagName.toLowerCase();
const inlineDefaults = INLINE_DEFAULTS[tag]; const inlineDefaults = INLINE_DEFAULTS[tag];
if (inlineDefaults) { if (inlineDefaults) {
if (inlineDefaults.fontStyle === 'italic') italic.value = true; if (inlineDefaults.fontStyle === 'italic') italic.value = true;
if (inlineDefaults.fontWeight === 'bold') bold.value = true; if (inlineDefaults.fontWeight === 'bold') bold.value = true;
if (inlineDefaults.color) color.value = inlineDefaults.color;
if (inlineDefaults.textDecorationLine) textDecorationLine.value = inlineDefaults.textDecorationLine;
} }
loadValuesFromStylesheet(true); loadValuesFromStylesheet(true);

View file

@ -5,7 +5,7 @@ import * as cssComments from '../utils/css-comments';
import prettier from 'prettier/standalone'; import prettier from 'prettier/standalone';
import parserPostcss from 'prettier/plugins/postcss'; import parserPostcss from 'prettier/plugins/postcss';
import { getCsrfToken } from '../utils/kirby-auth'; import { getCsrfToken } from '../utils/kirby-auth';
import { PAGE_DEFAULTS, TEXT_DEFAULTS, INLINE_DEFAULTS } from '../utils/defaults'; import { PAGE_DEFAULTS, TEXT_DEFAULTS, HEADING_DEFAULTS, INLINE_DEFAULTS } from '../utils/defaults';
export const useStylesheetStore = defineStore('stylesheet', () => { export const useStylesheetStore = defineStore('stylesheet', () => {
// Base state // Base state
@ -195,10 +195,28 @@ export const useStylesheetStore = defineStore('stylesheet', () => {
set('p', 'font-size', TEXT_DEFAULTS.fontSize.value, TEXT_DEFAULTS.fontSize.unit); set('p', 'font-size', TEXT_DEFAULTS.fontSize.value, TEXT_DEFAULTS.fontSize.unit);
set('p', 'line-height', TEXT_DEFAULTS.lineHeight.value, TEXT_DEFAULTS.lineHeight.unit); set('p', 'line-height', TEXT_DEFAULTS.lineHeight.value, TEXT_DEFAULTS.lineHeight.unit);
// Inline element defaults (em, i, strong, b) // Heading defaults (h1, h2, ...)
for (const [tag, props] of Object.entries(HEADING_DEFAULTS)) {
if (props.fontSize) set(tag, 'font-size', props.fontSize.value, props.fontSize.unit);
if (props.background) set(tag, 'background', props.background);
if (props.padding) {
for (const side of ['top', 'right', 'bottom', 'left']) {
if (props.padding[side]) set(tag, `padding-${side}`, props.padding[side].value, props.padding[side].unit);
}
}
if (props.margin) {
for (const side of ['top', 'right', 'bottom', 'left']) {
if (props.margin[side]) set(tag, `margin-${side}`, props.margin[side].value, props.margin[side].unit);
}
}
}
// Inline element defaults (em, i, strong, b, a)
for (const [tag, props] of Object.entries(INLINE_DEFAULTS)) { for (const [tag, props] of Object.entries(INLINE_DEFAULTS)) {
if (props.fontStyle) set(tag, 'font-style', props.fontStyle); if (props.fontStyle) set(tag, 'font-style', props.fontStyle);
if (props.fontWeight) set(tag, 'font-weight', props.fontWeight); if (props.fontWeight) set(tag, 'font-weight', props.fontWeight);
if (props.color) set(tag, 'color', props.color);
if (props.textDecorationLine) set(tag, 'text-decoration-line', props.textDecorationLine);
} }
}; };

View file

@ -43,9 +43,27 @@ export const ELEMENT_DEFAULTS = Object.freeze({
textUnderlineOffset: Object.freeze({ value: 1, unit: 'px' }), textUnderlineOffset: Object.freeze({ value: 1, unit: 'px' }),
}); });
export const HEADING_DEFAULTS = Object.freeze({
h1: Object.freeze({
fontSize: Object.freeze({ value: 38, unit: 'px' }),
lineHeight: Object.freeze({ value: 38, unit: 'px' }),
background: 'rgba(255, 255, 255, 0.521)',
padding: Object.freeze({
top: Object.freeze({ value: 10, unit: 'px' }),
right: Object.freeze({ value: 20, unit: 'px' }),
bottom: Object.freeze({ value: 10, unit: 'px' }),
left: Object.freeze({ value: 20, unit: 'px' }),
}),
margin: Object.freeze({
top: Object.freeze({ value: 60, unit: 'px' }),
}),
}),
});
export const INLINE_DEFAULTS = Object.freeze({ export const INLINE_DEFAULTS = Object.freeze({
em: Object.freeze({ fontStyle: 'italic' }), em: Object.freeze({ fontStyle: 'italic' }),
i: Object.freeze({ fontStyle: 'italic' }), i: Object.freeze({ fontStyle: 'italic' }),
strong: Object.freeze({ fontWeight: 'bold' }), strong: Object.freeze({ fontWeight: 'bold' }),
b: Object.freeze({ fontWeight: 'bold' }), b: Object.freeze({ fontWeight: 'bold' }),
a: Object.freeze({ color: '#0000EE', textDecorationLine: 'underline' }),
}); });