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) {
 | 
			
		||||
  const outerBlock = this.parentElement!
 | 
			
		||||
  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 height = collapsed ? this.scrollHeight : outerBlock.scrollHeight
 | 
			
		||||
  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
 | 
			
		||||
  }
 | 
			
		||||
  content.style.gridTemplateRows = collapsed ? "0fr" : "1fr"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function setupCallout() {
 | 
			
		||||
@@ -27,15 +12,15 @@ function setupCallout() {
 | 
			
		||||
    `callout is-collapsible`,
 | 
			
		||||
  ) as HTMLCollectionOf<HTMLElement>
 | 
			
		||||
  for (const div of collapsible) {
 | 
			
		||||
    const title = div.firstElementChild
 | 
			
		||||
    if (!title) continue
 | 
			
		||||
    const title = div.getElementsByClassName("callout-title")[0] as HTMLElement
 | 
			
		||||
    const content = div.getElementsByClassName("callout-content")[0] as HTMLElement
 | 
			
		||||
    if (!title || !content) continue
 | 
			
		||||
 | 
			
		||||
    title.addEventListener("click", toggleCallout)
 | 
			
		||||
    window.addCleanup(() => title.removeEventListener("click", toggleCallout))
 | 
			
		||||
 | 
			
		||||
    const collapsed = div.classList.contains("is-collapsed")
 | 
			
		||||
    const height = collapsed ? title.scrollHeight : div.scrollHeight
 | 
			
		||||
    div.style.maxHeight = height + "px"
 | 
			
		||||
    content.style.gridTemplateRows = collapsed ? "0fr" : "1fr"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
                node.children.splice(0, 1, ...blockquoteContent)
 | 
			
		||||
 | 
			
		||||
@@ -485,21 +509,6 @@ export const ObsidianFlavoredMarkdown: QuartzTransformerPlugin<Partial<Options>>
 | 
			
		||||
                    "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;
 | 
			
		||||
  padding: 0 1rem;
 | 
			
		||||
  overflow-y: hidden;
 | 
			
		||||
  transition: max-height 0.3s ease;
 | 
			
		||||
  box-sizing: border-box;
 | 
			
		||||
 | 
			
		||||
  & > .callout-content > :first-child {
 | 
			
		||||
    margin-top: 0;
 | 
			
		||||
  & > .callout-content {
 | 
			
		||||
    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>');
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user