Defaul stylesheet print → new defaults.js (nique source of truth) + automatic generation of the default stylesheet for paged.js

This commit is contained in:
Julie Blanc 2026-03-08 09:24:53 +01:00
parent ccdd9bda05
commit 47bf70bb36
7 changed files with 115 additions and 49 deletions

View file

@ -1,8 +1,4 @@
@page {
size: A5;
margin: 20mm 15mm 26mm 15mm;
background: rgba(255, 255, 255, 1);
}
@page { @page {
@bottom-center { @bottom-center {
@ -10,9 +6,6 @@
} }
} }
body {
font-family: "DM Sans", sans-serif;
}
@ -21,10 +14,7 @@ figure, img{
margin: 0; margin: 0;
} }
p{
font-size: 14px;
line-height: 18px;
}
/* /*

View file

@ -197,6 +197,7 @@
<script setup> <script setup>
import { ref, computed, watch, onMounted, inject, nextTick } from 'vue'; import { ref, computed, watch, onMounted, inject, nextTick } from 'vue';
import bookIcon from '/assets/svg/book.svg?raw'; import bookIcon from '/assets/svg/book.svg?raw';
import { PAGE_DEFAULTS } from '../../utils/defaults';
import { useStylesheetStore } from '../../stores/stylesheet'; import { useStylesheetStore } from '../../stores/stylesheet';
import { useDebounce } from '../../composables/useDebounce'; import { useDebounce } from '../../composables/useDebounce';
import { useCssSync } from '../../composables/useCssSync'; import { useCssSync } from '../../composables/useCssSync';
@ -212,25 +213,18 @@ const activeTab = inject('activeTab', ref('document'));
let isUpdatingFromStore = false; let isUpdatingFromStore = false;
const pageFormat = ref('A4'); const pageFormat = ref(PAGE_DEFAULTS.format);
const pageFormats = PAGE_DEFAULTS.formats;
const pageFormats = { const customWidth = ref(PAGE_DEFAULTS.formats[PAGE_DEFAULTS.format].width);
A4: { width: 210, height: 297 }, const customHeight = ref(PAGE_DEFAULTS.formats[PAGE_DEFAULTS.format].height);
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 isCustomFormat = computed(() => pageFormat.value === 'custom');
const margins = ref({ const margins = ref({
top: { value: 20, unit: 'mm' }, top: { ...PAGE_DEFAULTS.margins.top },
bottom: { value: 20, unit: 'mm' }, bottom: { ...PAGE_DEFAULTS.margins.bottom },
left: { value: 20, unit: 'mm' }, left: { ...PAGE_DEFAULTS.margins.left },
right: { value: 20, unit: 'mm' }, right: { ...PAGE_DEFAULTS.margins.right },
}); });
const updateMarginUnit = (side, newUnit) => { const updateMarginUnit = (side, newUnit) => {

View file

@ -81,6 +81,7 @@
<script setup> <script setup>
import { ref, watch, onMounted, nextTick } from 'vue'; import { ref, watch, onMounted, nextTick } from 'vue';
import textIcon from '/assets/svg/text.svg?raw'; import textIcon from '/assets/svg/text.svg?raw';
import { TEXT_DEFAULTS } from '../../utils/defaults';
import { initColoris } from '../../composables/useColoris'; import { initColoris } from '../../composables/useColoris';
import InputWithUnit from '../ui/InputWithUnit.vue'; import InputWithUnit from '../ui/InputWithUnit.vue';
import { useCssUpdater } from '../../composables/useCssUpdater'; import { useCssUpdater } from '../../composables/useCssUpdater';
@ -95,11 +96,11 @@ const { debouncedUpdate } = useDebounce(500);
const textDefaults = useTextDefaults(); const textDefaults = useTextDefaults();
const { fonts: projectFonts, loadFont, loadAllFontPreviews } = useProjectFonts(); const { fonts: projectFonts, loadFont, loadAllFontPreviews } = useProjectFonts();
// State initial values match stylesheet.print.css (overwritten by syncFromStore) // State initial values from defaults.js (overwritten by syncFromStore)
const font = ref('sans-serif'); const font = ref(TEXT_DEFAULTS.fontFamily);
const fontSize = ref({ value: 14, unit: 'px' }); const fontSize = ref({ ...TEXT_DEFAULTS.fontSize });
const lineHeight = ref({ value: 18, unit: 'px' }); const lineHeight = ref({ ...TEXT_DEFAULTS.lineHeight });
const color = ref('rgb(0, 0, 0)'); const color = ref(TEXT_DEFAULTS.color);
const colorInput = ref(null); const colorInput = ref(null);
// Start true to block immediate watchers from overwriting textDefaults during setup // Start true to block immediate watchers from overwriting textDefaults during setup

View file

@ -1,4 +1,5 @@
import { ref, reactive, computed, watch, nextTick } from 'vue'; import { ref, reactive, computed, watch, nextTick } from 'vue';
import { ELEMENT_DEFAULTS } from '../utils/defaults';
import { useStylesheetStore } from '../stores/stylesheet'; import { useStylesheetStore } from '../stores/stylesheet';
import { useDebounce } from './useDebounce'; import { useDebounce } from './useDebounce';
import { useTextDefaults } from './useTextDefaults'; import { useTextDefaults } from './useTextDefaults';
@ -15,26 +16,26 @@ export function useElementSettings({ margin, padding, basePopup }) {
// --- Selector state --- // --- Selector state ---
const selector = ref(''); const selector = ref('');
// --- Style refs --- // --- Style refs (initial values from defaults.js) ---
const fontFamily = ref('sans-serif'); const fontFamily = ref(ELEMENT_DEFAULTS.fontFamily);
const italic = ref(false); const italic = ref(ELEMENT_DEFAULTS.italic);
const bold = ref(false); const bold = ref(ELEMENT_DEFAULTS.bold);
const textAlign = ref('left'); const textAlign = ref(ELEMENT_DEFAULTS.textAlign);
const color = ref('rgb(0, 0, 0)'); const color = ref(ELEMENT_DEFAULTS.color);
const background = ref('transparent'); const background = ref(ELEMENT_DEFAULTS.background);
const fontSize = reactive({ value: 23, unit: 'px' }); const fontSize = reactive({ ...ELEMENT_DEFAULTS.fontSize });
const fontSizeModel = computed({ const fontSizeModel = computed({
get: () => ({ value: fontSize.value, unit: fontSize.unit }), get: () => ({ value: fontSize.value, unit: fontSize.unit }),
set: (v) => { fontSize.value = v.value; fontSize.unit = v.unit; }, set: (v) => { fontSize.value = v.value; fontSize.unit = v.unit; },
}); });
const lineHeight = reactive({ value: 28, unit: 'px' }); const lineHeight = reactive({ ...ELEMENT_DEFAULTS.lineHeight });
const lineHeightModel = computed({ const lineHeightModel = computed({
get: () => ({ value: lineHeight.value, unit: lineHeight.unit }), get: () => ({ value: lineHeight.value, unit: lineHeight.unit }),
set: (v) => { lineHeight.value = v.value; lineHeight.unit = v.unit; }, set: (v) => { lineHeight.value = v.value; lineHeight.unit = v.unit; },
}); });
const borderWidth = reactive({ value: 1, unit: 'px' }); const borderWidth = reactive({ ...ELEMENT_DEFAULTS.borderWidth });
const borderStyle = ref('solid'); const borderStyle = ref(ELEMENT_DEFAULTS.borderStyle);
const borderColor = ref('#000000'); const borderColor = ref(ELEMENT_DEFAULTS.borderColor);
// --- Toggle state --- // --- Toggle state ---
const settingEnabled = reactive({ const settingEnabled = reactive({

View file

@ -1,12 +1,12 @@
import { reactive } from 'vue'; import { reactive } from 'vue';
import { TEXT_DEFAULTS } from '../utils/defaults';
// Singleton reactive — TextSettings writes here, ElementPopup reads when disabled // Singleton reactive — TextSettings writes here, ElementPopup reads when disabled
// Initial values match stylesheet.print.css (overwritten by syncFromStore on first mount) // Initial values from defaults.js (overwritten by syncFromStore on first mount)
const defaults = reactive({ const defaults = reactive({
fontSize: { value: 14, unit: 'px' }, ...TEXT_DEFAULTS,
lineHeight: { value: 18, unit: 'px' }, fontSize: { ...TEXT_DEFAULTS.fontSize },
fontFamily: 'sans-serif', lineHeight: { ...TEXT_DEFAULTS.lineHeight },
color: 'rgb(0, 0, 0)',
_initialized: false, _initialized: false,
}); });

View file

@ -5,6 +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 } from '../utils/defaults';
export const useStylesheetStore = defineStore('stylesheet', () => { export const useStylesheetStore = defineStore('stylesheet', () => {
// Base state // Base state
@ -167,6 +168,34 @@ export const useStylesheetStore = defineStore('stylesheet', () => {
return baseCss.value; return baseCss.value;
}; };
// Inject default CSS values if not already present in base or custom CSS
const applyDefaultsCss = () => {
const has = (selector, property) => {
return cssParsingUtils.extractCssValue(baseCss.value, selector, property)
|| cssParsingUtils.extractCssValue(customCss.value, selector, property);
};
const set = (selector, property, value, unit = '') => {
if (!has(selector, property)) {
customCss.value = cssParsingUtils.updateCssValue({
css: customCss.value, selector, property, value, unit,
});
}
};
// Page defaults
set('@page', 'size', PAGE_DEFAULTS.format);
const m = PAGE_DEFAULTS.margins;
set('@page', 'margin', `${m.top.value}${m.top.unit} ${m.right.value}${m.right.unit} ${m.bottom.value}${m.bottom.unit} ${m.left.value}${m.left.unit}`);
// Text defaults
const fontVal = TEXT_DEFAULTS.fontFamily === 'sans-serif' ? 'sans-serif' : `"${TEXT_DEFAULTS.fontFamily}"`;
set('body', 'font-family', fontVal);
set('body', 'color', TEXT_DEFAULTS.color);
set('p', 'font-size', TEXT_DEFAULTS.fontSize.value, TEXT_DEFAULTS.fontSize.unit);
set('p', 'line-height', TEXT_DEFAULTS.lineHeight.value, TEXT_DEFAULTS.lineHeight.unit);
};
// Initialize from narrative data (base + custom CSS) // Initialize from narrative data (base + custom CSS)
const initializeFromNarrative = async (narrativeData) => { const initializeFromNarrative = async (narrativeData) => {
// Set initializing flag to prevent marking as dirty during init // Set initializing flag to prevent marking as dirty during init
@ -182,6 +211,9 @@ export const useStylesheetStore = defineStore('stylesheet', () => {
// Get custom CSS if exists // Get custom CSS if exists
customCss.value = narrativeData.customCss || ''; customCss.value = narrativeData.customCss || '';
// Inject defaults if missing from CSS
applyDefaultsCss();
// Set last saved info // Set last saved info
if (narrativeData.modified) { if (narrativeData.modified) {
lastSaved.value = narrativeData.modified; lastSaved.value = narrativeData.modified;

48
src/utils/defaults.js Normal file
View file

@ -0,0 +1,48 @@
/**
* Default values for the application.
* Single source of truth imported by components, composables, and stores.
* These are static launch values, NOT user-configured defaults.
*/
export const PAGE_DEFAULTS = Object.freeze({
format: 'A5',
formats: Object.freeze({
A4: Object.freeze({ width: 210, height: 297 }),
A5: Object.freeze({ width: 148, height: 210 }),
}),
margins: Object.freeze({
top: Object.freeze({ value: 16, unit: 'mm' }),
bottom: Object.freeze({ value: 16, unit: 'mm' }),
left: Object.freeze({ value: 16, unit: 'mm' }),
right: Object.freeze({ value: 16, unit: 'mm' }),
}),
background: '',
});
export const TEXT_DEFAULTS = Object.freeze({
fontFamily: 'sans-serif',
fontSize: Object.freeze({ value: 14, unit: 'px' }),
lineHeight: Object.freeze({ value: 18, unit: 'px' }),
color: 'rgb(0, 0, 0)',
});
export const ELEMENT_DEFAULTS = Object.freeze({
fontFamily: 'sans-serif',
italic: false,
bold: false,
textAlign: 'left',
color: 'rgb(0, 0, 0)',
background: 'transparent',
fontSize: Object.freeze({ value: 14, unit: 'px' }),
lineHeight: Object.freeze({ value: 18, unit: 'px' }),
borderWidth: Object.freeze({ value: 1, unit: 'px' }),
borderStyle: 'solid',
borderColor: '#000000',
});
export const INLINE_DEFAULTS = Object.freeze({
em: Object.freeze({ fontStyle: 'italic' }),
i: Object.freeze({ fontStyle: 'italic' }),
strong: Object.freeze({ fontWeight: 'bold' }),
b: Object.freeze({ fontWeight: 'bold' }),
});