Ex_doc: Reference Popovers

Created on 8 Feb 2019  路  15Comments  路  Source: elixir-lang/ex_doc

I'd like to propose to introduce popovers like below. This would be a quick way to see related docs without leaving the current page.

Mockups:

screenshot 2019-02-08 at 14 16 40

screenshot 2019-02-08 at 14 17 40

One thing to keep in mind is local references in the popover will usually be _remote_ as far as the current page is concerned. Also, we won't support nested popovers.

Implementation-wise I can think of two options:

  1. Pre-render them
  2. Load popover on demand

I think it'd be nice to pre-render them so that they can work offline, but I'm concerned about resulting docs size and making parsing more complicated.

Thoughts?

Elixir HTML JavaScript Feature

All 15 comments

I like this a lot but I am concerned about the implementation.

  1. Pre-rendering would increase the page size considerably.

  2. Load popover on demand likely requires AJAX?

Maybe a compromise is to show only the signature and first paragraph. We can include the signatures and short description in the sidebarNodes JS var.

Thoughts?

I think signature + first paragraph is a very good compromise 馃憤

I like this a lot but I am concerned about the implementation.

This would actually be a great use-case for unpoly actually, it would use ajax but can cache it, precache it if wanted, only one request even with repeated loads of the popup, etc... etc... ^.^

We avoid anything with AJAX because we want the docs to work from disk too.

Ah true, in that case it would fall back to a full page redirect then, which would still work but is not what the user expects... In that case I'm unsure of a better way than just embedding snippets of other files into the file referencing the other file, which would definitely balloon the size... :-/

Although... keeping the page snippets in javascript and saving that out to another file would work... Could just generate the docs as normal, referencing a not-yet-existing javascript file at root of the project, gathering all references to other parts, then do another pass over to generate the snippets and save them all to a single javascript file. It would potentially be sizeable in some project but it would de-dup references across all files and be only a single download regardless. If it's a simple file of javascript strings (of the html to output) then it would load super fast unlike JS frameworks and can be used very simply to just write html on a page where and when necessary.

Ok, I have an idea on how to make this work without increasing the file size. The pop-over can actually be an iframe, that is going to link to a specific page with a query_string that says ?focused=true. When focused is present, we will use JS+CSS to hide everything in the page except the module docs or a given function/type/callback if an anchor # is given in the URL.

The nice thing about this approach is that we can make it work in the future for docs across projects. However for now we should only support in the current project. Once enough projects have been running on latest ExDoc, we can enable the cross-docs one.

I've starting experimenting with this idea a bit. I've prepared a rough prototype of the feature (based on the @josevalim's iframe idea) just to get a feeling for what will be needed, potential problems etc.

I've uploaded a demo (very rough draft, for now handles simple functions only :) )

Hover over a local reference, i.e. will trigger the popover.


(^ transition is not really visible, because of the low FPS :) )

Some thoughts:

  • I think the iFrame + focus approach works really well!
  • I think that wikipedia-style popover is a way to go. By that I mean small animation on fadeIn/fadeOut + short delay before showing.

  • A small delay before popover show is needed (because the iframe needs a moment to load the content and apply the focus styles).

    • In the prototype it's a simple timeout, but in the final version the iframe should communicate to the parent (via postMessage) when it's ready and then start the fadeIn animation).
    • Sending a message to the iframe's parent will also solve the cross-project compatibility issue. If another project does not support popovers/focus mode it will never send us a message - and the popover will not appear.
  • Small delay also prevents pop-ups from appearing when we are just scrolling, moving the cursor around etc.

  • I went with a static height, but it'll need to be dynamic - the signatures can be quite long, and paragraph length varies between entries.

  • Popovers are simple to code, no external deps needed.

This looks awesome! :heart:

Sending a message to the iframe's parent will also solve the cross-project compatibility issue.

Great idea!

How do you currently detect which URLs you should try the pop-over? Are you doing it on all of them or are you checking for relative links or something of sorts? Is there something we should do from the Elixir side when generating the HTML?

Thanks!

How do you currently detect which URLs you should try the pop-over? Are you doing it on all of them or are you checking for relative links or something of sorts? Is there something we should do from the Elixir side when generating the HTML?

For the prototype I've not implemented any detection - right now it just grabs all a.no-uderlines. :)

When it comes to detecting "popoverable" links I think we don't need anything special. I assume external projects will always be docs hosted on hex.pm. Then I think it will be enough to:
1) Grab all popoverable elements with a jQuery CSS selector
2) On hover, check (via a simple regexp) if the link is on hex.pm or local

One more thing I forgot to ask about - should popoverable be displayed on mobile?

I have some ideas how tooltips might look on smaller screens, but at the same time I think they are not that useful on mobile devices (with no hover and limited space). In the demo I've decided to disable them on smaller screens.

When it comes to detecting "popoverable" links I think we don't need anything special. I assume external projects will always be docs hosted on hex.pm.

Sounds good. Note the .no-underline thing is added on JS, so feel free to rename it to something more meaningful or attach the event in the same place (if you don't do it yet):

function fixLinks () {
  CONTENT.find('a').has('code').addClass('no-underline')
  CONTENT.find('a').has('img').addClass('no-underline')
}

One more thing I forgot to ask about - should popoverable be displayed on mobile?

I think it is fine to have it disabled on mobile for now.

Looks great and I agree with all decisions!

When loading data into iframe, for local links do we need to make another request or there's an easy way to optimize it? For external links, if we hover over the same link twice it currently sends 2 requests, right? Do you think it's worth optimising, e.g. to limit number of requests, or should we just let browser do it's thing?

Btw, Fastly sits in front of all hexdocs assets, if there're any improvements we can make there, let us know. Here's example response:

curl --head https://hexdocs.pm/elixir/master/Kernel.html
HTTP/2 200
x-guploader-uploadid: AEnB2UopqmEJzG3bjXjVRSgNF9g7_Y6eCP0LTnVg1vm6kgewHEpvtQmMsFNJNidQHaD48DJWmR9RuApkrTkyGrNLb8jMmifAKg
cache-control: public,max-age=3600
expires: Mon, 01 Apr 2019 22:56:07 GMT
last-modified: Mon, 01 Apr 2019 12:20:59 GMT
etag: "35275ea73ab6e746b9bfd2280f4c58ae"
x-amz-meta-goog-reserved-file-mtime: 1554121234
x-amz-meta-surrogate-control: public,max-age=604800
x-amz-meta-surrogate-key: docspage/elixir/master
content-type: text/html
x-goog-hash: crc32c=LdOGMA==
x-goog-hash: md5=NSdepzq250a5v9IoD0xYrg==
x-goog-storage-class: REGIONAL
server: UploadServer
via: 1.1 varnish
strict-transport-security: max-age=31536000
accept-ranges: bytes
date: Mon, 01 Apr 2019 21:56:21 GMT
via: 1.1 varnish
age: 14
x-served-by: cache-iad2136-IAD, cache-fra19123-FRA
x-cache: MISS, HIT
x-cache-hits: 0, 1
x-timer: S1554155782.694739,VS0,VE1
vary: Accept-Encoding
content-length: 479334

should we just let browser do it's thing?

That's my vote :D especially since both the browser and fast.ly are meant to cache it!

I've cleaned up the code, added specs, improved UX, and handled a bunch of edge-cases.

I've already opened the PR when I've described the changes more extensively - it's available at https://github.com/elixir-lang/ex_doc/pull/1025 :)

Closing in favour of the PR!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ericmj picture ericmj  路  8Comments

njonsson picture njonsson  路  11Comments

eksperimental picture eksperimental  路  11Comments

josevalim picture josevalim  路  3Comments

aswinmohanme picture aswinmohanme  路  5Comments