fix(callout): Grid-based callout collapsible animation (#1944)
* Fixed broken nested callout maxHeight calculation * Implemented grid-based callout collapsible
This commit is contained in:
		@@ -1,25 +1,10 @@
 | 
				
			|||||||
function toggleCallout(this: HTMLElement) {
 | 
					function toggleCallout(this: HTMLElement) {
 | 
				
			||||||
  const outerBlock = this.parentElement!
 | 
					  const outerBlock = this.parentElement!
 | 
				
			||||||
  outerBlock.classList.toggle("is-collapsed")
 | 
					  outerBlock.classList.toggle("is-collapsed")
 | 
				
			||||||
 | 
					  const content = outerBlock.getElementsByClassName("callout-content")[0] as HTMLElement
 | 
				
			||||||
 | 
					  if (!content) return
 | 
				
			||||||
  const collapsed = outerBlock.classList.contains("is-collapsed")
 | 
					  const collapsed = outerBlock.classList.contains("is-collapsed")
 | 
				
			||||||
  const height = collapsed ? this.scrollHeight : outerBlock.scrollHeight
 | 
					  content.style.gridTemplateRows = collapsed ? "0fr" : "1fr"
 | 
				
			||||||
  outerBlock.style.maxHeight = height + "px"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // walk and adjust height of all parents
 | 
					 | 
				
			||||||
  let current = outerBlock
 | 
					 | 
				
			||||||
  let parent = outerBlock.parentElement
 | 
					 | 
				
			||||||
  while (parent) {
 | 
					 | 
				
			||||||
    if (!parent.classList.contains("callout")) {
 | 
					 | 
				
			||||||
      return
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const collapsed = parent.classList.contains("is-collapsed")
 | 
					 | 
				
			||||||
    const height = collapsed ? parent.scrollHeight : parent.scrollHeight + current.scrollHeight
 | 
					 | 
				
			||||||
    parent.style.maxHeight = height + "px"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    current = parent
 | 
					 | 
				
			||||||
    parent = parent.parentElement
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function setupCallout() {
 | 
					function setupCallout() {
 | 
				
			||||||
@@ -27,15 +12,15 @@ function setupCallout() {
 | 
				
			|||||||
    `callout is-collapsible`,
 | 
					    `callout is-collapsible`,
 | 
				
			||||||
  ) as HTMLCollectionOf<HTMLElement>
 | 
					  ) as HTMLCollectionOf<HTMLElement>
 | 
				
			||||||
  for (const div of collapsible) {
 | 
					  for (const div of collapsible) {
 | 
				
			||||||
    const title = div.firstElementChild
 | 
					    const title = div.getElementsByClassName("callout-title")[0] as HTMLElement
 | 
				
			||||||
    if (!title) continue
 | 
					    const content = div.getElementsByClassName("callout-content")[0] as HTMLElement
 | 
				
			||||||
 | 
					    if (!title || !content) continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    title.addEventListener("click", toggleCallout)
 | 
					    title.addEventListener("click", toggleCallout)
 | 
				
			||||||
    window.addCleanup(() => title.removeEventListener("click", toggleCallout))
 | 
					    window.addCleanup(() => title.removeEventListener("click", toggleCallout))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const collapsed = div.classList.contains("is-collapsed")
 | 
					    const collapsed = div.classList.contains("is-collapsed")
 | 
				
			||||||
    const height = collapsed ? title.scrollHeight : div.scrollHeight
 | 
					    content.style.gridTemplateRows = collapsed ? "0fr" : "1fr"
 | 
				
			||||||
    div.style.maxHeight = height + "px"
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -464,6 +464,30 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>>
 | 
				
			|||||||
                  })
 | 
					                  })
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // For the rest of the MD callout elements other than the title, wrap them with
 | 
				
			||||||
 | 
					                // two nested HTML <div>s (use some hacked mdhast component to achieve this) of
 | 
				
			||||||
 | 
					                // class `callout-content` and `callout-content-inner` respectively for
 | 
				
			||||||
 | 
					                // grid-based collapsible animation.
 | 
				
			||||||
 | 
					                if (calloutContent.length > 0) {
 | 
				
			||||||
 | 
					                  node.children = [
 | 
				
			||||||
 | 
					                    node.children[0],
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                      data: { hProperties: { className: ["callout-content"] }, hName: "div" },
 | 
				
			||||||
 | 
					                      type: "blockquote",
 | 
				
			||||||
 | 
					                      children: [
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                          data: {
 | 
				
			||||||
 | 
					                            hProperties: { className: ["callout-content-inner"] },
 | 
				
			||||||
 | 
					                            hName: "div",
 | 
				
			||||||
 | 
					                          },
 | 
				
			||||||
 | 
					                          type: "blockquote",
 | 
				
			||||||
 | 
					                          children: [...calloutContent],
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                      ],
 | 
				
			||||||
 | 
					                    },
 | 
				
			||||||
 | 
					                  ]
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                // replace first line of blockquote with title and rest of the paragraph text
 | 
					                // replace first line of blockquote with title and rest of the paragraph text
 | 
				
			||||||
                node.children.splice(0, 1, ...blockquoteContent)
 | 
					                node.children.splice(0, 1, ...blockquoteContent)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -485,21 +509,6 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>>
 | 
				
			|||||||
                    "data-callout-metadata": calloutMetaData,
 | 
					                    "data-callout-metadata": calloutMetaData,
 | 
				
			||||||
                  },
 | 
					                  },
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					 | 
				
			||||||
                // Add callout-content class to callout body if it has one.
 | 
					 | 
				
			||||||
                if (calloutContent.length > 0) {
 | 
					 | 
				
			||||||
                  const contentData: BlockContent | DefinitionContent = {
 | 
					 | 
				
			||||||
                    data: {
 | 
					 | 
				
			||||||
                      hProperties: {
 | 
					 | 
				
			||||||
                        className: "callout-content",
 | 
					 | 
				
			||||||
                      },
 | 
					 | 
				
			||||||
                      hName: "div",
 | 
					 | 
				
			||||||
                    },
 | 
					 | 
				
			||||||
                    type: "blockquote",
 | 
					 | 
				
			||||||
                    children: [...calloutContent],
 | 
					 | 
				
			||||||
                  }
 | 
					 | 
				
			||||||
                  node.children = [node.children[0], contentData]
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,11 +7,19 @@
 | 
				
			|||||||
  border-radius: 5px;
 | 
					  border-radius: 5px;
 | 
				
			||||||
  padding: 0 1rem;
 | 
					  padding: 0 1rem;
 | 
				
			||||||
  overflow-y: hidden;
 | 
					  overflow-y: hidden;
 | 
				
			||||||
  transition: max-height 0.3s ease;
 | 
					 | 
				
			||||||
  box-sizing: border-box;
 | 
					  box-sizing: border-box;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  & > .callout-content > :first-child {
 | 
					  & > .callout-content {
 | 
				
			||||||
    margin-top: 0;
 | 
					    display: grid;
 | 
				
			||||||
 | 
					    transition: grid-template-rows 0.3s ease;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    & > .callout-content-inner {
 | 
				
			||||||
 | 
					      overflow: hidden;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      & > :first-child {
 | 
				
			||||||
 | 
					        margin-top: 0;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  --callout-icon-note: url('data:image/svg+xml; utf8, <svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="2" x2="22" y2="6"></line><path d="M7.5 20.5 19 9l-4-4L3.5 16.5 2 22z"></path></svg>');
 | 
					  --callout-icon-note: url('data:image/svg+xml; utf8, <svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="2" x2="22" y2="6"></line><path d="M7.5 20.5 19 9l-4-4L3.5 16.5 2 22z"></path></svg>');
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user