PluginProbe ʕ •ᴥ•ʔ
VK Blocks / trunk
VK Blocks vtrunk
1.123.0 1.122.0 1.121.0 1.120.0 1.119.2 1.119.1 1.119.0 1.118.7 1.82.0.0 1.82.0.1 1.83.0.0 1.83.0.1 1.84.0.0 1.84.0.1 1.85.0.2 1.85.0.3 1.85.1.0 1.85.1.1 1.86.0.0 1.86.0.1 1.86.1.0 1.87.0.0 1.87.0.1 1.88.0.1 1.88.0.2 1.89.0.0 1.9.2 1.90.0.0 1.90.0.1 1.90.1.1 1.91.0.0 1.91.1.1 1.92.0.0 1.92.0.1 1.92.1.0 1.92.1.1 1.93.0.0 trunk 1.93.0.1 0.1.1 1.93.1.0 0.10.3 1.93.1.1 0.11.0 1.94.0.0 0.12.6 1.94.0.1 0.15.1 1.94.1.0 0.16.2 1.94.2.1 0.17.6 1.94.2.2 0.2.2 1.95.0.3 0.22.4 1.95.0.4 0.26.7 1.96.2.0 0.3.2 1.96.2.1 0.31.0 1.97.0.1 0.35.5 1.97.0.2 0.37.5 1.99.0.0 0.38.8 1.99.0.1 0.39.4 0.4.7 0.41.0 0.42.0 0.44.13 0.45.2 0.46.1 0.47.0 0.48.0 0.49.8 0.5.2 0.50.3 0.51.0 0.52.2 0.53.2 0.54.0 0.55.0 0.56.2 0.56.3 0.57.6 0.58.10 0.59.0 0.6.0 0.60.1 0.61.2 0.7.1 0.8.2 1.0.16 1.10.0 1.100.0.0 1.100.0.1 1.102.0.0 1.102.0.1 1.103.0.0 1.104.0.0 1.104.0.1 1.105.1.0 1.105.1.1 1.106.0.0 1.106.0.1 1.107.0.0 1.107.0.1 1.107.0.2 1.108.0.0 1.108.0.1 1.109.0.0 1.109.0.1 1.11.4 1.110.0.0 1.110.0.1 1.111.0.0 1.111.0.1 1.111.0.2 1.112.0.0 1.112.0.1 1.113.0.0 1.113.0.1 1.114.0.0 1.114.0.1 1.114.2.0 1.114.2.1 1.115.0.0 1.115.0.1 1.115.1.0 1.115.1.1 1.115.2.0 1.115.2.1 1.116.0.0 1.116.0.1 1.116.1.0 1.116.1.1 1.116.2.0 1.117.0.0 1.117.0.1 1.117.1.0 1.118.2 1.118.5 1.12.0 1.13.2 1.14.1 1.15.1 1.16.11 1.17.0 1.18.6 1.19.0 1.19.1 1.2.3 1.20.7 1.21.0 1.22.4 1.23.0 1.24.5 1.25.1 1.26.2 1.27.7.2 1.28.0.1 1.29.2.0 1.3.9 1.30.0.1 1.31.0.1 1.32.0.2 1.33.2.1 1.36.1.5 1.37.0.0 1.39.2.1 1.4.6 1.40.0.1 1.40.1.0 1.40.1.1 1.41.0.0 1.41.0.1 1.41.2.2 1.41.2.3 1.43.0.0 1.43.0.1 1.43.0.2 1.44.0.0 1.44.0.1 1.45.0.0 1.45.0.1 1.46.0.0 1.46.0.1 1.47.0.0 1.47.0.1 1.47.1.0 1.48.0.0 1.48.0.1 1.48.0.2 1.48.1.0 1.48.1.1 1.5.0 1.50.0.0 1.50.0.1 1.51.0.0 1.51.0.1 1.52.0.0 1.52.0.1 1.53.0.0 1.53.0.1 1.54.0.0 1.54.0.1 1.55.0.0 1.55.0.1 1.56.0.0 1.56.0.1 1.57.0.0 1.57.0.2 1.57.0.3 1.57.0.4 1.57.0.5 1.57.1.0 1.57.1.1 1.57.1.2 1.58.0.0 1.58.0.1 1.59.0.0 1.59.0.1 1.6.0 1.60.0.0 1.60.0.1 1.63.0.0 1.63.0.1 1.64.0.0 1.64.0.1 1.64.1.0 1.64.1.2 1.67.0.0 1.67.0.1 1.68.0.0 1.68.0.1 1.69.0.0 1.69.0.1 1.69.1.1 1.69.1.2 1.7.1 1.70.0.0 1.70.0.1 1.71.0.0 1.71.0.1 1.72.0.0 1.72.1.0 1.72.1.1 1.73.0.0 1.73.0.1 1.74.0.0 1.74.0.1 1.75.0.0 1.75.1.0 1.75.1.1 1.76.0.0 1.76.0.1 1.76.1.0 1.76.1.1 1.76.2.0 1.76.2.1 1.77.0.0 1.77.0.1 1.78.0.0 1.78.0.1 1.79.0.0 1.79.0.1 1.79.0.2 1.79.0.3 1.79.1.0 1.79.1.1 1.8.2 1.80.1.0 1.80.1.1 1.80.1.2 1.81.0.1 1.81.0.2
vk-blocks / src / components / display-condition / index.js
vk-blocks / src / components / display-condition Last commit date
index.js 2 weeks ago
index.js
565 lines
1 import { __, sprintf } from '@wordpress/i18n';
2 import {
3 RangeControl,
4 PanelBody,
5 BaseControl,
6 SelectControl,
7 CheckboxControl,
8 __experimentalToggleGroupControl as ToggleGroupControl,
9 __experimentalToggleGroupControlOption as ToggleGroupControlOption,
10 TextControl,
11 FormTokenField,
12 } from '@wordpress/components';
13 import { useState, useEffect, useMemo } from '@wordpress/element';
14 import { useSelect } from '@wordpress/data';
15 // useTaxonomies はこのコンポーネント専用のためローカル定義
16 const useTaxonomies = () => {
17 return useSelect((select) => {
18 return select('core').getTaxonomies({ per_page: -1 }) || [];
19 }, []);
20 };
21 import { fixBrokenUnicode } from '@vkblocks/utils/fixBrokenUnicode';
22
23 // Load VK Blocks Compornents
24 import { AdvancedCheckboxControl } from '@vkblocks/components/advanced-checkbox-control';
25 import { PostExclusionControl } from '@vkblocks/components/post-exclusion-control';
26
27 export function DisplayCondition(props) {
28 const { attributes, setAttributes, postTypesProps, termsByTaxonomyName } =
29 props;
30 const {
31 numberPosts,
32 isCheckedPostType,
33 taxQueryRelation,
34 isCheckedTerms,
35 exclusionTerms,
36 offset,
37 targetPeriod,
38 order,
39 orderby,
40 selfIgnore,
41 pagedlock,
42 stickyPosts,
43 } = attributes;
44
45 // 以前の値を切り替え
46 useEffect(() => {
47 if (targetPeriod === undefined) {
48 setAttributes({ targetPeriod: 'all' });
49 }
50 }, [targetPeriod, setAttributes]);
51
52 const [isCheckedTermsData, setIsCheckedTermsData] = useState(
53 JSON.parse(fixBrokenUnicode(isCheckedTerms))
54 );
55 const [isCheckedPostTypeData, setIsCheckedPostTypeData] = useState(
56 JSON.parse(fixBrokenUnicode(isCheckedPostType))
57 );
58 // 除外するタームの選択状�
59 � / Selection state of terms to exclude.
60 const [isExcludedTermsData, setIsExcludedTermsData] = useState(
61 JSON.parse(fixBrokenUnicode(exclusionTerms || '[]'))
62 );
63
64 const postTypeToTaxonomyMap = {};
65 const taxonomies = useTaxonomies();
66
67 taxonomies.forEach((taxonomy) => {
68 taxonomy.types.forEach((postType) => {
69 if (!postTypeToTaxonomyMap[postType]) {
70 postTypeToTaxonomyMap[postType] = [];
71 }
72 postTypeToTaxonomyMap[postType].push(taxonomy.slug);
73 });
74 });
75
76 const saveStateTerms = (termId) => {
77 if (!isCheckedTermsData.includes(termId)) {
78 const updatedTerms = [...isCheckedTermsData, termId];
79 setIsCheckedTermsData(updatedTerms);
80 setAttributes({ isCheckedTerms: JSON.stringify(updatedTerms) });
81 }
82 };
83
84 const removeStateTerms = (termId) => {
85 const newTermsData = isCheckedTermsData.filter((id) => id !== termId);
86 setIsCheckedTermsData(newTermsData);
87 setAttributes({
88 isCheckedTerms: JSON.stringify(newTermsData),
89 });
90 };
91
92 // 除外タームの追加 / Add an exclusion term.
93 const saveStateExcludedTerms = (termId) => {
94 if (!isExcludedTermsData.includes(termId)) {
95 const updatedTerms = [...isExcludedTermsData, termId];
96 setIsExcludedTermsData(updatedTerms);
97 setAttributes({ exclusionTerms: JSON.stringify(updatedTerms) });
98 }
99 };
100
101 // 除外タームの削除 / Remove an exclusion term.
102 const removeStateExcludedTerms = (termId) => {
103 const newTermsData = isExcludedTermsData.filter((id) => id !== termId);
104 setIsExcludedTermsData(newTermsData);
105 setAttributes({
106 exclusionTerms: JSON.stringify(newTermsData),
107 });
108 };
109
110 const saveStatePostTypes = (slug) => {
111 let newPostTypeData = [...isCheckedPostTypeData];
112 let newTermsData = [...isCheckedTermsData];
113 // 除外タームも絞り込みタームと同様にクリーンアップする
114 // Clean up the excluded terms in the same way as the filter terms.
115 let newExcludedTermsData = [...isExcludedTermsData];
116 if (!newPostTypeData.includes(slug)) {
117 newPostTypeData.push(slug);
118 } else {
119 newPostTypeData = newPostTypeData.filter((type) => type !== slug);
120 const postTypeTaxonomies = postTypeToTaxonomyMap[slug] || [];
121 postTypeTaxonomies.forEach((taxonomy) => {
122 const terms = termsByTaxonomyName[taxonomy] || [];
123 terms.forEach((term) => {
124 newTermsData = newTermsData.filter(
125 (id) => id !== term.term_id
126 );
127 // 解除した投稿タイプのタクソノミーに属するタームを除外設定からも除く
128 // Also drop terms of the deselected post type's taxonomies from the exclusion settings.
129 newExcludedTermsData = newExcludedTermsData.filter(
130 (id) => id !== term.term_id
131 );
132 });
133 });
134 }
135 setIsCheckedPostTypeData(newPostTypeData);
136 setIsCheckedTermsData(newTermsData);
137 setIsExcludedTermsData(newExcludedTermsData);
138 setAttributes({
139 isCheckedPostType: JSON.stringify(newPostTypeData),
140 isCheckedTerms: JSON.stringify(newTermsData),
141 exclusionTerms: JSON.stringify(newExcludedTermsData),
142 });
143 };
144
145 // メディアと再利用ブロックを除外
146 const filteredPostTypesProps = useMemo(() => {
147 return postTypesProps.filter(
148 (postType) =>
149 'attachment' !== postType.slug && 'wp_block' !== postType.slug
150 );
151 }, [postTypesProps]);
152
153 const replaceIsCheckedTermData = (taxonomyRestbase, termIds, newIds) => {
154 const removedTermIds = termIds.filter((termId) => {
155 let find = false;
156 termsByTaxonomyName[taxonomyRestbase].forEach((term) => {
157 if (term.term_id === termId) {
158 find = true;
159 }
160 });
161 return !find;
162 });
163 return removedTermIds.concat(newIds);
164 };
165
166 const getTaxonomiesByPostType = (postType) => {
167 return taxonomies.filter((taxonomy) => {
168 return (
169 taxonomy.types.includes(postType) &&
170 termsByTaxonomyName[taxonomy.slug]?.length
171 );
172 });
173 };
174
175 // termFormTokenFields ////////////////////////////////////////////////////////
176 // Tag Filter
177 const selectedPostTypes = isCheckedPostTypeData;
178 const filteredTaxonomies = selectedPostTypes.flatMap((postType) =>
179 getTaxonomiesByPostType(postType)
180 );
181
182 // 絞り込み用・除外用で�
183 �通のターム選択UIを生成するファクトリ。
184 // state / schema / ラベル / ハンドラのみが異なるため、�
185 �通化して重複と乖離リスクを排除する。
186 // Factory that builds the shared term-selection UI for both the filter and the exclusion.
187 // Only the state / schema / label / handlers differ, so this is centralized to avoid duplication and divergence.
188 const buildTermFields = ({
189 checkedData,
190 setCheckedData,
191 schema,
192 labelFormat,
193 saveState,
194 removeState,
195 baseControlId,
196 }) => {
197 // 非階層タクソノミー(タグ等)→ FormTokenField / Non-hierarchical taxonomies ( tags etc. ) -> FormTokenField.
198 const tokenFields = filteredTaxonomies
199 .filter((taxonomy) => {
200 return (
201 !taxonomy.hierarchical && termsByTaxonomyName[taxonomy.slug]
202 );
203 })
204 .map((taxonomy) => {
205 const termsMapByName = termsByTaxonomyName[
206 taxonomy.slug
207 ].reduce((acc, term) => {
208 return {
209 ...acc,
210 [term.name]: term,
211 };
212 }, {});
213
214 const termsMapById = termsByTaxonomyName[taxonomy.slug].reduce(
215 (acc, term) => {
216 return {
217 ...acc,
218 [term.term_id]: term,
219 };
220 },
221 {}
222 );
223
224 const termNames = termsByTaxonomyName[taxonomy.slug].map(
225 (term) => term.name
226 );
227
228 return termsByTaxonomyName[taxonomy.slug] &&
229 termsByTaxonomyName[taxonomy.slug]?.length > 0 ? (
230 <FormTokenField
231 key={taxonomy.slug}
232 label={sprintf(labelFormat, taxonomy.labels.name)}
233 value={checkedData
234 .filter((termId) => {
235 return termId in termsMapById;
236 })
237 .map((termId) => {
238 return termsMapById[termId].name;
239 })}
240 suggestions={termNames}
241 onChange={(newTerms) => {
242 const termIds = newTerms.map((termName) => {
243 return termsMapByName[termName].term_id;
244 });
245 const replacedTermsData = replaceIsCheckedTermData(
246 taxonomy.slug,
247 checkedData,
248 termIds
249 );
250 setCheckedData(replacedTermsData);
251 setAttributes({
252 [schema]: JSON.stringify(replacedTermsData),
253 });
254 }}
255 ></FormTokenField>
256 ) : null;
257 }, taxonomies);
258
259 // 階層タクソノミー(カテゴリー等)→ チェックボックス / Hierarchical taxonomies ( categories etc. ) -> checkboxes.
260 const checkBoxes = filteredTaxonomies
261 .filter((taxonomy) => {
262 return (
263 taxonomy.hierarchical === true &&
264 termsByTaxonomyName[taxonomy.slug]?.length
265 );
266 })
267 .map(function (taxonomy, index) {
268 const taxonomiesProps = (
269 termsByTaxonomyName[taxonomy.slug] || []
270 ).map((term) => {
271 return {
272 label: term.name,
273 slug: term.term_id,
274 };
275 });
276
277 return (
278 <BaseControl
279 label={sprintf(labelFormat, taxonomy.labels.name)}
280 id={baseControlId}
281 key={index}
282 >
283 <AdvancedCheckboxControl
284 schema={schema}
285 rawData={taxonomiesProps}
286 checkedData={checkedData}
287 saveState={saveState}
288 removeState={removeState}
289 {...props}
290 />
291 </BaseControl>
292 );
293 }, termsByTaxonomyName);
294
295 return { tokenFields, checkBoxes };
296 };
297
298 // 絞り込み(include)用のターム選択UI / Term-selection UI for the filter ( include ).
299 const { tokenFields: termFormTokenFields, checkBoxes: taxonomiesCheckBox } =
300 buildTermFields({
301 checkedData: isCheckedTermsData,
302 setCheckedData: setIsCheckedTermsData,
303 schema: 'isCheckedTerms',
304 // translators: Filter by %s
305 labelFormat: __('Filter by %s', 'vk-blocks'),
306 saveState: saveStateTerms,
307 removeState: removeStateTerms,
308 baseControlId: 'vk_postList-terms',
309 });
310
311 // 除外(exclude)用のターム選択UI / Term-selection UI for the exclusion ( exclude ).
312 const {
313 tokenFields: excludedTermFormTokenFields,
314 checkBoxes: excludedTaxonomiesCheckBox,
315 } = buildTermFields({
316 checkedData: isExcludedTermsData,
317 setCheckedData: setIsExcludedTermsData,
318 schema: 'exclusionTerms',
319 // translators: Exclude by %s
320 labelFormat: __('Exclude by %s', 'vk-blocks'),
321 saveState: saveStateExcludedTerms,
322 removeState: removeStateExcludedTerms,
323 baseControlId: 'vk_postList-excludeTerms',
324 });
325
326 // `offset`が空の場合に0に設定する
327 useEffect(() => {
328 if (offset === undefined || offset === null || offset === '') {
329 setAttributes({ offset: 0 });
330 }
331 }, [offset]);
332
333 // state を属性へ再同期する安�
334 �網。
335 // AdvancedCheckboxControl の onChange は saveState/removeState 直後に
336 // 古いクロージャの checkedData で属性を上書きするため、属性が1操作分�
337 れる。
338 // state 変化時に毎回 state から属性を再同期して、このズレを打ち消す。
339 // Safety net that re-syncs the state into the attributes.
340 // AdvancedCheckboxControl's onChange overwrites the attributes with a stale
341 // checkedData closure right after saveState/removeState, so the attributes lag
342 // one operation behind. Re-syncing from state on every change cancels this lag.
343 useEffect(() => {
344 setAttributes({
345 isCheckedPostType: JSON.stringify(isCheckedPostTypeData),
346 isCheckedTerms: JSON.stringify(isCheckedTermsData),
347 exclusionTerms: JSON.stringify(isExcludedTermsData),
348 });
349 }, [isCheckedPostTypeData, isCheckedTermsData, isExcludedTermsData]);
350
351 return (
352 <PanelBody
353 title={__('Display conditions', 'vk-blocks')}
354 initialOpen={false}
355 >
356 <BaseControl
357 label={__('Filter by PostTypes', 'vk-blocks')}
358 id={`vk_postList-postTypes`}
359 >
360 <AdvancedCheckboxControl
361 schema={'isCheckedPostType'}
362 rawData={filteredPostTypesProps}
363 checkedData={isCheckedPostTypeData}
364 setAttributes={setAttributes}
365 saveState={saveStatePostTypes}
366 removeState={removeStateTerms}
367 {...props}
368 />
369 </BaseControl>
370 <hr />
371 <h4 className={`mt-0 mb-2`}>
372 {__('Taxonomy filter condition', 'vk-blocks')}
373 </h4>
374 <ToggleGroupControl
375 value={taxQueryRelation}
376 onChange={(value) => setAttributes({ taxQueryRelation: value })}
377 isBlock
378 className="mb-3"
379 >
380 <ToggleGroupControlOption
381 value="OR"
382 label={__('OR ( Whichever apply )', 'vk-blocks')}
383 />
384 <ToggleGroupControlOption
385 value="AND"
386 label={__('AND ( All apply )', 'vk-blocks')}
387 />
388 </ToggleGroupControl>
389
390 {taxonomiesCheckBox}
391 {termFormTokenFields}
392 <BaseControl
393 label={__('Filter by Date', 'vk-blocks')}
394 id={`vk_postList-dateFilter`}
395 >
396 <SelectControl
397 label={__('Period of Time', 'vk-blocks')}
398 value={targetPeriod}
399 onChange={(value) => setAttributes({ targetPeriod: value })}
400 options={[
401 {
402 value: 'all',
403 label: __('Whole Period', 'vk-blocks'),
404 },
405 {
406 value: 'within-year',
407 label: __('Within a year', 'vk-blocks'),
408 },
409 {
410 value: 'from-today',
411 label: __('From Today', 'vk-blocks'),
412 },
413 {
414 value: 'from-now',
415 label: __('From Now', 'vk-blocks'),
416 },
417 {
418 value: 'from-tomorrow',
419 label: __('From Tomorrow', 'vk-blocks'),
420 },
421 ]}
422 />
423 <p>
424 {__(
425 '* If you choose a future period, you will need to customize it so that future posts will be published immediately.',
426 'vk-blocks'
427 )}
428 </p>
429 </BaseControl>
430 <hr />
431 <h4 className={`mt-0 mb-2`}>
432 {__('Exclusion settings', 'vk-blocks')}
433 </h4>
434 {excludedTaxonomiesCheckBox}
435 {excludedTermFormTokenFields}
436 <PostExclusionControl
437 attributes={attributes}
438 setAttributes={setAttributes}
439 selectedPostTypes={isCheckedPostTypeData}
440 />
441 <BaseControl>
442 <CheckboxControl
443 label={__('Ignore this post', 'vk-blocks')}
444 checked={selfIgnore}
445 onChange={(v) => setAttributes({ selfIgnore: v })}
446 />
447 </BaseControl>
448 <hr />
449 <BaseControl
450 label={__('Number of Posts', 'vk-blocks')}
451 id={`vk_postList-numberPosts`}
452 >
453 <RangeControl
454 value={numberPosts}
455 onChange={(value) => setAttributes({ numberPosts: value })}
456 min="1"
457 max="100"
458 />
459 </BaseControl>
460 <BaseControl
461 label={__('Order', 'vk-blocks')}
462 id={`vk_postList-order`}
463 >
464 <SelectControl
465 value={order}
466 onChange={(v) => setAttributes({ order: v })}
467 options={[
468 {
469 value: 'ASC',
470 label: __('ASC', 'vk-blocks'),
471 },
472 {
473 value: 'DESC',
474 label: __('DESC', 'vk-blocks'),
475 },
476 ]}
477 />
478 </BaseControl>
479 <BaseControl
480 label={__('Order by', 'vk-blocks')}
481 id={`vk_postList-orderBy`}
482 >
483 <SelectControl
484 value={orderby}
485 onChange={(v) => setAttributes({ orderby: v })}
486 options={[
487 {
488 value: 'date',
489 label: __('Published Date', 'vk-blocks'),
490 },
491 {
492 value: 'modified',
493 label: __('Modified Date', 'vk-blocks'),
494 },
495 {
496 value: 'title',
497 label: __('Title', 'vk-blocks'),
498 },
499 {
500 value: 'rand',
501 label: __('Random', 'vk-blocks'),
502 },
503 ]}
504 />
505 </BaseControl>
506 <BaseControl
507 label={__('offset', 'vk-blocks')}
508 id={`vk_postList-offset`}
509 >
510 <TextControl
511 value={offset}
512 onChange={(v) =>
513 setAttributes({
514 offset: v === '' ? 0 : parseInt(v, 10),
515 })
516 }
517 type="number"
518 min="0"
519 />
520 </BaseControl>
521 <BaseControl>
522 <CheckboxControl
523 label={__(
524 'Display from the first post always',
525 'vk-blocks'
526 )}
527 checked={pagedlock}
528 onChange={(v) => setAttributes({ pagedlock: v })}
529 help={__(
530 'Display from the first post even on pages beyond the second page.',
531 'vk-blocks'
532 )}
533 />
534 </BaseControl>
535 <BaseControl>
536 <SelectControl
537 label={__('Sticky Posts', 'vk-blocks')}
538 value={stickyPosts}
539 options={[
540 {
541 label: __('Include', 'vk-blocks'),
542 value: 'include',
543 },
544 {
545 label: __('Exclude', 'vk-blocks'),
546 value: 'exclude',
547 },
548 { label: __('Only', 'vk-blocks'), value: 'only' },
549 ]}
550 onChange={(value) => {
551 setAttributes({ stickyPosts: value });
552 if (value === 'include') {
553 setAttributes({ stickyPosts: false });
554 }
555 }}
556 help={__(
557 'Sticky posts always appear first, regardless of their publish date.',
558 'vk-blocks'
559 )}
560 />
561 </BaseControl>
562 </PanelBody>
563 );
564 }
565