Refactor blocks architecture to modular approach
- Restore "1/2, 1/2" layout for flexible column combinations - Simplify beforeafter block: remove toggle and text field, keep only image comparison - Create new video block with URL support (YouTube/Vimeo/direct files) - Create horizontal-gallery block for scrollable image galleries - Add H4 heading level support - All blocks now modular: combine with text blocks in 2-column layouts Blocks available: - Text, Heading (h2-h4), Image, Video - Before/After comparison (no text) - Horizontal gallery (with text below) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
6251d8f09f
commit
561932724b
23 changed files with 539 additions and 252 deletions
|
|
@ -13,6 +13,9 @@ fields:
|
||||||
- value: h3
|
- value: h3
|
||||||
icon: h3
|
icon: h3
|
||||||
text: H3
|
text: H3
|
||||||
|
- value: h4
|
||||||
|
icon: h4
|
||||||
|
text: H4
|
||||||
text:
|
text:
|
||||||
label: field.blocks.heading.text
|
label: field.blocks.heading.text
|
||||||
type: writer
|
type: writer
|
||||||
|
|
|
||||||
|
|
@ -44,11 +44,14 @@ tabs:
|
||||||
type: layout
|
type: layout
|
||||||
layouts:
|
layouts:
|
||||||
- "1/1"
|
- "1/1"
|
||||||
|
- "1/2, 1/2"
|
||||||
fieldsets:
|
fieldsets:
|
||||||
- text
|
- text
|
||||||
- heading
|
- heading
|
||||||
- image
|
- image
|
||||||
|
- video
|
||||||
- beforeafter
|
- beforeafter
|
||||||
|
- horizontalgallery
|
||||||
- width: 1/6
|
- width: 1/6
|
||||||
fields:
|
fields:
|
||||||
emptyRight:
|
emptyRight:
|
||||||
|
|
|
||||||
|
|
@ -1,52 +1,23 @@
|
||||||
name: Image avec texte
|
name: Comparaison Avant/Après
|
||||||
icon: images
|
icon: images
|
||||||
preview: beforeafter
|
preview: beforeafter
|
||||||
fields:
|
fields:
|
||||||
isBeforeAfter:
|
|
||||||
label: Mode comparaison Avant/Après
|
|
||||||
type: toggle
|
|
||||||
default: false
|
|
||||||
image:
|
|
||||||
label: Image
|
|
||||||
type: files
|
|
||||||
multiple: false
|
|
||||||
layout: cards
|
|
||||||
query: page.images
|
|
||||||
when:
|
|
||||||
isBeforeAfter: false
|
|
||||||
imageBefore:
|
imageBefore:
|
||||||
label: Image "Avant"
|
label: Image "Avant"
|
||||||
type: files
|
type: files
|
||||||
multiple: false
|
multiple: false
|
||||||
layout: cards
|
layout: cards
|
||||||
query: page.images
|
query: page.images
|
||||||
uploads: false
|
|
||||||
help: Image affichée à gauche / dessous
|
help: Image affichée à gauche / dessous
|
||||||
width: 1/2
|
width: 1/2
|
||||||
when:
|
|
||||||
isBeforeAfter: true
|
|
||||||
imageAfter:
|
imageAfter:
|
||||||
label: Image "Après"
|
label: Image "Après"
|
||||||
type: files
|
type: files
|
||||||
multiple: false
|
multiple: false
|
||||||
layout: cards
|
layout: cards
|
||||||
query: page.images
|
query: page.images
|
||||||
uploads: false
|
|
||||||
help: Image affichée à droite / dessus
|
help: Image affichée à droite / dessus
|
||||||
width: 1/2
|
width: 1/2
|
||||||
when:
|
|
||||||
isBeforeAfter: true
|
|
||||||
caption:
|
caption:
|
||||||
label: Légende
|
label: Légende
|
||||||
type: text
|
type: text
|
||||||
text:
|
|
||||||
label: Texte
|
|
||||||
type: writer
|
|
||||||
marks:
|
|
||||||
- bold
|
|
||||||
- italic
|
|
||||||
- link
|
|
||||||
nodes:
|
|
||||||
- bulletList
|
|
||||||
- orderedList
|
|
||||||
- paragraph
|
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
.imagetext-preview[data-v-caec09ec]{cursor:pointer;border-radius:var(--rounded);overflow:hidden;background:var(--color-background);border:1px solid var(--color-gray-300)}.imagetext-preview__container[data-v-caec09ec]{display:grid;grid-template-columns:1fr 1fr;gap:1rem;padding:1rem}.imagetext-preview__media[data-v-caec09ec]{display:flex;flex-direction:column;gap:.5rem}.imagetext-preview__slider[data-v-caec09ec]{position:relative;width:100%;height:200px;background:var(--color-gray-200);border-radius:var(--rounded-sm);overflow:hidden}.imagetext-preview__image[data-v-caec09ec]{position:absolute;top:0;left:0;width:100%;height:100%;object-fit:cover}.imagetext-preview__image--before[data-v-caec09ec]{clip-path:polygon(0 0,50% 0,50% 100%,0 100%)}.imagetext-preview__divider[data-v-caec09ec]{position:absolute;left:50%;top:0;bottom:0;width:2px;background:#fff;box-shadow:0 0 8px #0000004d;transform:translate(-1px)}.imagetext-preview__single-image[data-v-caec09ec]{width:100%;height:200px;object-fit:cover;border-radius:var(--rounded-sm);background:var(--color-gray-200)}.imagetext-preview__empty-media[data-v-caec09ec]{width:100%;height:200px;display:flex;align-items:center;justify-content:center;background:var(--color-gray-200);border-radius:var(--rounded-sm);color:var(--color-gray-500);font-size:var(--text-sm)}.imagetext-preview__caption[data-v-caec09ec]{font-size:var(--text-sm);color:var(--color-gray-600);font-style:italic;margin:0;padding:0 .25rem}.imagetext-preview__text[data-v-caec09ec]{font-size:var(--text-sm);color:var(--color-gray-700);line-height:1.5}.imagetext-preview__empty-text[data-v-caec09ec]{color:var(--color-gray-500);font-style:italic}.imagetext-preview[data-v-caec09ec]:hover{border-color:var(--color-gray-400)}
|
.beforeafter-preview[data-v-7994b7b1]{cursor:pointer;border-radius:var(--rounded);overflow:hidden;background:var(--color-background);border:1px solid var(--color-gray-300)}.beforeafter-preview__slider[data-v-7994b7b1]{position:relative;width:100%;height:200px;background:var(--color-gray-200)}.beforeafter-preview__image[data-v-7994b7b1]{position:absolute;top:0;left:0;width:100%;height:100%;object-fit:cover}.beforeafter-preview__image--before[data-v-7994b7b1]{clip-path:polygon(0 0,50% 0,50% 100%,0 100%)}.beforeafter-preview__divider[data-v-7994b7b1]{position:absolute;left:50%;top:0;bottom:0;width:2px;background:#fff;box-shadow:0 0 8px #0000004d;transform:translate(-1px)}.beforeafter-preview__caption[data-v-7994b7b1]{padding:.75rem;font-size:var(--text-sm);color:var(--color-gray-600);font-style:italic;background:var(--color-background);margin:0}.beforeafter-preview__empty[data-v-7994b7b1]{padding:3rem 1rem;text-align:center;color:var(--color-gray-500);font-size:var(--text-sm);background:var(--color-gray-100)}.beforeafter-preview[data-v-7994b7b1]:hover{border-color:var(--color-gray-400)}
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
(function(){"use strict";function m(a,e,t,i,s,c,r,n){var o=typeof a=="function"?a.options:a;return e&&(o.render=e,o.staticRenderFns=t,o._compiled=!0),o._scopeId="data-v-"+c,{exports:a,options:o}}const l={__name:"BeforeAfterBlock",props:{content:Object},setup(a){const e=a,t=Vue.computed(()=>{var r,n;return((r=e.content)==null?void 0:r.isbeforeafter)===!0||((n=e.content)==null?void 0:n.isbeforeafter)==="true"}),i=Vue.computed(()=>{var n;if(!((n=e.content)!=null&&n.image)||!e.content.image.length)return null;const r=e.content.image[0];return(r==null?void 0:r.url)||null}),s=Vue.computed(()=>{var n;if(!((n=e.content)!=null&&n.imagebefore)||!e.content.imagebefore.length)return null;const r=e.content.imagebefore[0];return(r==null?void 0:r.url)||null}),c=Vue.computed(()=>{var n;if(!((n=e.content)!=null&&n.imageafter)||!e.content.imageafter.length)return null;const r=e.content.imageafter[0];return(r==null?void 0:r.url)||null});return{__sfc:!0,props:e,isBeforeAfter:t,imageUrl:i,imageBeforeUrl:s,imageAfterUrl:c}}};var _=function(){var e=this,t=e._self._c,i=e._self._setupProxy;return t("div",{staticClass:"imagetext-preview",on:{click:function(s){return e.$emit("open")}}},[t("div",{staticClass:"imagetext-preview__container"},[t("div",{staticClass:"imagetext-preview__media"},[i.isBeforeAfter&&(i.imageBeforeUrl||i.imageAfterUrl)?t("div",{staticClass:"imagetext-preview__slider"},[i.imageAfterUrl?t("img",{staticClass:"imagetext-preview__image imagetext-preview__image--after",attrs:{src:i.imageAfterUrl,alt:"Après"}}):e._e(),i.imageBeforeUrl?t("img",{staticClass:"imagetext-preview__image imagetext-preview__image--before",attrs:{src:i.imageBeforeUrl,alt:"Avant"}}):e._e(),i.imageBeforeUrl&&i.imageAfterUrl?t("div",{staticClass:"imagetext-preview__divider"}):e._e()]):!i.isBeforeAfter&&i.imageUrl?t("img",{staticClass:"imagetext-preview__single-image",attrs:{src:i.imageUrl,alt:"Image"}}):t("div",{staticClass:"imagetext-preview__empty-media"},[e._v(" Aucune image ")]),e.content.caption?t("p",{staticClass:"imagetext-preview__caption"},[e._v(" "+e._s(e.content.caption)+" ")]):e._e()]),t("div",{staticClass:"imagetext-preview__text"},[e.content.text?t("div",{domProps:{innerHTML:e._s(e.content.text)}}):t("div",{staticClass:"imagetext-preview__empty-text"},[e._v(" Aucun texte ")])])])])},f=[],g=m(l,_,f,!1,null,"caec09ec");const u=g.exports;window.panel.plugin("index/beforeafter",{blocks:{beforeafter:u}})})();
|
(function(){"use strict";function f(a,e,r,t,n,o,u,p){var i=typeof a=="function"?a.options:a;return e&&(i.render=e,i.staticRenderFns=r,i._compiled=!0),i._scopeId="data-v-"+o,{exports:a,options:i}}const s={__name:"BeforeAfterBlock",props:{content:Object},setup(a){const e=a,r=Vue.computed(()=>{var o;if(!((o=e.content)!=null&&o.imagebefore)||!e.content.imagebefore.length)return null;const n=e.content.imagebefore[0];return(n==null?void 0:n.url)||null}),t=Vue.computed(()=>{var o;if(!((o=e.content)!=null&&o.imageafter)||!e.content.imageafter.length)return null;const n=e.content.imageafter[0];return(n==null?void 0:n.url)||null});return{__sfc:!0,props:e,imageBeforeUrl:r,imageAfterUrl:t}}};var c=function(){var e=this,r=e._self._c,t=e._self._setupProxy;return r("div",{staticClass:"beforeafter-preview",on:{click:function(n){return e.$emit("open")}}},[t.imageBeforeUrl||t.imageAfterUrl?r("div",{staticClass:"beforeafter-preview__slider"},[t.imageAfterUrl?r("img",{staticClass:"beforeafter-preview__image beforeafter-preview__image--after",attrs:{src:t.imageAfterUrl,alt:"Après"}}):e._e(),t.imageBeforeUrl?r("img",{staticClass:"beforeafter-preview__image beforeafter-preview__image--before",attrs:{src:t.imageBeforeUrl,alt:"Avant"}}):e._e(),t.imageBeforeUrl&&t.imageAfterUrl?r("div",{staticClass:"beforeafter-preview__divider"}):e._e()]):e._e(),e.content.caption?r("p",{staticClass:"beforeafter-preview__caption"},[e._v(" "+e._s(e.content.caption)+" ")]):e._e(),!t.imageBeforeUrl&&!t.imageAfterUrl?r("div",{staticClass:"beforeafter-preview__empty"},[e._v(" Cliquer pour ajouter des images ")]):e._e()])},l=[],_=f(s,c,l,!1,null,"7994b7b1");const m=_.exports;window.panel.plugin("index/beforeafter",{blocks:{beforeafter:m}})})();
|
||||||
|
|
|
||||||
|
|
@ -1,121 +1,98 @@
|
||||||
<?php
|
<?php
|
||||||
/** @var \Kirby\Cms\Block $block */
|
/** @var \Kirby\Cms\Block $block */
|
||||||
$isBeforeAfter = $block->isBeforeAfter()->toBool();
|
|
||||||
$image = $block->image()->toFile();
|
|
||||||
$imageBefore = $block->imageBefore()->toFile();
|
$imageBefore = $block->imageBefore()->toFile();
|
||||||
$imageAfter = $block->imageAfter()->toFile();
|
$imageAfter = $block->imageAfter()->toFile();
|
||||||
$caption = $block->caption()->value();
|
$caption = $block->caption()->value();
|
||||||
$text = $block->text()->value();
|
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="subsection-w-media">
|
<?php if ($imageBefore || $imageAfter): ?>
|
||||||
|
<div class="container slider-before-after">
|
||||||
<div class="media">
|
<div class="image-container">
|
||||||
<?php if ($isBeforeAfter && ($imageBefore || $imageAfter)): ?>
|
<?php if ($imageBefore): ?>
|
||||||
<!-- Mode avant/après : slider -->
|
<img class="image-before slider-image"
|
||||||
<div class="container slider-before-after">
|
src="<?= $imageBefore->url() ?>"
|
||||||
<div class="image-container">
|
alt="<?= $imageBefore->alt()->or('Image avant')->esc() ?>" />
|
||||||
<?php if ($imageBefore): ?>
|
|
||||||
<img class="image-before slider-image"
|
|
||||||
src="<?= $imageBefore->url() ?>"
|
|
||||||
alt="<?= $imageBefore->alt()->or('Image avant')->esc() ?>" />
|
|
||||||
<?php endif ?>
|
|
||||||
|
|
||||||
<?php if ($imageAfter): ?>
|
|
||||||
<img class="image-after slider-image"
|
|
||||||
src="<?= $imageAfter->url() ?>"
|
|
||||||
alt="<?= $imageAfter->alt()->or('Image après')->esc() ?>" />
|
|
||||||
<?php endif ?>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<input
|
|
||||||
type="range"
|
|
||||||
min="0"
|
|
||||||
max="100"
|
|
||||||
value="50"
|
|
||||||
aria-label="Pourcentage de la photo avant affichée"
|
|
||||||
class="slider"
|
|
||||||
/>
|
|
||||||
<div class="slider-line" aria-hidden="true"></div>
|
|
||||||
<div class="slider-button" aria-hidden="true">
|
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
width="30"
|
|
||||||
height="30"
|
|
||||||
fill="currentColor"
|
|
||||||
viewBox="0 0 256 256"
|
|
||||||
>
|
|
||||||
<rect width="256" height="256" fill="none"></rect>
|
|
||||||
<line
|
|
||||||
x1="128"
|
|
||||||
y1="40"
|
|
||||||
x2="128"
|
|
||||||
y2="216"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="16"
|
|
||||||
></line>
|
|
||||||
<line
|
|
||||||
x1="96"
|
|
||||||
y1="128"
|
|
||||||
x2="16"
|
|
||||||
y2="128"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="16"
|
|
||||||
></line>
|
|
||||||
<polyline
|
|
||||||
points="48 160 16 128 48 96"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="16"
|
|
||||||
></polyline>
|
|
||||||
<line
|
|
||||||
x1="160"
|
|
||||||
y1="128"
|
|
||||||
x2="240"
|
|
||||||
y2="128"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="16"
|
|
||||||
></line>
|
|
||||||
<polyline
|
|
||||||
points="208 96 240 128 208 160"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
stroke-linecap="round"
|
|
||||||
stroke-linejoin="round"
|
|
||||||
stroke-width="16"
|
|
||||||
></polyline>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<?php elseif (!$isBeforeAfter && $image): ?>
|
|
||||||
<!-- Mode simple : une seule image -->
|
|
||||||
<div class="container-figure fig-simple">
|
|
||||||
<figure>
|
|
||||||
<img src="<?= $image->url() ?>" alt="<?= $image->alt()->esc() ?>" />
|
|
||||||
</figure>
|
|
||||||
</div>
|
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
|
|
||||||
<?php if ($caption): ?>
|
<?php if ($imageAfter): ?>
|
||||||
<p class="caption"><?= $caption ?></p>
|
<img class="image-after slider-image"
|
||||||
|
src="<?= $imageAfter->url() ?>"
|
||||||
|
alt="<?= $imageAfter->alt()->or('Image après')->esc() ?>" />
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<?php if ($text): ?>
|
<input
|
||||||
<div class="subsection-txt">
|
type="range"
|
||||||
<?= $text ?>
|
min="0"
|
||||||
|
max="100"
|
||||||
|
value="50"
|
||||||
|
aria-label="Pourcentage de la photo avant affichée"
|
||||||
|
class="slider"
|
||||||
|
/>
|
||||||
|
<div class="slider-line" aria-hidden="true"></div>
|
||||||
|
<div class="slider-button" aria-hidden="true">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="30"
|
||||||
|
height="30"
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 256 256"
|
||||||
|
>
|
||||||
|
<rect width="256" height="256" fill="none"></rect>
|
||||||
|
<line
|
||||||
|
x1="128"
|
||||||
|
y1="40"
|
||||||
|
x2="128"
|
||||||
|
y2="216"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="16"
|
||||||
|
></line>
|
||||||
|
<line
|
||||||
|
x1="96"
|
||||||
|
y1="128"
|
||||||
|
x2="16"
|
||||||
|
y2="128"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="16"
|
||||||
|
></line>
|
||||||
|
<polyline
|
||||||
|
points="48 160 16 128 48 96"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="16"
|
||||||
|
></polyline>
|
||||||
|
<line
|
||||||
|
x1="160"
|
||||||
|
y1="128"
|
||||||
|
x2="240"
|
||||||
|
y2="128"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="16"
|
||||||
|
></line>
|
||||||
|
<polyline
|
||||||
|
points="208 96 240 128 208 160"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="16"
|
||||||
|
></polyline>
|
||||||
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<?php endif ?>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<?php if ($caption): ?>
|
||||||
|
<p class="caption"><?= $caption ?></p>
|
||||||
|
<?php endif ?>
|
||||||
|
<?php endif ?>
|
||||||
|
|
|
||||||
|
|
@ -1,51 +1,27 @@
|
||||||
<template>
|
<template>
|
||||||
<div @click="$emit('open')" class="imagetext-preview">
|
<div @click="$emit('open')" class="beforeafter-preview">
|
||||||
<div class="imagetext-preview__container">
|
<div v-if="imageBeforeUrl || imageAfterUrl" class="beforeafter-preview__slider">
|
||||||
<!-- Zone image / slider -->
|
<img
|
||||||
<div class="imagetext-preview__media">
|
v-if="imageAfterUrl"
|
||||||
<!-- Mode avant/après -->
|
:src="imageAfterUrl"
|
||||||
<div v-if="isBeforeAfter && (imageBeforeUrl || imageAfterUrl)" class="imagetext-preview__slider">
|
class="beforeafter-preview__image beforeafter-preview__image--after"
|
||||||
<img
|
alt="Après"
|
||||||
v-if="imageAfterUrl"
|
/>
|
||||||
:src="imageAfterUrl"
|
<img
|
||||||
class="imagetext-preview__image imagetext-preview__image--after"
|
v-if="imageBeforeUrl"
|
||||||
alt="Après"
|
:src="imageBeforeUrl"
|
||||||
/>
|
class="beforeafter-preview__image beforeafter-preview__image--before"
|
||||||
<img
|
alt="Avant"
|
||||||
v-if="imageBeforeUrl"
|
/>
|
||||||
:src="imageBeforeUrl"
|
<div v-if="imageBeforeUrl && imageAfterUrl" class="beforeafter-preview__divider"></div>
|
||||||
class="imagetext-preview__image imagetext-preview__image--before"
|
</div>
|
||||||
alt="Avant"
|
|
||||||
/>
|
|
||||||
<div v-if="imageBeforeUrl && imageAfterUrl" class="imagetext-preview__divider"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Mode simple -->
|
<p v-if="content.caption" class="beforeafter-preview__caption">
|
||||||
<img
|
{{ content.caption }}
|
||||||
v-else-if="!isBeforeAfter && imageUrl"
|
</p>
|
||||||
:src="imageUrl"
|
|
||||||
class="imagetext-preview__single-image"
|
|
||||||
alt="Image"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- Placeholder si pas d'image -->
|
<div v-if="!imageBeforeUrl && !imageAfterUrl" class="beforeafter-preview__empty">
|
||||||
<div v-else class="imagetext-preview__empty-media">
|
Cliquer pour ajouter des images
|
||||||
Aucune image
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Légende -->
|
|
||||||
<p v-if="content.caption" class="imagetext-preview__caption">
|
|
||||||
{{ content.caption }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Zone texte -->
|
|
||||||
<div class="imagetext-preview__text">
|
|
||||||
<div v-if="content.text" v-html="content.text"></div>
|
|
||||||
<div v-else class="imagetext-preview__empty-text">
|
|
||||||
Aucun texte
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -57,21 +33,6 @@ const props = defineProps({
|
||||||
content: Object
|
content: Object
|
||||||
});
|
});
|
||||||
|
|
||||||
// Attention: les clés sont en minuscules dans Kirby
|
|
||||||
const isBeforeAfter = computed(() => {
|
|
||||||
return props.content?.isbeforeafter === true || props.content?.isbeforeafter === "true";
|
|
||||||
});
|
|
||||||
|
|
||||||
// Mode simple : une seule image
|
|
||||||
const imageUrl = computed(() => {
|
|
||||||
if (!props.content?.image || !props.content.image.length) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const file = props.content.image[0];
|
|
||||||
return file?.url || null;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Mode avant/après : deux images
|
|
||||||
const imageBeforeUrl = computed(() => {
|
const imageBeforeUrl = computed(() => {
|
||||||
if (!props.content?.imagebefore || !props.content.imagebefore.length) {
|
if (!props.content?.imagebefore || !props.content.imagebefore.length) {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -90,7 +51,7 @@ const imageAfterUrl = computed(() => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.imagetext-preview {
|
.beforeafter-preview {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: var(--rounded);
|
border-radius: var(--rounded);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
@ -98,30 +59,14 @@ const imageAfterUrl = computed(() => {
|
||||||
border: 1px solid var(--color-gray-300);
|
border: 1px solid var(--color-gray-300);
|
||||||
}
|
}
|
||||||
|
|
||||||
.imagetext-preview__container {
|
.beforeafter-preview__slider {
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: 1rem;
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Zone média (image ou slider) */
|
|
||||||
.imagetext-preview__media {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.imagetext-preview__slider {
|
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 200px;
|
height: 200px;
|
||||||
background: var(--color-gray-200);
|
background: var(--color-gray-200);
|
||||||
border-radius: var(--rounded-sm);
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.imagetext-preview__image {
|
.beforeafter-preview__image {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|
@ -130,11 +75,11 @@ const imageAfterUrl = computed(() => {
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
.imagetext-preview__image--before {
|
.beforeafter-preview__image--before {
|
||||||
clip-path: polygon(0 0, 50% 0, 50% 100%, 0 100%);
|
clip-path: polygon(0 0, 50% 0, 50% 100%, 0 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.imagetext-preview__divider {
|
.beforeafter-preview__divider {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|
@ -145,47 +90,24 @@ const imageAfterUrl = computed(() => {
|
||||||
transform: translateX(-1px);
|
transform: translateX(-1px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.imagetext-preview__single-image {
|
.beforeafter-preview__caption {
|
||||||
width: 100%;
|
padding: 0.75rem;
|
||||||
height: 200px;
|
|
||||||
object-fit: cover;
|
|
||||||
border-radius: var(--rounded-sm);
|
|
||||||
background: var(--color-gray-200);
|
|
||||||
}
|
|
||||||
|
|
||||||
.imagetext-preview__empty-media {
|
|
||||||
width: 100%;
|
|
||||||
height: 200px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
background: var(--color-gray-200);
|
|
||||||
border-radius: var(--rounded-sm);
|
|
||||||
color: var(--color-gray-500);
|
|
||||||
font-size: var(--text-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
.imagetext-preview__caption {
|
|
||||||
font-size: var(--text-sm);
|
font-size: var(--text-sm);
|
||||||
color: var(--color-gray-600);
|
color: var(--color-gray-600);
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
|
background: var(--color-background);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0 0.25rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Zone texte */
|
.beforeafter-preview__empty {
|
||||||
.imagetext-preview__text {
|
padding: 3rem 1rem;
|
||||||
font-size: var(--text-sm);
|
text-align: center;
|
||||||
color: var(--color-gray-700);
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.imagetext-preview__empty-text {
|
|
||||||
color: var(--color-gray-500);
|
color: var(--color-gray-500);
|
||||||
font-style: italic;
|
font-size: var(--text-sm);
|
||||||
|
background: var(--color-gray-100);
|
||||||
}
|
}
|
||||||
|
|
||||||
.imagetext-preview:hover {
|
.beforeafter-preview:hover {
|
||||||
border-color: var(--color-gray-400);
|
border-color: var(--color-gray-400);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
2
site/plugins/horizontal-gallery/.gitignore
vendored
Normal file
2
site/plugins/horizontal-gallery/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
node_modules/
|
||||||
|
*.log
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
name: Galerie horizontale
|
||||||
|
icon: images
|
||||||
|
preview: horizontalgallery
|
||||||
|
fields:
|
||||||
|
images:
|
||||||
|
label: Images
|
||||||
|
type: files
|
||||||
|
multiple: true
|
||||||
|
layout: cards
|
||||||
|
query: page.images
|
||||||
|
help: Pensez à ajouter une légende à chaque image via son blueprint (champ Caption)
|
||||||
|
text:
|
||||||
|
label: Texte accompagnant
|
||||||
|
type: writer
|
||||||
|
marks:
|
||||||
|
- bold
|
||||||
|
- italic
|
||||||
|
- link
|
||||||
|
nodes:
|
||||||
|
- bulletList
|
||||||
|
- orderedList
|
||||||
|
- paragraph
|
||||||
1
site/plugins/horizontal-gallery/index.css
Normal file
1
site/plugins/horizontal-gallery/index.css
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
.hgallery-preview[data-v-9cf511cf]{cursor:pointer;border-radius:var(--rounded);overflow:hidden;background:var(--color-background);border:1px solid var(--color-gray-300)}.hgallery-preview__container[data-v-9cf511cf]{display:flex;flex-direction:column;gap:1rem;padding:1rem}.hgallery-preview__gallery[data-v-9cf511cf]{width:100%}.hgallery-preview__scroll[data-v-9cf511cf]{display:flex;gap:1rem;overflow-x:auto;padding-bottom:.5rem;scroll-behavior:smooth}.hgallery-preview__scroll[data-v-9cf511cf]::-webkit-scrollbar{height:6px}.hgallery-preview__scroll[data-v-9cf511cf]::-webkit-scrollbar-track{background:var(--color-gray-200);border-radius:3px}.hgallery-preview__scroll[data-v-9cf511cf]::-webkit-scrollbar-thumb{background:var(--color-gray-400);border-radius:3px}.hgallery-preview__scroll[data-v-9cf511cf]::-webkit-scrollbar-thumb:hover{background:var(--color-gray-500)}.hgallery-preview__slide[data-v-9cf511cf]{flex-shrink:0;width:250px;display:flex;flex-direction:column;gap:.5rem}.hgallery-preview__slide img[data-v-9cf511cf]{width:100%;height:180px;object-fit:cover;border-radius:var(--rounded-sm);background:var(--color-gray-200)}.hgallery-preview__caption[data-v-9cf511cf]{font-size:var(--text-xs);color:var(--color-gray-600);font-style:italic;margin:0;line-height:1.4}.hgallery-preview__empty[data-v-9cf511cf]{padding:3rem 1rem;text-align:center;color:var(--color-gray-500);font-size:var(--text-sm);background:var(--color-gray-100);border-radius:var(--rounded-sm)}.hgallery-preview__text[data-v-9cf511cf]{font-size:var(--text-sm);color:var(--color-gray-700);line-height:1.5;padding-top:.5rem;border-top:1px solid var(--color-gray-200)}.hgallery-preview[data-v-9cf511cf]:hover{border-color:var(--color-gray-400)}
|
||||||
1
site/plugins/horizontal-gallery/index.js
Normal file
1
site/plugins/horizontal-gallery/index.js
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
(function(){"use strict";function r(l,e,t,a,n,s,v,d){var i=typeof l=="function"?l.options:l;return e&&(i.render=e,i.staticRenderFns=t,i._compiled=!0),i._scopeId="data-v-"+s,{exports:l,options:i}}const o={__name:"HorizontalGalleryBlock",props:{content:Object},setup(l){const e=l,t=Vue.computed(()=>{var a;return!((a=e.content)!=null&&a.images)||!e.content.images.length?[]:e.content.images.map(n=>({url:n.url,caption:n.text||n.caption||null}))});return{__sfc:!0,props:e,images:t}}};var c=function(){var e=this,t=e._self._c,a=e._self._setupProxy;return t("div",{staticClass:"hgallery-preview",on:{click:function(n){return e.$emit("open")}}},[t("div",{staticClass:"hgallery-preview__container"},[t("div",{staticClass:"hgallery-preview__gallery"},[a.images.length>0?t("div",{staticClass:"hgallery-preview__scroll"},e._l(a.images,function(n,s){return t("div",{key:s,staticClass:"hgallery-preview__slide"},[t("img",{attrs:{src:n.url,alt:n.caption||"Image"}}),n.caption?t("p",{staticClass:"hgallery-preview__caption"},[e._v(" "+e._s(n.caption)+" ")]):e._e()])}),0):t("div",{staticClass:"hgallery-preview__empty"},[e._v(" Aucune image sélectionnée ")])]),e.content.text?t("div",{staticClass:"hgallery-preview__text"},[t("div",{domProps:{innerHTML:e._s(e.content.text)}})]):e._e()])])},_=[],p=r(o,c,_,!1,null,"9cf511cf");const u=p.exports;window.panel.plugin("index/horizontal-gallery",{blocks:{horizontalgallery:u}})})();
|
||||||
10
site/plugins/horizontal-gallery/index.php
Normal file
10
site/plugins/horizontal-gallery/index.php
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
Kirby::plugin('index/horizontal-gallery', [
|
||||||
|
'blueprints' => [
|
||||||
|
'blocks/horizontalgallery' => __DIR__ . '/blueprints/blocks/horizontalgallery.yml'
|
||||||
|
],
|
||||||
|
'snippets' => [
|
||||||
|
'blocks/horizontalgallery' => __DIR__ . '/snippets/blocks/horizontalgallery.php'
|
||||||
|
]
|
||||||
|
]);
|
||||||
12
site/plugins/horizontal-gallery/package.json
Normal file
12
site/plugins/horizontal-gallery/package.json
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"name": "horizontal-gallery-block",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "npx -y kirbyup src/index.js --watch",
|
||||||
|
"build": "npx -y kirbyup src/index.js"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vitejs/plugin-vue": "^6.0.1",
|
||||||
|
"vite": "^7.1.7"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
/** @var \Kirby\Cms\Block $block */
|
||||||
|
$images = $block->images()->toFiles();
|
||||||
|
$text = $block->text()->value();
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="subsection-w-hscroll">
|
||||||
|
|
||||||
|
<div class="horizontal-scroll-spacer"></div>
|
||||||
|
|
||||||
|
<div class="horizontal-scroll">
|
||||||
|
<div class="horizontal-scroll-wrapper">
|
||||||
|
<?php foreach ($images as $image): ?>
|
||||||
|
<div class="horizontal-scroll-slide">
|
||||||
|
<div class="horizontal-scroll-slide__inner">
|
||||||
|
<figure>
|
||||||
|
<img src="<?= $image->url() ?>" alt="<?= $image->alt()->esc() ?>" />
|
||||||
|
</figure>
|
||||||
|
<?php if ($image->caption()->isNotEmpty()): ?>
|
||||||
|
<p class="caption"><?= $image->caption()->html() ?></p>
|
||||||
|
<?php endif ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php endforeach ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="horizontal-scroll-button-prev"></div>
|
||||||
|
<div class="horizontal-scroll-button-next"></div>
|
||||||
|
<div class="horizontal-scroll-pagination"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if ($text): ?>
|
||||||
|
<div class="subsection-txt">
|
||||||
|
<?= $text ?>
|
||||||
|
</div>
|
||||||
|
<?php endif ?>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
@ -0,0 +1,142 @@
|
||||||
|
<template>
|
||||||
|
<div @click="$emit('open')" class="hgallery-preview">
|
||||||
|
<div class="hgallery-preview__container">
|
||||||
|
<!-- Zone galerie horizontale -->
|
||||||
|
<div class="hgallery-preview__gallery">
|
||||||
|
<div v-if="images.length > 0" class="hgallery-preview__scroll">
|
||||||
|
<div
|
||||||
|
v-for="(image, index) in images"
|
||||||
|
:key="index"
|
||||||
|
class="hgallery-preview__slide"
|
||||||
|
>
|
||||||
|
<img :src="image.url" :alt="image.caption || 'Image'" />
|
||||||
|
<p v-if="image.caption" class="hgallery-preview__caption">
|
||||||
|
{{ image.caption }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else class="hgallery-preview__empty">
|
||||||
|
Aucune image sélectionnée
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Zone texte -->
|
||||||
|
<div v-if="content.text" class="hgallery-preview__text">
|
||||||
|
<div v-html="content.text"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed } from "vue";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
content: Object
|
||||||
|
});
|
||||||
|
|
||||||
|
const images = computed(() => {
|
||||||
|
if (!props.content?.images || !props.content.images.length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return props.content.images.map(file => ({
|
||||||
|
url: file.url,
|
||||||
|
caption: file.text || file.caption || null
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.hgallery-preview {
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: var(--rounded);
|
||||||
|
overflow: hidden;
|
||||||
|
background: var(--color-background);
|
||||||
|
border: 1px solid var(--color-gray-300);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hgallery-preview__container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Galerie horizontale scrollable */
|
||||||
|
.hgallery-preview__gallery {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hgallery-preview__scroll {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hgallery-preview__scroll::-webkit-scrollbar {
|
||||||
|
height: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hgallery-preview__scroll::-webkit-scrollbar-track {
|
||||||
|
background: var(--color-gray-200);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hgallery-preview__scroll::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--color-gray-400);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hgallery-preview__scroll::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--color-gray-500);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hgallery-preview__slide {
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 250px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hgallery-preview__slide img {
|
||||||
|
width: 100%;
|
||||||
|
height: 180px;
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: var(--rounded-sm);
|
||||||
|
background: var(--color-gray-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hgallery-preview__caption {
|
||||||
|
font-size: var(--text-xs);
|
||||||
|
color: var(--color-gray-600);
|
||||||
|
font-style: italic;
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hgallery-preview__empty {
|
||||||
|
padding: 3rem 1rem;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--color-gray-500);
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
background: var(--color-gray-100);
|
||||||
|
border-radius: var(--rounded-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Zone texte */
|
||||||
|
.hgallery-preview__text {
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
color: var(--color-gray-700);
|
||||||
|
line-height: 1.5;
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
border-top: 1px solid var(--color-gray-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hgallery-preview:hover {
|
||||||
|
border-color: var(--color-gray-400);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
7
site/plugins/horizontal-gallery/src/index.js
Normal file
7
site/plugins/horizontal-gallery/src/index.js
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
import HorizontalGalleryBlock from "./components/HorizontalGalleryBlock.vue";
|
||||||
|
|
||||||
|
window.panel.plugin("index/horizontal-gallery", {
|
||||||
|
blocks: {
|
||||||
|
horizontalgallery: HorizontalGalleryBlock
|
||||||
|
}
|
||||||
|
});
|
||||||
2
site/plugins/video/.gitignore
vendored
Normal file
2
site/plugins/video/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
node_modules/
|
||||||
|
*.log
|
||||||
11
site/plugins/video/blueprints/blocks/video.yml
Normal file
11
site/plugins/video/blueprints/blocks/video.yml
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
name: Vidéo
|
||||||
|
icon: video
|
||||||
|
preview: video
|
||||||
|
fields:
|
||||||
|
url:
|
||||||
|
label: URL de la vidéo
|
||||||
|
type: url
|
||||||
|
help: URL YouTube, Vimeo ou lien direct vers un fichier vidéo
|
||||||
|
caption:
|
||||||
|
label: Légende
|
||||||
|
type: text
|
||||||
10
site/plugins/video/index.php
Normal file
10
site/plugins/video/index.php
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
Kirby::plugin('index/video', [
|
||||||
|
'blueprints' => [
|
||||||
|
'blocks/video' => __DIR__ . '/blueprints/blocks/video.yml'
|
||||||
|
],
|
||||||
|
'snippets' => [
|
||||||
|
'blocks/video' => __DIR__ . '/snippets/blocks/video.php'
|
||||||
|
]
|
||||||
|
]);
|
||||||
12
site/plugins/video/package.json
Normal file
12
site/plugins/video/package.json
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"name": "video-block",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "npx -y kirbyup src/index.js --watch",
|
||||||
|
"build": "npx -y kirbyup src/index.js"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@vitejs/plugin-vue": "^6.0.1",
|
||||||
|
"vite": "^7.1.7"
|
||||||
|
}
|
||||||
|
}
|
||||||
39
site/plugins/video/snippets/blocks/video.php
Normal file
39
site/plugins/video/snippets/blocks/video.php
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
<?php
|
||||||
|
/** @var \Kirby\Cms\Block $block */
|
||||||
|
$url = $block->url()->value();
|
||||||
|
$caption = $block->caption()->value();
|
||||||
|
|
||||||
|
// Fonction pour détecter le type de vidéo
|
||||||
|
function getVideoEmbedCode($url) {
|
||||||
|
// YouTube
|
||||||
|
if (preg_match('/youtube\.com\/watch\?v=([^&]+)/', $url, $matches) ||
|
||||||
|
preg_match('/youtu\.be\/([^?]+)/', $url, $matches)) {
|
||||||
|
$videoId = $matches[1];
|
||||||
|
return '<iframe src="https://www.youtube.com/embed/' . $videoId . '" frameborder="0" allowfullscreen></iframe>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vimeo
|
||||||
|
if (preg_match('/vimeo\.com\/(\d+)/', $url, $matches)) {
|
||||||
|
$videoId = $matches[1];
|
||||||
|
return '<iframe src="https://player.vimeo.com/video/' . $videoId . '" frameborder="0" allowfullscreen></iframe>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vidéo directe (mp4, webm, etc.)
|
||||||
|
if (preg_match('/\.(mp4|webm|ogg)$/i', $url)) {
|
||||||
|
return '<video controls><source src="' . $url . '" type="video/' . pathinfo($url, PATHINFO_EXTENSION) . '"></video>';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Par défaut, iframe
|
||||||
|
return '<iframe src="' . $url . '" frameborder="0" allowfullscreen></iframe>';
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
|
||||||
|
<?php if ($url): ?>
|
||||||
|
<div class="container-video">
|
||||||
|
<?= getVideoEmbedCode($url) ?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if ($caption): ?>
|
||||||
|
<p class="caption"><?= $caption ?></p>
|
||||||
|
<?php endif ?>
|
||||||
|
<?php endif ?>
|
||||||
95
site/plugins/video/src/components/VideoBlock.vue
Normal file
95
site/plugins/video/src/components/VideoBlock.vue
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
<template>
|
||||||
|
<div @click="$emit('open')" class="video-preview">
|
||||||
|
<div v-if="content.url" class="video-preview__container">
|
||||||
|
<div class="video-preview__placeholder">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="currentColor">
|
||||||
|
<path d="M8 5v14l11-7z"/>
|
||||||
|
</svg>
|
||||||
|
<p class="video-preview__url">{{ truncatedUrl }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p v-if="content.caption" class="video-preview__caption">
|
||||||
|
{{ content.caption }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div v-if="!content.url" class="video-preview__empty">
|
||||||
|
Cliquer pour ajouter une URL de vidéo
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed } from "vue";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
content: Object
|
||||||
|
});
|
||||||
|
|
||||||
|
const truncatedUrl = computed(() => {
|
||||||
|
if (!props.content?.url) return '';
|
||||||
|
const url = props.content.url;
|
||||||
|
return url.length > 50 ? url.substring(0, 50) + '...' : url;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.video-preview {
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: var(--rounded);
|
||||||
|
overflow: hidden;
|
||||||
|
background: var(--color-background);
|
||||||
|
border: 1px solid var(--color-gray-300);
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-preview__container {
|
||||||
|
width: 100%;
|
||||||
|
height: 180px;
|
||||||
|
background: var(--color-gray-900);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-preview__placeholder {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
color: var(--color-gray-400);
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-preview__placeholder svg {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-preview__url {
|
||||||
|
font-size: var(--text-xs);
|
||||||
|
color: var(--color-gray-500);
|
||||||
|
margin: 0;
|
||||||
|
text-align: center;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-preview__caption {
|
||||||
|
padding: 0.75rem;
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
color: var(--color-gray-600);
|
||||||
|
font-style: italic;
|
||||||
|
background: var(--color-background);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-preview__empty {
|
||||||
|
padding: 3rem 1rem;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--color-gray-500);
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
background: var(--color-gray-100);
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-preview:hover {
|
||||||
|
border-color: var(--color-gray-400);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
7
site/plugins/video/src/index.js
Normal file
7
site/plugins/video/src/index.js
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
import VideoBlock from "./components/VideoBlock.vue";
|
||||||
|
|
||||||
|
window.panel.plugin("index/video", {
|
||||||
|
blocks: {
|
||||||
|
video: VideoBlock
|
||||||
|
}
|
||||||
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue