Defaul stylesheet print → new defaults.js (nique source of truth) + automatic generation of the default stylesheet for paged.js
This commit is contained in:
parent
ccdd9bda05
commit
47bf70bb36
7 changed files with 115 additions and 49 deletions
|
|
@ -1,8 +1,4 @@
|
|||
@page {
|
||||
size: A5;
|
||||
margin: 20mm 15mm 26mm 15mm;
|
||||
background: rgba(255, 255, 255, 1);
|
||||
}
|
||||
|
||||
|
||||
@page {
|
||||
@bottom-center {
|
||||
|
|
@ -10,9 +6,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "DM Sans", sans-serif;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
@ -21,10 +14,7 @@ figure, img{
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
p{
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ───────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -197,6 +197,7 @@
|
|||
<script setup>
|
||||
import { ref, computed, watch, onMounted, inject, nextTick } from 'vue';
|
||||
import bookIcon from '/assets/svg/book.svg?raw';
|
||||
import { PAGE_DEFAULTS } from '../../utils/defaults';
|
||||
import { useStylesheetStore } from '../../stores/stylesheet';
|
||||
import { useDebounce } from '../../composables/useDebounce';
|
||||
import { useCssSync } from '../../composables/useCssSync';
|
||||
|
|
@ -212,25 +213,18 @@ const activeTab = inject('activeTab', ref('document'));
|
|||
|
||||
let isUpdatingFromStore = false;
|
||||
|
||||
const pageFormat = ref('A4');
|
||||
const pageFormat = ref(PAGE_DEFAULTS.format);
|
||||
const pageFormats = PAGE_DEFAULTS.formats;
|
||||
|
||||
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 customWidth = ref(PAGE_DEFAULTS.formats[PAGE_DEFAULTS.format].width);
|
||||
const customHeight = ref(PAGE_DEFAULTS.formats[PAGE_DEFAULTS.format].height);
|
||||
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' },
|
||||
top: { ...PAGE_DEFAULTS.margins.top },
|
||||
bottom: { ...PAGE_DEFAULTS.margins.bottom },
|
||||
left: { ...PAGE_DEFAULTS.margins.left },
|
||||
right: { ...PAGE_DEFAULTS.margins.right },
|
||||
});
|
||||
|
||||
const updateMarginUnit = (side, newUnit) => {
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@
|
|||
<script setup>
|
||||
import { ref, watch, onMounted, nextTick } from 'vue';
|
||||
import textIcon from '/assets/svg/text.svg?raw';
|
||||
import { TEXT_DEFAULTS } from '../../utils/defaults';
|
||||
import { initColoris } from '../../composables/useColoris';
|
||||
import InputWithUnit from '../ui/InputWithUnit.vue';
|
||||
import { useCssUpdater } from '../../composables/useCssUpdater';
|
||||
|
|
@ -95,11 +96,11 @@ const { debouncedUpdate } = useDebounce(500);
|
|||
const textDefaults = useTextDefaults();
|
||||
const { fonts: projectFonts, loadFont, loadAllFontPreviews } = useProjectFonts();
|
||||
|
||||
// State — initial values match stylesheet.print.css (overwritten by syncFromStore)
|
||||
const font = ref('sans-serif');
|
||||
const fontSize = ref({ value: 14, unit: 'px' });
|
||||
const lineHeight = ref({ value: 18, unit: 'px' });
|
||||
const color = ref('rgb(0, 0, 0)');
|
||||
// State — initial values from defaults.js (overwritten by syncFromStore)
|
||||
const font = ref(TEXT_DEFAULTS.fontFamily);
|
||||
const fontSize = ref({ ...TEXT_DEFAULTS.fontSize });
|
||||
const lineHeight = ref({ ...TEXT_DEFAULTS.lineHeight });
|
||||
const color = ref(TEXT_DEFAULTS.color);
|
||||
const colorInput = ref(null);
|
||||
|
||||
// Start true to block immediate watchers from overwriting textDefaults during setup
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { ref, reactive, computed, watch, nextTick } from 'vue';
|
||||
import { ELEMENT_DEFAULTS } from '../utils/defaults';
|
||||
import { useStylesheetStore } from '../stores/stylesheet';
|
||||
import { useDebounce } from './useDebounce';
|
||||
import { useTextDefaults } from './useTextDefaults';
|
||||
|
|
@ -15,26 +16,26 @@ export function useElementSettings({ margin, padding, basePopup }) {
|
|||
// --- Selector state ---
|
||||
const selector = ref('');
|
||||
|
||||
// --- Style refs ---
|
||||
const fontFamily = ref('sans-serif');
|
||||
const italic = ref(false);
|
||||
const bold = ref(false);
|
||||
const textAlign = ref('left');
|
||||
const color = ref('rgb(0, 0, 0)');
|
||||
const background = ref('transparent');
|
||||
const fontSize = reactive({ value: 23, unit: 'px' });
|
||||
// --- Style refs (initial values from defaults.js) ---
|
||||
const fontFamily = ref(ELEMENT_DEFAULTS.fontFamily);
|
||||
const italic = ref(ELEMENT_DEFAULTS.italic);
|
||||
const bold = ref(ELEMENT_DEFAULTS.bold);
|
||||
const textAlign = ref(ELEMENT_DEFAULTS.textAlign);
|
||||
const color = ref(ELEMENT_DEFAULTS.color);
|
||||
const background = ref(ELEMENT_DEFAULTS.background);
|
||||
const fontSize = reactive({ ...ELEMENT_DEFAULTS.fontSize });
|
||||
const fontSizeModel = computed({
|
||||
get: () => ({ value: fontSize.value, unit: fontSize.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({
|
||||
get: () => ({ value: lineHeight.value, unit: lineHeight.unit }),
|
||||
set: (v) => { lineHeight.value = v.value; lineHeight.unit = v.unit; },
|
||||
});
|
||||
const borderWidth = reactive({ value: 1, unit: 'px' });
|
||||
const borderStyle = ref('solid');
|
||||
const borderColor = ref('#000000');
|
||||
const borderWidth = reactive({ ...ELEMENT_DEFAULTS.borderWidth });
|
||||
const borderStyle = ref(ELEMENT_DEFAULTS.borderStyle);
|
||||
const borderColor = ref(ELEMENT_DEFAULTS.borderColor);
|
||||
|
||||
// --- Toggle state ---
|
||||
const settingEnabled = reactive({
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import { reactive } from 'vue';
|
||||
import { TEXT_DEFAULTS } from '../utils/defaults';
|
||||
|
||||
// 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({
|
||||
fontSize: { value: 14, unit: 'px' },
|
||||
lineHeight: { value: 18, unit: 'px' },
|
||||
fontFamily: 'sans-serif',
|
||||
color: 'rgb(0, 0, 0)',
|
||||
...TEXT_DEFAULTS,
|
||||
fontSize: { ...TEXT_DEFAULTS.fontSize },
|
||||
lineHeight: { ...TEXT_DEFAULTS.lineHeight },
|
||||
_initialized: false,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import * as cssComments from '../utils/css-comments';
|
|||
import prettier from 'prettier/standalone';
|
||||
import parserPostcss from 'prettier/plugins/postcss';
|
||||
import { getCsrfToken } from '../utils/kirby-auth';
|
||||
import { PAGE_DEFAULTS, TEXT_DEFAULTS } from '../utils/defaults';
|
||||
|
||||
export const useStylesheetStore = defineStore('stylesheet', () => {
|
||||
// Base state
|
||||
|
|
@ -167,6 +168,34 @@ export const useStylesheetStore = defineStore('stylesheet', () => {
|
|||
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)
|
||||
const initializeFromNarrative = async (narrativeData) => {
|
||||
// Set initializing flag to prevent marking as dirty during init
|
||||
|
|
@ -182,6 +211,9 @@ export const useStylesheetStore = defineStore('stylesheet', () => {
|
|||
// Get custom CSS if exists
|
||||
customCss.value = narrativeData.customCss || '';
|
||||
|
||||
// Inject defaults if missing from CSS
|
||||
applyDefaultsCss();
|
||||
|
||||
// Set last saved info
|
||||
if (narrativeData.modified) {
|
||||
lastSaved.value = narrativeData.modified;
|
||||
|
|
|
|||
48
src/utils/defaults.js
Normal file
48
src/utils/defaults.js
Normal 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' }),
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue