Compare commits

..

No commits in common. "d88758b22692e18e81d8485bbac631137ee93a4a" and "ea755a2dc6aa43712cb75573e6e0c2c2be871e16" have entirely different histories.

17 changed files with 96 additions and 320 deletions

View file

@ -1,8 +1,10 @@
.settings-section {
margin: var(--space-m) 0;
margin-top: 3em;
// .cons
h2 {
@ -10,10 +12,21 @@
font-weight: 600;
font-size: 1.4rem;
// border-radius: var(--border-radius);
// height: var(--input-h);
// padding: 0 1ch;
// display: flex;
// align-items: center;
// color: var(--color-interface-050);
// background-color: var(--color-800);
// display: inline-block;
border-bottom: 1px solid var(--color-200);
color: var(--color-800);
// border-bottom: 2px solid currentColor;
// margin-bottom: 2rem;
}
.infos{

View file

@ -5,9 +5,7 @@ input[type="number"] {
border: 1px solid var(--color-interface-200);
background-color: var(--color-interface-100);
font-family: var(--sans-serif);
color: var(--color-txt);
font-size: 1rem;
padding-left: 0.5ch;
// min-width: var(--input-w);
// width: 100%;
// padding: 0 1ch;
@ -87,22 +85,10 @@ input[type="number"] {
label{
font-weight: 400;
margin-left: 0.75ch;
color: var(--color-txt);
}
}
}
.field-text-size{
input[type="number"]{
width: var(--input-w-small);
padding-left: 0.75ch;
}
input[type="range"]{
flex-grow: 2;
flex-shrink: 2;
}
}
.field-margin, .field-size{
display: inline-grid;
width: calc(50% - 1ch);
@ -210,13 +196,9 @@ input[type="number"] {
top: 0;
button{
height: calc(var(--input-h)*0.5);
height: calc(var(--input-h)*0.6);
cursor: pointer;
padding: 0;
svg{
width: 10px;
height: auto;
}
svg path{
fill: var(--color-interface-600);
}
@ -228,9 +210,8 @@ input[type="number"] {
}
.spinner-down{
svg{
// position: relative;
// top: -2px;
position: relative;
top: -2px;
}
}

View file

@ -25,7 +25,3 @@ button {
background-color: transparent;
border: none;
}
img {
width: 100%;
}

View file

@ -9,7 +9,7 @@
--color-txt: var(--color-interface-900);
--color-txt: var(--color-interface-800);
--color-panel-bg: var(--color-interface-050);
--color-page-highlight: #ff8a50;
@ -20,8 +20,6 @@
--space-xs: 0.5rem;
--space-s: 1rem;
--space: 1.5rem;
--space-m: 2rem;
--space-big: 3em;
--curve: cubic-bezier(0.86, 0, 0.07, 1);
@ -36,5 +34,4 @@
font-size: 14px;
--panel-w: 540px;
--panel-nav-h: 60px;
}

View file

@ -216,14 +216,10 @@ button {
border: none;
}
img {
width: 100%;
}
:root {
--color-browngray-050: #f5f3f0;
--color-browngray-200: #d0c4ba;
--color-txt: var(--color-interface-900);
--color-txt: var(--color-interface-800);
--color-panel-bg: var(--color-interface-050);
--color-page-highlight: #ff8a50;
--color-purple: #7136ff;
@ -231,8 +227,6 @@ img {
--space-xs: 0.5rem;
--space-s: 1rem;
--space: 1.5rem;
--space-m: 2rem;
--space-big: 3em;
--curve: cubic-bezier(0.86, 0, 0.07, 1);
--sans-serif: "DM Sans", sans-serif;
--mono: "Inconsolata", monospace;
@ -242,7 +236,6 @@ img {
--label-w: 18ch;
font-size: 14px;
--panel-w: 540px;
--panel-nav-h: 60px;
}
body {
@ -276,9 +269,7 @@ input[type=number] {
border: 1px solid var(--color-interface-200);
background-color: var(--color-interface-100);
font-family: var(--sans-serif);
color: var(--color-txt);
font-size: 1rem;
padding-left: 0.5ch;
}
.field {
@ -338,16 +329,6 @@ input[type=number] {
.field-font .field-checkbox label {
font-weight: 400;
margin-left: 0.75ch;
color: var(--color-txt);
}
.field-text-size input[type=number] {
width: var(--input-w-small);
padding-left: 0.75ch;
}
.field-text-size input[type=range] {
flex-grow: 2;
flex-shrink: 2;
}
.field-margin, .field-size {
@ -436,22 +417,23 @@ input[type=number] {
top: 0;
}
.number-input .spinner-buttons button {
height: calc(var(--input-h) * 0.5);
height: calc(var(--input-h) * 0.6);
cursor: pointer;
padding: 0;
}
.number-input .spinner-buttons button svg {
width: 10px;
height: auto;
}
.number-input .spinner-buttons button svg path {
fill: var(--color-interface-600);
}
.number-input .spinner-buttons button:hover svg path {
fill: var(--color-interface-900);
}
.number-input .spinner-buttons .spinner-down svg {
position: relative;
top: -2px;
}
.settings-section {
margin: var(--space-m) 0;
margin-top: 3em;
}
.settings-section h2 {
margin-bottom: var(--space);

File diff suppressed because one or more lines are too long

View file

@ -24,13 +24,5 @@ h2 {
p {
font-size: 1rem;
margin: 0mm 0mm 10mm 0mm;
}
img {
width: 100%;
}
li p {
margin-bottom: 0;
margin: 0mm 0mm 24mm 0mm;
}

View file

@ -16,7 +16,7 @@ return array(
'PHPMailer\\PHPMailer\\' => array($vendorDir . '/phpmailer/phpmailer/src'),
'League\\ColorExtractor\\' => array($vendorDir . '/league/color-extractor/src'),
'Laminas\\Escaper\\' => array($vendorDir . '/laminas/laminas-escaper/src'),
'Kirby\\' => array($baseDir . '/kirby/src', $vendorDir . '/getkirby/composer-installer/src'),
'Kirby\\' => array($vendorDir . '/getkirby/composer-installer/src', $baseDir . '/kirby/src'),
'Composer\\Semver\\' => array($vendorDir . '/composer/semver/src'),
'Base32\\' => array($vendorDir . '/christian-riesen/base32/src'),
);

View file

@ -96,8 +96,8 @@ class ComposerStaticInit0b7fb803e22a45eb87e24172337208aa
),
'Kirby\\' =>
array (
0 => __DIR__ . '/../..' . '/kirby/src',
1 => __DIR__ . '/..' . '/getkirby/composer-installer/src',
0 => __DIR__ . '/..' . '/getkirby/composer-installer/src',
1 => __DIR__ . '/../..' . '/kirby/src',
),
'Composer\\Semver\\' =>
array (

View file

@ -1,9 +1,9 @@
<?php return array(
'root' => array(
'name' => 'getkirby/plainkit',
'pretty_version' => 'dev-main',
'version' => 'dev-main',
'reference' => '76274fff04c54514230ad2bb0aca362139618411',
'pretty_version' => '5.1.4',
'version' => '5.1.4.0',
'reference' => null,
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@ -65,9 +65,9 @@
'dev_requirement' => false,
),
'getkirby/plainkit' => array(
'pretty_version' => 'dev-main',
'version' => 'dev-main',
'reference' => '76274fff04c54514230ad2bb0aca362139618411',
'pretty_version' => '5.1.4',
'version' => '5.1.4.0',
'reference' => null,
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),

View file

@ -274,7 +274,6 @@
import { ref, computed, watch } from 'vue';
import { useStylesheetStore } from '../stores/stylesheet';
import { usePopupPosition } from '../composables/usePopupPosition';
import { useDebounce } from '../composables/useDebounce';
import NumberInput from './ui/NumberInput.vue';
import Coloris from '@melloware/coloris';
import '@melloware/coloris/dist/coloris.css';
@ -308,7 +307,7 @@ const colorInput = ref(null);
const backgroundInput = ref(null);
let isUpdatingFromStore = false;
const { debouncedUpdate } = useDebounce(500);
let updateTimer = null;
// Style properties
const fontFamily = ref({ value: 'Alegreya Sans' });
@ -321,6 +320,11 @@ const background = ref({ value: 'transparent' });
const marginOuter = ref({ value: 0, unit: 'mm' });
const paddingInner = ref({ value: 0, unit: 'mm' });
const debouncedUpdate = (callback) => {
clearTimeout(updateTimer);
updateTimer = setTimeout(callback, 1000);
};
const immediateUpdate = (callback) => {
callback();
};
@ -369,81 +373,9 @@ const elementCss = computed(() => {
return stylesheetStore.extractBlock(selector.value) || '';
});
// Generate a preview CSS block from current field values
const generatePreviewCss = () => {
if (!selector.value) return '';
const properties = [];
if (fontFamily.value.value) {
properties.push(` font-family: ${fontFamily.value.value};`);
}
if (fontStyle.value.italic) {
properties.push(` font-style: italic;`);
}
if (fontWeight.value.value) {
properties.push(` font-weight: ${fontWeight.value.value};`);
}
if (fontSize.value.value) {
properties.push(` font-size: ${fontSize.value.value}${fontSize.value.unit};`);
}
if (textAlign.value.value) {
properties.push(` text-align: ${textAlign.value.value};`);
}
if (color.value.value && color.value.value !== 'rgb(0, 0, 0)') {
properties.push(` color: ${color.value.value};`);
}
if (background.value.value && background.value.value !== 'transparent') {
properties.push(` background: ${background.value.value};`);
}
if (marginOuter.value.value) {
properties.push(` margin: ${marginOuter.value.value}${marginOuter.value.unit};`);
}
if (paddingInner.value.value) {
properties.push(` padding: ${paddingInner.value.value}${paddingInner.value.unit};`);
}
if (properties.length === 0) return '';
return `${selector.value} {\n${properties.join('\n')}\n}`;
};
const displayedCss = computed(() => {
if (!selector.value) return '';
// If unlocked, show the actual CSS block from stylesheet
if (!inheritanceLocked.value) {
return elementCss.value || generatePreviewCss();
}
// If locked, show commented preview of what would be applied
const preview = generatePreviewCss();
if (!preview) return '';
return '/* Héritage verrouillé - déverrouiller pour appliquer */\n/* ' +
preview.split('\n').join('\n ') +
' */';
});
// Remove the element-specific CSS block to restore inheritance
const removeElementBlock = () => {
if (!selector.value) return;
const block = stylesheetStore.extractBlock(selector.value);
if (block) {
// Escape special regex characters in selector
const escaped = selector.value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
// Remove the block and any surrounding whitespace
stylesheetStore.content = stylesheetStore.content.replace(
new RegExp(`\\n?${escaped}\\s*\\{[^}]*\\}\\n?`),
'\n'
);
}
};
const highlightedCss = computed(() => {
if (!displayedCss.value) return '';
return hljs.highlight(displayedCss.value, { language: 'css' }).value;
if (!elementCss.value) return '';
return hljs.highlight(elementCss.value, { language: 'css' }).value;
});
// Update functions for each property
@ -492,21 +424,6 @@ const updatePaddingInner = () => {
stylesheetStore.updateProperty(selector.value, 'padding', paddingInner.value.value, paddingInner.value.unit);
};
// Apply all current field values to create/update the CSS block
const applyAllStyles = () => {
if (!selector.value) return;
updateFontFamily();
updateFontStyle();
updateFontWeight();
updateFontSize();
updateTextAlign();
updateColor();
updateBackground();
updateMarginOuter();
updatePaddingInner();
};
// Watch for changes
watch(() => fontFamily.value.value, () => {
if (isUpdatingFromStore) return;
@ -688,9 +605,6 @@ const open = (element, event, count = null) => {
// Store instance count if provided, otherwise calculate it
elementInstanceCount.value = count !== null ? count : getInstanceCount(selector.value);
// Read inheritance state from element's data attribute
inheritanceLocked.value = element.dataset.inheritanceUnlocked !== 'true';
// Load values from stylesheet
loadValuesFromStylesheet();
@ -754,25 +668,8 @@ const handleIframeClick = (event, targetElement = null, elementCount = null) =>
};
const toggleInheritance = () => {
const wasLocked = inheritanceLocked.value;
inheritanceLocked.value = !inheritanceLocked.value;
// Store the inheritance state in the element's data attribute
if (selectedElement.value) {
if (inheritanceLocked.value) {
delete selectedElement.value.dataset.inheritanceUnlocked;
} else {
selectedElement.value.dataset.inheritanceUnlocked = 'true';
}
}
if (inheritanceLocked.value && !wasLocked) {
// Re-locking: remove the element-specific CSS block to restore inheritance
removeElementBlock();
} else if (!inheritanceLocked.value && wasLocked) {
// Unlocking: apply all current field values to create the CSS block
applyAllStyles();
}
// TODO: Implement CSS priority logic when unlocked
};
defineExpose({ handleIframeClick, close, visible });

View file

@ -285,7 +285,6 @@
import { ref, computed, watch, onMounted } from 'vue';
import { useStylesheetStore } from '../stores/stylesheet';
import { usePopupPosition } from '../composables/usePopupPosition';
import { useDebounce } from '../composables/useDebounce';
import NumberInput from './ui/NumberInput.vue';
import Coloris from '@melloware/coloris';
import '@melloware/coloris/dist/coloris.css';
@ -313,7 +312,7 @@ const inheritanceLocked = ref(true);
const backgroundColorInput = ref(null);
let isUpdatingFromStore = false;
const { debouncedUpdate } = useDebounce(500);
let updateTimer = null;
const margins = ref({
top: { value: 0, unit: 'mm' },
@ -329,6 +328,11 @@ const background = ref({
const pattern = ref('');
const debouncedUpdate = (callback) => {
clearTimeout(updateTimer);
updateTimer = setTimeout(callback, 1000);
};
const immediateUpdate = (callback) => {
callback();
};
@ -383,9 +387,9 @@ const removeTemplateBlock = () => {
}
};
const updateMargins = (force = false) => {
// Only update if inheritance is unlocked (unless forced)
if (!force && inheritanceLocked.value) return;
const updateMargins = () => {
// Only update if inheritance is unlocked
if (inheritanceLocked.value) return;
const marginValue = `${margins.value.top.value}${margins.value.top.unit} ${margins.value.right.value}${margins.value.right.unit} ${margins.value.bottom.value}${margins.value.bottom.unit} ${margins.value.left.value}${margins.value.left.unit}`;
@ -415,9 +419,9 @@ const updateMargins = (force = false) => {
}
};
const updateBackground = (force = false) => {
// Only update if inheritance is unlocked (unless forced)
if (!force && inheritanceLocked.value) return;
const updateBackground = () => {
// Only update if inheritance is unlocked
if (inheritanceLocked.value) return;
if (!background.value.value) return;
const currentBlock = getOrCreateTemplateBlock();
@ -444,12 +448,6 @@ const updateBackground = (force = false) => {
}
};
// Apply all current field values to create/update the CSS block
const applyAllStyles = () => {
updateMargins(true);
updateBackground(true);
};
// Watch margin values (number inputs) with debounce
watch(
() => [
@ -538,8 +536,8 @@ const open = (pageElement, event, count = 1) => {
// Extract template name from data-page-type attribute
templateName.value = pageElement.getAttribute('data-page-type') || '';
// Read inheritance state from page element's data attribute
inheritanceLocked.value = pageElement.dataset.inheritanceUnlocked !== 'true';
// Reset inheritance state when opening
inheritanceLocked.value = true;
// Load values from stylesheet (@page block)
loadValuesFromStylesheet();
@ -589,22 +587,12 @@ const toggleInheritance = () => {
const wasLocked = inheritanceLocked.value;
inheritanceLocked.value = !inheritanceLocked.value;
// Store the inheritance state in the page element's data attribute
if (selectedPageElement.value) {
if (inheritanceLocked.value) {
delete selectedPageElement.value.dataset.inheritanceUnlocked;
} else {
selectedPageElement.value.dataset.inheritanceUnlocked = 'true';
}
}
if (inheritanceLocked.value && !wasLocked) {
// Re-locking: remove the template-specific block
// Fields keep their values, but preview returns to @page defaults
removeTemplateBlock();
} else if (!inheritanceLocked.value && wasLocked) {
// Unlocking: apply all current field values to create the CSS block
applyAllStyles();
}
// When unlocking: fields already have values, block will be created on first edit
};
const pageCss = computed(() => {
@ -616,47 +604,9 @@ const pageCss = computed(() => {
return stylesheetStore.extractBlock('@page') || '';
});
// Generate a preview CSS block from current field values
const generatePreviewCss = () => {
if (!templateName.value) return '';
const properties = [];
const marginValue = `${margins.value.top.value}${margins.value.top.unit} ${margins.value.right.value}${margins.value.right.unit} ${margins.value.bottom.value}${margins.value.bottom.unit} ${margins.value.left.value}${margins.value.left.unit}`;
properties.push(` margin: ${marginValue};`);
if (background.value.value) {
properties.push(` background: ${background.value.value};`);
}
if (properties.length === 0) return '';
return `@page ${templateName.value} {\n${properties.join('\n')}\n}`;
};
const displayedCss = computed(() => {
// If unlocked, show the actual CSS block from stylesheet
if (!inheritanceLocked.value) {
return pageCss.value;
}
// If locked, show commented preview of what would be applied
if (!templateName.value) {
// For base @page, just show it normally
return pageCss.value;
}
const preview = generatePreviewCss();
if (!preview) return pageCss.value;
return '/* Héritage verrouillé - déverrouiller pour appliquer */\n/* ' +
preview.split('\n').join('\n ') +
' */';
});
const highlightedCss = computed(() => {
if (!displayedCss.value) return '';
return hljs.highlight(displayedCss.value, { language: 'css' }).value;
if (!pageCss.value) return '';
return hljs.highlight(pageCss.value, { language: 'css' }).value;
});
let cssDebounceTimer = null;
@ -669,7 +619,6 @@ const handleCssInput = (event) => {
}
cssDebounceTimer = setTimeout(() => {
// Get the actual CSS block (not the commented preview)
const oldBlock = pageCss.value;
if (oldBlock) {
stylesheetStore.content = stylesheetStore.content.replace(

View file

@ -119,11 +119,11 @@ nav {
position: relative;
left: calc(var(--panel-w)*-1);
padding: 4rem 0;
background-color: var(--color-panel-bg);
box-shadow: -5px 0px 12px;
transition: left 0.3s var(--curve);
}
@ -132,10 +132,10 @@ nav {
}
.tab-panel {
height: calc(100% - var(--panel-nav-h)*2);
height: 100%;
overflow-y: auto;
overflow-x: hidden;
padding: 0 2em;
margin-top: var(--panel-nav-h);
// padding-left: 1em;
}
</style>

View file

@ -288,17 +288,16 @@
<script setup>
import { ref, computed, watch, onMounted, inject } from 'vue';
import { useStylesheetStore } from '../../stores/stylesheet';
import { useDebounce } from '../../composables/useDebounce';
import Coloris from '@melloware/coloris';
import NumberInput from '../ui/NumberInput.vue';
import '@melloware/coloris/dist/coloris.css';
const stylesheetStore = useStylesheetStore();
const { debouncedUpdate } = useDebounce(500);
const backgroundColorInput = ref(null);
const activeTab = inject('activeTab', ref('document'));
let isUpdatingFromStore = false;
let updateTimer = null;
const pageFormat = ref('A4');
@ -329,6 +328,11 @@ const pattern = ref('');
const pageNumbers = ref(false);
const runningTitle = ref(false);
const debouncedUpdate = (callback) => {
clearTimeout(updateTimer);
updateTimer = setTimeout(callback, 1000);
};
const immediateUpdate = (callback) => {
callback();
};

View file

@ -34,7 +34,7 @@
<!-- Taille du texte -->
<div class="settings-subsection">
<div class="field field-text-size">
<div class="field">
<label for="text-size-range" class="label-with-tooltip" data-css="font-size">Taille du texte</label>
<InputWithUnit
v-model="fontSize"
@ -130,11 +130,9 @@ import InputWithUnit from '../ui/InputWithUnit.vue';
import MarginEditor from '../ui/MarginEditor.vue';
import { useCssUpdater } from '../../composables/useCssUpdater';
import { useCssSync } from '../../composables/useCssSync';
import { useDebounce } from '../../composables/useDebounce';
const { updateStyle, setMargin, setDetailedMargins, setPadding, setDetailedPadding } = useCssUpdater();
const { extractValue, extractNumericValue, extractSpacing } = useCssSync();
const { debouncedUpdate } = useDebounce(500);
// Constants
const fonts = ['Alegreya Sans', 'Arial', 'Georgia', 'Helvetica', 'Times New Roman'];
@ -205,32 +203,26 @@ watch(weight, (val) => {
watch(fontSize, (val) => {
if (isUpdatingFromStore) return;
debouncedUpdate(() => {
updateStyle('p', 'font-size', `${val.value}${val.unit}`);
});
updateStyle('p', 'font-size', `${val.value}${val.unit}`);
}, { deep: true });
// Margin/Padding handlers
const handleMarginOuterChange = ({ type, simple, detailed }) => {
if (isUpdatingFromStore) return;
debouncedUpdate(() => {
if (type === 'simple') {
setMargin('p', simple.value, simple.unit);
} else {
setDetailedMargins('p', detailed.top, detailed.right, detailed.bottom, detailed.left);
}
});
if (type === 'simple') {
setMargin('p', simple.value, simple.unit);
} else {
setDetailedMargins('p', detailed.top, detailed.right, detailed.bottom, detailed.left);
}
};
const handleMarginInnerChange = ({ type, simple, detailed }) => {
if (isUpdatingFromStore) return;
debouncedUpdate(() => {
if (type === 'simple') {
setPadding('p', simple.value, simple.unit);
} else {
setDetailedPadding('p', detailed.top, detailed.right, detailed.bottom, detailed.left);
}
});
if (type === 'simple') {
setPadding('p', simple.value, simple.unit);
} else {
setDetailedPadding('p', detailed.top, detailed.right, detailed.bottom, detailed.left);
}
};
// Sync from store

View file

@ -1,15 +0,0 @@
/**
* Composable for debounced updates
* @param {number} delay - Debounce delay in milliseconds (default: 500ms)
* @returns {Function} debouncedUpdate function
*/
export function useDebounce(delay = 500) {
let timer = null;
const debouncedUpdate = (callback) => {
clearTimeout(timer);
timer = setTimeout(callback, delay);
};
return { debouncedUpdate };
}

View file

@ -16,32 +16,20 @@ const extractCssValue = (css, selector, property) => {
const updateCssValue = ({ css, selector, property, value, unit }) => {
const escaped = selector.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const formattedValue = unit === '' || unit === undefined ? value : `${value}${unit}`;
// Check if selector exists
const selectorRegex = new RegExp(`${escaped}\\s*{([^}]*)}`, 'i');
const selectorMatch = css.match(selectorRegex);
if (!selectorMatch) {
// Selector doesn't exist, create it
return css + `\n${selector} {\n ${property}: ${formattedValue};\n}\n`;
if (unit === '') {
const regex = new RegExp(
`(${escaped}\\s*{[^}]*${property}:\\s*)[^;]+`,
'gi'
);
return css.replace(regex, `$1${value}`);
}
const blockContent = selectorMatch[1];
// Check if property exists in the block
const propertyRegex = new RegExp(`(${property}\\s*:\\s*)([^;]+)(;?)`, 'i');
const propertyMatch = blockContent.match(propertyRegex);
if (!propertyMatch) {
// Property doesn't exist, add it to the block
const newBlockContent = blockContent.trimEnd() + `\n ${property}: ${formattedValue};`;
return css.replace(selectorRegex, `${selector} {${newBlockContent}}`);
}
// Property exists, replace the entire value
const newBlockContent = blockContent.replace(propertyRegex, `$1${formattedValue}$3`);
return css.replace(selectorRegex, `${selector} {${newBlockContent}}`);
const regex = new RegExp(
`(${escaped}\\s*{[^}]*${property}:\\s*)[\\d.]+(px|rem|em|mm|cm|in)`,
'gi'
);
return css.replace(regex, `$1${value}${unit}`);
};
const cssParsingUtils = { extractCssBlock, extractCssValue, updateCssValue };