futurepress / epub.js

Enhanced eBooks in the browser.
http://futurepress.org
Other
6.48k stars 1.11k forks source link

How to Sync TOC with current location #1259

Open gobookji2 opened 2 years ago

gobookji2 commented 2 years ago

Hello All - I'm new to epub.js and was looking to recreate the functionality that exists in the "Spreads Demo" at https://futurepress.github.io/epub.js/examples/spreads.html where the Table of Contents selection list gets updated when use navigates through pages.

In my implementation I am reading in the Moby Dick archived epub that I downloaded from https://www.gutenberg.org/.

I am using the same code to build the TOC that is present in the demo:

    book.loaded.navigation.then(function(toc) {
        var $select = document.getElementById("toc");
        var docfrag = document.createDocumentFragment();

        toc.forEach(function(chapter) {
            var option = document.createElement("option");
            option.textContent = chapter.label;
            option.setAttribute("ref", chapter.href);

            docfrag.appendChild(option);
        });

        $select.appendChild(docfrag);

        $select.onchange = function(){
                var index = $select.selectedIndex;
                var url = $select.options[index].getAttribute("ref");
                rendition.display(url);
                return false;
        };
   }); 

This produces

In the Gutenberg version of the epub, each chapter is not its own html file. Multiple chapters are contained in each html file. As such, instead of implementing the logic to update the TOC in rendition.on("rendered", function(section){...}), I am trying to implemented the logic in rendition.on("relocated", function(location){...} ).

From the location variable, I can get the 'start' and 'end' location CFIs, but I don't understand how map the TOC entries to the CFIs.

What would the code look like in my rendition.on("relocated", function(location){...} ) function. Any help would be greatly appreciated

gobookji2 commented 2 years ago

I came across this answer from johnfactotum at https://github.com/futurepress/epub.js/issues/986.

Upon first navigation, I looped over the TOC hrefs and called the getCfiFromHref asynchronous, building an associative array mapping the returned cfi to the toc href.

Then in gRendition.on("relocated", function(location){..} I used location.start.cfi and location.end.cfi to see if an entry existed for either in my associate array. Unfortunately there were always not matched as I navigated through the epub by clicking the right and left arrows.

I am I not getting the current location correct? Any greatly appeciated

anwersolangi commented 2 years ago

Any solution so far? I'm also stuck at the same problem?

imPrafull commented 1 year ago

If anyone is still looking a solution for this, the following solution works for me: I store all the chapters in an array and on location change, I compare the href of the current location with all the chapters and the chapter for which the current location href matches is set as current chapter. decodeURIComponent is used to handle special characters in chapter href.

let currentChapter
let chapters = []

this.book.loaded.navigation.then((navigation) => {
    this.chapters = navigation.toc;
    if (!currentChapter) {
        currentChapter = getCurrentChapter(this.chapters, decodeURIComponent(section.href))
    }
});

rendition.on('locationChanged', (location) => {
    currentChapter = this.getCurrentChapter(this.chapters, decodeURIComponent(location.href))
})

getCurrentChapter(chapters: NavItem[], chapterHref: string) {
    let chapter: CurrentChapter = {
        index: -1
    }
    chapter.index = chapters?.findIndex(chapter => chapter.href.includes(chapterHref))
    chapter.navItem = chapters["" + chapter.index]
    if (!chapter) {
        for(let i = 0; i < chapters.length; i++) {
            if (chapters[i].subitems?.length > 0) {
                chapter = getCurrentChapter(chapters[i].subitems, chapterHref)
                if (chapter) break
            }
        }
    }
    return {
        index: chapter.index,
        navItem: chapter.navItem
    }
}