squidfunk / mkdocs-material

Documentation that simply works
https://squidfunk.github.io/mkdocs-material/
MIT License
20.1k stars 3.47k forks source link

Search: improve search highlighting in hidden components (tabs, details, ...) #4125

Open rfay opened 2 years ago

rfay commented 2 years ago

Contribution guidelines

I've found a bug and checked that ...

Description

I've switched our docs to lots of use tabs for alternate approaches in alternate environments.

However, when you search for something that might be in a tab, and find it, and click on the item, you don't land on useful or even discoverable information. This is easy to demonstrate with the mkdocs-material docs. If you search for an item that is not under the default tab, you'll get to the right section, but your target content is nowhere to be found.

Expected behaviour

When you do a search and click through, you should land somewhere that you can find the content you searched for.

Actual behaviour

You land on a page with a tab that doesn't show the content you were looking for at all.

Steps to reproduce

  1. Visit https://squidfunk.github.io/mkdocs-material/
  2. In the search box, type include_search_page - You'll get a reference to minimal configuration
  3. Click the link. You land there... but "include_search_page" is nowhere to be found. That's because it's hidden under the "git" tab, so you'd have to go clicking on all the tabs to find it, and scan all the results.

Package versions

This example uses your docs, so I'm sure you know all the answers.

Configuration

Your docs.

System information

Your docs. All OS

squidfunk commented 2 years ago

Thanks for reporting. This is a known issue, which unfortunately is very hard to fix, because there are dozens of special cases. For example, what happens if there's a match in the 2nd tab, as well as in the 3rd tab? Should we focus the 2nd or the 3rd? IMHO there's no canonical way of safely determining what is the relevant tab (or element) to show.

What I could imagine is "tagging" triggers for hidden content with the highlight color, to denote that there's a match hidden inside the content hosted by the trigger. Those include:

All those cases would need separate handling. This would mean a fundamental refactor of search highlighting. I'm curious: how are other documentation solutions solving the problem at hand?

rfay commented 2 years ago

One option would be having the tabs have useful named anchors, and search use those. That would also make links be more useful.

For example, the URL https://squidfunk.github.io/mkdocs-material/getting-started/#__tabbed_2_2 is not very descriptive and very error prone if used for sharing. If search could use a good tab URL for indexing maybe that would help.

Unfortunately, although the tabs I introduced into ddev docs massively improve the flow of the docs (huge, thanks!) I'm quite concerned about the usability of the search now, and IMO search is the main way people use those docs.

squidfunk commented 2 years ago

One option would be having the tabs have useful named anchors, and search use those. That would also make links be more useful.

I agree. You can take this to Python Markdown Extensions, which is used to generate the tab markup. The Attribute Lists extensions could be used for those purposes, but it's currently not supported by Tabbed. Note however, that search does not index tabs as separate items. Only pages and sections are indexed, so that doesn't help here.

Unfortunately, although the tabs I introduced into ddev docs massively improve the flow of the docs (huge, thanks!) I'm quite concerned about the usability of the search now, and IMO search is the main way people use those docs.

A project of this size is an ongoing effort. Once you introduce new features, all sorts of special cases pop up. I'm going to revisit search highlighting at some point, but I currently can't put an estimate when this will be the case. Maybe later this year. IMHO, adding visual indicators as noted in my last comment is the way to go, but maybe I find a better way that works for more (best: all) components than just tabs when prototyping a solution.

I'll leave this issue open and give it a more general title.

rfay commented 2 years ago

As an open-source project maintainer, of course, I completely understand. However, this seems hugely important for even your own docs, so I hope it can get prioritized. Thanks!

squidfunk commented 2 years ago

Honestly, it's been a long time like this, so it's certainly not urgent. There are other features on the roadmap that provide more value than improving search highlighting. Nonetheless, I agree, that it would be a great addition, though.

If you need it now, I'm available for contract work.

rfay commented 2 years ago

The DDEV project has some sponsorship funds accumulated and can sponsor something, of course already sponsoring you but at a low level. This seems super-critical to nearly all users of mkdocs-material. If you can suggest what it would take to prioritize this, I think there is potential.

squidfunk commented 2 years ago

Thanks for your offering. As said, I could temporarily exempt myself from all priorities for contract work, but this would be pay-per-hour. If you want to discuss rates, you can reach me at martin.donath@squidfunk.com.

rfay commented 2 years ago

This really isn't just a problem with search. Navigation items on left-hand nav bar also don't work if they're not in the primary tab. So you can see all the navigation items on the left, but if you click on them, and they happen to be inside a tab, nothing happens. I imagine this is the same set of problems, right?

Example: https://ddev.readthedocs.io/en/latest/users/install/docker-installation/

Click on Docker Desktop for Windows. Nothing happens, because it's under the "Windows" tab.

squidfunk commented 2 years ago

Known problem. Related:

cbjoldham commented 2 years ago

Just a note that we found this scenario while searching for content in Admonishment blocks that were folded by default, with an example (attached) and discussion on Gitter mkdocs-material-folded-admonition-search.zip

I like the concept of at least highlighting the visible handle of hidden content as a call to action - we have a similar solution in the product I help maintain and that seems to work well with users.

rfay commented 2 years ago

I'd be open to suggestions about restructuring docs. It sounds like tabs with any significant content are not going to work properly with either search or nav bar. I'm thinking that although people initially love the tabs because of the improvement in readability, they probably have to be restructured (again) as traditional content, or neither nav nor search works with them.

squidfunk commented 2 years ago

I'd be open to suggestions about restructuring docs. It sounds like tabs with any significant content are not going to work properly with either search or nav bar. I'm thinking that although people initially love the tabs because of the improvement in readability, they probably have to be restructured (again) as traditional content, or neither nav nor search works with them.

I'd be curious to learn how other docs solutions solve this very problem. If you're open to investing some time into research, it would make my work on the problem much easier!

mikesnoeren commented 2 years ago

I made a fix for DDEV that opens the tab that contains the link you want to navigate to, if it helps anyone, feel free to use the code below:

const fixTabs = () => {
    // Get current hash
    let currentHash = window.location.href.split('#')[1];
    if (!currentHash) return

    // Get element associated with hash
    let hashElement = document.getElementById(currentHash);
    if (!hashElement) return

    // Detect if element is located in a tab
    let parentElement = hashElement.closest('.tabbed-block');
    if (!parentElement) return

    // Get all tab labels
    let allTabs = hashElement.closest('.tabbed-set').querySelector('.tabbed-labels').children;
    if (!allTabs) return

    // get index of tab to click on
    let index = [...parentElement.parentNode.children].indexOf(parentElement)

    // Simulate mouse click click on our tab label
    allTabs[index].click();

    // If our tab is nested within another tab, open parent tab..
    if (allTabs[0].getAttribute('for').startsWith('__tabbed_2')) {
        // Get outer tabs
        let outerTabs = document.querySelector('.tabbed-labels').children

        // Get outer tab block
        let outerParent = allTabs[0].closest('.tabbed-block');

        // Get outer tab index
        let outerIndex = [...outerParent.parentNode.children].indexOf(outerParent)

        // Active outer tab
        outerTabs[outerIndex].click();
    }

    if (allTabs[0].getAttribute('for').startsWith('__tabbed_3')) {
        console.log('While 1 nested tab is debatable 2 should be avoided as it is definitely not user friendly.')
    }

    // scroll to hash
    hashElement.scrollIntoView();
};

// Fires after initial page load
window.addEventListener('load', fixTabs, false);

// Fires whenever the hash changes
window.addEventListener('hashchange', fixTabs, false);

Things to keep in mind:

feasgal commented 1 year ago

I made collapsed admonishments (<details> elements) open if they contain search results, but I gave up and used jQuery to do it (I know, heresy).

TL;DR:

  1. Include jQuery by your favorite method. I used main.html to override {% block libs %}{% endblock %} and add the library.
  2. Define your extra_javascript location in mkdocs.yml.
  3. Open <details> elements that contain a highlighted search result by including this in extra.js:

    $(window).on("load", function () {
      $( "mark[data-md-highlight]" ).parents( "details" ).attr( "open", "" );
    });

More info: I had to bind the function to load to make sure my code ran after the code that wraps the search results in <mark></mark>. Otherwise there's nothing for the getter to get or the setter to set.

Hope this helps someone!

squidfunk commented 1 year ago

I've built a prototype for improved search result navigation on the page. It should solve the following problems:

What's still missing is a little indicator on tabs and details, showing that there are results inside the container, if the user doesn't use the stepping functionality. Additionally, the marker offset (e.g. 10/55) must be bound to the scroll offset and update automatically, so the steppers don't get out of sync.

https://user-images.githubusercontent.com/932156/211209369-ce4df824-9f17-4a6b-888b-57e3183bd3fd.mp4

This is only a prototype, any feedback is appreciated.

rfay commented 1 year ago

Looks like some great progress, looking forward to trying it out. /cc @mattstein.

cbjoldham commented 1 year ago

Apologies for delayed feedback Martin.

I like the functionality but it took me a moment to notice the step controls. Possibly that is because playing back the screen capture the player controls get in the way a bit, but I wonder if it's because the search that would start this flow is at the top so my natural attention will be near the header and the search box?

rfay commented 1 year ago

Oh, I like what I see in the screenshot, but would like to try it out. It hasn't made it into current versions has it?

squidfunk commented 1 year ago

Not yet, but we're working on it. Search will receive a major update later this year and better result navigation will be a part of it. What can be seen in the video is only a first prototype.

rfay commented 1 year ago

Thanks for the hard work on this!

squidfunk commented 1 year ago

Yeah, my pleasure! As an addendum why implementing something like this is tricky: there are already several places in the application where "jump to X" behavior is handled, mostly through anchors. With more and more features being linkable (with code line ranges being the latest cool kid on the block) we need to consolidate partially different implementations.

As a particularly pathological case, take the following nested constructs:

Tab 1
Tab 2
  Collapsed Details
    Tab 1
    Tab 2
    Tab 3
      Code annotation
        Code line range
          # Target anchor or search result

The application needs to detect the path of the targeted anchor or search result and open/expand all constructs that are on the corresponding activation path. In that case, the following needs to be done automatically:

  1. Switch to Tab 2
  2. Expand Details
  3. Switch to Tab 3
  4. Open code annotation
  5. Jump to line range

Those paths should automatically activate when paginating through search results. Additionally, each of the constructs that toggle hidden content need additional states displaying that a search result is hidden behind them.

rfay commented 1 year ago

We have lots of pathological cases in https://ddev.readthedocs.io/en/latest/

rfay commented 10 months ago

Remaining interested in this, thanks!

squidfunk commented 10 months ago

All open change requests will eventually be implemented. Unfortunately, we are a very small team and have to prioritize.

rfay commented 10 months ago

Totally understood. We're in the same world :) Thanks for keeping it open then!

SamirSaidani commented 1 month ago

Thanks for reporting. This is a known issue, which unfortunately is very hard to fix, because there are dozens of special cases. For example, what happens if there's a match in the 2nd tab, as well as in the 3rd tab? Should we focus the 2nd or the 3rd? IMHO there's no canonical way of safely determining what is the relevant tab (or element) to show.

Hi,

I'm new to MkDocs and testing it for our governance documentation. We use tabs extensively for readability (e.g., one tab for department overview, another for projects, another for policies).

I've bumped into this issue: when searching, results show matches from both the 2nd and 3rd tabs. It would be helpful if search results focused on the tab currently selected by the user in the search box, as that seems the canonical way to determine the relevant tab. Additionally, nested tabs are less common, so it would be great to solve this issue with non-nested tabs at least.

squidfunk commented 1 month ago

We're still actively considering this, but we currently have no estimate when it will happen. The reason is that we're currently working heavily on bigger picture topics in the background. Once that's done, we'll come back to implement the open feature requests.