Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Relative anchor ID routing #3

Open
danielnehrig opened this issue Oct 11, 2022 · 13 comments
Open

Relative anchor ID routing #3

danielnehrig opened this issue Oct 11, 2022 · 13 comments

Comments

@danielnehrig
Copy link

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
Copy link
Author

danielnehrig commented Oct 11, 2022

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 lukechu10 changed the title Relative anker ID routing Relative anchor ID routing Oct 11, 2022
@lukechu10
Copy link
Owner

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
Copy link
Author

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

@lukechu10
Copy link
Owner

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
Copy link
Owner

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

@danielnehrig
Copy link
Author

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
Copy link
Author

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
Copy link
Author

@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
Copy link
Owner

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
Copy link
Author

danielnehrig commented Oct 13, 2022

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
Copy link
Owner

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:

mdsycx/mdsycx/src/parser.rs

Lines 104 to 112 in 7fbb72b

events.push(Event::Start(
String::from_utf8(start.name().0.to_vec()).unwrap().into(),
));
for attr in start.html_attributes().with_checks(false).flatten() {
events.push(Event::Attr(
String::from_utf8(attr.key.0.to_vec()).unwrap().into(),
String::from_utf8(attr.value.to_vec()).unwrap().into(),
));
}

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
Copy link
Author

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

@lukechu10
Copy link
Owner

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants