UNPKG

itmar-block-packages

Version:

We have put together a package of common React components used for WordPress custom blocks.

516 lines (477 loc) 17.2 kB
import { useState, useEffect } from "@wordpress/element"; import { __ } from "@wordpress/i18n"; import { ComboboxControl, CheckboxControl, ToggleControl, } from "@wordpress/components"; import apiFetch from "@wordpress/api-fetch"; //const _ = require("lodash"); export const restFetchData = async (path) => { try { const ret_data = await apiFetch({ path: path }); return ret_data; } catch (error) { console.error("Error fetching data:", error.message); } }; //コンボボックスコントロールのレンダリング関数 const SelectControl = (props) => { const { selectedSlug, label, homeUrl, fetchOptions } = props; const [options, setOptions] = useState([]); useEffect(() => { const fetchData = async () => { try { const fetchedOptions = await fetchOptions(homeUrl); setOptions(fetchedOptions); } catch (error) { console.error("Error fetching data:", error.message); } }; fetchData(); }, [fetchOptions]); const selectedInfo = options.find((info) => info.slug === selectedSlug); return ( <ComboboxControl label={label} options={options} value={selectedInfo ? selectedInfo.value : -1} onChange={(newValue) => { const newInfo = options.find((info) => info.value === newValue); props.onChange(newInfo); }} /> ); }; //選択コントロールのレンダリング関数 const ChoiceControl = (props) => { const { selectedSlug, choiceItems, dispTaxonomies, type, blockMap, textDomain, fetchFunction, } = props; const [choices, setChoices] = useState([]); useEffect(() => { if (!selectedSlug) return; //ポストタイプのスラッグが選択されていないときは処理終了 const fetchData = async () => { try { const fetchChoices = await fetchFunction(selectedSlug); setChoices(fetchChoices); } catch (error) { console.error("Error fetching data:", error.message); } }; fetchData(); }, [selectedSlug, fetchFunction]); //選択肢が変わったときに選択されている項目の配列内容を更新するハンドラ const handleChoiceChange = (checked, target, setItems) => { if (checked) { // targetが重複していない場合のみ追加 if (!setItems.some((item) => _.isEqual(item, target))) { return [...setItems, target]; } } else { // targetを配列から削除 return setItems.filter((item) => !_.isEqual(item, target)); } return setItems; }; //階層化されたカスタムフィールドのフィールド名を表示する関数 let groupLabel = ""; const dispCustumFields = (obj, prefix = "", onChange) => { return Object.entries(obj).map(([key, value]) => { const fieldName = prefix ? `${prefix}.${key}` : key; //prefixはグループ名 const fieldLabel = key.replace(/^(meta_|acf_)/, ""); if (typeof value === "object" && value !== null) { groupLabel = `${fieldLabel}.`; return ( <div className="group_area"> <div className="group_label">{fieldLabel}</div> <div key={fieldName} className="field_group"> {dispCustumFields(value, fieldName, onChange)} </div> </div> ); } else { if (key === "meta__acf_changed" || key === "meta_footnotes") return; //_acf_changedは対象外 //フィールドを表示するブロックの選択肢 const options = [ { value: "itmar/design-title", label: "itmar/design-title" }, { value: "core/paragraph", label: "core/paragraph" }, { value: "core/image", label: "core/image" }, ]; return ( <div className="itmar_custom_field_set"> <ToggleControl key={fieldName} className="field_choice" label={fieldLabel} checked={choiceItems.some( (choiceField) => choiceField === fieldName )} onChange={(checked) => { const newChoiceFields = handleChoiceChange( checked, fieldName, choiceItems ); props.onChange(newChoiceFields); }} /> <ComboboxControl options={options} value={ blockMap[`${prefix ? groupLabel : ""}${fieldLabel}`] || "itmar/design-title" } onChange={(newValue) => { const fieldKey = prefix ? `${groupLabel}${fieldLabel}` : `${fieldLabel}`; const newBlockMap = { ...blockMap, [fieldKey]: newValue }; props.onBlockMapChange(newBlockMap); }} /> </div> ); } }); }; return ( <div className={`${type}_section`}> {type === "taxonomy" && choices.map((choice, index) => { return ( <div key={index} className="term_section"> <div className="tax_label"> {choice.name} <ToggleControl label={__("Display", "block-collections")} checked={dispTaxonomies.some((tax) => tax === choice.slug)} onChange={(checked) => { const newChoiceFields = handleChoiceChange( checked, choice.slug, dispTaxonomies ); props.onSetDispTax(newChoiceFields); }} /> </div> {choice.terms.map((term, index) => { return ( <CheckboxControl className="term_check" key={index} label={term.name} checked={choiceItems.some((choiceTerm) => { return ( choiceTerm.taxonomy === choice.slug && choiceTerm.term.id === term.id ); })} onChange={(checked) => { const target = { taxonomy: choice.slug, term: { id: term.id, slug: term.slug, name: term.name }, }; const newChoiceTerms = handleChoiceChange( checked, target, choiceItems ); props.onChange(newChoiceTerms); }} /> ); })} </div> ); })} {type === "field" && choices.map((choice, index) => { //metaの対象カスタムフィールドが含まれるかのフラグ const metaFlg = choice.meta && !Object.keys(choice.meta).every( (key) => key === "_acf_changed" || key === "footnotes" ); //acfの対象カスタムフィールドが含まれるかのフラグ const acfFlg = choice.acf && typeof choice.acf === "object" && !Array.isArray(choice.acf); return ( <div key={index} className="field_section"> {choice.title && ( <ToggleControl className="field_choice" label={__("Title", "block-collections")} checked={choiceItems.some( (choiceField) => choiceField === "title" )} onChange={(checked) => { const newChoiceFields = handleChoiceChange( checked, "title", choiceItems ); props.onChange(newChoiceFields); }} /> )} {choice.date && ( <ToggleControl className="field_choice" label={__("Date", "block-collections")} checked={choiceItems.some( (choiceField) => choiceField === "date" )} onChange={(checked) => { const newChoiceFields = handleChoiceChange( checked, "date", choiceItems ); props.onChange(newChoiceFields); }} /> )} {choice.excerpt && ( <ToggleControl className="field_choice" label={__("Excerpt", "block-collections")} checked={choiceItems.some( (choiceField) => choiceField === "excerpt" )} onChange={(checked) => { const newChoiceFields = handleChoiceChange( checked, "excerpt", choiceItems ); props.onChange(newChoiceFields); }} /> )} {(choice.featured_media || choice.featured_media === 0) && ( <ToggleControl className="field_choice" label={__("Featured Image", "block-collections")} checked={choiceItems.some( (choiceField) => choiceField === "featured_media" )} onChange={(checked) => { const newChoiceFields = handleChoiceChange( checked, "featured_media", choiceItems ); props.onChange(newChoiceFields); }} /> )} {choice.link && ( <div className="itmar_custom_field_set"> <ToggleControl className="field_choice" label={__("Single Page Link", "block-collections")} checked={choiceItems.some( (choiceField) => choiceField === "link" )} onChange={(checked) => { const newChoiceFields = handleChoiceChange( checked, "link", choiceItems ); props.onChange(newChoiceFields); }} /> <ComboboxControl options={[ { value: "itmar/design-button", label: "itmar/design-button", }, { value: "itmar/design-title", label: "itmar/design-title", }, ]} value={blockMap["link"]} onChange={(newValue) => { const newBlockMap = { ...blockMap, link: newValue, }; props.onBlockMapChange(newBlockMap); }} /> <p> {__( "If no block is specified, a link will be set to the parent block, Design Group.", "block-collections" )} </p> </div> )} {(metaFlg || acfFlg) && ( <> <div className="custom_field_label"> {__("Custom Field", "block-collections")} </div> <div className="custom_field_area"> {dispCustumFields({ ...Object.entries(choice.meta).reduce( (acc, [key, value]) => ({ ...acc, [`meta_${key}`]: value, }), {} ), ...Object.entries(choice.acf).reduce( (acc, [key, value]) => ({ ...acc, [`acf_${key}`]: value, }), {} ), })} </div> </> )} </div> ); })} </div> ); }; //固定ページ取得RestAPI関数 export const fetchPagesOptions = async (home_url) => { const pages = await apiFetch({ path: "/wp/v2/pages" }); //ページIDが-1である要素をホーム要素として作成 if (pages && !pages.some((page) => page.id === -1)) { pages.unshift({ id: -1, title: { rendered: "ホーム" }, link: home_url, slug: "", }); } const ret_pages = pages ? pages.map((page) => ({ value: page.id, slug: page.slug, label: page.title.rendered, link: `${home_url}/${page.slug}`, })) : []; return ret_pages; }; //アーカイブ情報取得RestAPI関数 export const fetchArchiveOptions = async (home_url) => { const response = await apiFetch({ path: "/wp/v2/types" }); let idCounter = 0; return Object.keys(response).reduce((acc, key) => { const postType = response[key]; if (postType.has_archive === true) { acc.push({ value: idCounter++, slug: postType.slug, rest_base: postType.rest_base, link: `${home_url}/${postType.slug}`, label: postType.name, }); } else if (typeof postType.has_archive === "string") { //アーカイブ名がついているとき acc.push({ value: idCounter++, slug: postType.slug, rest_base: postType.rest_base, link: `${home_url}/${postType.has_archive}`, label: postType.name, }); } return acc; }, []); }; //タクソノミー取得RestAPI関数 export const restTaxonomies = async (post_type) => { if (!post_type) return; const response = await apiFetch({ path: `/wp/v2/types/${post_type}?context=edit`, }); const taxonomies = response.taxonomies; const taxonomyPromises = taxonomies.map(async (slug) => { const taxonomyResponse = await apiFetch({ path: `/wp/v2/taxonomies/${slug}?context=edit`, }); const terms = await apiFetch({ path: `/wp/v2/${taxonomyResponse.rest_base}`, }); return { slug: slug, name: taxonomyResponse.name, rest_base: taxonomyResponse.rest_base, terms: terms, }; }); const taxonomyObjects = await Promise.all(taxonomyPromises); return taxonomyObjects; }; //タームの文字列化 export const termToDispObj = (terms, connectString) => { // taxonomyごとにterm.nameをまとめる const result = terms.reduce((acc, item) => { const taxonomy = item.taxonomy; const termName = item.term.name; // taxonomyがまだ存在しない場合は初期化 if (!acc[taxonomy]) { acc[taxonomy] = []; } // term.nameを配列に追加 acc[taxonomy].push(termName); return acc; }, {}); // 各taxonomyの配列を connectString でつなげて文字列化 for (const taxonomy in result) { result[taxonomy] = result[taxonomy].join(connectString); } return result; }; //フィールド情報取得RestAPI関数 export const restFieldes = async (rest_base) => { //投稿データに以下のフィールドが含まれているかを調べる const selectedFields = [ "title", "date", "excerpt", "featured_media", "link", "meta", "acf", ]; const fieldsParam = selectedFields.join(","); //最新の投稿データから1件分のデータを抽出 const response = await apiFetch({ path: `/wp/v2/${rest_base}?_fields=${fieldsParam}&per_page=1&order=desc`, }); return response; }; export const PageSelectControl = (props) => ( <SelectControl {...props} fetchOptions={fetchPagesOptions} /> ); export const ArchiveSelectControl = (props) => ( <SelectControl {...props} fetchOptions={fetchArchiveOptions} /> ); export const TermChoiceControl = (props) => ( <ChoiceControl {...props} fetchFunction={restTaxonomies} /> ); export const FieldChoiceControl = (props) => ( <ChoiceControl {...props} fetchFunction={restFieldes} /> );