diff --git a/readme.txt b/readme.txt index f1433bc19..e117e796f 100644 --- a/readme.txt +++ b/readme.txt @@ -107,10 +107,11 @@ e.g. == Changelog == [ Add Block ] Add Post list Slider block. -[ Other ][ Slider ] Refactoring editor script. +[ Add function ][ Fixed Display (Pro) ] Added a timer feature to control block appearance/disappearance and a "Do not show again for returning visitors" option. Session Storage is used, and no personal data is collected or tracked. Since Session Storage is used, the block may reappear after the browser is closed or in private browsing mode. [ Bug fix ][ Cover ] Fixed an issue where, after setting a link in the Cover block and adding two unstyled headings inside it, the content positioning would not apply upon returning to the editing screen (editing screen only). [ Bug Fix ][ Grid Column Card (Pro) ] Fixed only dots being displayed as separators. [ Bug fix ][ Post List (Pro) ] Since WordPress 6.7.1 started outputting contain-intrinsic-size: 3000px 1500px, add a reset for this in .card. +[ Other ][ Slider ] Refactoring editor script. = 1.91.2 = [ Bug fix ] Fix checkbox misalignment in the admin panel. diff --git a/src/blocks/_pro/fixed-display/block.json b/src/blocks/_pro/fixed-display/block.json index a335bcd31..f1199d00d 100644 --- a/src/blocks/_pro/fixed-display/block.json +++ b/src/blocks/_pro/fixed-display/block.json @@ -39,6 +39,18 @@ "type": "string", "default": "svh" }, + "displayAfterSeconds": { + "type": "number", + "default": 0 + }, + "hideAfterSeconds": { + "type": "number", + "default": 0 + }, + "dontShowAgain": { + "type": "boolean", + "default": false + }, "clientId": { "type": "string" }, diff --git a/src/blocks/_pro/fixed-display/deprecated/1.86.0/save.js b/src/blocks/_pro/fixed-display/deprecated/1.86.0/save.js new file mode 100644 index 000000000..4a73fe296 --- /dev/null +++ b/src/blocks/_pro/fixed-display/deprecated/1.86.0/save.js @@ -0,0 +1,39 @@ +import { InnerBlocks, useBlockProps } from '@wordpress/block-editor'; + +export default function save({ attributes }) { + const { + mode, + position, + scrollTiming, + scrollTimingUnit, + blockId, + scrollPersistVisible, + fixedPositionType, + fixedPositionValue, + fixedPositionUnit, + } = attributes; + + const blockProps = useBlockProps.save({ + className: `vk_fixed-display vk_fixed-display-mode-${mode} vk_fixed-display-position-${position} ${ + ['right', 'left'].includes(position) + ? `vk_fixed-display-position-from-${fixedPositionType}` + : '' + } vk_fixed-display-${blockId}`, + style: { + [fixedPositionType]: ['right', 'left'].includes(position) + ? `${fixedPositionValue}${fixedPositionUnit}` + : undefined, + }, + ...(mode === 'show-on-scroll' && { + 'data-scroll-timing': scrollTiming.toString(), + 'data-scroll-timing-unit': scrollTimingUnit, + 'data-persist-visible': scrollPersistVisible ? 'true' : 'false', + }), + }); + + return ( +
+ +
+ ); +} diff --git a/src/blocks/_pro/fixed-display/deprecated/index.js b/src/blocks/_pro/fixed-display/deprecated/index.js index 4094da18d..c6eda15f8 100644 --- a/src/blocks/_pro/fixed-display/deprecated/index.js +++ b/src/blocks/_pro/fixed-display/deprecated/index.js @@ -1,5 +1,6 @@ import save1_75_0 from './1.75.0/save'; import save1_81_1 from './1.81.1/save'; +import save1_86_0 from './1.86.0/save'; const blockAttributes = { mode: { @@ -54,7 +55,27 @@ const blockAttributes2 = { }, }; +const blockAttributes3 = { + ...blockAttributes2, + displayAfterSeconds: { + type: 'number', + default: 0 + }, + hideAfterSeconds: { + type: 'number', + default: 0 + }, + dontShowAgain: { + type: 'boolean', + default: false, + }, +}; + const deprecated = [ + { + attributes: blockAttributes3, + save: save1_86_0, + }, { attributes: blockAttributes2, save: save1_81_1, diff --git a/src/blocks/_pro/fixed-display/edit.js b/src/blocks/_pro/fixed-display/edit.js index 6199d1edf..83cbef1a9 100644 --- a/src/blocks/_pro/fixed-display/edit.js +++ b/src/blocks/_pro/fixed-display/edit.js @@ -11,8 +11,9 @@ import { ToggleControl, PanelRow, RadioControl, + TextControl, } from '@wordpress/components'; -import { useEffect } from '@wordpress/element'; +import { useRef, useState, useEffect } from '@wordpress/element'; import { isParentReusableBlock } from '@vkblocks/utils/is-parent-reusable-block'; const units = [ @@ -30,14 +31,65 @@ export default function FixedDisplayEdit(props) { mode, position, blockId, - scrollTiming = 100, - scrollTimingUnit = 'px', - scrollPersistVisible = false, - fixedPositionType = 'top', - fixedPositionValue = 50, - fixedPositionUnit = 'svh', + scrollTiming, + scrollTimingUnit, + scrollPersistVisible, + fixedPositionType, + fixedPositionValue, + fixedPositionUnit, + displayAfterSeconds, + hideAfterSeconds, + dontShowAgain, } = attributes; + const [tempScrollTiming, setTempScrollTiming] = useState( + scrollTiming || '0' + ); + const [tempScrollTimingUnit, setTempScrollTimingUnit] = useState( + scrollTimingUnit || 'px' + ); + + const [tempDisplayAfterSeconds, setTempDisplayAfterSeconds] = useState( + displayAfterSeconds || '0' + ); + const [tempHideAfterSeconds, setTempHideAfterSeconds] = useState( + hideAfterSeconds || '0' + ); + + // モードが切り替わる際に関連する属性をリセットし、一時変数もリセット + const prevModeRef = useRef(mode); + + useEffect(() => { + const prevMode = prevModeRef.current; + + if (prevMode !== mode) { + resetModeAttributes(); + } + + prevModeRef.current = mode; + }, [mode]); + + const resetModeAttributes = () => { + setAttributes({ + scrollTiming: undefined, + scrollTimingUnit: undefined, + scrollPersistVisible: false, + displayAfterSeconds: undefined, + hideAfterSeconds: undefined, + }); + setTempScrollTiming('0'); + setTempScrollTimingUnit('px'); + setTempDisplayAfterSeconds('0'); + setTempHideAfterSeconds('0'); + }; + + useEffect(() => { + setTempScrollTiming(scrollTiming || '0'); + setTempScrollTimingUnit(scrollTimingUnit || 'px'); + setTempDisplayAfterSeconds(displayAfterSeconds || '0'); + setTempHideAfterSeconds(hideAfterSeconds || '0'); + }, [scrollTiming, scrollTimingUnit, displayAfterSeconds, hideAfterSeconds]); + useEffect(() => { if ( blockId === undefined || @@ -45,6 +97,7 @@ export default function FixedDisplayEdit(props) { ) { setAttributes({ blockId: clientId }); } + setAttributes({ mode: mode || 'always-visible', position: position || 'right', @@ -55,18 +108,19 @@ export default function FixedDisplayEdit(props) { }); }, [clientId, mode, position, blockId, scrollPersistVisible]); + const handlePositionChange = (newPosition) => { + if (['top', 'bottom'].includes(newPosition)) { + setAttributes({ fixedPositionType: '' }); + } + setAttributes({ position: newPosition }); + }; + const blockProps = useBlockProps({ className: `vk_fixed-display vk_fixed-display-mode-${mode} vk_fixed-display-position-${position} ${ ['right', 'left'].includes(position) ? `vk_fixed-display-position-from-${fixedPositionType}` : '' } vk_fixed-display-${blockId}`, - style: { - [fixedPositionType]: - typeof window === 'undefined' || !window.wp?.blockEditor - ? `${fixedPositionValue}${fixedPositionUnit}` - : undefined, - }, }); return ( @@ -94,12 +148,19 @@ export default function FixedDisplayEdit(props) { label: __('Show on Scroll', 'vk-blocks-pro'), value: 'show-on-scroll', }, + { + label: __( + 'Display and Hide After Time', + 'vk-blocks-pro' + ), + value: 'display-hide-after-time', + }, ]} /> setAttributes({ position: value })} + onChange={handlePositionChange} options={[ { label: __('Top', 'vk-blocks-pro'), value: 'top' }, { @@ -172,39 +233,138 @@ export default function FixedDisplayEdit(props) { /> )} - {mode === 'show-on-scroll' && ( - <> - { - const unit = - units.find((unit) => - nextValue.endsWith(unit.value) - ) || units[0]; - const value = parseFloat(nextValue) || 0; - setAttributes({ - scrollTiming: value, - scrollTimingUnit: unit.value, - }); - }} - units={units} - /> - + {mode === 'show-on-scroll' && ( + + { + const unit = + units.find((unit) => + nextValue.endsWith(unit.value) + ) || units[0]; + const value = parseFloat(nextValue) || 0; + setTempScrollTiming(value); + setTempScrollTimingUnit(unit.value); + setAttributes({ + scrollTiming: value, + scrollTimingUnit: unit.value, + }); + }} + units={units} + /> + + setAttributes({ scrollPersistVisible: value }) + } + /> + + )} + {['display-hide-after-time', 'show-on-scroll'].includes( + mode + ) && ( + + +

+ {__( + 'Set the timing for display and hide. Enter 0 to disable timing for each option.', 'vk-blocks-pro' )} - checked={scrollPersistVisible} - onChange={(value) => - setAttributes({ - scrollPersistVisible: value, - }) - } - /> - - )} - +

+
+ {mode === 'display-hide-after-time' && ( + <> + { + setTempDisplayAfterSeconds(value); + }} + onBlur={() => { + const parsedValue = parseFloat( + tempDisplayAfterSeconds + ); + const finalValue = + isNaN(parsedValue) || + parsedValue < 0 + ? 0 + : parsedValue; + setAttributes({ + displayAfterSeconds: finalValue, + }); + setTempDisplayAfterSeconds( + finalValue.toString() + ); + }} + type="number" + min="0" + step="0.1" + /> + + )} + { + setTempHideAfterSeconds(value); + }} + onBlur={() => { + const parsedValue = + parseFloat(tempHideAfterSeconds); + const finalValue = + isNaN(parsedValue) || parsedValue < 0 + ? 0 + : parsedValue; + setAttributes({ + hideAfterSeconds: finalValue, + }); + setTempHideAfterSeconds(finalValue.toString()); + }} + type="number" + min="0" + step="0.1" + disabled={scrollPersistVisible} + /> +
+ )} + {mode === 'display-hide-after-time' && ( + + + setAttributes({ dontShowAgain: value }) + } + /> + +

+ {__( + 'When enabled, the same content will not be shown again until the visitor closes their browser.', + 'vk-blocks-pro' + )} +

+
+
+ )}
]*class="[^"]*vk_fixed-display-mode-(display-hide-after-time|show-on-scroll)[^"]*")/i', '$1 style="opacity:0;"', $content ); + } + return $content; +} + +/** + * Registers the fixed display block. * * @return void */ function vk_blocks_register_block_fixed_display() { - // Register Style. + // Register Style if ( ! is_admin() ) { wp_register_style( 'vk-blocks/fixed-display', @@ -21,7 +36,7 @@ function vk_blocks_register_block_fixed_display() { ); } - // Register Style. + // Register Script if ( ! is_admin() ) { wp_register_script( 'vk-blocks/fixed-display-script', @@ -39,10 +54,11 @@ function vk_blocks_register_block_fixed_display() { // 分割読み込みが有効な場合のみ、分割読み込み用のスクリプトを登録する if ( method_exists( 'VK_Blocks_Block_Loader', 'should_load_separate_assets' ) && VK_Blocks_Block_Loader::should_load_separate_assets() ) { $assets = array( - 'style' => 'vk-blocks/fixed-display', - 'script' => 'vk-blocks/fixed-display-script', - 'editor_style' => 'vk-blocks-build-editor-css', - 'editor_script' => 'vk-blocks-build-js', + 'style' => 'vk-blocks/fixed-display', + 'script' => 'vk-blocks/fixed-display-script', + 'editor_style' => 'vk-blocks-build-editor-css', + 'editor_script' => 'vk-blocks-build-js', + 'render_callback' => 'vk_blocks_fixed_display_render_callback', ); } diff --git a/src/blocks/_pro/fixed-display/save.js b/src/blocks/_pro/fixed-display/save.js index 4a73fe296..14fc8ddac 100644 --- a/src/blocks/_pro/fixed-display/save.js +++ b/src/blocks/_pro/fixed-display/save.js @@ -6,29 +6,67 @@ export default function save({ attributes }) { position, scrollTiming, scrollTimingUnit, - blockId, scrollPersistVisible, fixedPositionType, fixedPositionValue, fixedPositionUnit, + displayAfterSeconds, + hideAfterSeconds, + dontShowAgain, + blockId, } = attributes; + // dataAttributesとクラスを初期化 + const dataAttributes = { 'data-block-id': blockId || '' }; + let extraClasses = ''; + + // "show-on-scroll"モードのときにのみスクロール関連の属性を設定 + if (mode === 'show-on-scroll') { + dataAttributes['data-scroll-timing'] = scrollTiming.toString(); + dataAttributes['data-scroll-timing-unit'] = scrollTimingUnit; + if (scrollPersistVisible) { + dataAttributes['data-persist-visible'] = 'true'; + } else if (hideAfterSeconds > 0) { + dataAttributes['data-hide-after-seconds'] = + hideAfterSeconds.toString(); + } + extraClasses += ' vk_fixed-display-mode-show-on-scroll'; + } else if (mode === 'display-hide-after-time') { + // "display-hide-after-time"モードのときにのみタイミング属性を設定 + if (displayAfterSeconds > 0) { + dataAttributes['data-display-after-seconds'] = + displayAfterSeconds.toString(); + extraClasses += ' is-timed-display'; + } + if (hideAfterSeconds > 0) { + dataAttributes['data-hide-after-seconds'] = + hideAfterSeconds.toString(); + } + extraClasses += ' vk_fixed-display-mode-display-hide-after-time'; + } else if (mode === 'always-visible') { + // "always-visible"モード専用のクラス + extraClasses += ' vk_fixed-display-mode-always-visible'; + } + + // dontShowAgain が true のときにデータ属性を追加 + if (dontShowAgain) { + dataAttributes['data-dont-show-again'] = 'true'; + } + + // ブロックのプロパティを設定し、不要なクラスが残らないようにする const blockProps = useBlockProps.save({ - className: `vk_fixed-display vk_fixed-display-mode-${mode} vk_fixed-display-position-${position} ${ - ['right', 'left'].includes(position) + className: `vk_fixed-display vk_fixed-display-position-${position} ${ + ['right', 'left'].includes(position) && fixedPositionType ? `vk_fixed-display-position-from-${fixedPositionType}` : '' - } vk_fixed-display-${blockId}`, + } vk_fixed-display-${blockId} ${extraClasses}`, style: { + // モードに応じて必要な場合のみポジションスタイルを適用 [fixedPositionType]: ['right', 'left'].includes(position) ? `${fixedPositionValue}${fixedPositionUnit}` : undefined, }, - ...(mode === 'show-on-scroll' && { - 'data-scroll-timing': scrollTiming.toString(), - 'data-scroll-timing-unit': scrollTimingUnit, - 'data-persist-visible': scrollPersistVisible ? 'true' : 'false', - }), + ...dataAttributes, }); return ( diff --git a/src/blocks/_pro/fixed-display/style.scss b/src/blocks/_pro/fixed-display/style.scss index af564dc53..a2b90f085 100644 --- a/src/blocks/_pro/fixed-display/style.scss +++ b/src/blocks/_pro/fixed-display/style.scss @@ -9,6 +9,16 @@ position: fixed; margin-block-start: 0; z-index: 99999; + + > * { + &:first-of-type { + margin-block-start: 0; + } + + &:last-of-type { + margin-block-end: 0; + } + } } .vk_fixed-display-mode { @@ -17,14 +27,39 @@ } &-show-on-scroll { - opacity: 0; transition: opacity 0.3s ease; - + pointer-events: none; + opacity: 0; + &.is-visible { - opacity: 1; + opacity: 1 !important; + pointer-events: auto; } } + &-display-hide-after-time { + opacity: 1 !important; + transition: opacity 0.3s ease; + + &.is-timed-display { + opacity: 0 !important; + pointer-events: none; + + &.is-timed-visible { + opacity: 1 !important; + } + } + + &.is-timed-hide { + transition: opacity 0.3s ease; + + &:not(.is-timed-visible), + &.is-timed-hidden { + opacity: 0 !important; + pointer-events: none; + } + } + } } .vk_fixed-display-position { @@ -83,6 +118,14 @@ /* 編集画面用 /*-------------------------------------------*/ .block-editor-block-list__block { + &.vk_fixed-display { + + /* タイマー機能用クラス */ + &.is-timed-hide, + &.is-timed-display { + opacity: unset; + } + } &.vk_fixed-display-position { width: auto; @@ -110,13 +153,20 @@ } } } + + &-from-top { + top: unset !important; + } + &-from-bottom { + bottom: unset !important; + } } } /*-------------------------------------------*/ /* 管理画面用 /*-------------------------------------------*/ -.logged-in.admin-bar:not(.scrolled) { +.logged-in.admin-bar { .vk_fixed-display-position { &-top { @@ -130,14 +180,3 @@ } } } - -.block-editor-block-list__layout { - .vk_fixed-display-position { - &-from-top { - top: unset !important; - } - &-from-bottom { - bottom: unset !important; - } - } -} diff --git a/src/blocks/_pro/fixed-display/view.js b/src/blocks/_pro/fixed-display/view.js index abeddd80e..d3700251a 100644 --- a/src/blocks/_pro/fixed-display/view.js +++ b/src/blocks/_pro/fixed-display/view.js @@ -1,30 +1,147 @@ /* eslint-env browser */ -// スクロールイベントリスナーを追加 +// SessionStorage設定 +function setSessionStorageFlag(key, value) { + sessionStorage.setItem(key, value); +} + +function getSessionStorageFlag(key) { + return sessionStorage.getItem(key) !== null; +} + +// 状態管理用のマップを初期化 +const blockStates = new Map(); + +// スクロールタイミング設定 window.addEventListener('scroll', function () { const items = document.querySelectorAll( '.vk_fixed-display-mode-show-on-scroll' ); items.forEach((item) => { - // data属性からスクロールタイミングと単位、及びscrollPersistVisibleを取得 + const blockId = item.getAttribute('data-block-id'); + const timing = parseInt(item.getAttribute('data-scroll-timing'), 10); const unit = item.getAttribute('data-scroll-timing-unit'); const scrollPersistVisible = item.getAttribute('data-persist-visible') === 'true'; - - // 単位に応じたピクセル値への変換 const timingInPixels = convertUnitToPixels(timing, unit); - // スクロール位置が指定したタイミングを超えた場合に.is-visibleクラスを付与 - if (window.scrollY > timingInPixels) { - item.classList.add('is-visible'); - } else if (!scrollPersistVisible) { + // 初期状態をマップに設定 + if (!blockStates.has(blockId)) { + blockStates.set(blockId, { wasHidden: false }); + } + + // ブロックの状態を取得 + const blockState = blockStates.get(blockId); + + // 一度非表示にされたら再表示しない (ページリロードでリセットされる) + if (window.scrollY > timingInPixels && !blockState.wasHidden) { + if (!item.classList.contains('is-visible')) { + item.classList.add('is-visible'); + + const hideAfterSecondsAttr = item.getAttribute( + 'data-hide-after-seconds' + ); + const hideAfterSeconds = parseFloat(hideAfterSecondsAttr) || 0; + + // 非表示タイマーの処理 + if (hideAfterSeconds > 0 && !scrollPersistVisible) { + setTimeout(() => { + if (item.classList.contains('is-visible')) { + item.classList.remove('is-visible'); + blockStates.set(blockId, { wasHidden: true }); + } + }, hideAfterSeconds * 1000); + } + } + } else if (!scrollPersistVisible && blockState.wasHidden) { + // タイミングを超えなかった場合、Persistが無効な時に非表示 item.classList.remove('is-visible'); } }); }); +// 表示・非表示タイマー設定 +function handleVisibility( + item, + displayAfterSeconds, + hideAfterSeconds, + blockId, + dontShowAgain +) { + // displayAfterSeconds が 0 以上の場合に表示 + if (displayAfterSeconds >= 0) { + setTimeout(() => { + if (!item.classList.contains('is-timed-visible')) { + item.classList.add('is-timed-visible'); + if (dontShowAgain) { + setSessionStorageFlag(`displayed_${blockId}`, 'true'); + } + } + }, displayAfterSeconds * 1000); + } + + // hideAfterSeconds が 0 より大きい場合に非表示 + if (hideAfterSeconds > 0) { + setTimeout( + () => { + if (item.classList.contains('is-timed-visible')) { + item.classList.remove('is-timed-visible'); + } + }, + (displayAfterSeconds || 0) * 1000 + hideAfterSeconds * 1000 + ); + } +} + +// DOM読み込み後に初期化関数を実行 +function initializeDisplayHide() { + const items = document.querySelectorAll( + '.vk_fixed-display-mode-display-hide-after-time, .vk_fixed-display-mode-show-on-scroll, .vk_fixed-display-mode-always-visible' + ); + + items.forEach((item) => { + const displayAfterSecondsAttr = item.getAttribute( + 'data-display-after-seconds' + ); + const hideAfterSeconds = + parseFloat(item.getAttribute('data-hide-after-seconds')) || 0; + const displayAfterSeconds = displayAfterSecondsAttr + ? parseFloat(displayAfterSecondsAttr) + : null; + const blockId = item.getAttribute('data-block-id'); + const dontShowAgain = + item.getAttribute('data-dont-show-again') === 'true'; + + // dontShowAgain が有効で、セッションストレージに記録がある場合はスキップ + if (dontShowAgain && getSessionStorageFlag(`displayed_${blockId}`)) { + return; + } + + // モードに応じて適切な処理を実行 + const mode = item.getAttribute('data-mode'); + + if (mode === 'show-on-scroll') { + // スクロール時の動作は scroll イベントで制御 + return; + } + + // 他のモードは初期表示とタイマーを適用 + handleVisibility( + item, + displayAfterSeconds, + hideAfterSeconds, + blockId, + dontShowAgain + ); + }); +} + +window.initializeDisplayHide = initializeDisplayHide; +window.addEventListener('DOMContentLoaded', initializeDisplayHide); +window.addEventListener('load', initializeDisplayHide); + // 単位をピクセル値に変換する関数 function convertUnitToPixels(value, unit) { switch (unit) { @@ -47,6 +164,6 @@ function convertUnitToPixels(value, unit) { case 'svh': return (value * window.innerHeight) / 100; default: - return value; // 単位が不明な場合はピクセル値として扱う + return value; } } diff --git a/test/e2e-tests/fixtures/blocks/vk-blocks__fixed-display__default.html b/test/e2e-tests/fixtures/blocks/vk-blocks__fixed-display__default.html index 0b6a88368..d0fe90bc6 100644 --- a/test/e2e-tests/fixtures/blocks/vk-blocks__fixed-display__default.html +++ b/test/e2e-tests/fixtures/blocks/vk-blocks__fixed-display__default.html @@ -1,5 +1,5 @@ - -
+ +

あああああああああ

- + \ No newline at end of file diff --git a/test/e2e-tests/fixtures/blocks/vk-blocks__fixed-display__deprecated-1-86-0.html b/test/e2e-tests/fixtures/blocks/vk-blocks__fixed-display__deprecated-1-86-0.html new file mode 100644 index 000000000..a72daca27 --- /dev/null +++ b/test/e2e-tests/fixtures/blocks/vk-blocks__fixed-display__deprecated-1-86-0.html @@ -0,0 +1,5 @@ + +
+

あああああああああ

+
+ \ No newline at end of file