คู่มือการใช้งาน
คู่มือการใช้งานบล็อก (Gutenberg)

บล็อก (Gutenberg)

นี่คือวิธีการดึงข้อมูลบล็อก Gutenberg

GraphQL schema มีการเพิ่มฟิลด์ต่อไปนี้ให้กับ CustomPost ทุกประเภท (เช่น Post และ Page):

  • blocks
  • blockDataItems
  • blockFlattenedDataItems

ฟิลด์เหล่านี้จะไม่ถูกเปิดใช้งานหากปลั๊กอิน Classic Editor ทำงานอยู่

blocks

ฟิลด์ CustomPost.blocks: [BlockUnion!] จะดึงรายการบล็อกทั้งหมดที่อยู่ในโพสต์แบบกำหนดเอง

blocks จะคืนค่าเป็นรายการของ Block type ที่ถูกแมปเข้ากับ GraphQL schema แล้ว Block type เหล่านี้ล้วนเป็นส่วนหนึ่งของ BlockUnion type และ implement อินเทอร์เฟซ Block

ปลั๊กอินนี้ implement Block type หนึ่งตัวคือ GenericBlock ซึ่งเพียงพอสำหรับการดึงข้อมูลของบล็อกใด ๆ อยู่แล้ว (ผ่านฟิลด์ attributes: JSONObject)

คิวรีนี้:

{
  post(by: { id: 1 }) {
    blocks {
      ...on Block {
        name
        attributes
        innerBlocks {
          ...on Block {
              name
              attributes
              innerBlocks {
                ...on Block {
                  name
                  attributes
                }
              }
            }
          }
        }
      }
    }
  }
}

...จะสร้างเรสปอนส์นี้:

{
  "data": {
    "post": {
      "blocks": [
        {
          "name": "core/gallery",
          "attributes": {
            "linkTo": "none",
            "className": "alignnone",
            "images": [
              {
                "url": "https://d.pr/i/zd7Ehu+",
                "alt": "",
                "id": "1706"
              },
              {
                "url": "https://d.pr/i/jXLtzZ+",
                "alt": "",
                "id": "1705"
              }
            ],
            "ids": [],
            "shortCodeTransforms": [],
            "imageCrop": true,
            "fixedHeight": true,
            "sizeSlug": "large",
            "allowResize": false
          },
          "innerBlocks": null
        },
        {
          "name": "core/heading",
          "attributes": {
            "content": "List Block",
            "level": 2
          },
          "innerBlocks": null
        },
        {
          "name": "core/list",
          "attributes": {
            "ordered": false,
            "values": "<li>List item 1</li><li>List item 2</li><li>List item 3</li><li>List item 4</li>"
          },
          "innerBlocks": null
        },
        {
          "name": "core/heading",
          "attributes": {
            "className": "has-top-margin",
            "content": "Columns Block",
            "level": 2
          },
          "innerBlocks": null
        },
        {
          "name": "core/columns",
          "attributes": {
            "isStackedOnMobile": true
          },
          "innerBlocks": [
            {
              "name": "core/column",
              "attributes": {},
              "innerBlocks": [
                {
                  "name": "core/image",
                  "attributes": {
                    "id": 1701,
                    "className": "layout-column-1",
                    "url": "https://d.pr/i/fW6V3V+",
                    "alt": ""
                  },
                  "innerBlocks": null
                }
              ]
            },
            {
              "name": "core/column",
              "attributes": {},
              "innerBlocks": [
                {
                  "name": "core/paragraph",
                  "attributes": {
                    "className": "layout-column-2",
                    "content": "Phosfluorescently morph intuitive relationships rather than customer directed human capital.",
                    "dropCap": false
                  },
                  "innerBlocks": null
                }
              ]
            }
          ]
        },
        {
          "name": "core/heading",
          "attributes": {
            "content": "Columns inside Columns (nested inner blocks)",
            "level": 2
          },
          "innerBlocks": null
        },
        {
          "name": "core/columns",
          "attributes": {
            "isStackedOnMobile": true
          },
          "innerBlocks": [
            {
              "name": "core/column",
              "attributes": {},
              "innerBlocks": [
                {
                  "name": "core/image",
                  "attributes": {
                    "id": 1701,
                    "className": "layout-column-1",
                    "url": "https://d.pr/i/fW6V3V+",
                    "alt": ""
                  },
                  "innerBlocks": null
                },
                {
                  "name": "core/columns",
                  "attributes": {
                    "isStackedOnMobile": true
                  },
                  "innerBlocks": [
                    {
                      "name": "core/column",
                      "attributes": {
                        "width": "33.33%"
                      },
                      "innerBlocks": [
                        {
                          "name": "core/heading",
                          "attributes": {
                            "fontSize": "large",
                            "content": "Life is so rich",
                            "level": 2
                          },
                          "innerBlocks": null
                        },
                        {
                          "name": "core/heading",
                          "attributes": {
                            "level": 3,
                            "content": "Life is so dynamic"
                          },
                          "innerBlocks": null
                        }
                      ]
                    },
                    {
                      "name": "core/column",
                      "attributes": {
                        "width": "66.66%"
                      },
                      "innerBlocks": [
                        {
                          "name": "core/paragraph",
                          "attributes": {
                            "content": "This rhyming poem is the spark that can reignite the fires within you. It challenges you to go out and live your life in the present moment as a \u201chero\u201d and leave your mark on this world.",
                            "dropCap": false
                          },
                          "innerBlocks": null
                        },
                        {
                          "name": "core/columns",
                          "attributes": {
                            "isStackedOnMobile": true
                          },
                          "innerBlocks": [
                            {
                              "name": "core/column",
                              "attributes": {},
                              "innerBlocks": [
                                {
                                  "name": "core/image",
                                  "attributes": {
                                    "id": 361,
                                    "sizeSlug": "large",
                                    "linkDestination": "none",
                                    "url": "https://gato-graphql.lndo.site/wp-content/uploads/2022/05/graphql-voyager-public-1024x622.jpg",
                                    "alt": ""
                                  },
                                  "innerBlocks": null
                                }
                              ]
                            },
                            {
                              "name": "core/column",
                              "attributes": {},
                              "innerBlocks": null
                            },
                            {
                              "name": "core/column",
                              "attributes": {},
                              "innerBlocks": [
                                {
                                  "name": "core/image",
                                  "attributes": {
                                    "id": 362,
                                    "sizeSlug": "large",
                                    "linkDestination": "none",
                                    "url": "https://gato-graphql.lndo.site/wp-content/uploads/2022/05/namespaced-interactive-schema-1024x598.webp",
                                    "alt": ""
                                  },
                                  "innerBlocks": null
                                }
                              ]
                            }
                          ]
                        }
                      ]
                    }
                  ]
                }
              ]
            }
          ]
        }
      ]
    }
  }
}

GraphQL schema สำหรับ Block type มีลักษณะดังนี้:

interface Block {
  name: String!
  attributes: JSONObject
  innerBlocks: [BlockUnion!]
  contentSource: HTML!
}
 
type GenericBlock implements Block {
  name: String!
  attributes: JSONObject
  innerBlocks: [BlockUnion!]
  contentSource: HTML!
}
 
union BlockUnion = GenericBlock

ฟิลด์ของ Block

อินเทอร์เฟซ Block (และด้วยเหตุนี้ GeneralBlock type) มีฟิลด์ต่อไปนี้:

  • name ดึงชื่อของบล็อก: "core/paragraph", "core/heading" "core/image" เป็นต้น
  • attributes ดึงออบเจ็กต์ JSON ที่มีแอตทริบิวต์ทั้งหมดของบล็อก
  • innerBlocks ดึงค่า [BlockUnion!] ดังนั้นเราจึงสามารถคิวรีเพื่อนำทางไปตามลำดับชั้นของบล็อกที่มีบล็อกภายใน และดึงข้อมูลของบล็อกเหล่านั้นทั้งหมดได้ลึกลงไปเท่าจำนวนระดับที่มีอยู่ในเนื้อหาของเรา
  • contentSource ดึงซอร์สโค้ด HTML (Gutenberg) ของบล็อก รวมถึงตัวคั่นคอมเมนต์ที่มีแอตทริบิวต์อยู่ด้วย อย่างไรก็ตาม ฟิลด์นี้ไม่ได้ดึงข้อมูลที่ตรงกันทุกประการกับที่จัดเก็บไว้ในฐานข้อมูล (ดู #2346) ดังนั้นโปรดใช้ฟิลด์นี้อย่างระมัดระวัง

การดึง GeneralBlock โดยตรง (แทน BlockUnion)

เนื่องจากปัจจุบันมี Block type ที่แมปบล็อกเพียงตัวเดียวคือ GeneralBlock จึงสมเหตุสมผลที่จะให้ CustomPost.blocks (และ Block.innerBlocks ด้วย) ดึง type นี้โดยตรงแทนที่จะเป็น BlockUnion type

เราสามารถทำสิ่งนี้ได้ในหน้า Settings ภายใต้แท็บ Blocks โดยติ๊กเลือกตัวเลือก Use single type instead of union type?:

การตั้งค่าเพื่อดึง `GeneralBlock` โดยตรงแทน `BlockUnion`
การตั้งค่าเพื่อดึง `GeneralBlock` โดยตรงแทน `BlockUnion`

จากนั้น คิวรี GraphQL จะถูกทำให้ง่ายขึ้น:

{
  post(by: { id: 1 }) {
    blocks {
      name
      attributes
      innerBlocks {
        name
        attributes
      }
    }
  }
}

โปรดสังเกตว่าการคงประเภทเรสปอนส์ไว้เป็น BlockUnion นั้นดีต่อความเข้ากันได้ในอนาคต: หากเราตัดสินใจเพิ่ม type เฉพาะของบล็อกเข้าไปใน schema (ดูในส่วนด้านล่าง) ก็จะไม่มีการเปลี่ยนแปลงที่ทำให้เกิดความเสียหาย

การแมป type เฉพาะของบล็อก

JSONObject type (ที่ดึงโดย Block.attributes) ไม่ได้ถูกกำหนด type อย่างเข้มงวด: พรอเพอร์ตี้ของมันสามารถมี type และคาร์ดินาลิตีใด ๆ ก็ได้ (String, Int, [Boolean!] เป็นต้น) ดังนั้นเราจึงจำเป็นต้องทราบข้อมูลนี้สำหรับทุกบล็อกและจัดการแต่ละกรณีในฝั่งไคลเอนต์

หากเราต้องการการกำหนด type อย่างเข้มงวด เราต้องขยาย GraphQL schema ผ่านโค้ด PHP โดยเพิ่ม type เฉพาะของบล็อกที่แมปแอตทริบิวต์เฉพาะของบล็อกเป็นฟิลด์ และทำให้มันเป็นส่วนหนึ่งของ BlockUnion

ตัวอย่างเช่น เราสามารถเพิ่ม type CoreParagraphBlock ที่แมปบล็อก core/paragraph พร้อมฟิลด์ content ที่เป็น type String

โปรดดูเอกสารใน GatoGraphQL/GatoGraphQL เพื่อเรียนรู้วิธีขยาย GraphQL schema (ปัจจุบันยังอยู่ระหว่างดำเนินการ)

การกรองบล็อก

ฟิลด์ CustomPost.blocks มีอาร์กิวเมนต์ filterBy ซึ่งมีพรอเพอร์ตี้สองตัวคือ include และ exclude เราสามารถใช้สิ่งเหล่านี้เพื่อกรองว่าจะดึงบล็อกใดบ้าง โดยอ้างอิงจากชื่อบล็อก:

{
  post(by: { id: 1 }) {
    id
    blocks(
      filterBy: {
        include: [
          "core/heading",
          "core/gallery"
        ]
      }
    ) {
      name
      attributes
    }
  }
}

สิ่งนี้จะสร้าง:

{
  "data": {
    "post": {
      "blocks": [
        {
          "name": "core/gallery",
          "attributes": {
            "linkTo": "none",
            "className": "alignnone",
            "images": [
              {
                "url": "https://d.pr/i/zd7Ehu+",
                "alt": "",
                "id": "1706"
              },
              {
                "url": "https://d.pr/i/jXLtzZ+",
                "alt": "",
                "id": "1705"
              }
            ],
            "ids": [],
            "shortCodeTransforms": [],
            "imageCrop": true,
            "fixedHeight": true,
            "sizeSlug": "large",
            "allowResize": false
          },
          "innerBlocks": null
        },
        {
          "name": "core/heading",
          "attributes": {
            "content": "List Block",
            "level": 2
          },
          "innerBlocks": null
        },
        {
          "name": "core/heading",
          "attributes": {
            "className": "has-top-margin",
            "content": "Columns Block",
            "level": 2
          },
          "innerBlocks": null
        },
        {
          "name": "core/heading",
          "attributes": {
            "content": "Columns inside Columns (nested inner blocks)",
            "level": 2
          },
          "innerBlocks": null
        }
      ]
    }
  }
}

โปรดสังเกตว่าไม่ใช่บล็อกประเภท core/heading ทั้งหมดที่ถูกรวมเข้ามา: บล็อกที่ถูกซ้อนอยู่ภายใต้ core/column ถูกแยกออก เนื่องจากไม่มีทางเข้าถึงพวกมันได้ (เพราะบล็อก core/columns และ core/column เองก็ถูกแยกออก)

ความไม่สะดวกของฟิลด์ blocks

ฟิลด์ blocks ก่อให้เกิดความยุ่งยากตรงที่ ในการดึงข้อมูลบล็อกทั้งหมดที่อยู่ในโพสต์แบบกำหนดเอง รวมถึงข้อมูลของบล็อกภายใน และบล็อกภายในของพวกมันอีกที เป็นต้น เราจำเป็นต้องทราบว่ามีระดับการซ้อนของบล็อกกี่ระดับในเนื้อหา และสะท้อนข้อมูลนี้ลงในคิวรี GraphQL

หรือหากเราไม่ทราบ เราต้องประกอบคิวรีให้มีจำนวนระดับมากเพียงพอเพื่อให้แน่ใจว่าจะดึงข้อมูลทั้งหมดได้

ตัวอย่างเช่น คิวรีนี้ดึงการซ้อนของบล็อกภายในได้ลึกถึง 7 ระดับ:

{
  post(by: { id: 1 }) {
    blocks {
      ...BlockData
    }
  }
}
 
fragment BlockData on Block {
  name
  attributes
  innerBlocks {
    ...on Block {
      name
      attributes
      innerBlocks {
        ...on Block {
          name
          attributes
          innerBlocks {
            ...on Block {
              name
              attributes
              innerBlocks {
                ...on Block {
                  name
                  attributes
                  innerBlocks {
                    ...on Block {
                      name
                      attributes
                      innerBlocks {
                        ...on Block {
                          name
                          attributes
                          innerBlocks {
                            ...on Block {
                              name
                              attributes
                            }
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

blockDataItems

เพื่อหลีกเลี่ยงความไม่สะดวกของวิธีที่ฟิลด์ blocks ดึงข้อมูลทั้งหมด (รวมถึงบล็อกภายในของมัน และบล็อกภายในของพวกมันอีกที เป็นต้น) จึงมีฟิลด์ CustomPost.blockDataItems

ฟิลด์นี้แทนที่จะคืนค่า [BlockUnion] จะคืนค่า [JSONObject!]:

type CustomPost {
  blockDataItems: [JSONObject!]
}

กล่าวอีกนัยหนึ่ง แทนที่จะทำตามวิธีการทั่วไปของ GraphQL ที่ให้เอนทิตีเชื่อมโยงกับเอนทิตีและนำทางข้ามไปมา ทุกเอนทิตี Block ที่ระดับบนสุดจะสร้างข้อมูลบล็อกทั้งหมดของตัวมันเองและของลูกทั้งหมดไว้แล้ว ภายในผลลัพธ์ JSONObject เดียว

ออบเจ็กต์ JSON นี้มีพรอเพอร์ตี้สำหรับบล็อก (ภายใต้รายการ name และ attributes) และสำหรับบล็อกภายในของมัน (ภายใต้รายการ innerBlocks) แบบเรียกซ้ำ

ตัวอย่างเช่น คิวรีต่อไปนี้:

{
  post(by: { id: 1 }) {
    blockDataItems
  }
}

...จะสร้าง:

{
  "data": {
    "post": {
      "blockDataItems": [
        {
          "name": "core/gallery",
          "attributes": {
            "linkTo": "none",
            "className": "alignnone",
            "images": [
              {
                "url": "https://d.pr/i/zd7Ehu+",
                "alt": "",
                "id": "1706"
              },
              {
                "url": "https://d.pr/i/jXLtzZ+",
                "alt": "",
                "id": "1705"
              }
            ],
            "ids": [],
            "shortCodeTransforms": [],
            "imageCrop": true,
            "fixedHeight": true,
            "sizeSlug": "large",
            "allowResize": false
          }
        },
        {
          "name": "core/heading",
          "attributes": {
            "content": "List Block",
            "level": 2
          }
        },
        {
          "name": "core/list",
          "attributes": {
            "ordered": false,
            "values": "<li>List item 1</li><li>List item 2</li><li>List item 3</li><li>List item 4</li>"
          }
        },
        {
          "name": "core/heading",
          "attributes": {
            "className": "has-top-margin",
            "content": "Columns Block",
            "level": 2
          }
        },
        {
          "name": "core/columns",
          "attributes": {
            "isStackedOnMobile": true
          },
          "innerBlocks": [
            {
              "name": "core/column",
              "attributes": {},
              "innerBlocks": [
                {
                  "name": "core/image",
                  "attributes": {
                    "id": 1701,
                    "className": "layout-column-1",
                    "url": "https://d.pr/i/fW6V3V+",
                    "alt": ""
                  }
                }
              ]
            },
            {
              "name": "core/column",
              "attributes": {},
              "innerBlocks": [
                {
                  "name": "core/paragraph",
                  "attributes": {
                    "className": "layout-column-2",
                    "content": "Phosfluorescently morph intuitive relationships rather than customer directed human capital.",
                    "dropCap": false
                  }
                }
              ]
            }
          ]
        },
        {
          "name": "core/heading",
          "attributes": {
            "content": "Columns inside Columns (nested inner blocks)",
            "level": 2
          }
        },
        {
          "name": "core/columns",
          "attributes": {
            "isStackedOnMobile": true
          },
          "innerBlocks": [
            {
              "name": "core/column",
              "attributes": {},
              "innerBlocks": [
                {
                  "name": "core/image",
                  "attributes": {
                    "id": 1701,
                    "className": "layout-column-1",
                    "url": "https://d.pr/i/fW6V3V+",
                    "alt": ""
                  }
                },
                {
                  "name": "core/columns",
                  "attributes": {
                    "isStackedOnMobile": true
                  },
                  "innerBlocks": [
                    {
                      "name": "core/column",
                      "attributes": {
                        "width": "33.33%"
                      },
                      "innerBlocks": [
                        {
                          "name": "core/heading",
                          "attributes": {
                            "fontSize": "large",
                            "content": "Life is so rich",
                            "level": 2
                          }
                        },
                        {
                          "name": "core/heading",
                          "attributes": {
                            "level": 3,
                            "content": "Life is so dynamic"
                          }
                        }
                      ]
                    },
                    {
                      "name": "core/column",
                      "attributes": {
                        "width": "66.66%"
                      },
                      "innerBlocks": [
                        {
                          "name": "core/paragraph",
                          "attributes": {
                            "content": "This rhyming poem is the spark that can reignite the fires within you. It challenges you to go out and live your life in the present moment as a \u201chero\u201d and leave your mark on this world.",
                            "dropCap": false
                          }
                        },
                        {
                          "name": "core/columns",
                          "attributes": {
                            "isStackedOnMobile": true
                          },
                          "innerBlocks": [
                            {
                              "name": "core/column",
                              "attributes": {},
                              "innerBlocks": [
                                {
                                  "name": "core/image",
                                  "attributes": {
                                    "id": 361,
                                    "sizeSlug": "large",
                                    "linkDestination": "none",
                                    "url": "https://gato-graphql.lndo.site/wp-content/uploads/2022/05/graphql-voyager-public-1024x622.jpg",
                                    "alt": ""
                                  }
                                }
                              ]
                            },
                            {
                              "name": "core/column",
                              "attributes": {}
                            },
                            {
                              "name": "core/column",
                              "attributes": {},
                              "innerBlocks": [
                                {
                                  "name": "core/image",
                                  "attributes": {
                                    "id": 362,
                                    "sizeSlug": "large",
                                    "linkDestination": "none",
                                    "url": "https://gato-graphql.lndo.site/wp-content/uploads/2022/05/namespaced-interactive-schema-1024x598.webp",
                                    "alt": ""
                                  }
                                }
                              ]
                            }
                          ]
                        }
                      ]
                    }
                  ]
                }
              ]
            }
          ]
        }
      ]
    }
  }
}

การกรองรายการข้อมูลบล็อก

เช่นเดียวกับ blocks ฟิลด์ blockDataItems ก็อนุญาตให้กรองว่าจะดึงบล็อกใดบ้างได้เช่นกัน ผ่านอาร์กิวเมนต์ filterBy

คิวรีนี้:

{
  post(by: { id: 1 }) {
    id
    blockDataItems(
      filterBy: {
        include: [
          "core/heading"
        ]
      }
    )
  }
}

...จะสร้าง:

{
  "data": {
    "post": {
      "blockDataItems": [
        {
          "name": "core/heading",
          "attributes": {
            "content": "List Block",
            "level": 2
          },
          "innerBlocks": null
        },
        {
          "name": "core/heading",
          "attributes": {
            "className": "has-top-margin",
            "content": "Columns Block",
            "level": 2
          },
          "innerBlocks": null
        },
        {
          "name": "core/heading",
          "attributes": {
            "content": "Columns inside Columns (nested inner blocks)",
            "level": 2
          },
          "innerBlocks": null
        }
      ]
    }
  }
}

โปรดสังเกตว่า เช่นเดียวกับ blocks ไม่ใช่บล็อกประเภท core/heading ทั้งหมดที่ถูกรวมเข้ามา: บล็อกที่ถูกซ้อนอยู่ภายใต้ core/column ถูกแยกออก เนื่องจากไม่มีทางเข้าถึงพวกมันได้ (เพราะบล็อก core/columns และ core/column เองก็ถูกแยกออก)

blockFlattenedDataItems

ทั้งฟิลด์ blocks และ blockDataItems อนุญาตให้กรองว่าจะดึงบล็อกใดบ้าง (ผ่านอาร์กิวเมนต์ filterBy) ในทั้งสองกรณี หากบล็อกใดเข้าเงื่อนไขการรวมเข้ามา แต่ถูกซ้อนอยู่ภายในบล็อกที่ไม่เข้าเงื่อนไข บล็อกนั้นก็จะถูกแยกออก

อย่างไรก็ตาม มีบางครั้งที่เราจำเป็นต้องดึงบล็อกประเภทหนึ่ง ๆ ทั้งหมดจากโพสต์แบบกำหนดเอง โดยไม่คำนึงว่าบล็อกเหล่านั้นจะอยู่ตรงไหนในลำดับชั้น ตัวอย่างเช่น เราอาจต้องการรวมบล็อกประเภท core/image ทั้งหมด เพื่อดึงรูปภาพทั้งหมดที่อยู่ในโพสต์บล็อก

เพื่อตอบสนองความต้องการนี้จึงมีฟิลด์ CustomPost.blockFlattenedDataItems ซึ่งต่างจากฟิลด์ blocks และ blockDataItems ตรงที่มันจะแบนลำดับชั้นของบล็อกให้เหลือเพียงระดับเดียว

คิวรีนี้:

{
  post(by: { id: 1 }) {
    blockFlattenedDataItems
  }
}

...จะสร้าง:

{
  "data": {
    "post": {
      "blockFlattenedDataItems": [
        {
          "name": "core/gallery",
          "attributes": {
            "linkTo": "none",
            "className": "alignnone",
            "images": [
              {
                "url": "https://d.pr/i/zd7Ehu+",
                "alt": "",
                "id": "1706"
              },
              {
                "url": "https://d.pr/i/jXLtzZ+",
                "alt": "",
                "id": "1705"
              }
            ],
            "ids": [],
            "shortCodeTransforms": [],
            "imageCrop": true,
            "fixedHeight": true,
            "sizeSlug": "large",
            "allowResize": false
          },
          "innerBlockPositions": null,
          "parentBlockPosition": null
        },
        {
          "name": "core/heading",
          "attributes": {
            "content": "List Block",
            "level": 2
          },
          "innerBlockPositions": null,
          "parentBlockPosition": null
        },
        {
          "name": "core/list",
          "attributes": {
            "ordered": false,
            "values": "<li>List item 1</li><li>List item 2</li><li>List item 3</li><li>List item 4</li>"
          },
          "innerBlockPositions": null,
          "parentBlockPosition": null
        },
        {
          "name": "core/heading",
          "attributes": {
            "className": "has-top-margin",
            "content": "Columns Block",
            "level": 2
          },
          "innerBlockPositions": null,
          "parentBlockPosition": null
        },
        {
          "name": "core/columns",
          "attributes": {
            "isStackedOnMobile": true
          },
          "innerBlockPositions": [
            5,
            7
          ],
          "parentBlockPosition": null
        },
        {
          "name": "core/column",
          "attributes": {},
          "parentBlockPosition": 4,
          "innerBlockPositions": [
            6
          ]
        },
        {
          "name": "core/image",
          "attributes": {
            "id": 1701,
            "className": "layout-column-1",
            "url": "https://d.pr/i/fW6V3V+",
            "alt": ""
          },
          "parentBlockPosition": 5,
          "innerBlockPositions": null
        },
        {
          "name": "core/column",
          "attributes": {},
          "parentBlockPosition": 4,
          "innerBlockPositions": [
            8
          ]
        },
        {
          "name": "core/paragraph",
          "attributes": {
            "className": "layout-column-2",
            "content": "Phosfluorescently morph intuitive relationships rather than customer directed human capital.",
            "dropCap": false
          },
          "parentBlockPosition": 7,
          "innerBlockPositions": null
        },
        {
          "name": "core/heading",
          "attributes": {
            "content": "Columns inside Columns (nested inner blocks)",
            "level": 2
          },
          "innerBlockPositions": null,
          "parentBlockPosition": null
        },
        {
          "name": "core/columns",
          "attributes": {
            "isStackedOnMobile": true
          },
          "innerBlockPositions": [
            11
          ],
          "parentBlockPosition": null
        },
        {
          "name": "core/column",
          "attributes": {},
          "parentBlockPosition": 10,
          "innerBlockPositions": [
            12,
            13
          ]
        },
        {
          "name": "core/image",
          "attributes": {
            "id": 1701,
            "className": "layout-column-1",
            "url": "https://d.pr/i/fW6V3V+",
            "alt": ""
          },
          "parentBlockPosition": 11,
          "innerBlockPositions": null
        },
        {
          "name": "core/columns",
          "attributes": {
            "isStackedOnMobile": true
          },
          "parentBlockPosition": 11,
          "innerBlockPositions": [
            14,
            17
          ]
        },
        {
          "name": "core/column",
          "attributes": {
            "width": "33.33%"
          },
          "parentBlockPosition": 13,
          "innerBlockPositions": [
            15,
            16
          ]
        },
        {
          "name": "core/heading",
          "attributes": {
            "fontSize": "large",
            "content": "Life is so rich",
            "level": 2
          },
          "parentBlockPosition": 14,
          "innerBlockPositions": null
        },
        {
          "name": "core/heading",
          "attributes": {
            "level": 3,
            "content": "Life is so dynamic"
          },
          "parentBlockPosition": 14,
          "innerBlockPositions": null
        },
        {
          "name": "core/column",
          "attributes": {
            "width": "66.66%"
          },
          "parentBlockPosition": 13,
          "innerBlockPositions": [
            18,
            19
          ]
        },
        {
          "name": "core/paragraph",
          "attributes": {
            "content": "This rhyming poem is the spark that can reignite the fires within you. It challenges you to go out and live your life in the present moment as a \u201chero\u201d and leave your mark on this world.",
            "dropCap": false
          },
          "parentBlockPosition": 17,
          "innerBlockPositions": null
        },
        {
          "name": "core/columns",
          "attributes": {
            "isStackedOnMobile": true
          },
          "parentBlockPosition": 17,
          "innerBlockPositions": [
            20,
            22,
            23
          ]
        },
        {
          "name": "core/column",
          "attributes": {},
          "parentBlockPosition": 19,
          "innerBlockPositions": [
            21
          ]
        },
        {
          "name": "core/image",
          "attributes": {
            "id": 361,
            "sizeSlug": "large",
            "linkDestination": "none",
            "url": "https://gato-graphql.lndo.site/wp-content/uploads/2022/05/graphql-voyager-public-1024x622.jpg",
            "alt": ""
          },
          "parentBlockPosition": 20,
          "innerBlockPositions": null
        },
        {
          "name": "core/column",
          "attributes": {},
          "parentBlockPosition": 19,
          "innerBlockPositions": null
        },
        {
          "name": "core/column",
          "attributes": {},
          "parentBlockPosition": 19,
          "innerBlockPositions": [
            24
          ]
        },
        {
          "name": "core/image",
          "attributes": {
            "id": 362,
            "sizeSlug": "large",
            "linkDestination": "none",
            "url": "https://gato-graphql.lndo.site/wp-content/uploads/2022/05/namespaced-interactive-schema-1024x598.webp",
            "alt": ""
          },
          "parentBlockPosition": 23,
          "innerBlockPositions": null
        }
      ]
    }
  }
}

โปรดสังเกตว่าแอตทริบิวต์ innerBlocks หายไป เนื่องจากบล็อกไม่ได้ถูกซ้อนกันอีกต่อไป แทนที่ของมัน เรสปอนส์จะมีแอตทริบิวต์อีกสองตัว (ซึ่งช่วยให้เราสร้างลำดับชั้นของบล็อกขึ้นมาใหม่ได้):

  • parentBlockPosition: ตำแหน่งของบล็อกแม่ของบล็อกนั้นภายในอาร์เรย์ที่คืนค่ามา หรือ null หากเป็นบล็อกระดับบนสุด
  • innerBlockPositions: อาร์เรย์ที่มีตำแหน่งของบล็อกภายในของบล็อกนั้นภายในอาร์เรย์ที่คืนค่ามา

การกรองรายการข้อมูลบล็อกแบบแบน

เมื่อลำดับชั้นของบล็อกถูกทำให้แบนแล้ว การกรองด้วย core/heading จะสร้างบล็อกเหล่านี้ออกมาทั้งหมด (แม้ว่าบล็อกใดในนั้นจะถูกซ้อนอยู่ภายใต้บล็อกที่ถูกแยกออกในตอนแรกก็ตาม)

คิวรีนี้:

{
  post(by: { id: 1 }) {
    id
    blockFlattenedDataItems(
      filterBy: {
        include: [
          "core/heading"
        ]
      }
    )
  }
}

...จะสร้าง:

{
  "data": {
    "post": {
      "blockFlattenedDataItems": [
        {
          "name": "core/heading",
          "attributes": {
            "content": "List Block",
            "level": 2
          }
        },
        {
          "name": "core/heading",
          "attributes": {
            "className": "has-top-margin",
            "content": "Columns Block",
            "level": 2
          }
        },
        {
          "name": "core/heading",
          "attributes": {
            "content": "Columns inside Columns (nested inner blocks)",
            "level": 2
          }
        },
        {
          "name": "core/heading",
          "attributes": {
            "fontSize": "large",
            "content": "Life is so rich",
            "level": 2
          }
        },
        {
          "name": "core/heading",
          "attributes": {
            "level": 3,
            "content": "Life is so dynamic"
          }
        }
      ]
    }
  }
}

โปรดสังเกตว่าแอตทริบิวต์เพิ่มเติมสองตัวคือ parentBlockPosition และ innerBlockPositions จะถูกลบออกเมื่อทำการกรอง เนื่องจากมันไม่มีความหมายอีกต่อไป