lukechu10 / mdsycx

markdown with Sycamore
https://lukechu.dev/mdsycx/
MIT License
15 stars 1 forks source link

Relative anchor ID routing #3

Open danielnehrig opened 2 years ago

danielnehrig commented 2 years ago

given following scenario: I have a markdown file with a TOC which route to headings in the article / parsed markdown. since most article based pages usually route their blog posts and articles over a url looking something like this example.com/article/some-cool-article-you-should-checkout

now imagine you're inside this article and press the TOC anker tag you'll be redirected to the root page with the id attached like this example.com/#i-pressed-a-toc-link but the desired anker should be example.com/article/some-cool-article-you-should-checkout#i-pressed-a-toc-link while generating a anker tag current path of the url should be used instead of root.

example md link:

- [Intro](#intro)
danielnehrig commented 2 years ago

example: https://dnehrig.com/article/personalized-dev-environment

do you think this may be a change in how anker tag's are generated or do you think it's correct as it is? and we have to control the routing ourself like so [Intro](https://example.com/article/some-article#intro)

lukechu10 commented 2 years ago

I was a bit surprised to see this. I did some searching and I'm afraid this is just how browsers work: https://stackoverflow.com/questions/8108836/make-anchor-links-refer-to-the-current-page-when-using-base

However, in your case, I don't think the <base> tag is really necessary so you might want to consider just removing it.

Also there might be a bug with sycamore-router where anchor tags take you back to the top of the page instead of the desired location. The TOC on the sycamore book website has been broken for quite a while and I never found time to fix it. I don't have an open issue for it so if you encounter this bug as well, please feel free to open a new issue.

danielnehrig commented 2 years ago

i've been doing web for quite a while and neither did I know

lukechu10 commented 2 years ago

Also another thing you could consider instead of getting rid of the <base> tag is overriding the <a> element with your own component that has custom logic for routing.

lukechu10 commented 2 years ago

Perhaps using this: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView

danielnehrig commented 2 years ago

th

Also another thing you could consider instead of getting rid of the <base> tag is overriding the <a> element with your own component that has custom logic for routing.

getting rid of base means not using perseus server or doing a change request on perseus side since the base comes from there https://github.com/framesurge/perseus/blob/9e7363f97cdf0c79928ea892bafcdf5c830a9b4d/packages/perseus/src/server/html_shell.rs#L131

danielnehrig commented 2 years ago

Perhaps using this: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView

i'll update my solution here tomorrow short i use above mentioned with a custom link component that handles scrollinto view with web_sys

danielnehrig commented 2 years ago

@lukechu10 can we add id's to headings ? we take the children value and use it inside the id attr with to_lowercase().replace(" ", "-") what do you think ? you can't scroll into view if you can not properly select the Tag i mean i can build my own component to do that but i think this should be default behavior

lukechu10 commented 2 years ago

Yeah we should definitely add automatic header ids. However, if you want to add ids to headers right now, I believe you can use Heading IDs in the extended Markdown syntax which should be supported.

danielnehrig commented 2 years ago

Yeah we should definitely add automatic header ids. However, if you want to add ids to headers right now, I believe you can use Heading IDs in the extended Markdown syntax which should be supported.

Nice! this works! you want to add the automatic id's or can i do it ? also my solution is like this

#[derive(Prop, FromMd)]
struct LinkProps<'a, G: Html> {
    children: Children<'a, G>,
    t: String,
}

#[component]
fn Link<'a, G: Html>(cx: Scope<'a>, props: LinkProps<'a, G>) -> View<G> {
    let t = create_ref(cx, props.t);

    #[cfg(target_arch = "wasm32")]
    if G::IS_BROWSER {
        let on_click = |e: web_sys::Event| {
            e.prevent_default();
            let window = web_sys::window().expect("Window not found");
            let el = window
                .document()
                .expect("Document not found")
                .get_element_by_id(&t.to_lowercase().replace(' ', "-"))
                .expect("Element not found");
            el.scroll_into_view();
        };

        #[cfg(target_arch = "wasm32")]
        return view! { cx,
            a(href=format!("#{}", t), rel="external", on:click=on_click) {
                (*t)
            }
        };
    }

    view! { cx,
        a(href=format!("#{}", t), rel="external") {
            (*t)
        }
    }
}
- <Link t="Intro" />
## Intro {#intro}

rel="external" is important it'll disable sycamore routing without it it won't work

lukechu10 commented 2 years ago

Nice! this works! you want to add the automatic id's or can i do it ?

If you want to do it, go ahead, although you mind find it a bit more involved than it seems like.

We probably want this to be done in the the parsing phase rather than just adding a custom component override because the latter solution would prevent users from overriding the header tags themselves. Also there isn't an API for getting innerText on SSR yet so we this would only work on client side.

A somewhat tricky part would be to recursively get the child text based on the html AST. The xml parser is a pull parser so instead of building up a full AST, we actually get events. I think the simplest way of extracting the inner text would be after this block here: https://github.com/lukechu10/mdsycx/blob/7fbb72b1c0930f7ec9f7ae118d58a2618852b93a/mdsycx/src/parser.rs#L104-L112

instead of just returning, we could instead keep on matching events until we find the corresponding End event. Until then, we keep a buffer to store the text events. The emit event function would also probably need to be extracted so that you could call it recursively when matching events.

danielnehrig commented 2 years ago

it's good enough when it works only on client side since the user interaction is client side anyways

lukechu10 commented 2 years ago

it's good enough when it works only on client side since the user interaction is client side anyways

Yes but you would want to have the ids ssr-ed as well. When hydrating, the client might suppose that the id attribute was already there and so not do anything.