mirror of
				https://github.com/notion-enhancer/notion-enhancer.git
				synced 2025-10-31 22:28:08 +11:00 
			
		
		
		
	new extension: collapsible headers (#320)
This commit is contained in:
		
							parent
							
								
									caa2360a3d
								
							
						
					
					
						commit
						38dded687e
					
				
							
								
								
									
										86
									
								
								repo/collapsible-headers/app.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								repo/collapsible-headers/app.css
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,86 @@ | |||||||
|  | /* | ||||||
|  |  * collapsible headers | ||||||
|  |  * (c) 2020 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/) | ||||||
|  |  * (c) 2020 CloudHill | ||||||
|  |  * under the MIT license | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  |  .notion-page-content .notion-selectable[collapsed] { | ||||||
|  |   max-height: 0px; | ||||||
|  |   overflow: hidden; | ||||||
|  |   opacity: 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .notion-page-content .notion-selectable[collapsed] .notion-selectable { | ||||||
|  |   pointer-events: none; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .collapse-header { | ||||||
|  |   flex-grow: 0; | ||||||
|  |   flex-shrink: 0; | ||||||
|  |   align-self: center; | ||||||
|  |   width: 24px; | ||||||
|  |   height: 24px; | ||||||
|  |   padding: 6px; | ||||||
|  |   margin: 0 6px; | ||||||
|  |   border-radius: 3px; | ||||||
|  |   display: flex; | ||||||
|  |   align-items: center; | ||||||
|  |   justify-content: center; | ||||||
|  |   z-index: 1; | ||||||
|  |   cursor: pointer; | ||||||
|  |   transition: 200ms ease-in; | ||||||
|  | } | ||||||
|  | .collapse-header:hover { | ||||||
|  |   background: var(--theme--interactive_hover); | ||||||
|  | } | ||||||
|  | /* position: left */ | ||||||
|  | .collapse-header:first-child { | ||||||
|  |   margin-left: 2px; | ||||||
|  | } | ||||||
|  | /* position: right / inline */ | ||||||
|  | .collapse-header:last-child { | ||||||
|  |   opacity: 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* show toggle on: collapsed, hover, focus */ | ||||||
|  | [data-collapsed="true"] .collapse-header:last-child, | ||||||
|  | [data-collapsed]:hover .collapse-header:last-child, | ||||||
|  | [data-collapsed] :focus + .collapse-header:last-child  { | ||||||
|  |   opacity: 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .collapse-header svg { | ||||||
|  |   width: 100%; | ||||||
|  |   height: 100%; | ||||||
|  |   transition: transform 200ms ease-out 0s; | ||||||
|  | } | ||||||
|  | /* position: left */ | ||||||
|  | .collapse-header:first-child svg { | ||||||
|  |   transform: rotateZ(90deg); | ||||||
|  | } | ||||||
|  | /* position: right / inline */ | ||||||
|  | .collapse-header:last-child svg { | ||||||
|  |   transform: rotateZ(270deg); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | [data-collapsed="false"] .collapse-header svg { | ||||||
|  |   transform: rotateZ(180deg); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /* position: inline */ | ||||||
|  | [inline-toggle] { | ||||||
|  |   position: relative; | ||||||
|  |   overflow: hidden; | ||||||
|  | } | ||||||
|  | [inline-toggle] [placeholder] { | ||||||
|  |   width: auto !important; | ||||||
|  | } | ||||||
|  | [inline-toggle] [placeholder]::after { | ||||||
|  |   content: ''; | ||||||
|  |   position: absolute; | ||||||
|  |   top: 0; | ||||||
|  |   width: 100%; | ||||||
|  |   height: 100%; | ||||||
|  |   cursor: text; | ||||||
|  | } | ||||||
							
								
								
									
										475
									
								
								repo/collapsible-headers/mod.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										475
									
								
								repo/collapsible-headers/mod.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,475 @@ | |||||||
|  | /* | ||||||
|  |  * collapsible headers | ||||||
|  |  * (c) 2020 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
 | ||||||
|  |  * (c) 2020 CloudHill | ||||||
|  |  * under the MIT license | ||||||
|  |  */ | ||||||
|  | 
 | ||||||
|  | 'use strict'; | ||||||
|  | 
 | ||||||
|  | const { createElement } = require('../../pkg/helpers.js'); | ||||||
|  | 
 | ||||||
|  | module.exports = { | ||||||
|  |   id: '548fe2d7-174a-44dd-88d8-35c7f9a093a7', | ||||||
|  |   tags: ['extension'], | ||||||
|  |   name: 'collapsible headers', | ||||||
|  |   desc: 'adds toggles to collapse header sections.', | ||||||
|  |   version: '1.0.0', | ||||||
|  |   author: 'CloudHill', | ||||||
|  |   options: [ | ||||||
|  |     { | ||||||
|  |       key: 'toggle', | ||||||
|  |       label: 'toggle position', | ||||||
|  |       type: 'select', | ||||||
|  |       value: ['left', 'right', 'inline'], | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       key: 'animate', | ||||||
|  |       label: 'enable animation', | ||||||
|  |       type: 'toggle', | ||||||
|  |       value: true, | ||||||
|  |     }, | ||||||
|  |     { | ||||||
|  |       key: 'divBreak', | ||||||
|  |       label: 'use divider blocks to break header sections', | ||||||
|  |       type: 'toggle', | ||||||
|  |       value: false, | ||||||
|  |     }, | ||||||
|  |   ], | ||||||
|  |   hacks: { | ||||||
|  |     'renderer/preload.js'(store, __exports) { | ||||||
|  |       document.addEventListener('readystatechange', (event) => { | ||||||
|  |         if (document.readyState !== 'complete') return false; | ||||||
|  |         const attempt_interval = setInterval(enhance, 500); | ||||||
|  |         function enhance() { | ||||||
|  |           if (!document.querySelector('.notion-frame')) return; | ||||||
|  |           clearInterval(attempt_interval); | ||||||
|  | 
 | ||||||
|  |           if (!store().collapsed_ids) store().collapsed_ids = []; | ||||||
|  |            | ||||||
|  |           window.addEventListener('hashchange', showSelectedHeader); | ||||||
|  | 
 | ||||||
|  |           // add toggles to headers whenever blocks are added/removed
 | ||||||
|  |           const contentObserver = new MutationObserver((list, observer) => { | ||||||
|  |             list.forEach(m => { | ||||||
|  |               let node = m.addedNodes[0] || m.removedNodes[0]; | ||||||
|  |               if ( | ||||||
|  |                 ( | ||||||
|  |                   node?.nodeType === Node.ELEMENT_NODE && | ||||||
|  |                   ( | ||||||
|  |                     node.className !== 'notion-selectable-halo' && | ||||||
|  |                     !node.style.cssText.includes('z-index: 88;') | ||||||
|  |                   ) | ||||||
|  |                 ) &&  | ||||||
|  |                 ( | ||||||
|  |                   m.target.className === 'notion-page-content' || | ||||||
|  |                   m.target.className.includes('notion-selectable') | ||||||
|  |                 ) | ||||||
|  |               ) { | ||||||
|  |                 // if a collapsed header is removed
 | ||||||
|  |                 if ( | ||||||
|  |                   node.dataset?.collapsed === 'true' && | ||||||
|  |                   !node.nextElementSibling | ||||||
|  |                 ) showHeaderContent(node); | ||||||
|  | 
 | ||||||
|  |                 initHeaderToggles(); | ||||||
|  |               } | ||||||
|  |             }) | ||||||
|  |           }); | ||||||
|  | 
 | ||||||
|  |           // observe for page changes
 | ||||||
|  |           let queue = []; | ||||||
|  |           const pageObserver = new MutationObserver((list, observer) => { | ||||||
|  |             if (!queue.length) requestAnimationFrame(() => process(queue)); | ||||||
|  |             queue.push(...list); | ||||||
|  |           }); | ||||||
|  |           pageObserver.observe(document.body, { | ||||||
|  |             childList: true, | ||||||
|  |             subtree: true, | ||||||
|  |           }); | ||||||
|  |           function process(list) { | ||||||
|  |             queue = []; | ||||||
|  |             for (let { addedNodes } of list) { | ||||||
|  |               if ( | ||||||
|  |                 addedNodes[0] && | ||||||
|  |                 addedNodes[0].className === 'notion-page-content' | ||||||
|  |               ) { | ||||||
|  |                 showSelectedHeader(); | ||||||
|  |                 initHeaderToggles(); | ||||||
|  |                 contentObserver.disconnect(); | ||||||
|  |                 contentObserver.observe(addedNodes[0], { | ||||||
|  |                   childList: true, | ||||||
|  |                   subtree: true, | ||||||
|  |                 }); | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  | 
 | ||||||
|  |           // bind to ctrl + enter
 | ||||||
|  |           document.addEventListener('keyup', e => { | ||||||
|  |             const hotkey = { | ||||||
|  |               key: 'Enter', | ||||||
|  |               ctrlKey: true, | ||||||
|  |               metaKey: false, | ||||||
|  |               altKey: false, | ||||||
|  |               shiftKey: false, | ||||||
|  |             }; | ||||||
|  |             for (let prop in hotkey) | ||||||
|  |               if (hotkey[prop] !== e[prop]) return; | ||||||
|  |             // toggle active/selected headers
 | ||||||
|  |             const active = document.activeElement; | ||||||
|  |             let toggle; | ||||||
|  |             if ( | ||||||
|  |               (toggle = active.nextElementSibling || active.previousElementSibling)  &&  | ||||||
|  |               toggle.className === 'collapse-header' | ||||||
|  |             ) { | ||||||
|  |               toggle.click(); | ||||||
|  |             } else { | ||||||
|  |               toggleHeaders( getSelectedHeaders() ); | ||||||
|  |             } | ||||||
|  |           }); | ||||||
|  | 
 | ||||||
|  |           function initHeaderToggles() { | ||||||
|  |             const headerBlocks = document | ||||||
|  |               .querySelectorAll('.notion-page-content [class*="header-block"]'); | ||||||
|  | 
 | ||||||
|  |             headerBlocks.forEach(header => { | ||||||
|  |               const nextBlock = header.nextElementSibling; | ||||||
|  | 
 | ||||||
|  |               // if header is moved
 | ||||||
|  |               if ( | ||||||
|  |                 header.dataset.collapsed &&  | ||||||
|  |                 header.collapsedBlocks && | ||||||
|  |                 header.collapsedBlocks[0] !== nextBlock    | ||||||
|  |               ) { | ||||||
|  |                 showHeaderContent(header); | ||||||
|  |               } | ||||||
|  | 
 | ||||||
|  |               // if header has no content
 | ||||||
|  |               if ( | ||||||
|  |                 !nextBlock || | ||||||
|  |                 getHeaderLevel(nextBlock) <= getHeaderLevel(header) || | ||||||
|  |                 ( | ||||||
|  |                   store().divBreak && | ||||||
|  |                   nextBlock.classList && | ||||||
|  |                   nextBlock.classList.contains('notion-divider-block') | ||||||
|  |                 ) | ||||||
|  |               ) { | ||||||
|  |                 if (header.dataset.collapsed) { | ||||||
|  |                   delete header.dataset.collapsed; | ||||||
|  |                   const toggle = header.querySelector('.collapse-header'); | ||||||
|  |                   if (toggle) toggle.remove(); | ||||||
|  |                 } | ||||||
|  |                 return; | ||||||
|  |               }; | ||||||
|  | 
 | ||||||
|  |               // if header already has a toggle
 | ||||||
|  |               if (header.querySelector('.collapse-header')) return; | ||||||
|  | 
 | ||||||
|  |               // add toggle to headers
 | ||||||
|  |               const toggle = createElement(` | ||||||
|  |                 <div class="collapse-header"> | ||||||
|  |                   <svg viewBox="0 0 100 100" class="triangle"> | ||||||
|  |                     <polygon points="5.9,88.2 50,11.8 94.1,88.2" /> | ||||||
|  |                   </svg> | ||||||
|  |                 </div> | ||||||
|  |               `)
 | ||||||
|  | 
 | ||||||
|  |               if (store().toggle === 'left') header.firstChild.prepend(toggle); | ||||||
|  |               else header.firstChild.appendChild(toggle); | ||||||
|  | 
 | ||||||
|  |               if (store().toggle === 'inline') | ||||||
|  |                 header.firstChild.setAttribute('inline-toggle', ''); | ||||||
|  | 
 | ||||||
|  |               toggle.header = header; | ||||||
|  |               toggle.addEventListener('click', toggleHeaderContent); | ||||||
|  | 
 | ||||||
|  |               // check store for header
 | ||||||
|  |               header.dataset.collapsed = false; | ||||||
|  |               if (store().collapsed_ids.includes(header.dataset.blockId)) | ||||||
|  |                 collapseHeaderContent(header, false); | ||||||
|  |             }); | ||||||
|  |           } | ||||||
|  | 
 | ||||||
|  |           function toggleHeaderContent(e) { | ||||||
|  |             e.stopPropagation(); | ||||||
|  |             const toggle = e.currentTarget; | ||||||
|  |             const header = toggle.header; | ||||||
|  | 
 | ||||||
|  |             const selected = getSelectedHeaders(); | ||||||
|  |             if (selected && selected.includes(header)) return toggleHeaders(selected); | ||||||
|  | 
 | ||||||
|  |             if (header.dataset.collapsed === 'true') showHeaderContent(header); | ||||||
|  |             else collapseHeaderContent(header); | ||||||
|  |           } | ||||||
|  | 
 | ||||||
|  |           function collapseHeaderContent(header, animate = true) { | ||||||
|  |             if ( | ||||||
|  |               !header.className.includes('header-block') || | ||||||
|  |               header.dataset.collapsed === 'true' | ||||||
|  |             ) return; | ||||||
|  |             header.dataset.collapsed = true; | ||||||
|  | 
 | ||||||
|  |             // store collapsed headers
 | ||||||
|  |             if (!store().collapsed_ids.includes(header.dataset.blockId)) { | ||||||
|  |               store().collapsed_ids.push(header.dataset.blockId); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             const headerLevel = getHeaderLevel(header); | ||||||
|  |             const toggle = header.querySelector('.collapse-header'); | ||||||
|  | 
 | ||||||
|  |             header.collapsedBlocks = getHeaderContent(header); | ||||||
|  |             header.collapsedBlocks.forEach(block => { | ||||||
|  |               // don't collapse already collapsed blocks
 | ||||||
|  |               if (block.hasAttribute('collapsed')) { | ||||||
|  |                 if (+(block.getAttribute('collapsed')) < headerLevel) { | ||||||
|  |                   block.setAttribute('collapsed', headerLevel); | ||||||
|  |                   if (block.storeAttributes) block.storeAttributes.header = header; | ||||||
|  |                 } | ||||||
|  |                 return; | ||||||
|  |               }; | ||||||
|  | 
 | ||||||
|  |               block.storeAttributes = { | ||||||
|  |                 marginTop: block.style.marginTop, | ||||||
|  |                 marginBottom: block.style.marginBottom, | ||||||
|  |                 header: header, | ||||||
|  |               } | ||||||
|  |               block.style.marginTop = 0; | ||||||
|  |               block.style.marginBottom = 0; | ||||||
|  |                | ||||||
|  |               if (!store().animate) { | ||||||
|  |                 block.setAttribute('collapsed', headerLevel); | ||||||
|  |                 toggleInnerBlocks(block, true); | ||||||
|  |               } else { | ||||||
|  |                 const height = block.offsetHeight; | ||||||
|  |                 block.storeAttributes.height = height + 'px'; | ||||||
|  |                 block.setAttribute('collapsed', headerLevel); | ||||||
|  |                  | ||||||
|  |                 if (!animate) toggleInnerBlocks(block, true); | ||||||
|  |                 else { | ||||||
|  |                   if (toggle) toggle.removeEventListener('click', toggleHeaderContent); | ||||||
|  |                   block.animate( | ||||||
|  |                     [ | ||||||
|  |                       {  | ||||||
|  |                         maxHeight: height + 'px', | ||||||
|  |                         opacity: 1, | ||||||
|  |                         marginTop: block.storeAttributes.marginTop,  | ||||||
|  |                         marginBottom: block.storeAttributes.marginBottom,  | ||||||
|  |                       }, | ||||||
|  |                       {  | ||||||
|  |                         maxHeight: (height - 100 > 0 ? height - 100 : 0) + 'px',  | ||||||
|  |                         opacity: 0, marginTop: 0, marginBottom: 0, | ||||||
|  |                       }, | ||||||
|  |                       { | ||||||
|  |                         maxHeight: 0, opacity: 0, marginTop: 0, marginBottom: 0, | ||||||
|  |                       } | ||||||
|  |                     ],  | ||||||
|  |                     { | ||||||
|  |                       duration: 300, | ||||||
|  |                       easing: 'ease-out' | ||||||
|  |                     } | ||||||
|  |                   ).onfinish = () => { | ||||||
|  |                     if (toggle) toggle.addEventListener('click', toggleHeaderContent); | ||||||
|  |                     toggleInnerBlocks(block, true); | ||||||
|  |                   }; | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |             }); | ||||||
|  |           } | ||||||
|  | 
 | ||||||
|  |           function showHeaderContent(header, animate = true) { | ||||||
|  |             if ( | ||||||
|  |               !header.className.includes('header-block') || | ||||||
|  |               header.dataset.collapsed === 'false' | ||||||
|  |             ) return; | ||||||
|  |             header.dataset.collapsed = false; | ||||||
|  | 
 | ||||||
|  |             // remove header from store
 | ||||||
|  |             const collapsed_ids = store().collapsed_ids; | ||||||
|  |             if (collapsed_ids.includes(header.dataset.blockId)) { | ||||||
|  |               store().collapsed_ids = collapsed_ids.filter(id => id !== header.dataset.blockId); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (!header.collapsedBlocks) return; | ||||||
|  |             const toggle = header.querySelector('.collapse-header'); | ||||||
|  | 
 | ||||||
|  |             showBlockHeader(header); | ||||||
|  | 
 | ||||||
|  |             header.collapsedBlocks.forEach(block => { | ||||||
|  |               // don't toggle blocks collapsed under other headers
 | ||||||
|  |               if ( | ||||||
|  |                 +(block.getAttribute('collapsed')) > getHeaderLevel(header) || | ||||||
|  |                 !block.storeAttributes | ||||||
|  |               ) return; | ||||||
|  | 
 | ||||||
|  |               block.style.marginTop = block.storeAttributes.marginTop; | ||||||
|  |               block.style.marginBottom = block.storeAttributes.marginBottom; | ||||||
|  | 
 | ||||||
|  |               if (!store().animate) { | ||||||
|  |                 block.removeAttribute('collapsed'); | ||||||
|  |                 toggleInnerBlocks(block, false); | ||||||
|  | 
 | ||||||
|  |               } else if (block.storeAttributes) { | ||||||
|  |                 toggleInnerBlocks(block, false); | ||||||
|  | 
 | ||||||
|  |                 if (!animate) block.removeAttribute('collapsed'); | ||||||
|  |                 else { | ||||||
|  |                   const height = parseInt(block.storeAttributes.height); | ||||||
|  |                   if (toggle) toggle.removeEventListener('click', toggleHeaderContent); | ||||||
|  |                   block.animate( | ||||||
|  |                     [ | ||||||
|  |                       { | ||||||
|  |                         maxHeight: 0, opacity: 0, marginTop: 0, marginBottom: 0, | ||||||
|  |                       }, | ||||||
|  |                       { | ||||||
|  |                         maxHeight: (height - 100 > 0 ? height - 100 : 0) + 'px', | ||||||
|  |                         opacity: 1, | ||||||
|  |                         marginTop: block.storeAttributes.marginTop, | ||||||
|  |                         marginBottom: block.storeAttributes.marginBottom,  | ||||||
|  |                       }, | ||||||
|  |                       {  | ||||||
|  |                         maxHeight: height + 'px', | ||||||
|  |                         opacity: 1, | ||||||
|  |                         marginTop: block.storeAttributes.marginTop, | ||||||
|  |                         marginBottom: block.storeAttributes.marginBottom,  | ||||||
|  |                       } | ||||||
|  |                     ], | ||||||
|  |                     { | ||||||
|  |                       duration: 300, | ||||||
|  |                       easing: 'ease-out' | ||||||
|  |                     } | ||||||
|  |                   ).onfinish = () => { | ||||||
|  |                     if (toggle) toggle.addEventListener('click', toggleHeaderContent); | ||||||
|  |                     block.removeAttribute('collapsed'); | ||||||
|  |                   }; | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |               delete block.storeAttributes; | ||||||
|  |             }); | ||||||
|  |             delete header.collapsedBlocks; | ||||||
|  |           } | ||||||
|  | 
 | ||||||
|  |           // query for headers marked with the selection halo
 | ||||||
|  |           function  getSelectedHeaders() { | ||||||
|  |             const selectedHeaders = Array.from( | ||||||
|  |               document.querySelectorAll('[class*="header-block"] .notion-selectable-halo') | ||||||
|  |             ).map(halo => halo.parentElement); | ||||||
|  | 
 | ||||||
|  |             if (selectedHeaders.length > 0) return selectedHeaders; | ||||||
|  |             return null; | ||||||
|  |           } | ||||||
|  | 
 | ||||||
|  |           // toggle an array of headers
 | ||||||
|  |           function toggleHeaders(headers) { | ||||||
|  |             if (!headers) return; | ||||||
|  |             headers = headers | ||||||
|  |               .filter(h =>  | ||||||
|  |                 !( h.hasAttribute('collapsed') && h.dataset.collapsed === 'false' ) | ||||||
|  |               ); | ||||||
|  |              | ||||||
|  |             if (headers && headers.length > 0) { | ||||||
|  |               const collapsed = headers | ||||||
|  |                 .filter(h => h.dataset.collapsed === 'true').length; | ||||||
|  |               headers.forEach(h => { | ||||||
|  |                 if (collapsed >= headers.length) showHeaderContent(h); | ||||||
|  |                 else collapseHeaderContent(h); | ||||||
|  |               }); | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  | 
 | ||||||
|  |           // get subsequent blocks
 | ||||||
|  |           function getHeaderContent(header) { | ||||||
|  |             let blockList = []; | ||||||
|  |             let nextBlock = header.nextElementSibling; | ||||||
|  |             while (nextBlock) { | ||||||
|  |               if ( | ||||||
|  |                 getHeaderLevel(nextBlock) <= getHeaderLevel(header) ||  | ||||||
|  |                 ( | ||||||
|  |                   store().divBreak && | ||||||
|  |                   nextBlock.classList && | ||||||
|  |                   nextBlock.classList.contains('notion-divider-block') | ||||||
|  |                 ) | ||||||
|  |               ) break; | ||||||
|  |               blockList.push(nextBlock); | ||||||
|  |               nextBlock = nextBlock.nextElementSibling; | ||||||
|  |             } | ||||||
|  |             return blockList; | ||||||
|  |           } | ||||||
|  | 
 | ||||||
|  |           // toggles a header from one of its collapsed blocks
 | ||||||
|  |           function showBlockHeader(block) { | ||||||
|  |             if ( | ||||||
|  |               block?.hasAttribute('collapsed') &&  | ||||||
|  |               block.storeAttributes?.header | ||||||
|  |             ) { | ||||||
|  |               showHeaderContent(block.storeAttributes.header); | ||||||
|  |               return true; | ||||||
|  |             }  | ||||||
|  |             return false; | ||||||
|  |           } | ||||||
|  | 
 | ||||||
|  |           function getHeaderLevel(header) { | ||||||
|  |             if (!header.className || !header.className.includes('header-block')) return 9; | ||||||
|  |             const subCount = header.classList[1].match(/sub/gi) || ''; | ||||||
|  |             let headerLevel = 1 + subCount.length; | ||||||
|  |             return headerLevel; | ||||||
|  |           } | ||||||
|  | 
 | ||||||
|  |           // ensures that any columns and indented blocks are also hidden
 | ||||||
|  |           // true => collapse, false => show
 | ||||||
|  |           function toggleInnerBlocks(block, collapse) { | ||||||
|  |             const header = block.storeAttributes?.header; | ||||||
|  |             Array.from( | ||||||
|  |               block.querySelectorAll('.notion-selectable') | ||||||
|  |             ).forEach(b => { | ||||||
|  |               if (!b.getAttribute('collapsed')) { | ||||||
|  |                 if (collapse) { | ||||||
|  |                   if (!b.storeAttributes) { | ||||||
|  |                     b.storeAttributes = { | ||||||
|  |                       height: b.offsetHeight, | ||||||
|  |                       marginTop: b.style.marginTop, | ||||||
|  |                       marginBottom: b.style.marginBottom, | ||||||
|  |                       header: header, | ||||||
|  |                     }; | ||||||
|  |                   } | ||||||
|  |                   b.setAttribute('collapsed', '') | ||||||
|  |                 } | ||||||
|  |                 else { | ||||||
|  |                   b.removeAttribute('collapsed'); | ||||||
|  |                   delete b.storeAttributes; | ||||||
|  |                 } | ||||||
|  |               } | ||||||
|  |             }); | ||||||
|  |           } | ||||||
|  | 
 | ||||||
|  |           function showSelectedHeader() { | ||||||
|  |             setTimeout(() => { | ||||||
|  |               const halo = document.querySelector('.notion-selectable-halo'); | ||||||
|  |               const header = halo?.parentElement; | ||||||
|  | 
 | ||||||
|  |               if (!header?.className?.includes('header-block')) return; | ||||||
|  |                | ||||||
|  |               // clear hash so that the same header can be toggled again
 | ||||||
|  |               location.hash = ''; | ||||||
|  |                | ||||||
|  |               if (showBlockHeader(header)) {     | ||||||
|  |                 setTimeout( | ||||||
|  |                   () => { | ||||||
|  |                     // is header in view?
 | ||||||
|  |                     var rect = header.getBoundingClientRect(); | ||||||
|  |                     if ( | ||||||
|  |                       (rect.top >= 0) &&  | ||||||
|  |                       (rect.bottom <= window.innerHeight) | ||||||
|  |                     ) return; | ||||||
|  |                     // if not, scroll to header
 | ||||||
|  |                     header.scrollIntoView({ behavior: 'smooth' }); | ||||||
|  |                   }, 400 | ||||||
|  |                 ) | ||||||
|  |               } | ||||||
|  |             }, 0) | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  | }; | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user