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

Render images as links? #462

Closed
IronOxidizer opened this issue Aug 15, 2020 · 7 comments
Closed

Render images as links? #462

IronOxidizer opened this issue Aug 15, 2020 · 7 comments

Comments

@IronOxidizer
Copy link

Is it possible to render images as links? For my use case it would be ideal if users didn't need to load large images upon loading other users' markdown.

@marcusklaas
Copy link
Collaborator

marcusklaas commented Aug 15, 2020

Certainly. The most straightforward way to do this is to replace image events with link events before passing them onto the renderer.

For example, this will work:

    let parser = Parser::new(markdown_input)
        .map(|event| match event {
            Event::Start(Tag::Image(linktype, url, title)) =>
                Event::Start(Tag::Link(linktype, url, title)),
            Event::End(Tag::Image(linktype, url, title)) =>
                Event::End(Tag::Link(linktype, url, title)),
            _ => event,
        });

    let mut html_output = String::new();
    html::push_html(&mut html_output, parser);

Note that this will create empty links for images without titles, so you may want to include a special case for them by expanding the match like this:

        match event {
            Event::Start(Tag::Image(linktype, url, title)) if title.is_empty() =>
                Event::Start(Tag::Link(linktype, url.clone(), url)),
            Event::Start(Tag::Image(linktype, url, title)) =>
                Event::Start(Tag::Link(linktype, url, title)),
            Event::End(Tag::Image(linktype, url, title)) if title.is_empty() =>
                Event::End(Tag::Link(linktype, url.clone(), url)),
            Event::End(Tag::Image(linktype, url, title)) =>
                Event::End(Tag::Link(linktype, url, title)),
            _ => event,
        }

Hope this answers your question. Feel free to reopen if it doesn't :-)

@IronOxidizer
Copy link
Author

Thanks! This is exactly what I was looking for.

@IronOxidizer
Copy link
Author

IronOxidizer commented Aug 15, 2020

@marcusklaas One more thing. I'm assuming initializing the parser like this has a performance cost. Is there a way to reuse the parser for new text with the events configured? My use case has me creating the parser as much as thousands of times per page and the performance costs can really add up.

https://github.com/IronOxidizer/lemmy-lite/blob/master/src/templates.rs#L610

@marcusklaas
Copy link
Collaborator

That's an interesting question. It isn't currently possible, but it may be worthwhile to 'reset' the parser so that its resources (read: allocations) can be reused. We took care to do minimal allocations, but if you're doing thousands of very small parses, it may make a measurable difference. I encourage you to open a separate issue for this so we can investigate this!

@IronOxidizer
Copy link
Author

@marcusklaas I did some tests and it appears that the image-link substitution is not working. The images are no longer rendered but no text is displayed as well:

Here's an example:
https://lemmylite.crabdance.com/dev.lemmy.ml/post/38946/comment/15308

For reference, here's the original markdown:

`zsh` with [starship](https://starship.rs/guide/) as my prompt. on my main PC it looks like this:

![](https://dev.lemmy.ml/pictrs/image/9NzVJ1To0E.png)

sort of not related because it's shell-agnostic, but I really like starship because it has a bunch of cool plugins. e.g. at work, I have k8s and AWS plugins enabled, so it looks like this:

![](https://dev.lemmy.ml/pictrs/image/w4G5UHj6eL.png)

It doesn't appear to be an issue with the parser as printing the URL on every image tag instance correctly prints the URL:

https://dev.lemmy.ml/pictrs/image/9NzVJ1To0E.png
https://dev.lemmy.ml/pictrs/image/w4G5UHj6eL.png

Curiously, this appears to only be a problem when there is no title which leads me to believe that the lack of text/link is possibly a result of a lifetime issue however, I'm not sure.

Here's an example of a working image link, only because it has a title:

http://lemmylite.crabdance.com/dev.lemmy.ml/post/39163

And its markdown:

![test2c](https://live.staticflickr.com/3699/13157377715_b0630526c0_b.jpg)

@marcusklaas
Copy link
Collaborator

Ah yes, my bad. Link contents are actually separate events. I forgot about that. You'd have to insert your own events into the event stream. Unfortunately, this isn't possible using the built-in iterator functions (I think!). We would need a custom iterator.

Something like this seems to work:

use pulldown_cmark::{html, Options, Parser, Event, Tag, CowStr};

struct ImageSwapper<'a, I> {
    iter: I,
    image_title: Option<CowStr<'a>>,
}

impl<'a, I> ImageSwapper<'a, I> {
    fn new(iter: I) -> Self {
        ImageSwapper {
            iter: iter,
            image_title: None,
        }
    }
}

impl<'a, I> Iterator for ImageSwapper<'a, I>
    where I: ::std::iter::Iterator<Item = Event<'a>>
{
    type Item = Event<'a>;

    fn next(&mut self) -> Option<Self::Item> {
        let mut title = None;
        ::std::mem::swap(&mut self.image_title, &mut title);

        match title {
            None => self.iter.next().map(|event| match event {
                Event::Start(Tag::Image(linktype, url, title)) if title.is_empty() => {
                    self.image_title = Some(url.clone());
                    Event::Start(Tag::Link(linktype, url, title))
                }
                Event::Start(Tag::Image(linktype, url, title)) => {
                    self.image_title = Some(title.clone());
                    Event::Start(Tag::Link(linktype, url, title))
                }
                Event::End(Tag::Image(linktype, url, title)) =>
                    Event::End(Tag::Link(linktype, url, title)),
                _ => event,
            }),
            Some(title) => {
                self.image_title = None;
                Some(Event::Text(title))
            }
        }
    }
}

fn main() {
    let markdown_input: &str = "![](https://dev.lemmy.ml/pictrs/image/9NzVJ1To0E.png)";
    println!("Parsing the following markdown string:\n{}", markdown_input);

    // Set up options and parser. Strikethroughs are not part of the CommonMark standard
    // and we therefore must enable it explicitly.
    let mut options = Options::empty();
    options.insert(Options::ENABLE_STRIKETHROUGH);
    let mut parser = ImageSwapper::new(Parser::new(markdown_input));

    // Write to String buffer.
    let mut html_output: String = String::with_capacity(markdown_input.len() * 3 / 2);
    html::push_html(&mut html_output, &mut parser);

    // Write result to stdout.
    println!("\nHTML output:\n{}", &html_output);
}

This produces the following:

Parsing the following markdown string:
![](https://dev.lemmy.ml/pictrs/image/9NzVJ1To0E.png)

HTML output:
<p><a href="https://dev.lemmy.ml/pictrs/image/9NzVJ1To0E.png">https://dev.lemmy.ml/pictrs/image/9NzVJ1To0E.png</a></p>

which looks about right.

@IronOxidizer
Copy link
Author

That works perfectly. Thanks you very much!

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