Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/SLB-451--conditional-content-blo…
Browse files Browse the repository at this point in the history
…ck' into dev
  • Loading branch information
Leksat committed Jul 19, 2024
2 parents d136871 + 3091d2d commit 834a6fb
Show file tree
Hide file tree
Showing 9 changed files with 393 additions and 24 deletions.
171 changes: 171 additions & 0 deletions packages/drupal/gutenberg_blocks/src/blocks/conditional.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import clsx from 'clsx';
import { InnerBlocks, InspectorControls } from 'wordpress__block-editor';
import { registerBlockType } from 'wordpress__blocks';
import { BaseControl, PanelBody } from 'wordpress__components';

// @ts-ignore
const { t: __ } = Drupal;

registerBlockType(`custom/conditional`, {
title: __('Conditional content'),
category: 'layout',
icon: 'category',
// Allow the block only at the root level to avoid GraphQL fragment recursion.
parent: ['custom/content'],
attributes: {
displayFrom: {
type: 'string',
default: '',
},
displayTo: {
type: 'string',
default: '',
},
purpose: {
type: 'string',
default: '',
},
},
edit(props) {
const { attributes, setAttributes } = props;

const displayFrom = attributes.displayFrom as string | undefined;
const displayTo = attributes.displayTo as string | undefined;
const purpose = ((attributes.purpose as string) || '').trim();

// Same logic as in BlockConditional.tsx
const active = {
scheduledDisplay: [
displayFrom
? new Date(displayFrom).getTime() <= new Date().getTime()
: true,
displayTo ? new Date(displayTo).getTime() > new Date().getTime() : true,
].every(Boolean),
};
const isActive = Object.values(active).every(Boolean);

const conditions = {
scheduledDisplay:
displayFrom || displayTo
? '🕒 ' +
__('Scheduled display') +
': ' +
[
displayFrom
? __('From') + ' ' + new Date(displayFrom).toLocaleString()
: '',
displayTo
? __('To') + ' ' + new Date(displayTo).toLocaleString()
: '',
]
.filter(Boolean)
.join(' ')
: '',
};
const hasConditions = Object.values(conditions).some(Boolean);
const summary = hasConditions ? (
Object.entries(conditions)
.filter(([, value]) => !!value)
.map(([key, value]) => <div key={key}>{value}</div>)
) : (
<div>{'ℹ️ ' + __('No conditions set')}</div>
);

return (
<div className={clsx('container-wrapper', { 'bg-gray-100': !isActive })}>
<div className={'container-label'}>{__('Conditional content')}</div>
<div className="text-sm text-gray-500">{summary}</div>
<details open={isActive}>
<summary
style={{ cursor: 'pointer', padding: 0 }}
className="text-sm text-gray-500"
>
{purpose || __('Content')}
</summary>
<InnerBlocks
templateLock={false}
template={[['core/paragraph', {}]]}
/>
</details>
<InspectorControls>
<PanelBody title={__('Purpose')}>
<BaseControl id="purpose">
<input
type="text"
id="purpose"
defaultValue={purpose}
onChange={(event) => {
setAttributes({ purpose: event.target.value });
}}
/>
</BaseControl>
<BaseControl
id="purpose-decription"
label={__(
'The value is not exposed to the frontend and serves to identify the reason of the conditional content (e.g. Summer Campaign).',
)}
>
<div />
</BaseControl>
</PanelBody>
<PanelBody title={__('Scheduled display')}>
<BaseControl id="displayFrom" label={__('From')}>
<br />
<input
type="datetime-local"
id="displayFrom"
defaultValue={displayFrom ? isoToLocalTime(displayFrom) : ''}
onChange={(event) => {
setAttributes({
displayFrom: event.target.value
? localToIsoTime(event.target.value)
: '',
});
}}
/>
</BaseControl>
<BaseControl id="displayTo" label={__('To')}>
<br />
<input
type="datetime-local"
id="displayTo"
defaultValue={displayTo ? isoToLocalTime(displayTo) : ''}
onChange={(event) => {
setAttributes({
displayTo: event.target.value
? localToIsoTime(event.target.value)
: '',
});
}}
/>
</BaseControl>
<BaseControl
id="decription"
label={
__('Time zone') +
': ' +
Intl.DateTimeFormat().resolvedOptions().timeZone
}
>
<div />
</BaseControl>
</PanelBody>
</InspectorControls>
</div>
);
},

save() {
return <InnerBlocks.Content />;
},
});

function localToIsoTime(localTime: string) {
return new Date(localTime).toISOString();
}

function isoToLocalTime(isoTime: string) {
const date = new Date(isoTime);
date.setMinutes(date.getMinutes() - date.getTimezoneOffset());
return date.toISOString().slice(0, 16);
}
1 change: 1 addition & 0 deletions packages/drupal/gutenberg_blocks/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import './blocks/image-with-text';
import './filters/list';
import './blocks/cta';
import './blocks/quote';
import './blocks/conditional';
import './blocks/horizontal-separator';
import './blocks/accordion';
import './blocks/accordion-item-text';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
_meta:
version: '1.0'
entity_type: node
uuid: 52ee5cc7-0ac5-49b5-8550-ce59476bd4ac
bundle: page
default_langcode: en
default:
revision_uid:
-
target_id: 1
status:
-
value: true
uid:
-
target_id: 1
title:
-
value: 'Conditional blocks'
created:
-
value: 1715684657
promote:
-
value: false
sticky:
-
value: false
moderation_state:
-
value: published
path:
-
alias: /conditional-blocks
langcode: en
pathauto: 0
content_translation_source:
-
value: und
content_translation_outdated:
-
value: false
body:
-
value: |-
<!-- wp:custom/hero /-->
<!-- wp:custom/content -->
<!-- wp:custom/conditional {"displayFrom":"2024-05-16T11:05:00.000Z","displayTo":"2024-05-23T13:03:00.000Z","purpose":"Summer Campaign"} -->
<!-- wp:paragraph -->
<p>Complete</p>
<!-- /wp:paragraph -->
<!-- /wp:custom/conditional -->
<!-- wp:custom/conditional -->
<!-- wp:paragraph -->
<p>No conditions</p>
<!-- /wp:paragraph -->
<!-- /wp:custom/conditional -->
<!-- /wp:custom/content -->
format: gutenberg
summary: ''
1 change: 1 addition & 0 deletions packages/schema/src/fragments/Page.gql
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ fragment Page on Page {
...BlockHorizontalSeparator
...BlockAccordion
...BlockInfoGrid
...BlockConditional
}
metaTags {
tag
Expand Down
17 changes: 17 additions & 0 deletions packages/schema/src/fragments/PageContent/BlockConditional.gql
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
fragment BlockConditional on BlockConditional {
displayFrom
displayTo
content {
__typename
...BlockMarkup
...BlockMedia
...BlockForm
...BlockImageTeasers
...BlockCta
...BlockImageWithText
...BlockQuote
...BlockHorizontalSeparator
...BlockAccordion
...BlockInfoGrid
}
}
19 changes: 19 additions & 0 deletions packages/schema/src/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,18 @@ type Hero {
@webformIdToUrl(id: "$")
}

union CommonContent @resolveEditorBlockType =
| BlockMarkup
| BlockMedia
| BlockForm
| BlockImageTeasers
| BlockCta
| BlockImageWithText
| BlockQuote
| BlockHorizontalSeparator
| BlockAccordion
| BlockInfoGrid

union PageContent @resolveEditorBlockType =
| BlockMarkup
| BlockMedia
Expand All @@ -214,6 +226,7 @@ union PageContent @resolveEditorBlockType =
| BlockHorizontalSeparator
| BlockAccordion
| BlockInfoGrid
| BlockConditional

type BlockForm @type(id: "custom/form") {
url: Url @resolveEditorBlockAttribute(key: "formId") @webformIdToUrl(id: "$")
Expand Down Expand Up @@ -335,6 +348,12 @@ type BlockHorizontalSeparator @type(id: "custom/horizontal-separator") {
markup: Markup! @resolveEditorBlockMarkup
}

type BlockConditional @type(id: "custom/conditional") {
displayFrom: String @resolveEditorBlockAttribute(key: "displayFrom")
displayTo: String @resolveEditorBlockAttribute(key: "displayTo")
content: [CommonContent] @resolveEditorBlockChildren
}

input PaginationInput {
limit: Int!
offset: Int!
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { BlockConditionalFragment } from '@custom/schema';
import React, { useEffect, useState } from 'react';

import { isTruthy } from '../../../utils/isTruthy';
import { CommonContent } from '../PageDisplay';

export function BlockConditional(props: BlockConditionalFragment) {
const [isActive, setIsActive] = useState(false);
useEffect(() => {
const active = {
scheduledDisplay: [
props.displayFrom
? new Date(props.displayFrom).getTime() <= new Date().getTime()
: true,
props.displayTo
? new Date(props.displayTo).getTime() > new Date().getTime()
: true,
].every(Boolean),
};
setIsActive(Object.values(active).every(Boolean));
}, []);

return isActive ? (
<>
{props.content?.filter(isTruthy).map((block, index) => {
return <CommonContent key={index} {...block} />;
})}
</>
) : null;
}
Loading

0 comments on commit 834a6fb

Please sign in to comment.