add all images has .block-image
All checks were successful
Deploy / Build and Deploy to Production (push) Successful in 22s
All checks were successful
Deploy / Build and Deploy to Production (push) Successful in 22s
This commit is contained in:
parent
2eaf9aab7e
commit
bdda8dc8a5
6 changed files with 352 additions and 73 deletions
|
|
@ -6,13 +6,13 @@
|
||||||
:display-css="displayedCss"
|
:display-css="displayedCss"
|
||||||
:editable-css="editableCss"
|
:editable-css="editableCss"
|
||||||
:popup-width="440"
|
:popup-width="440"
|
||||||
:popup-height="200"
|
:popup-height="320"
|
||||||
:show-inheritance="false"
|
:show-inheritance="false"
|
||||||
@close="handleClose"
|
@close="handleClose"
|
||||||
@css-input="handleCssInput"
|
@css-input="handleCssInput"
|
||||||
>
|
>
|
||||||
<template #header-left>
|
<template #header-left>
|
||||||
<span class="image-label">{{ selector || '' }}</span>
|
<span class="image-label">{{ selector.replace('.block-image.', '.') || '' }}</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template #controls>
|
<template #controls>
|
||||||
|
|
@ -22,16 +22,10 @@
|
||||||
class="setting__section"
|
class="setting__section"
|
||||||
data-setting="width"
|
data-setting="width"
|
||||||
:class="{ 'setting-disabled': !widthEnabled }"
|
:class="{ 'setting-disabled': !widthEnabled }"
|
||||||
@click="onSectionClick"
|
@click="onWidthSectionClick"
|
||||||
>
|
>
|
||||||
<div class="setting__header">
|
<div class="setting__header">
|
||||||
<input
|
<input type="checkbox" id="toggle-width" class="toggle-setting" :checked="widthEnabled" @change="onToggleWidth($event.target.checked)" /><label for="toggle-width" aria-label="Activer le réglage de largeur" @click.stop></label>
|
||||||
type="checkbox"
|
|
||||||
id="toggle-width"
|
|
||||||
class="toggle-setting"
|
|
||||||
:checked="widthEnabled"
|
|
||||||
@change="onToggleWidth($event.target.checked)"
|
|
||||||
/><label for="toggle-width" aria-label="Activer le réglage de largeur" @click.stop></label>
|
|
||||||
<label class="label-with-tooltip" data-css="width">Largeur</label>
|
<label class="label-with-tooltip" data-css="width">Largeur</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting__body">
|
<div class="setting__body">
|
||||||
|
|
@ -46,6 +40,40 @@
|
||||||
<p class="info-default">Valeur par défaut : {{ imageDefaults.width.value }}{{ imageDefaults.width.unit }}</p>
|
<p class="info-default">Valeur par défaut : {{ imageDefaults.width.value }}{{ imageDefaults.width.unit }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Hauteur -->
|
||||||
|
<div
|
||||||
|
class="setting__section"
|
||||||
|
data-setting="height"
|
||||||
|
:class="{ 'setting-disabled': !heightEnabled }"
|
||||||
|
@click="onHeightSectionClick"
|
||||||
|
>
|
||||||
|
<div class="setting__header">
|
||||||
|
<input type="checkbox" id="toggle-height" class="toggle-setting" :checked="heightEnabled" @change="onToggleHeight($event.target.checked)" /><label for="toggle-height" aria-label="Activer le réglage de hauteur" @click.stop></label>
|
||||||
|
<label class="label-with-tooltip" data-css="height">Hauteur</label>
|
||||||
|
</div>
|
||||||
|
<div class="setting__body">
|
||||||
|
<div class="field-checkbox" @click.stop>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="height-auto"
|
||||||
|
:checked="heightAuto"
|
||||||
|
@change="onToggleHeightAuto(!heightAuto)"
|
||||||
|
/>
|
||||||
|
<label for="height-auto">Auto</label>
|
||||||
|
</div>
|
||||||
|
<InputWithUnit
|
||||||
|
v-if="!heightAuto"
|
||||||
|
:model-value="{ value: heightPx.value, unit: heightPx.unit }"
|
||||||
|
@update:model-value="onHeightValueChange"
|
||||||
|
:units="['px']"
|
||||||
|
:min="1"
|
||||||
|
:max="800"
|
||||||
|
showRange
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p class="info-default">Valeur par défaut : {{ imageDefaults.height.auto ? 'auto' : imageDefaults.height.value + imageDefaults.height.unit }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
</BasePopup>
|
</BasePopup>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -56,39 +84,68 @@ import InputWithUnit from './ui/InputWithUnit.vue';
|
||||||
import BasePopup from './ui/BasePopup.vue';
|
import BasePopup from './ui/BasePopup.vue';
|
||||||
import { useStylesheetStore } from '../stores/stylesheet';
|
import { useStylesheetStore } from '../stores/stylesheet';
|
||||||
import { useImageDefaults } from '../composables/useImageDefaults';
|
import { useImageDefaults } from '../composables/useImageDefaults';
|
||||||
|
import { IMAGE_DEFAULTS } from '../utils/defaults';
|
||||||
|
import { useCssUpdater } from '../composables/useCssUpdater';
|
||||||
|
import { useDebounce } from '../composables/useDebounce';
|
||||||
|
|
||||||
const emit = defineEmits(['close']);
|
const emit = defineEmits(['close']);
|
||||||
|
|
||||||
const stylesheetStore = useStylesheetStore();
|
const stylesheetStore = useStylesheetStore();
|
||||||
const imageDefaults = useImageDefaults();
|
const imageDefaults = useImageDefaults();
|
||||||
|
const { updateStyle, removeProperty } = useCssUpdater();
|
||||||
|
const { debouncedUpdate } = useDebounce(400);
|
||||||
|
|
||||||
const basePopup = ref(null);
|
const basePopup = ref(null);
|
||||||
const visible = computed(() => basePopup.value?.visible ?? false);
|
const visible = computed(() => basePopup.value?.visible ?? false);
|
||||||
|
const currentFigure = ref(null);
|
||||||
|
|
||||||
const selector = ref('');
|
const selector = ref('');
|
||||||
|
|
||||||
|
// --- Width state ---
|
||||||
const width = reactive({ value: 100, unit: '%' });
|
const width = reactive({ value: 100, unit: '%' });
|
||||||
const widthEnabled = ref(false);
|
const widthEnabled = ref(false);
|
||||||
const widthCache = ref(null); // { value, unit } | null — persists user value when toggle is OFF
|
const widthCache = ref(null);
|
||||||
|
|
||||||
|
// --- Height state ---
|
||||||
|
const heightAuto = ref(true);
|
||||||
|
const heightPx = reactive({ value: IMAGE_DEFAULTS.height.value, unit: IMAGE_DEFAULTS.height.unit });
|
||||||
|
const heightEnabled = ref(false);
|
||||||
|
const heightCache = ref(null); // { auto, value, unit } | null
|
||||||
|
const measuredHeight = ref(null); // Actual height captured on first uncheck, per open session
|
||||||
|
|
||||||
// Per-selector persistent state
|
|
||||||
const elementStates = new Map();
|
const elementStates = new Map();
|
||||||
|
|
||||||
let isUpdatingFromStore = false;
|
let isUpdatingFromStore = false;
|
||||||
|
|
||||||
// InputWithUnit model
|
// --- Models ---
|
||||||
const widthModel = computed({
|
const widthModel = computed({
|
||||||
get: () => ({ value: width.value, unit: width.unit }),
|
get: () => ({ value: width.value, unit: width.unit }),
|
||||||
set: (v) => { width.value = v.value; width.unit = v.unit; },
|
set: (v) => { width.value = v.value; width.unit = v.unit; },
|
||||||
});
|
});
|
||||||
|
|
||||||
// Watch width changes → update CSS when toggle is ON
|
|
||||||
|
// --- Watchers ---
|
||||||
|
|
||||||
watch(() => [width.value, width.unit], () => {
|
watch(() => [width.value, width.unit], () => {
|
||||||
if (isUpdatingFromStore || !widthEnabled.value || !selector.value) return;
|
if (isUpdatingFromStore || !widthEnabled.value || !selector.value) return;
|
||||||
stylesheetStore.updateProperty(selector.value, 'width', width.value, width.unit);
|
debouncedUpdate(() => {
|
||||||
saveState();
|
stylesheetStore.updateProperty(selector.value, 'width', width.value, width.unit);
|
||||||
|
saveState();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Sync greyed field with imageDefaults when toggle is OFF and no cache
|
const onHeightValueChange = (v) => {
|
||||||
|
heightPx.value = v.value;
|
||||||
|
heightPx.unit = v.unit;
|
||||||
|
if (isUpdatingFromStore || !heightEnabled.value || heightAuto.value || !selector.value) return;
|
||||||
|
measuredHeight.value = v.value;
|
||||||
|
debouncedUpdate(() => {
|
||||||
|
stylesheetStore.updateProperty(selector.value, 'height', heightPx.value, heightPx.unit);
|
||||||
|
saveState();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Sync greyed width field with imageDefaults when toggle OFF and no cache
|
||||||
watch(() => imageDefaults.width, (val) => {
|
watch(() => imageDefaults.width, (val) => {
|
||||||
if (!widthEnabled.value && widthCache.value === null) {
|
if (!widthEnabled.value && widthCache.value === null) {
|
||||||
isUpdatingFromStore = true;
|
isUpdatingFromStore = true;
|
||||||
|
|
@ -98,16 +155,26 @@ watch(() => imageDefaults.width, (val) => {
|
||||||
}
|
}
|
||||||
}, { deep: true });
|
}, { deep: true });
|
||||||
|
|
||||||
// --- Toggle ---
|
// Sync greyed height field with imageDefaults when toggle OFF and no cache
|
||||||
|
watch(() => imageDefaults.height, (val) => {
|
||||||
|
if (!heightEnabled.value && heightCache.value === null) {
|
||||||
|
isUpdatingFromStore = true;
|
||||||
|
heightAuto.value = val.auto;
|
||||||
|
heightPx.value = val.value;
|
||||||
|
heightPx.unit = val.unit;
|
||||||
|
nextTick(() => { isUpdatingFromStore = false; });
|
||||||
|
}
|
||||||
|
}, { deep: true });
|
||||||
|
|
||||||
const onSectionClick = () => {
|
// --- Width toggle ---
|
||||||
|
|
||||||
|
const onWidthSectionClick = () => {
|
||||||
if (!widthEnabled.value) onToggleWidth(true);
|
if (!widthEnabled.value) onToggleWidth(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onToggleWidth = (enabled) => {
|
const onToggleWidth = (enabled) => {
|
||||||
isUpdatingFromStore = true;
|
isUpdatingFromStore = true;
|
||||||
widthEnabled.value = enabled;
|
widthEnabled.value = enabled;
|
||||||
|
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
if (widthCache.value) {
|
if (widthCache.value) {
|
||||||
width.value = widthCache.value.value;
|
width.value = widthCache.value.value;
|
||||||
|
|
@ -117,23 +184,97 @@ const onToggleWidth = (enabled) => {
|
||||||
stylesheetStore.updateProperty(selector.value, 'width', width.value, width.unit);
|
stylesheetStore.updateProperty(selector.value, 'width', width.value, width.unit);
|
||||||
} else {
|
} else {
|
||||||
widthCache.value = { value: width.value, unit: width.unit };
|
widthCache.value = { value: width.value, unit: width.unit };
|
||||||
removeWidthProp();
|
removeProp('width');
|
||||||
width.value = imageDefaults.width.value;
|
width.value = imageDefaults.width.value;
|
||||||
width.unit = imageDefaults.width.unit;
|
width.unit = imageDefaults.width.unit;
|
||||||
}
|
}
|
||||||
|
|
||||||
saveState();
|
saveState();
|
||||||
nextTick(() => { isUpdatingFromStore = false; });
|
nextTick(() => { isUpdatingFromStore = false; });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// --- Height toggle ---
|
||||||
|
|
||||||
|
const onHeightSectionClick = () => {
|
||||||
|
if (!heightEnabled.value) onToggleHeight(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onToggleHeight = (enabled) => {
|
||||||
|
isUpdatingFromStore = true;
|
||||||
|
heightEnabled.value = enabled;
|
||||||
|
if (enabled) {
|
||||||
|
if (heightCache.value) {
|
||||||
|
heightAuto.value = heightCache.value.auto;
|
||||||
|
heightPx.value = heightCache.value.value;
|
||||||
|
heightPx.unit = heightCache.value.unit;
|
||||||
|
heightCache.value = null;
|
||||||
|
}
|
||||||
|
applyHeight();
|
||||||
|
if (!heightAuto.value) updateStyle(`${selector.value} img`, 'height', '100%');
|
||||||
|
} else {
|
||||||
|
heightCache.value = { auto: heightAuto.value, value: heightPx.value, unit: heightPx.unit };
|
||||||
|
removeProp('height');
|
||||||
|
removeProperty(`${selector.value} img`, 'height');
|
||||||
|
heightAuto.value = imageDefaults.height.auto;
|
||||||
|
heightPx.value = imageDefaults.height.value;
|
||||||
|
heightPx.unit = imageDefaults.height.unit;
|
||||||
|
}
|
||||||
|
saveState();
|
||||||
|
nextTick(() => { isUpdatingFromStore = false; });
|
||||||
|
};
|
||||||
|
|
||||||
|
// Auto checkbox (only active when heightEnabled)
|
||||||
|
const onToggleHeightAuto = (auto) => {
|
||||||
|
if (!heightEnabled.value) return;
|
||||||
|
isUpdatingFromStore = true;
|
||||||
|
if (!auto && heightAuto.value) {
|
||||||
|
// Unchecking auto → use measured height (capture once, reuse on subsequent unchecks)
|
||||||
|
if (measuredHeight.value === null) {
|
||||||
|
measuredHeight.value = getActualImageHeight();
|
||||||
|
}
|
||||||
|
heightPx.value = measuredHeight.value;
|
||||||
|
heightPx.unit = 'px';
|
||||||
|
}
|
||||||
|
heightAuto.value = auto;
|
||||||
|
applyHeight();
|
||||||
|
if (!auto) {
|
||||||
|
updateStyle(`${selector.value} img`, 'height', '100%');
|
||||||
|
} else {
|
||||||
|
removeProperty(`${selector.value} img`, 'height');
|
||||||
|
}
|
||||||
|
saveState();
|
||||||
|
nextTick(() => { isUpdatingFromStore = false; });
|
||||||
|
};
|
||||||
|
|
||||||
|
const getActualImageHeight = () => {
|
||||||
|
if (currentFigure.value) {
|
||||||
|
const img = currentFigure.value.querySelector('img');
|
||||||
|
if (img) {
|
||||||
|
const h = Math.round(img.getBoundingClientRect().height);
|
||||||
|
if (h > 0) return Math.min(h, 800);
|
||||||
|
}
|
||||||
|
const figH = Math.round(currentFigure.value.getBoundingClientRect().height);
|
||||||
|
if (figH > 0) return Math.min(figH, 800);
|
||||||
|
}
|
||||||
|
return IMAGE_DEFAULTS.height.value;
|
||||||
|
};
|
||||||
|
|
||||||
// --- CSS helpers ---
|
// --- CSS helpers ---
|
||||||
|
|
||||||
const removeWidthProp = () => {
|
const applyHeight = () => {
|
||||||
|
if (!selector.value) return;
|
||||||
|
if (heightAuto.value) {
|
||||||
|
stylesheetStore.updateProperty(selector.value, 'height', 'auto');
|
||||||
|
} else {
|
||||||
|
stylesheetStore.updateProperty(selector.value, 'height', heightPx.value, heightPx.unit);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeProp = (prop) => {
|
||||||
if (!selector.value) return;
|
if (!selector.value) return;
|
||||||
const block = stylesheetStore.extractBlock(selector.value);
|
const block = stylesheetStore.extractBlock(selector.value);
|
||||||
if (!block || !stylesheetStore.customCss.includes(block)) return;
|
if (!block || !stylesheetStore.customCss.includes(block)) return;
|
||||||
|
const escaped = prop.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||||
const newBlock = block.replace(/[ \t]*width\s*:[^;]*;[ \t]*\n?/g, '');
|
const newBlock = block.replace(new RegExp(`[ \\t]*${escaped}\\s*:[^;]*;[ \\t]*\\n?`, 'g'), '');
|
||||||
const inner = newBlock.replace(/^[^{]*\{/, '').replace(/\}[^}]*$/, '');
|
const inner = newBlock.replace(/^[^{]*\{/, '').replace(/\}[^}]*$/, '');
|
||||||
if (!inner.trim()) {
|
if (!inner.trim()) {
|
||||||
stylesheetStore.replaceInCustomCss(block, '');
|
stylesheetStore.replaceInCustomCss(block, '');
|
||||||
|
|
@ -146,10 +287,21 @@ const removeWidthProp = () => {
|
||||||
|
|
||||||
const displayedCss = computed(() => {
|
const displayedCss = computed(() => {
|
||||||
if (!selector.value) return '';
|
if (!selector.value) return '';
|
||||||
|
const lines = [];
|
||||||
|
|
||||||
|
// Width
|
||||||
const widthVal = `${width.value}${width.unit}`;
|
const widthVal = `${width.value}${width.unit}`;
|
||||||
const defaultVal = `${imageDefaults.width.value}${imageDefaults.width.unit}`;
|
const defaultWidthVal = `${imageDefaults.width.value}${imageDefaults.width.unit}`;
|
||||||
const comment = (!widthEnabled.value && widthVal === defaultVal) ? ' /* hérité de .block-image */' : '';
|
const widthComment = (!widthEnabled.value && widthVal === defaultWidthVal) ? ' /* hérité de .block-image */' : '';
|
||||||
return `${selector.value} {\n width: ${widthVal};${comment}\n}`;
|
lines.push(` width: ${widthVal};${widthComment}`);
|
||||||
|
|
||||||
|
// Height
|
||||||
|
const heightVal = heightAuto.value ? 'auto' : `${heightPx.value}${heightPx.unit}`;
|
||||||
|
const defaultHeightVal = imageDefaults.height.auto ? 'auto' : `${imageDefaults.height.value}${imageDefaults.height.unit}`;
|
||||||
|
const heightComment = (!heightEnabled.value && heightVal === defaultHeightVal) ? ' /* hérité de .block-image */' : '';
|
||||||
|
lines.push(` height: ${heightVal};${heightComment}`);
|
||||||
|
|
||||||
|
return `${selector.value} {\n${lines.join('\n')}\n}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
const editableCss = computed(() => {
|
const editableCss = computed(() => {
|
||||||
|
|
@ -161,13 +313,30 @@ const editableCss = computed(() => {
|
||||||
const handleCssInput = (newCss) => {
|
const handleCssInput = (newCss) => {
|
||||||
isUpdatingFromStore = true;
|
isUpdatingFromStore = true;
|
||||||
|
|
||||||
const match = newCss.match(/width\s*:\s*([\d.]+)(%|px)/i);
|
const widthMatch = newCss.match(/width\s*:\s*([\d.]+)(%|px)/i);
|
||||||
if (match) {
|
if (widthMatch) {
|
||||||
width.value = parseFloat(match[1]);
|
width.value = parseFloat(widthMatch[1]);
|
||||||
width.unit = match[2];
|
width.unit = widthMatch[2];
|
||||||
}
|
}
|
||||||
|
|
||||||
const newBlock = `${selector.value} {\n width: ${width.value}${width.unit};\n}\n`;
|
const heightMatch = newCss.match(/height\s*:\s*([^;]+)/i);
|
||||||
|
if (heightMatch) {
|
||||||
|
const hVal = heightMatch[1].trim();
|
||||||
|
if (hVal === 'auto') {
|
||||||
|
heightAuto.value = true;
|
||||||
|
} else {
|
||||||
|
const m = hVal.match(/([\d.]+)px/i);
|
||||||
|
if (m) {
|
||||||
|
heightAuto.value = false;
|
||||||
|
heightPx.value = parseFloat(m[1]);
|
||||||
|
heightPx.unit = 'px';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const widthLine = ` width: ${width.value}${width.unit};`;
|
||||||
|
const heightLine = ` height: ${heightAuto.value ? 'auto' : `${heightPx.value}${heightPx.unit}`};`;
|
||||||
|
const newBlock = `${selector.value} {\n${widthLine}\n${heightLine}\n}\n`;
|
||||||
const oldBlock = stylesheetStore.extractBlock(selector.value) || '';
|
const oldBlock = stylesheetStore.extractBlock(selector.value) || '';
|
||||||
if (oldBlock) {
|
if (oldBlock) {
|
||||||
stylesheetStore.replaceInCustomCss(oldBlock, newBlock);
|
stylesheetStore.replaceInCustomCss(oldBlock, newBlock);
|
||||||
|
|
@ -183,9 +352,13 @@ const handleCssInput = (newCss) => {
|
||||||
const saveState = () => {
|
const saveState = () => {
|
||||||
if (!selector.value) return;
|
if (!selector.value) return;
|
||||||
elementStates.set(selector.value, {
|
elementStates.set(selector.value, {
|
||||||
toggle: widthEnabled.value,
|
widthToggle: widthEnabled.value,
|
||||||
cache: widthCache.value ? { ...widthCache.value } : null,
|
widthCache: widthCache.value ? { ...widthCache.value } : null,
|
||||||
value: { value: width.value, unit: width.unit },
|
widthValue: { value: width.value, unit: width.unit },
|
||||||
|
heightToggle: heightEnabled.value,
|
||||||
|
heightCache: heightCache.value ? { ...heightCache.value } : null,
|
||||||
|
heightAuto: heightAuto.value,
|
||||||
|
heightValue: { value: heightPx.value, unit: heightPx.unit },
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -194,39 +367,58 @@ const saveState = () => {
|
||||||
const handleImageClick = (figure, event) => {
|
const handleImageClick = (figure, event) => {
|
||||||
const uniqueClass = Array.from(figure.classList).find(cls => cls.startsWith('block-image--'));
|
const uniqueClass = Array.from(figure.classList).find(cls => cls.startsWith('block-image--'));
|
||||||
const sel = uniqueClass
|
const sel = uniqueClass
|
||||||
? `.${uniqueClass}`
|
? `.block-image.${uniqueClass}`
|
||||||
: figure.classList.contains('geoformat-cover-image') ? '.geoformat-cover-image' : null;
|
: figure.classList.contains('geoformat-cover-image') ? '.geoformat-cover-image' : null;
|
||||||
if (!sel) return;
|
if (!sel) return;
|
||||||
|
|
||||||
isUpdatingFromStore = true;
|
isUpdatingFromStore = true;
|
||||||
selector.value = sel;
|
selector.value = sel;
|
||||||
|
currentFigure.value = figure;
|
||||||
|
measuredHeight.value = null;
|
||||||
|
|
||||||
const stored = elementStates.get(selector.value);
|
const stored = elementStates.get(sel);
|
||||||
if (stored) {
|
if (stored) {
|
||||||
widthEnabled.value = stored.toggle;
|
widthEnabled.value = stored.widthToggle;
|
||||||
widthCache.value = stored.cache ? { ...stored.cache } : null;
|
widthCache.value = stored.widthCache ? { ...stored.widthCache } : null;
|
||||||
if (stored.toggle) {
|
width.value = stored.widthToggle ? stored.widthValue.value : (stored.widthCache ? stored.widthCache.value : imageDefaults.width.value);
|
||||||
width.value = stored.value.value;
|
width.unit = stored.widthToggle ? stored.widthValue.unit : (stored.widthCache ? stored.widthCache.unit : imageDefaults.width.unit);
|
||||||
width.unit = stored.value.unit;
|
|
||||||
} else if (stored.cache) {
|
heightEnabled.value = stored.heightToggle;
|
||||||
width.value = stored.cache.value;
|
heightCache.value = stored.heightCache ? { ...stored.heightCache } : null;
|
||||||
width.unit = stored.cache.unit;
|
heightAuto.value = stored.heightAuto;
|
||||||
} else {
|
heightPx.value = stored.heightToggle ? stored.heightValue.value : (stored.heightCache ? stored.heightCache.value : imageDefaults.height.value);
|
||||||
width.value = imageDefaults.width.value;
|
heightPx.unit = stored.heightToggle ? stored.heightValue.unit : (stored.heightCache ? stored.heightCache.unit : imageDefaults.height.unit);
|
||||||
width.unit = imageDefaults.width.unit;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
|
// Width
|
||||||
widthEnabled.value = false;
|
widthEnabled.value = false;
|
||||||
widthCache.value = null;
|
widthCache.value = null;
|
||||||
|
const wCss = stylesheetStore.extractValue(sel, 'width');
|
||||||
const cssVal = stylesheetStore.extractValue(selector.value, 'width');
|
if (wCss && wCss.value !== undefined) {
|
||||||
if (cssVal && cssVal.value !== undefined) {
|
width.value = wCss.value;
|
||||||
width.value = cssVal.value;
|
width.unit = wCss.unit;
|
||||||
width.unit = cssVal.unit;
|
const isDefaultWidth = wCss.value === imageDefaults.width.value && wCss.unit === imageDefaults.width.unit;
|
||||||
widthEnabled.value = true;
|
if (!isDefaultWidth) widthEnabled.value = true;
|
||||||
} else {
|
} else {
|
||||||
width.value = imageDefaults.width.value;
|
width.value = imageDefaults.width.value; width.unit = imageDefaults.width.unit;
|
||||||
width.unit = imageDefaults.width.unit;
|
}
|
||||||
|
|
||||||
|
// Height
|
||||||
|
heightEnabled.value = false;
|
||||||
|
heightCache.value = null;
|
||||||
|
const hCss = stylesheetStore.extractValue(sel, 'height');
|
||||||
|
const hStr = typeof hCss === 'string' ? hCss : null;
|
||||||
|
if (hStr === 'auto') {
|
||||||
|
heightAuto.value = true; // toggle reste OFF — c'est la valeur par défaut
|
||||||
|
} else if (hStr) {
|
||||||
|
const m = hStr.match(/([\d.]+)px/i);
|
||||||
|
if (m) { heightAuto.value = false; heightPx.value = parseFloat(m[1]); heightPx.unit = 'px'; heightEnabled.value = true; }
|
||||||
|
else { heightAuto.value = imageDefaults.height.auto; heightPx.value = imageDefaults.height.value; heightPx.unit = imageDefaults.height.unit; }
|
||||||
|
} else if (hCss && hCss.value !== undefined) {
|
||||||
|
heightAuto.value = false; heightPx.value = hCss.value; heightPx.unit = hCss.unit; heightEnabled.value = true;
|
||||||
|
} else {
|
||||||
|
heightAuto.value = imageDefaults.height.auto;
|
||||||
|
heightPx.value = imageDefaults.height.value;
|
||||||
|
heightPx.unit = imageDefaults.height.unit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -239,6 +431,7 @@ const handleImageClick = (figure, event) => {
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
saveState();
|
saveState();
|
||||||
selector.value = '';
|
selector.value = '';
|
||||||
|
currentFigure.value = null;
|
||||||
if (basePopup.value?.visible) basePopup.value.close();
|
if (basePopup.value?.visible) basePopup.value.close();
|
||||||
emit('close');
|
emit('close');
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,9 @@
|
||||||
:data-page-type="item.template"
|
:data-page-type="item.template"
|
||||||
>
|
>
|
||||||
<h4>{{ item.title }}</h4>
|
<h4>{{ item.title }}</h4>
|
||||||
<img v-if="item.image" :src="item.image" class="carte-image" alt="" />
|
<figure v-if="item.image" class="block-carte">
|
||||||
|
<img :src="item.image" alt="" />
|
||||||
|
</figure>
|
||||||
<div v-if="item.tags && item.tags.length" class="tags">
|
<div v-if="item.tags && item.tags.length" class="tags">
|
||||||
<span v-for="tag in item.tags" :key="tag" class="tag">{{ tag }}</span>
|
<span v-for="tag in item.tags" :key="tag" class="tag">{{ tag }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -93,7 +95,11 @@
|
||||||
{{ marker.title }}
|
{{ marker.title }}
|
||||||
</h5>
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
<img v-if="marker.cover" :src="marker.cover" class="marker-cover" alt="" />
|
<ImageBlock
|
||||||
|
v-if="marker.cover"
|
||||||
|
:content="{ url: marker.cover, alt: '' }"
|
||||||
|
:block-id="`${(item.id || '').replace(/[^a-z0-9]/gi, '').slice(-6)}m${idx}`"
|
||||||
|
/>
|
||||||
<template v-if="marker.blocks">
|
<template v-if="marker.blocks">
|
||||||
<component
|
<component
|
||||||
v-for="block in visibleBlocks(marker.blocks)"
|
v-for="block in visibleBlocks(marker.blocks)"
|
||||||
|
|
@ -232,11 +238,17 @@ const getBlockComponent = (type) => {
|
||||||
break-before: page;
|
break-before: page;
|
||||||
}
|
}
|
||||||
|
|
||||||
.carte-image {
|
.block-carte {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.block-carte img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
.marker {
|
.marker {
|
||||||
margin-top: 1.5rem;
|
margin-top: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
@ -253,10 +265,4 @@ const getBlockComponent = (type) => {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
.marker-cover {
|
|
||||||
max-width: 100%;
|
|
||||||
height: auto;
|
|
||||||
margin: 0.5rem 0;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,27 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Hauteur -->
|
||||||
|
<div class="setting__section" data-setting="height">
|
||||||
|
<div class="setting__header">
|
||||||
|
<label class="label-with-tooltip" data-css="height">Hauteur</label>
|
||||||
|
</div>
|
||||||
|
<div class="setting__body">
|
||||||
|
<div class="field-checkbox">
|
||||||
|
<input type="checkbox" id="image-height-auto" :checked="heightAuto" @change="onHeightAutoChange(!heightAuto)" />
|
||||||
|
<label for="image-height-auto">Auto</label>
|
||||||
|
</div>
|
||||||
|
<InputWithUnit
|
||||||
|
v-if="!heightAuto"
|
||||||
|
v-model="height"
|
||||||
|
:units="['px']"
|
||||||
|
:min="1"
|
||||||
|
:max="800"
|
||||||
|
showRange
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -38,36 +59,83 @@ import { useCssSync } from '../../composables/useCssSync';
|
||||||
import { useDebounce } from '../../composables/useDebounce';
|
import { useDebounce } from '../../composables/useDebounce';
|
||||||
import { useImageDefaults } from '../../composables/useImageDefaults';
|
import { useImageDefaults } from '../../composables/useImageDefaults';
|
||||||
|
|
||||||
const { updateStyle } = useCssUpdater();
|
const { updateStyle, removeProperty } = useCssUpdater();
|
||||||
const { extractNumericValue } = useCssSync();
|
const { extractNumericValue, extractValue } = useCssSync();
|
||||||
const { debouncedUpdate } = useDebounce(500);
|
const { debouncedUpdate } = useDebounce(500);
|
||||||
const imageDefaults = useImageDefaults();
|
const imageDefaults = useImageDefaults();
|
||||||
|
|
||||||
// State — initial value from defaults.js (overwritten by syncFromStore)
|
const IMAGE_SELECTORS = ['.block-image', '.geoformat-cover-image'];
|
||||||
|
|
||||||
|
// Width
|
||||||
const width = ref({ ...IMAGE_DEFAULTS.width });
|
const width = ref({ ...IMAGE_DEFAULTS.width });
|
||||||
|
|
||||||
// Start true to block immediate watchers during setup
|
// Height
|
||||||
|
const heightAuto = ref(IMAGE_DEFAULTS.height.auto);
|
||||||
|
const height = ref({ value: IMAGE_DEFAULTS.height.value, unit: IMAGE_DEFAULTS.height.unit });
|
||||||
|
|
||||||
let isUpdatingFromStore = true;
|
let isUpdatingFromStore = true;
|
||||||
|
|
||||||
|
// Width watcher
|
||||||
watch(width, (val) => {
|
watch(width, (val) => {
|
||||||
if (isUpdatingFromStore) return;
|
if (isUpdatingFromStore) return;
|
||||||
imageDefaults.width = { value: val.value, unit: val.unit };
|
imageDefaults.width = { value: val.value, unit: val.unit };
|
||||||
debouncedUpdate(() => {
|
debouncedUpdate(() => {
|
||||||
updateStyle('.block-image', 'width', `${val.value}${val.unit}`);
|
IMAGE_SELECTORS.forEach(sel => updateStyle(sel, 'width', `${val.value}${val.unit}`));
|
||||||
updateStyle('.geoformat-cover-image', 'width', `${val.value}${val.unit}`);
|
|
||||||
});
|
});
|
||||||
}, { deep: true, immediate: true });
|
}, { deep: true, immediate: true });
|
||||||
|
|
||||||
|
// Height auto toggle
|
||||||
|
const onHeightAutoChange = (auto) => {
|
||||||
|
if (isUpdatingFromStore) return;
|
||||||
|
heightAuto.value = auto;
|
||||||
|
const cssVal = auto ? 'auto' : `${height.value.value}${height.value.unit}`;
|
||||||
|
IMAGE_SELECTORS.forEach(sel => {
|
||||||
|
updateStyle(sel, 'height', cssVal);
|
||||||
|
if (auto) {
|
||||||
|
removeProperty(`${sel} img`, 'height');
|
||||||
|
} else {
|
||||||
|
updateStyle(`${sel} img`, 'height', '100%');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
imageDefaults.height = { auto, value: height.value.value, unit: height.value.unit };
|
||||||
|
};
|
||||||
|
|
||||||
|
// Height value watcher (only when not auto)
|
||||||
|
watch(height, (val) => {
|
||||||
|
if (isUpdatingFromStore || heightAuto.value) return;
|
||||||
|
imageDefaults.height = { auto: false, value: val.value, unit: val.unit };
|
||||||
|
debouncedUpdate(() => {
|
||||||
|
IMAGE_SELECTORS.forEach(sel => {
|
||||||
|
updateStyle(sel, 'height', `${val.value}${val.unit}`);
|
||||||
|
updateStyle(`${sel} img`, 'height', '100%');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, { deep: true });
|
||||||
|
|
||||||
const syncFromStore = () => {
|
const syncFromStore = () => {
|
||||||
isUpdatingFromStore = true;
|
isUpdatingFromStore = true;
|
||||||
|
|
||||||
if (imageDefaults._initialized) {
|
if (imageDefaults._initialized) {
|
||||||
width.value = { value: imageDefaults.width.value, unit: imageDefaults.width.unit };
|
width.value = { value: imageDefaults.width.value, unit: imageDefaults.width.unit };
|
||||||
|
heightAuto.value = imageDefaults.height.auto;
|
||||||
|
height.value = { value: imageDefaults.height.value, unit: imageDefaults.height.unit };
|
||||||
} else {
|
} else {
|
||||||
const widthVal = extractNumericValue('.block-image', 'width', ['%', 'px']);
|
const widthVal = extractNumericValue('.block-image', 'width', ['%', 'px']);
|
||||||
if (widthVal) width.value = widthVal;
|
if (widthVal) width.value = widthVal;
|
||||||
|
|
||||||
|
const heightVal = extractValue('.block-image', 'height');
|
||||||
|
if (!heightVal || heightVal === 'auto') {
|
||||||
|
heightAuto.value = true;
|
||||||
|
} else {
|
||||||
|
const m = heightVal.match(/([\d.]+)px/i);
|
||||||
|
if (m) {
|
||||||
|
heightAuto.value = false;
|
||||||
|
height.value = { value: parseFloat(m[1]), unit: 'px' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
imageDefaults.width = { value: width.value.value, unit: width.value.unit };
|
imageDefaults.width = { value: width.value.value, unit: width.value.unit };
|
||||||
|
imageDefaults.height = { auto: heightAuto.value, value: height.value.value, unit: height.value.unit };
|
||||||
imageDefaults._initialized = true;
|
imageDefaults._initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { IMAGE_DEFAULTS } from '../utils/defaults';
|
||||||
// Singleton reactive — ImageSettings writes here, ImagePopup reads when toggle is disabled
|
// Singleton reactive — ImageSettings writes here, ImagePopup reads when toggle is disabled
|
||||||
const defaults = reactive({
|
const defaults = reactive({
|
||||||
width: { ...IMAGE_DEFAULTS.width },
|
width: { ...IMAGE_DEFAULTS.width },
|
||||||
|
height: { auto: IMAGE_DEFAULTS.height.auto, value: IMAGE_DEFAULTS.height.value, unit: IMAGE_DEFAULTS.height.unit },
|
||||||
_initialized: false,
|
_initialized: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -261,7 +261,17 @@ export const useStylesheetStore = defineStore('stylesheet', () => {
|
||||||
|
|
||||||
// Image defaults
|
// Image defaults
|
||||||
set('.block-image', 'width', IMAGE_DEFAULTS.width.value, IMAGE_DEFAULTS.width.unit);
|
set('.block-image', 'width', IMAGE_DEFAULTS.width.value, IMAGE_DEFAULTS.width.unit);
|
||||||
|
set('.block-image', 'height', 'auto');
|
||||||
|
set('.block-image img', 'object-fit', 'cover');
|
||||||
|
set('.block-image img', 'display', 'flex');
|
||||||
set('.geoformat-cover-image', 'width', IMAGE_DEFAULTS.width.value, IMAGE_DEFAULTS.width.unit);
|
set('.geoformat-cover-image', 'width', IMAGE_DEFAULTS.width.value, IMAGE_DEFAULTS.width.unit);
|
||||||
|
set('.geoformat-cover-image', 'height', 'auto');
|
||||||
|
set('.geoformat-cover-image img', 'object-fit', 'cover');
|
||||||
|
set('.geoformat-cover-image img', 'display', 'flex');
|
||||||
|
set('.block-carte', 'width', IMAGE_DEFAULTS.width.value, IMAGE_DEFAULTS.width.unit);
|
||||||
|
set('.block-carte', 'height', 'auto');
|
||||||
|
set('.block-carte img', 'object-fit', 'cover');
|
||||||
|
set('.block-carte img', 'display', 'flex');
|
||||||
|
|
||||||
// Inline element defaults (em, i, strong, b, a)
|
// 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)) {
|
||||||
|
|
|
||||||
|
|
@ -128,6 +128,7 @@ export const PARAGRAPH_CLASS_DEFAULTS = Object.freeze({
|
||||||
|
|
||||||
export const IMAGE_DEFAULTS = Object.freeze({
|
export const IMAGE_DEFAULTS = Object.freeze({
|
||||||
width: Object.freeze({ value: 100, unit: '%' }),
|
width: Object.freeze({ value: 100, unit: '%' }),
|
||||||
|
height: Object.freeze({ auto: true, value: 400, unit: 'px' }),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const INLINE_DEFAULTS = Object.freeze({
|
export const INLINE_DEFAULTS = Object.freeze({
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue