naturalcrit / homebrewery

Create authentic looking D&D homebrews using only markdown
https://homebrewery.naturalcrit.com
MIT License
1.05k stars 319 forks source link

Navigate via Headings overview #2840

Open ericscheid opened 1 year ago

ericscheid commented 1 year ago

Your idea:

In Overleaf and other editors, the editor maintains a nested list of headings in a sidebar. This overview of headings can be used to navigate the editor pane. The headings outline can be collapsed or expanded as desired. The headings overview could also be optionally collapsed/hidden.

Overleaf headings sidebar

Clicking a heading in the heading-overview would scroll the editor pane (not the preview pane).

This could make navigating a large brew much easier.

ericscheid commented 1 year ago

Note that PPR isn't involved here, in that the headings-outline would be used to scroll the editor pane, not the preview pane.

A brute force but simple approach would be to refresh the overview entirely on every keystroke. There are smarter approaches (e.g. only update the headings in the region of the edited line number) that can be used if the brute-simple approach turns out to be impacting on editing performance.

ericscheid commented 1 year ago

There is a risk of some false-positives sneaking into the Headings Outline.

Consider this brew source:

## heading two
blah blah text

<!-- 
### heading three
yada yadas
 -->

### heading four
lorem ipsum

A Headings Outline which is built via brew.text.matches(/^#+ .+$/g) won't realise that "heading three" has been commented out and thus will appear in the outline.

I would consider this to be an acceptable false-positive.

G-Ambatte commented 1 year ago

Without thinking about it too hard... We could build this into the divider bar, which already has the functions to move the editor and the rendered brew. This would then allow us to parse the brew.text for the positions of /^#+ .+$/g and build a map of each headings location (by calculating the number of preceding ^//page$) and utilize the existing movement functions to jump one or both views to the relevant heading's page location.

dbolack-ab commented 6 months ago

This seems like an idea feature if/when a pop-out editor is available. I think it might use up too much screen real estate. Maybe a roll-out/mouseover ?

5e-Cleric commented 6 months ago

if #3150 got fixed(literally one line) we could insert a fixed positioning div and put in there any heading as we do in the ToC as links. Very easy.

5e-Cleric commented 1 month ago

Nevermind my last comment, we can insert the element inside the frame besides the brewRenderer component, fill it with a list of headers obtained from parsing the virtual DOM, and use that. We could go a step further and give a ref to every heading.

Now, in #3506, i have some functions to find what page any element is in via its id, and scroll to it. We could use this to scroll to the element, without the need to scroll the editor too. I'd just need to have that merged and the list of headings when rendering.

mouse0270 commented 1 month ago

I wanted this type of sidebar for the share view of my PDF. Users have complained that its fairly annoying to have to scroll back up to get to the table of contents and find what they want and then scroll down.

I was trying to figure out how to make it so that my Table of Contents page was just fixed the side of the page when a user was viewing the page and then listing all of the links going down instead of being in columns... However, it appears a fixed div inside the container just sticks at the top when a user scrolls down.

It would be nice to basically have a special div that gets rendered on the share link that takes all .toc divs and appends them inside of it. Causing something that resembles the google docs sidebar like the image attached. I understand the need to make something that is either automated of more fancy, but I worry thats going to create headings or unexpected format, when I would rather only have a list of links using my .toc

image

ericscheid commented 1 month ago

However, it appears a fixed div inside the container just sticks at the top when a user scrolls down.

I thought position: sticky might be the trick .. but this explains that any sticky element only sticks inside it's parent element (which would be the singular <div class="page"> it's defined within).

This means it would need to be emitted by the brew render engine, not as part of the rendered brew. So, as a sibling element to <div class="popups"> and <div class="pages">

image
5e-Cleric commented 1 month ago

However, it appears a fixed div inside the container just sticks at the top when a user scrolls down.

I thought position: sticky might be the trick .. but this explains that any sticky element only sticks inside it's parent element (which would be the singular <div class="page"> it's defined within).

This means it would need to be emitted by the brew render engine, not as part of the rendered brew. So, as a sibling element to <div class="popups"> and <div class="pages">

As per one of my lasts comments, position sticky will not work inside the brewRenderer element, because we have will-change:transform permanently set. This will not change in the future (see https://github.com/naturalcrit/homebrewery/issues/3150)

(position sticky toggles the element between static and fixed positioning)

As i also said the fix for this is for the element to be a sibling of the brewRenderer element.

ericscheid commented 1 month ago

Apologies, it wasn't clear whether you used position: absolute or position: sticky.

Yes, there are two requirements:

  1. must be in a scope where will-change:transform won't block it
  2. must be a sibling to the scope that will scroll (either brewRenderer or pages)
5e-Cleric commented 1 month ago

Apologies, it wasn't clear whether you used position: absolute or position: sticky.

Yes, there are two requirements:

  1. must be in a scope where will-change:transform won't block it
  2. must be a sibling to the scope that will scroll (either brewRenderer or pages)

I don't really see why is 2. important, we can pass the scrolling info via props or context state, we will be scrolling via js anyway, please clarify if i'm missing something important here.

dbolack-ab commented 2 weeks ago

3262 assists here.