Skip to content

Instantly share code, notes, and snippets.

@KillyMXI
Last active April 3, 2024 19:53
Show Gist options
  • Star 15 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save KillyMXI/cbef8edff6dd55d9e6ea4df66567e9b1 to your computer and use it in GitHub Desktop.
Save KillyMXI/cbef8edff6dd55d9e6ea4df66567e9b1 to your computer and use it in GitHub Desktop.
Obsidian live-preview list threading and highlight

Obsidian live-preview list threading and highlight

Obsidian forum thread: https://forum.obsidian.md/t/plugin-for-bullet-threading/37317/22

Add these CSS files in Appearance settings.

Changelog:

  • 2023-10-02
    • highlight snippet
      • halved the alpha, making highlight more subtle
      • made 6th color a bit more distinct from 5th
    • threading snippet
      • removed accidental dependency on another CSS snippet
      • adjusted offsets to better match indentation lines with the default font size
      • using --list-indent variable to hopefully make the snippet more compatible with styles that alter it
      • providing height calculations that prioritize wrapped text over images (or any other oversized inline content with default vertical alignment) - requires manual edits in 3 places to switch
.HyperMD-list-line-1:hover,
.HyperMD-list-line-1:not(:has(~ .HyperMD-list-line-1 ~ .HyperMD-list-line:hover)):has(~ :is(.HyperMD-list-line-2, .HyperMD-list-line-3, .HyperMD-list-line-4, .HyperMD-list-line-5, .HyperMD-list-line-6):hover) {
background-color: hsl(23, 100%, 45%, 0.05);
}
.HyperMD-list-line-2:hover,
.HyperMD-list-line-2:not(:has(~ .HyperMD-list-line-2 ~ .HyperMD-list-line:hover)):has(~ :is(.HyperMD-list-line-3, .HyperMD-list-line-4, .HyperMD-list-line-5, .HyperMD-list-line-6):hover) {
background-color: hsl(46, 100%, 45%, 0.05);
}
.HyperMD-list-line-3:hover,
.HyperMD-list-line-3:not(:has(~ .HyperMD-list-line-3 ~ .HyperMD-list-line:hover)):has(~ :is(.HyperMD-list-line-4, .HyperMD-list-line-5, .HyperMD-list-line-6):hover) {
background-color: hsl(70, 100%, 45%, 0.05);
}
.HyperMD-list-line-4:hover,
.HyperMD-list-line-4:not(:has(~ .HyperMD-list-line-4 ~ .HyperMD-list-line:hover)):has(~ :is(.HyperMD-list-line-5, .HyperMD-list-line-6):hover) {
background-color: hsl(105, 100%, 45%, 0.05);
}
.HyperMD-list-line-5:hover,
.HyperMD-list-line-5:not(:has(~ .HyperMD-list-line-5 ~ .HyperMD-list-line:hover)):has(~ :is(.HyperMD-list-line-6):hover) {
background-color: hsl(187, 100%, 45%, 0.05);
}
.HyperMD-list-line-6:hover {
background-color: hsl(223, 100%, 55%, 0.05);
}
.HyperMD-list-line-1:not(:has(~ .HyperMD-list-line-1 ~ .HyperMD-list-line:hover)):has(~ :is(.HyperMD-list-line-6, .HyperMD-list-line-5, .HyperMD-list-line-4, .HyperMD-list-line-3, .HyperMD-list-line-2):hover)::after,
.HyperMD-list-line-1:not(:has(~ .HyperMD-list-line-1 ~ .HyperMD-list-line:hover)) ~ .HyperMD-list-line:has(~ .HyperMD-list-line-2:hover, ~ .HyperMD-list-line-2 ~ :is(.HyperMD-list-line-3, .HyperMD-list-line-4, .HyperMD-list-line-5, .HyperMD-list-line-6):hover)::before,
.HyperMD-list-line-2:not(:has(~ .HyperMD-list-line-2 ~ .HyperMD-list-line:hover)):is(:hover, :has(~ :is(.HyperMD-list-line-3, .HyperMD-list-line-4, .HyperMD-list-line-5, .HyperMD-list-line-6):hover))::before {
--list-threading-color: hsl(23, 100%, 45%, 0.3);
--list-threading-offset: calc(5px + 0.15em);
}
.HyperMD-list-line-2:not(:has(~ .HyperMD-list-line-2 ~ .HyperMD-list-line:hover)):has(~ :is(.HyperMD-list-line-6, .HyperMD-list-line-5, .HyperMD-list-line-4, .HyperMD-list-line-3):hover)::after,
.HyperMD-list-line-2:not(:has(~ .HyperMD-list-line-2 ~ .HyperMD-list-line:hover)) ~ .HyperMD-list-line:has(~ .HyperMD-list-line-3:hover, ~ .HyperMD-list-line-3 ~ :is(.HyperMD-list-line-4, .HyperMD-list-line-5, .HyperMD-list-line-6):hover)::before,
.HyperMD-list-line-3:not(:has(~ .HyperMD-list-line-3 ~ .HyperMD-list-line:hover)):is(:hover, :has(~ :is(.HyperMD-list-line-4, .HyperMD-list-line-5, .HyperMD-list-line-6):hover))::before {
--list-threading-color: hsl(46, 100%, 45%, 0.3);
--list-threading-offset: calc(5px + 0.15em + var(--list-indent));
}
.HyperMD-list-line-3:not(:has(~ .HyperMD-list-line-3 ~ .HyperMD-list-line:hover)):has(~ :is(.HyperMD-list-line-6, .HyperMD-list-line-5, .HyperMD-list-line-4):hover)::after,
.HyperMD-list-line-3:not(:has(~ .HyperMD-list-line-3 ~ .HyperMD-list-line:hover)) ~ .HyperMD-list-line:has(~ .HyperMD-list-line-4:hover, ~ .HyperMD-list-line-4 ~ :is(.HyperMD-list-line-5, .HyperMD-list-line-6):hover)::before,
.HyperMD-list-line-4:not(:has(~ .HyperMD-list-line-4 ~ .HyperMD-list-line:hover)):is(:hover, :has(~ :is(.HyperMD-list-line-5, .HyperMD-list-line-6):hover))::before {
--list-threading-color: hsl(70, 100%, 45%, 0.3);
--list-threading-offset: calc(5px + 0.15em + 2 * var(--list-indent));
}
.HyperMD-list-line-4:not(:has(~ .HyperMD-list-line-4 ~ .HyperMD-list-line:hover)):has(~ :is(.HyperMD-list-line-6, .HyperMD-list-line-5):hover)::after,
.HyperMD-list-line-4:not(:has(~ .HyperMD-list-line-4 ~ .HyperMD-list-line:hover)) ~ .HyperMD-list-line:has(~ .HyperMD-list-line-5:hover, ~ .HyperMD-list-line-5 ~ :is(.HyperMD-list-line-6):hover)::before,
.HyperMD-list-line-5:not(:has(~ .HyperMD-list-line-5 ~ .HyperMD-list-line:hover)):is(:hover, :has(~ :is(.HyperMD-list-line-6):hover))::before {
--list-threading-color: hsl(105, 100%, 45%, 0.3);
--list-threading-offset: calc(5px + 0.15em + 3 * var(--list-indent));
}
.HyperMD-list-line-5:not(:has(~ .HyperMD-list-line-5 ~ .HyperMD-list-line:hover)):has(~ :is(.HyperMD-list-line-6):hover)::after,
.HyperMD-list-line-5:not(:has(~ .HyperMD-list-line-5 ~ .HyperMD-list-line:hover)) ~ .HyperMD-list-line:has(~ .HyperMD-list-line-6:hover)::before,
.HyperMD-list-line-6:not(:has(~ .HyperMD-list-line-6 ~ .HyperMD-list-line:hover)):is(:hover)::before {
--list-threading-color: hsl(187, 100%, 45%, 0.3);
--list-threading-offset: calc(4px + 0.15em + 4 * var(--list-indent));
}
/* tails */
.HyperMD-list-line-1:not(:has(~ .HyperMD-list-line-1 ~ .HyperMD-list-line:hover)):has(~ :is(.HyperMD-list-line-6, .HyperMD-list-line-5, .HyperMD-list-line-4, .HyperMD-list-line-3, .HyperMD-list-line-2):hover)::after,
.HyperMD-list-line-2:not(:has(~ .HyperMD-list-line-2 ~ .HyperMD-list-line:hover)):has(~ :is(.HyperMD-list-line-6, .HyperMD-list-line-5, .HyperMD-list-line-4, .HyperMD-list-line-3):hover)::after,
.HyperMD-list-line-3:not(:has(~ .HyperMD-list-line-3 ~ .HyperMD-list-line:hover)):has(~ :is(.HyperMD-list-line-6, .HyperMD-list-line-5, .HyperMD-list-line-4):hover)::after,
.HyperMD-list-line-4:not(:has(~ .HyperMD-list-line-4 ~ .HyperMD-list-line:hover)):has(~ :is(.HyperMD-list-line-6, .HyperMD-list-line-5):hover)::after,
.HyperMD-list-line-5:not(:has(~ .HyperMD-list-line-5 ~ .HyperMD-list-line:hover)):has(~ :is(.HyperMD-list-line-6):hover)::after {
content: "";
position: absolute;
left: var(--list-threading-offset);
bottom: 0;
/* priority to wrapped text */
height: calc(100% - 0.75em);
/* priority to images */
height: 0.8em;
width: var(--size-2-1);
background-color: var(--list-threading-color);
}
.HyperMD-list-line.HyperMD-task-line::after {
/* priority to wrapped text */
max-height: calc(100% - 1.3em);
/* priority to images */
max-height: 0.275em;
}
/* in-between lines */
.HyperMD-list-line-1:not(:has(~ .HyperMD-list-line-1 ~ .HyperMD-list-line:hover)) ~ .HyperMD-list-line:has(~ .HyperMD-list-line-2:hover, ~ .HyperMD-list-line-2 ~ :is(.HyperMD-list-line-6, .HyperMD-list-line-5, .HyperMD-list-line-4, .HyperMD-list-line-3):hover)::before,
.HyperMD-list-line-2:not(:has(~ .HyperMD-list-line-2 ~ .HyperMD-list-line:hover)) ~ .HyperMD-list-line:has(~ .HyperMD-list-line-3:hover, ~ .HyperMD-list-line-3 ~ :is(.HyperMD-list-line-6, .HyperMD-list-line-5, .HyperMD-list-line-4):hover)::before,
.HyperMD-list-line-3:not(:has(~ .HyperMD-list-line-3 ~ .HyperMD-list-line:hover)) ~ .HyperMD-list-line:has(~ .HyperMD-list-line-4:hover, ~ .HyperMD-list-line-4 ~ :is(.HyperMD-list-line-6, .HyperMD-list-line-5):hover)::before,
.HyperMD-list-line-4:not(:has(~ .HyperMD-list-line-4 ~ .HyperMD-list-line:hover)) ~ .HyperMD-list-line:has(~ .HyperMD-list-line-5:hover, ~ .HyperMD-list-line-5 ~ :is(.HyperMD-list-line-6):hover)::before,
.HyperMD-list-line-5:not(:has(~ .HyperMD-list-line-5 ~ .HyperMD-list-line:hover)) ~ .HyperMD-list-line:has(~ .HyperMD-list-line-6:hover)::before {
content: "";
position: absolute;
left: var(--list-threading-offset);
top: 0;
height: 100%;
width: var(--size-2-1);
background-color: var(--list-threading-color);
}
/* elbows */
.HyperMD-list-line-2:not(:has(~ .HyperMD-list-line-2 ~ .HyperMD-list-line:hover)):is(:hover, :has(~ :is(.HyperMD-list-line-6, .HyperMD-list-line-5, .HyperMD-list-line-4, .HyperMD-list-line-3):hover))::before,
.HyperMD-list-line-3:not(:has(~ .HyperMD-list-line-3 ~ .HyperMD-list-line:hover)):is(:hover, :has(~ :is(.HyperMD-list-line-6, .HyperMD-list-line-5, .HyperMD-list-line-4):hover))::before,
.HyperMD-list-line-4:not(:has(~ .HyperMD-list-line-4 ~ .HyperMD-list-line:hover)):is(:hover, :has(~ :is(.HyperMD-list-line-6, .HyperMD-list-line-5):hover))::before,
.HyperMD-list-line-5:not(:has(~ .HyperMD-list-line-5 ~ .HyperMD-list-line:hover)):is(:hover, :has(~ :is(.HyperMD-list-line-6):hover))::before,
.HyperMD-list-line-6:not(:has(~ .HyperMD-list-line-6 ~ .HyperMD-list-line:hover)):is(:hover)::before {
content: "";
position: absolute;
left: var(--list-threading-offset);
width: var(--list-indent);
top: 0;
/* priority to wrapped text */
height: calc(0.75em);
/* priority to images */
height: calc(100% - 0.825em - var(--size-2-1) / 2);
border-bottom-left-radius: var(--radius-m);
border-bottom: var(--size-2-1) solid var(--list-threading-color);
border-left: var(--size-2-1) solid var(--list-threading-color);
}
.HyperMD-list-line.HyperMD-task-line::before {
max-width: calc(var(--list-indent) - 0.35em);
}
@pulkitgoyal56
Copy link

Hey, this looks awesome! Thanks! :)

But it doesn't seem to work like it does in your screenshots.

image

I switched off all the other CSS snippets in my vault and the issue persists.

Do you think there's some problem with my setup?
Perhaps some compatibility issues with another plugin?

@KillyMXI
Copy link
Author

KillyMXI commented Oct 1, 2023

@pulkitgoyal56

I found the reason why you don't see elbows. I used the variable --outline-guideline-width from the CSS snippet for threading in the read mode. It went unnoticed because, apparently, everyone including me uses that snippet along with mine. I will do additional checks and change the gist tomorrow, in order to remove the dependency. Following part from that snippet makes this work, so you can fix it now:

body {
  --outline-guideline-width: var(--size-2-1);
}

Something else affects list indentation for you. I don't know what standard settings can cause this. Tab indent size or Readable line length in the Editor settings doesn't do this for me.

You may try Restricted mode in Community plugins settings to quickly check it without plugins.

Themes - I use Standard, and also tried Minimal - it seems not affecting indentation sizes.

If you know how to use DevTools (Ctrl+Shift+I) - you might be able to find which styles affect the indentation, albeit that might be nontrivial.

@KillyMXI
Copy link
Author

KillyMXI commented Oct 2, 2023

@pulkitgoyal56

I updated the gist.
In case something in your vault changes the --list-indent variable - threading should work fine for you now.

@pulkitgoyal56
Copy link

pulkitgoyal56 commented Oct 2, 2023

@KillyMXI

Yeah, that fixed the elbows. Thanks!

Also for pointing out the indentation issue. It turns out, it's because I am using spaces for indentation (I have the settings Editor > Indent using tabs turned off), and the snippet doesn't seem to work with spaces.

For 3 spaces, it overshoots (see below), and for 4, it undershoots like above.

Is it possible to update the snippet so that it works with spaces as well?


I digress, but on that note, I also investigated this further and found that Tab indent size setting in Obsidian doesn't affect lists, which seem to have a fixed tab size, and their indentation is also unfased by the text font.

If I use the default font, Inter, it looks like this,
image

And if I use any other monospace font, in this case Fira Code (same results with Consolas), it looks like this,
image

Notice (for the second, monospace font case) how the tab size is not an integer number of spaces; it's somewhere between 3 and 4!


The compromise I guess is to just use tabs for indentation, but as a Python programmer it feels violating.

FYI, I am using the default theme. I am new to Obsidian, and have no experience with CSS.

@KillyMXI
Copy link
Author

KillyMXI commented Oct 2, 2023

@pulkitgoyal56

Thanks for investigating and finding the cause.
Yeah, initially I thought to use spaces as well, but somehow realized I'll be fighting windmills sooner or later. This is a case where tabs are more reliable, because the editor is inherently non-monospace, and various live-preview components use it in whatever way they like.
And on top of that, the editor component itself - the one behind live-preview and source mode - produces garbage HTML.
Indentation guide lines are not even shown correctly when you use spaces for indentation.

I'm afraid I can't make this work universally.

  • if you can stick with the same number of spaces - you should be able to edit --list-threading-offset values for all 5 offsets to the indentation values you get on screen;
  • otherwise, accept the tabs.

@pulkitgoyal56
Copy link

pulkitgoyal56 commented Oct 2, 2023

Thanks for the directions; I was able to modify it to get the following with 4 spaces.

image

I changed the following,

  • --list-threading-offset -> calc(7px + <n> * 39.5px)
  • --list-indent -> 39.5px

Looks great! :)

@lasseeboe
Copy link

Very nice! Is it possible to make the threading stick on cursor position instead of mouse position?

@KillyMXI
Copy link
Author

KillyMXI commented Oct 12, 2023

@lasseeboe good question.
And the answer is Yes!

Find and replace with your code editor of choice all :hover with .cm-active, and it seems to work nicely!

( Ctrl+H hotkey is typically used for the replace operation in many editors, please don't try to replace them one by one manually. I hope this is trivial enough, so I don't have to make a separate copy. )

@neuromaancer
Copy link

neuromaancer commented Oct 21, 2023

Hi This is excellent! Thank you very much! However, I have an issue, it works fine if I use the mouse, but I am a Vim user, so I actually use vim motion with vim obsidian plugin. I think this snippet won't allow me to do the same thing with vim cursor. Especially in Vim normal mode.

@pulkitgoyal56
Copy link

pulkitgoyal56 commented Oct 21, 2023

@lasseeboe good question. And the answer is Yes!

Find and replace with your code editor of choice all :hover with .cm-active, and it seems to work nicely!

( Ctrl+H hotkey is typically used for the replace operation in many editors, please don't try to replace them one by one manually. I hope this is trivial enough, so I don't have to make a separate copy. )

I use the Vim plugin as well and it works perfectly with this change.

I actually use both the mouse hover version and the cursor version together.

That setup also mostly works. Although there are cases where it doesn't work perfectly. Like having the cursor and mouse on different items at the same sublist level, the vertical line is not proper. Maybe @KillyMXI, would you suggest any modifications to make it work better in that setup? (It'd also be very nice if these two could be made visually more different, like maybe dotted lines for one and solid lines for the other, but I suppose that requires more changes. Any comments there?)

@KillyMXI
Copy link
Author

Oh gosh.
I was thinking how to make a combined version that would follow either the keyboard cursor or mouse, giving priority to mouse.
Making separate threading for keyboard and mouse might be possible, will try to pull it off without compromises.
Quick answer: With a compromise of losing tails, it is certainly possible - one copy will use ::before, the other copy will use ::after pseudo-element.

Dotted lines may not work very well - each thread is composed of small fragments - there are going to be a lot of visual artifacts.
When there are two threads, I would rather assign each one a constant color regardless of depth. Assuming low alpha, two distinct enough hues may blend well enough to see how they overlap.
Other tools to distinguish them are line offset and thickness.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment