Last active
May 6, 2025 16:06
-
-
Save stephanschielke/7a9fae189cdf138400376c7789b4ef37 to your computer and use it in GitHub Desktop.
Collapsible/expandable custom properties for `Logseq` `TODO` blocks with query by property values.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
- TODO Important stuff [[Tue, 6th May 2025]] | |
priority:: 4 | |
suggestions:: 🧩 Fit into next free slot, ⏰ Set a quick reminder, ⚡ Handle right away | |
urgency:: 🔴 | |
impact:: 🟡 | |
effort:: 🟢 | |
pomodoro:: {{renderer :pomodoro_pevja,25}} | |
:LOGBOOK: | |
CLOCK: [2025-05-06 Tue 06:01:51]--[2025-05-06 Tue 06:01:52] => 00:00:01 | |
:END: | |
- TODO Task [[Tue, 6th May 2025]] | |
priority:: 6 | |
suggestions:: 🗂️ Skip or batch later, 📉 Tackle during downtime, 🕰️ Do when you get a minute | |
urgency:: 🟡 | |
impact:: 🟢 | |
effort:: 🟢 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Header styling */ | |
.ls-block .block-properties-header { | |
cursor: pointer; | |
display: flex; | |
align-items: center; | |
margin-top: 1px; | |
padding: 1px; | |
user-select: none; | |
font-size: 0.9em; | |
/* ensure it sits above block-content */ | |
z-index: 1; | |
} | |
.ls-block .block-properties-header .arrow { | |
margin-left: 8px; | |
margin-right: 3px; | |
font-weight: lighter; | |
transition: transform 0.2s; | |
} | |
/* Hide ONLY todo properties by default (not page properties) */ | |
.ls-block .block-properties.todo-properties, | |
.block-content-wrapper .block-properties.todo-properties { | |
display: none; | |
margin-left: 25px; | |
padding: 5px; | |
} | |
/* Show when expanded */ | |
.ls-block .block-properties.todo-properties.expanded, | |
.block-content-wrapper .block-properties.todo-properties.expanded { | |
display: block; | |
} | |
/* Keep page properties visible */ | |
.ls-block .block-properties.page-properties { | |
display: block; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
console.log('custom.js loaded'); | |
function initCollapsibleProperties() { | |
// Find all TODO and DONE blocks, including those in the new wrapper structure | |
document | |
.querySelectorAll('.ls-block .block-content .inline.todo, .ls-block .block-content .inline.done, .block-content-wrapper .block-content .inline.done') | |
.forEach(todoElement => { | |
// Find the block-content element, handling both direct and wrapped structures | |
const blockContent = todoElement.closest('.block-content'); | |
if (!blockContent) return; | |
// Only target block-properties that are NOT page-properties | |
const propsElement = blockContent.querySelector('.block-properties:not(.page-properties)'); | |
// Skip if no properties or already processed | |
if (!propsElement || blockContent.querySelector('.block-properties-header')) return; | |
// Add a special class to mark this as a todo-properties | |
propsElement.classList.add('todo-properties'); | |
// Ensure it's hidden by default | |
propsElement.style.display = 'none'; | |
// Create header | |
const header = document.createElement('div'); | |
header.className = 'block-properties-header collapsed'; | |
header.innerHTML = '<span class="arrow">⨁</span> Properties'; | |
// Stop Logseq's edit-handler on pointerdown & mousedown | |
['pointerdown', 'mousedown'].forEach(evt => | |
header.addEventListener(evt, e => { | |
e.stopImmediatePropagation(); | |
}, { capture: true }) | |
); | |
// Toggle on click (capture-phase) and prevent Logseq from seeing it | |
header.addEventListener('click', e => { | |
e.preventDefault(); | |
e.stopImmediatePropagation(); | |
const expanded = propsElement.classList.toggle('expanded'); | |
header.classList.toggle('collapsed', !expanded); | |
header.classList.toggle('expanded', expanded); | |
header.querySelector('.arrow').textContent = expanded ? '⊖' : '⨁'; | |
propsElement.style.display = expanded ? 'block' : 'none'; | |
}, { capture: true }); | |
// Insert header and start collapsed | |
propsElement.before(header); | |
propsElement.classList.remove('expanded'); | |
}); | |
} | |
// Function to run initialization after a short delay | |
function delayedInit() { | |
setTimeout(initCollapsibleProperties, 100); | |
} | |
// Run on load and when the DOM is ready | |
if (document.readyState === 'loading') { | |
document.addEventListener('DOMContentLoaded', delayedInit); | |
} else { | |
delayedInit(); | |
} | |
// Also run when Logseq's main content is loaded | |
document.addEventListener('logseq:main-loaded', delayedInit); | |
// Watch for changes to the entire document | |
new MutationObserver(mutations => { | |
let shouldUpdate = false; | |
mutations.forEach(mutation => { | |
// Check for added nodes | |
mutation.addedNodes.forEach(node => { | |
if (node.nodeType === Node.ELEMENT_NODE) { | |
// Check if this is a TODO/DONE block with properties | |
const todoBlock = node.querySelector?.('.inline.todo, .inline.done'); | |
if (todoBlock) { | |
const blockContent = todoBlock.closest('.block-content'); | |
if (blockContent) { | |
const hasProperties = blockContent.querySelector('.block-properties:not(.page-properties)'); | |
if (hasProperties && !blockContent.querySelector('.block-properties-header')) { | |
shouldUpdate = true; | |
} | |
} | |
} | |
} | |
}); | |
// Check for attribute changes (for when TODO changes to DONE) | |
if (mutation.type === 'attributes' && | |
(mutation.target.classList.contains('todo') || mutation.target.classList.contains('done'))) { | |
shouldUpdate = true; | |
} | |
}); | |
if (shouldUpdate) { | |
delayedInit(); | |
} | |
}).observe(document.body, { | |
childList: true, | |
subtree: true, | |
attributes: true, | |
attributeFilter: ['class'] | |
}); | |
// Watch for scroll events to handle lazy loading | |
let scrollTimeout; | |
window.addEventListener('scroll', () => { | |
clearTimeout(scrollTimeout); | |
scrollTimeout = setTimeout(delayedInit, 100); | |
}, { passive: true }); | |
// Watch for visibility changes | |
const observer = new IntersectionObserver((entries) => { | |
entries.forEach(entry => { | |
if (entry.isIntersecting) { | |
delayedInit(); | |
} | |
}); | |
}, { | |
root: null, | |
rootMargin: '50px', | |
threshold: 0.1 | |
}); | |
// Observe the main content area | |
const mainContent = document.querySelector('.main-content-container'); | |
if (mainContent) { | |
observer.observe(mainContent); | |
} | |
// Also observe any new blocks that are added | |
new MutationObserver((mutations) => { | |
mutations.forEach(mutation => { | |
mutation.addedNodes.forEach(node => { | |
if (node.nodeType === Node.ELEMENT_NODE) { | |
const blocks = node.querySelectorAll('.ls-block'); | |
blocks.forEach(block => observer.observe(block)); | |
} | |
}); | |
}); | |
}).observe(document.body, { | |
childList: true, | |
subtree: true | |
}); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
query-table:: true | |
query-sort-by:: impact | |
query-sort-desc:: false | |
query-properties:: [:priority :pomodoro :block :urgency :impact :effort :suggestions :page] | |
#+BEGIN_QUERY | |
{:title [:h3 "All (with priorities)"] | |
:query | |
[:find (pull ?b [*]) | |
:where | |
;; only TODO or DOING blocks | |
[?b :block/marker ?marker] | |
[(contains? #{"TODO" "DOING"} ?marker)] | |
;; grab the properties map | |
[?b :block/properties ?props] | |
;; extract numeric priority | |
[(get ?props :priority) ?p] | |
;; filter for 1 ≤ priority ≤ 7 | |
[(>= ?p 1)] | |
[(<= ?p 7)]]} | |
#+END_QUERY |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment