<commit_before><template>

  <CoreFullscreen
    ref="epubRenderer"
    class="epub-renderer"
    :class="{small: windowIsSmall, scrolled: scrolled}"
    :style="epubRendererStyle"
    @changeFullscreen="isInFullscreen = $event"
  >

    <LoadingError v-if="errorLoading" :loaded="loaded" />

    <LoadingScreen v-else-if="!loaded" />

    <div
      class="epub-renderer-content"
      :dir="dir"
      @mousedown.stop="handleMouseDown"
      @keyup.esc="closeSideBar"
    >

      <TopBar
        ref="topBar"
        class="top-bar"
        :isInFullscreen="isInFullscreen"
        @tableOfContentsButtonClicked="handleTocToggle"
        @settingsButtonClicked="handleSettingToggle"
        @searchButtonClicked="handleSearchToggle"
        @fullscreenButtonClicked="$refs.epubRenderer.toggleFullscreen()"
      />

      <FocusLock
        :disabled="!tocSideBarIsOpen"
        :returnFocus="true"
      >
        <TocButton
          v-if="tocSideBarIsOpen"
          class="toc-button"
          @click="handleTocToggle"
        />

        <transition name="fade">
          <TableOfContentsSideBar
            v-show="tocSideBarIsOpen"
            ref="tocSideBar"
            :toc="toc"
            :currentSection="currentSection"
            class="side-bar side-bar-left"
            @tocNavigation="handleTocNavigation"
          />
        </transition>
      </FocusLock>

      <FocusLock
        :disabled="!settingsSideBarIsOpen"
        :returnFocus="false"
      >
        <SettingsButton
          v-if="settingsSideBarIsOpen"
          class="settings-button"
          @click="handleSettingToggle"
        />

        <transition name="fade">
          <SettingsSideBar
            v-show="settingsSideBarIsOpen"
            ref="settingsSideBar"
            class="side-bar side-bar-right"
            :theme="theme"
            :decreaseFontSizeDisabled="decreaseFontSizeDisabled"
            :increaseFontSizeDisabled="increaseFontSizeDisabled"
            @decreaseFontSize="decreaseFontSize"
            @increaseFontSize="increaseFontSize"
            @setTheme="setTheme"
          />
        </transition>
      </FocusLock>

      <FocusLock
        :disabled="!searchSideBarIsOpen"
        :returnFocus="false"
      >
        <SearchButton
          v-if="searchSideBarIsOpen"
          class="search-button"
          @click="handleSearchToggle"
        />

        <transition name="fade">
          <SearchSideBar
            v-show="searchSideBarIsOpen"
            ref="searchSideBar"
            class="side-bar side-bar-right"
            :book="book"
            @newSearchQuery="handleNewSearchQuery"
            @navigateToSearchResult="handleNavigateToSearchResult"
          />
        </transition>
      </FocusLock>

      <div
        class="navigation-and-epubjs"
        :style="backgroundColorStyle"
      >
        <div
          class="column epubjs-navigation"
        >
          <PreviousButton
            :color="navigationButtonColor"
            :style="backgroundColorStyle"
            :isRtl="isRtl"
            @goToPreviousPage="goToPreviousPage"
          />
        </div>
        <div
          ref="epubjsContainer"
          class="column epubjs-parent"
          :style="backgroundColorStyle"
        >
        </div>
        <div
          class="column epubjs-navigation"
        >
          <NextButton
            :color="navigationButtonColor"
            :style="backgroundColorStyle"
            :isRtl="isRtl"
            @goToNextPage="goToNextPage"
          />
        </div>
      </div>

      <BottomBar
        class="bottom-bar"
        :locationsAreReady="locations.length > 0"
        :heading="bottomBarHeading"
        :sliderValue="sliderValue"
        :sliderStep="sliderStep"
        @sliderChanged="handleSliderChanged"
      />
    </div>
  </CoreFullscreen>

</template>


<script>

  import Epub from 'epubjs/src/epub';
  import { EVENTS } from 'epubjs/src/utils/constants';

  import Mark from 'mark.js';
  import isEqual from 'lodash/isEqual';
  import Lockr from 'lockr';

  import FocusLock from 'vue-focus-lock';

  import { mapGetters } from 'vuex';
  import themeMixin from 'kolibri.coreVue.mixins.themeMixin';
  import CoreFullscreen from 'kolibri.coreVue.components.CoreFullscreen';
  import responsiveElement from 'kolibri.coreVue.mixins.responsiveElement';
  import responsiveWindow from 'kolibri.coreVue.mixins.responsiveWindow';
  import contentRendererMixin from 'kolibri.coreVue.mixins.contentRendererMixin';
  import { getContentLangDir } from 'kolibri.utils.i18n';

  import iFrameView from './SandboxIFrameView';
  import LoadingScreen from './LoadingScreen';
  import LoadingError from './LoadingError';
  import TopBar from './TopBar';
  import TableOfContentsSideBar from './TableOfContentsSideBar.vue';
  import SettingsSideBar from './SettingsSideBar';
  import SearchSideBar from './SearchSideBar';
  import BottomBar from './BottomBar';
  import PreviousButton from './PreviousButton';
  import NextButton from './NextButton';
  import TocButton from './TocButton';
  import SettingsButton from './SettingsButton';
  import SearchButton from './SearchButton';

  import { THEMES } from './EpubConstants';

  const FONT_SIZE_MIN = 8;
  const FONT_SIZE_MAX = 32;
  const FONT_SIZE_STEP = 4;

  const SIDE_BARS = {
    TOC: 'TOC',
    SEARCH: 'SEARCH',
    SETTINGS: 'SETTINGS',
  };

  const LOCATIONS_INTERVAL = 1000;

  const EPUB_RENDERER_SETTINGS_KEY = 'kolibriEpubRendererSettings';

  export default {
    name: 'EpubRendererIndex',
    components: {
      CoreFullscreen,
      TopBar,
      TableOfContentsSideBar,
      SettingsSideBar,
      SearchSideBar,
      LoadingScreen,
      BottomBar,
      PreviousButton,
      NextButton,
      FocusLock,
      TocButton,
      SettingsButton,
      SearchButton,
      LoadingError,
    },
    mixins: [responsiveWindow, responsiveElement, contentRendererMixin, themeMixin],
    data: () => ({
      book: null,
      rendition: null,
      toc: [],
      locations: [],
      loaded: false,
      errorLoading: false,
      sideBarOpen: null,
      theme: THEMES.WHITE,
      fontSize: null,
      isInFullscreen: false,
      markInstance: null,
      currentSection: null,
      searchQuery: null,
      sliderValue: 0,
      scrolled: false,

      currentLocation: null,
      updateContentStateInterval: null,
    }),
    computed: {
      ...mapGetters(['sessionTimeSpent']),
      savedLocation() {
        if (this.extraFields && this.extraFields.contentState) {
          return this.extraFields.contentState.savedLocation;
        }
        return null;
      },
      epubURL() {
        return this.defaultFile.storage_url;
      },
      backgroundColor() {
        return this.theme.backgroundColor;
      },
      backgroundColorStyle() {
        return {
          backgroundColor: this.backgroundColor,
        };
      },
      textColor() {
        return this.theme.textColor;
      },
      themeStyle() {
        const colorStyle = {
          'background-color': `${this.backgroundColor}!important`,
          color: `${this.textColor}!important`,
        };
        const alignmentStyle = {
          'text-align': `${this.isRtl ? 'right' : 'left'}!important`,
        };
        const fontSizeStyle = this.fontSize ? { 'font-size': `${this.fontSize}!important` } : {};

        const lineHeightStyle = {
          'line-height': `1.4em!important`,
        };

        // Display flex helps body size to it's content
        const htmlScrolledStyle = this.scrolled ? { display: 'flex' } : {};

        // Width style overrides the pixel width added by epub.js, and in conjunction with flex
        // above, helps SandboxIFrameView size containers according to true content width.
        // Padding override kills an arbitrary `padding:0 (width / 12)px` set by epub.js
        const bodyScrolledStyle = this.scrolled
          ? { width: 'auto!important', padding: '20px!important' }
          : {};

        return {
          html: { ...colorStyle, ...alignmentStyle, ...fontSizeStyle, ...htmlScrolledStyle },
          body: { ...colorStyle, ...alignmentStyle, ...fontSizeStyle, ...bodyScrolledStyle },
          p: { ...colorStyle, ...alignmentStyle, ...lineHeightStyle },
          h1: { ...colorStyle },
          h2: { ...colorStyle },
          h3: { ...colorStyle },
          h4: { ...colorStyle },
          h5: { ...colorStyle },
          'p:first-of-type::first-letter': { ...colorStyle },
          // help media not overflow their columns
          video: { 'max-width': '100%' },
          img: { 'max-width': '100%' },
        };
      },
      tocSideBarIsOpen() {
        return this.sideBarOpen === SIDE_BARS.TOC;
      },
      settingsSideBarIsOpen() {
        return this.sideBarOpen === SIDE_BARS.SETTINGS;
      },
      searchSideBarIsOpen() {
        return this.sideBarOpen === SIDE_BARS.SEARCH;
      },
      epubRendererStyle() {
        return {
          backgroundColor: this.$coreBgLight,
        };
      },
      navigationButtonColor() {
        return [THEMES.BLACK, THEMES.GREY].some(theme => isEqual(this.theme, theme))
          ? 'white'
          : 'black';
      },
      bottomBarHeading() {
        if (this.currentSection) {
          return this.currentSection.label.trim();
        }
        return '';
      },
      sliderStep() {
        if (this.locations.length > 0) {
          return Math.floor(Math.min(Math.max(100 / this.locations.length, 0.1), 100));
        }
        return 1;
      },
      decreaseFontSizeDisabled() {
        return this.fontSize === `${FONT_SIZE_MIN}px`;
      },
      increaseFontSizeDisabled() {
        return this.fontSize === `${FONT_SIZE_MAX}px`;
      },
      expectedTimeToRead() {
        const WORDS_PER_MINUTE = 300;
        const CHARS_PER_WORD = 10;
        const numberOfWords = (this.locations.length * LOCATIONS_INTERVAL) / CHARS_PER_WORD;
        const seconds = (numberOfWords * 60) / WORDS_PER_MINUTE;
        return seconds;
      },
      dir() {
        return getContentLangDir(this.lang);
      },
      isRtl() {
        return this.dir === 'rtl';
      },
    },
    watch: {
      sideBarOpen(newSideBar, oldSideBar) {
        this.$nextTick().then(() => {
          if (oldSideBar === SIDE_BARS.SEARCH) {
            this.clearMarks();
          }
          if (!newSideBar) {
            switch (oldSideBar) {
              case SIDE_BARS.TOC:
                this.$refs.topBar.focusOnTocButton();
                break;
              case SIDE_BARS.SETTINGS:
                this.$refs.topBar.focusOnSettingsButton();
                break;
              case SIDE_BARS.SEARCH:
                this.$refs.topBar.focusOnSearchButton();
                break;
              default:
                break;
            }
          }
          if (newSideBar === SIDE_BARS.SEARCH) {
            this.$refs.searchSideBar.focusOnInput();
            if (this.searchQuery) {
              this.clearMarks().then(this.createMarks(this.searchQuery));
            }
          }
        });
      },
    },
    created() {
      // Try to load the appropriate directional CSS for the particular content
      this.cssPromise = this.$options.contentModule.loadDirectionalCSS(this.dir);
    },
    beforeMount() {
      global.ePub = Epub;
      this.book = new Epub(this.epubURL);

      const { theme = this.theme, fontSize = this.fontSize } =
        Lockr.get(EPUB_RENDERER_SETTINGS_KEY) || {};
      this.theme = theme;
      this.fontSize = fontSize;
    },
    mounted() {
      Promise.all([this.cssPromise, this.book.ready]).then(() => {
        if (this.book.navigation) {
          this.toc = this.book.navigation.toc;
        }

        this.rendition = this.book.renderTo(this.$refs.epubjsContainer, {
          view: iFrameView,
          resizeOnOrientationChange: false,
          spread: 'auto',
          minSpreadWidth: 600,
        });

        this.rendition
          .display(this.savedLocation || undefined)
          .then(() => {
            const hasTables = this.rendition.getContents().reduce((hasTable, contents) => {
              return hasTable || contents.document.getElementsByTagName('table').length > 0;
            }, false);

            // Tables have issues rendering, so switch from paginated flow to scrolled
            if (hasTables) {
              this.scrolled = true;
              this.rendition.flow('scrolled');
              this.rendition.clear();
              // Re-renders and targets this.savedLocation (if set)
              return this.rendition.display(this.savedLocation || undefined);
            }

            // For paginated, update settings
            const bounds = this.$refs.epubjsContainer.getBoundingClientRect();
            Object.assign(this.rendition.manager.stage.settings, {
              width: bounds.width,
              height: bounds.height,
            });

            return this.rendition.q.enqueue(() => {
              this.rendition.resize();
            });
          })
          .then(() => {
            this.handleReadyRendition();
          });

        this.rendition.on(EVENTS.RENDITION.DISPLAY_ERROR, () => {
          this.errorLoading = true;
        });
      });
      this.book.on(EVENTS.BOOK.OPEN_FAILED, () => {
        this.errorLoading = true;
      });
    },
    beforeDestroy() {
      this.updateContentState();
      this.updateProgress();
      this.$emit('stopTracking');
      window.removeEventListener('mousedown', this.handleMouseDown, { passive: true });
      clearInterval(this.updateContentStateInterval);
    },
    destroyed() {
      delete global.ePub;
    },
    methods: {
      updateProgress() {
        if (this.locations.length > 0) {
          this.$emit('updateProgress', this.sessionTimeSpent / this.expectedTimeToRead);
        }
      },
      handleReadyRendition() {
        this.updateRenditionTheme(this.themeStyle);

        this.rendition.on(EVENTS.RENDITION.RELOCATED, location => this.relocatedHandler(location));
        this.rendition.on('keyup', this.handleKeyUps);
        this.rendition.on('click', () => this.closeSideBar());

        window.addEventListener('mousedown', this.handleMouseDown, { passive: true });

        this.loaded = true;

        this.book.locations.generate(LOCATIONS_INTERVAL).then(locations => {
          this.locations = locations;
          this.$emit('startTracking');
          this.updateContentStateInterval = setInterval(this.updateProgress, 30000);

          // Update current location, .currentLocation() can return Promise or value
          Promise.resolve()
            .then(() => this.rendition.currentLocation())
            .then(currentLocation => {
              if (currentLocation) {
                this.relocatedHandler(currentLocation);
              }
            });
        });
      },
      updateRenditionTheme(newTheme) {
        const themeName = JSON.stringify(newTheme);
        this.rendition.themes.register(themeName, newTheme);
        this.rendition.themes.select(themeName);
      },
      getIFrameView() {
        return this.rendition<commit_msg>Guard against initial display error in IE 11<commit_after><template>

  <CoreFullscreen
    ref="epubRenderer"
    class="epub-renderer"
    :class="{small: windowIsSmall, scrolled: scrolled}"
    :style="epubRendererStyle"
    @changeFullscreen="isInFullscreen = $event"
  >

    <LoadingError v-if="errorLoading" :loaded="loaded" />

    <LoadingScreen v-else-if="!loaded" />

    <div
      class="epub-renderer-content"
      :dir="dir"
      @mousedown.stop="handleMouseDown"
      @keyup.esc="closeSideBar"
    >

      <TopBar
        ref="topBar"
        class="top-bar"
        :isInFullscreen="isInFullscreen"
        @tableOfContentsButtonClicked="handleTocToggle"
        @settingsButtonClicked="handleSettingToggle"
        @searchButtonClicked="handleSearchToggle"
        @fullscreenButtonClicked="$refs.epubRenderer.toggleFullscreen()"
      />

      <FocusLock
        :disabled="!tocSideBarIsOpen"
        :returnFocus="true"
      >
        <TocButton
          v-if="tocSideBarIsOpen"
          class="toc-button"
          @click="handleTocToggle"
        />

        <transition name="fade">
          <TableOfContentsSideBar
            v-show="tocSideBarIsOpen"
            ref="tocSideBar"
            :toc="toc"
            :currentSection="currentSection"
            class="side-bar side-bar-left"
            @tocNavigation="handleTocNavigation"
          />
        </transition>
      </FocusLock>

      <FocusLock
        :disabled="!settingsSideBarIsOpen"
        :returnFocus="false"
      >
        <SettingsButton
          v-if="settingsSideBarIsOpen"
          class="settings-button"
          @click="handleSettingToggle"
        />

        <transition name="fade">
          <SettingsSideBar
            v-show="settingsSideBarIsOpen"
            ref="settingsSideBar"
            class="side-bar side-bar-right"
            :theme="theme"
            :decreaseFontSizeDisabled="decreaseFontSizeDisabled"
            :increaseFontSizeDisabled="increaseFontSizeDisabled"
            @decreaseFontSize="decreaseFontSize"
            @increaseFontSize="increaseFontSize"
            @setTheme="setTheme"
          />
        </transition>
      </FocusLock>

      <FocusLock
        :disabled="!searchSideBarIsOpen"
        :returnFocus="false"
      >
        <SearchButton
          v-if="searchSideBarIsOpen"
          class="search-button"
          @click="handleSearchToggle"
        />

        <transition name="fade">
          <SearchSideBar
            v-show="searchSideBarIsOpen"
            ref="searchSideBar"
            class="side-bar side-bar-right"
            :book="book"
            @newSearchQuery="handleNewSearchQuery"
            @navigateToSearchResult="handleNavigateToSearchResult"
          />
        </transition>
      </FocusLock>

      <div
        class="navigation-and-epubjs"
        :style="backgroundColorStyle"
      >
        <div
          class="column epubjs-navigation"
        >
          <PreviousButton
            :color="navigationButtonColor"
            :style="backgroundColorStyle"
            :isRtl="isRtl"
            @goToPreviousPage="goToPreviousPage"
          />
        </div>
        <div
          ref="epubjsContainer"
          class="column epubjs-parent"
          :style="backgroundColorStyle"
        >
        </div>
        <div
          class="column epubjs-navigation"
        >
          <NextButton
            :color="navigationButtonColor"
            :style="backgroundColorStyle"
            :isRtl="isRtl"
            @goToNextPage="goToNextPage"
          />
        </div>
      </div>

      <BottomBar
        class="bottom-bar"
        :locationsAreReady="locations.length > 0"
        :heading="bottomBarHeading"
        :sliderValue="sliderValue"
        :sliderStep="sliderStep"
        @sliderChanged="handleSliderChanged"
      />
    </div>
  </CoreFullscreen>

</template>


<script>

  import Epub from 'epubjs/src/epub';
  import { EVENTS } from 'epubjs/src/utils/constants';

  import Mark from 'mark.js';
  import isEqual from 'lodash/isEqual';
  import Lockr from 'lockr';

  import FocusLock from 'vue-focus-lock';

  import { mapGetters } from 'vuex';
  import themeMixin from 'kolibri.coreVue.mixins.themeMixin';
  import CoreFullscreen from 'kolibri.coreVue.components.CoreFullscreen';
  import responsiveElement from 'kolibri.coreVue.mixins.responsiveElement';
  import responsiveWindow from 'kolibri.coreVue.mixins.responsiveWindow';
  import contentRendererMixin from 'kolibri.coreVue.mixins.contentRendererMixin';
  import { getContentLangDir } from 'kolibri.utils.i18n';

  import iFrameView from './SandboxIFrameView';
  import LoadingScreen from './LoadingScreen';
  import LoadingError from './LoadingError';
  import TopBar from './TopBar';
  import TableOfContentsSideBar from './TableOfContentsSideBar.vue';
  import SettingsSideBar from './SettingsSideBar';
  import SearchSideBar from './SearchSideBar';
  import BottomBar from './BottomBar';
  import PreviousButton from './PreviousButton';
  import NextButton from './NextButton';
  import TocButton from './TocButton';
  import SettingsButton from './SettingsButton';
  import SearchButton from './SearchButton';

  import { THEMES } from './EpubConstants';

  const FONT_SIZE_MIN = 8;
  const FONT_SIZE_MAX = 32;
  const FONT_SIZE_STEP = 4;

  const SIDE_BARS = {
    TOC: 'TOC',
    SEARCH: 'SEARCH',
    SETTINGS: 'SETTINGS',
  };

  const LOCATIONS_INTERVAL = 1000;

  const EPUB_RENDERER_SETTINGS_KEY = 'kolibriEpubRendererSettings';

  export default {
    name: 'EpubRendererIndex',
    components: {
      CoreFullscreen,
      TopBar,
      TableOfContentsSideBar,
      SettingsSideBar,
      SearchSideBar,
      LoadingScreen,
      BottomBar,
      PreviousButton,
      NextButton,
      FocusLock,
      TocButton,
      SettingsButton,
      SearchButton,
      LoadingError,
    },
    mixins: [responsiveWindow, responsiveElement, contentRendererMixin, themeMixin],
    data: () => ({
      book: null,
      rendition: null,
      toc: [],
      locations: [],
      loaded: false,
      errorLoading: false,
      sideBarOpen: null,
      theme: THEMES.WHITE,
      fontSize: null,
      isInFullscreen: false,
      markInstance: null,
      currentSection: null,
      searchQuery: null,
      sliderValue: 0,
      scrolled: false,

      currentLocation: null,
      updateContentStateInterval: null,
    }),
    computed: {
      ...mapGetters(['sessionTimeSpent']),
      savedLocation() {
        if (this.extraFields && this.extraFields.contentState) {
          return this.extraFields.contentState.savedLocation;
        }
        return null;
      },
      epubURL() {
        return this.defaultFile.storage_url;
      },
      backgroundColor() {
        return this.theme.backgroundColor;
      },
      backgroundColorStyle() {
        return {
          backgroundColor: this.backgroundColor,
        };
      },
      textColor() {
        return this.theme.textColor;
      },
      themeStyle() {
        const colorStyle = {
          'background-color': `${this.backgroundColor}!important`,
          color: `${this.textColor}!important`,
        };
        const alignmentStyle = {
          'text-align': `${this.isRtl ? 'right' : 'left'}!important`,
        };
        const fontSizeStyle = this.fontSize ? { 'font-size': `${this.fontSize}!important` } : {};

        const lineHeightStyle = {
          'line-height': `1.4em!important`,
        };

        // Display flex helps body size to it's content
        const htmlScrolledStyle = this.scrolled ? { display: 'flex' } : {};

        // Width style overrides the pixel width added by epub.js, and in conjunction with flex
        // above, helps SandboxIFrameView size containers according to true content width.
        // Padding override kills an arbitrary `padding:0 (width / 12)px` set by epub.js
        const bodyScrolledStyle = this.scrolled
          ? { width: 'auto!important', padding: '20px!important' }
          : {};

        return {
          html: { ...colorStyle, ...alignmentStyle, ...fontSizeStyle, ...htmlScrolledStyle },
          body: { ...colorStyle, ...alignmentStyle, ...fontSizeStyle, ...bodyScrolledStyle },
          p: { ...colorStyle, ...alignmentStyle, ...lineHeightStyle },
          h1: { ...colorStyle },
          h2: { ...colorStyle },
          h3: { ...colorStyle },
          h4: { ...colorStyle },
          h5: { ...colorStyle },
          'p:first-of-type::first-letter': { ...colorStyle },
          // help media not overflow their columns
          video: { 'max-width': '100%' },
          img: { 'max-width': '100%' },
        };
      },
      tocSideBarIsOpen() {
        return this.sideBarOpen === SIDE_BARS.TOC;
      },
      settingsSideBarIsOpen() {
        return this.sideBarOpen === SIDE_BARS.SETTINGS;
      },
      searchSideBarIsOpen() {
        return this.sideBarOpen === SIDE_BARS.SEARCH;
      },
      epubRendererStyle() {
        return {
          backgroundColor: this.$coreBgLight,
        };
      },
      navigationButtonColor() {
        return [THEMES.BLACK, THEMES.GREY].some(theme => isEqual(this.theme, theme))
          ? 'white'
          : 'black';
      },
      bottomBarHeading() {
        if (this.currentSection) {
          return this.currentSection.label.trim();
        }
        return '';
      },
      sliderStep() {
        if (this.locations.length > 0) {
          return Math.floor(Math.min(Math.max(100 / this.locations.length, 0.1), 100));
        }
        return 1;
      },
      decreaseFontSizeDisabled() {
        return this.fontSize === `${FONT_SIZE_MIN}px`;
      },
      increaseFontSizeDisabled() {
        return this.fontSize === `${FONT_SIZE_MAX}px`;
      },
      expectedTimeToRead() {
        const WORDS_PER_MINUTE = 300;
        const CHARS_PER_WORD = 10;
        const numberOfWords = (this.locations.length * LOCATIONS_INTERVAL) / CHARS_PER_WORD;
        const seconds = (numberOfWords * 60) / WORDS_PER_MINUTE;
        return seconds;
      },
      dir() {
        return getContentLangDir(this.lang);
      },
      isRtl() {
        return this.dir === 'rtl';
      },
    },
    watch: {
      sideBarOpen(newSideBar, oldSideBar) {
        this.$nextTick().then(() => {
          if (oldSideBar === SIDE_BARS.SEARCH) {
            this.clearMarks();
          }
          if (!newSideBar) {
            switch (oldSideBar) {
              case SIDE_BARS.TOC:
                this.$refs.topBar.focusOnTocButton();
                break;
              case SIDE_BARS.SETTINGS:
                this.$refs.topBar.focusOnSettingsButton();
                break;
              case SIDE_BARS.SEARCH:
                this.$refs.topBar.focusOnSearchButton();
                break;
              default:
                break;
            }
          }
          if (newSideBar === SIDE_BARS.SEARCH) {
            this.$refs.searchSideBar.focusOnInput();
            if (this.searchQuery) {
              this.clearMarks().then(this.createMarks(this.searchQuery));
            }
          }
        });
      },
    },
    created() {
      // Try to load the appropriate directional CSS for the particular content
      this.cssPromise = this.$options.contentModule.loadDirectionalCSS(this.dir);
    },
    beforeMount() {
      global.ePub = Epub;
      this.book = new Epub(this.epubURL);

      const { theme = this.theme, fontSize = this.fontSize } =
        Lockr.get(EPUB_RENDERER_SETTINGS_KEY) || {};
      this.theme = theme;
      this.fontSize = fontSize;
    },
    mounted() {
      Promise.all([this.cssPromise, this.book.ready]).then(() => {
        if (this.book.navigation) {
          this.toc = this.book.navigation.toc;
        }

        this.rendition = this.book.renderTo(this.$refs.epubjsContainer, {
          view: iFrameView,
          resizeOnOrientationChange: false,
          spread: 'auto',
          minSpreadWidth: 600,
        });

        this.rendition
          .display(this.savedLocation || undefined)
          .then(() => {
            const hasTables = this.rendition.getContents().reduce((hasTable, contents) => {
              return hasTable || contents.document.getElementsByTagName('table').length > 0;
            }, false);

            // Tables have issues rendering, so switch from paginated flow to scrolled
            if (hasTables) {
              this.scrolled = true;
              this.rendition.flow('scrolled');
              this.rendition.clear();
              // Re-renders and targets this.savedLocation (if set)
              return this.rendition.display(this.savedLocation || undefined);
            }

            // For paginated, update settings
            const bounds = this.$refs.epubjsContainer.getBoundingClientRect();
            Object.assign(this.rendition.manager.stage.settings, {
              width: bounds.width,
              height: bounds.height,
            });

            const resize = new Promise((resolve, reject) => {
              this.rendition.once(EVENTS.RENDITION.DISPLAYED, resolve);
              this.rendition.once(EVENTS.RENDITION.DISPLAY_ERROR, reject);
            });

            return this.rendition.q
              .enqueue(() => this.rendition.resize())
              .then(() => resize)
              .then(
                () => {},
                () => {
                  // In IE 11, EVENTS.RENDITION.DISPLAY_ERROR occurs when calling .resize(),
                  // so we'll try to redisplay, and if that's successful remove `errorLoading`
                  return this.rendition.display(this.savedLocation || undefined);
                }
              )
              .then(() => {
                this.errorLoading = false;
              });
          })
          .then(
            () => {
              this.handleReadyRendition();
            },
            () => {
              this.errorLoading = true;
            }
          );

        this.rendition.on(EVENTS.RENDITION.DISPLAY_ERROR, () => {
          this.errorLoading = true;
        });
      });
      this.book.on(EVENTS.BOOK.OPEN_FAILED, () => {
        this.errorLoading = true;
      });
    },
    beforeDestroy() {
      this.updateContentState();
      this.updateProgress();
      this.$emit('stopTracking');
      window.removeEventListener('mousedown', this.handleMouseDown, { passive: true });
      clearInterval(this.updateContentStateInterval);
    },
    destroyed() {
      delete global.ePub;
    },
    methods: {
      updateProgress() {
        if (this.locations.length > 0) {
          this.$emit('updateProgress', this.sessionTimeSpent / this.expectedTimeToRead);
        }
      },
      handleReadyRendition() {
        this.updateRenditionTheme(this.themeStyle);

        this.rendition.on(EVENTS.RENDITION.RELOCATED, location => this.relocatedHandler(location));
        this.rendition.on('keyup', this.handleKeyUps);
        this.rendition.on('click', () => this.closeSideBar());

        window.addEventListener('mousedown', this.handleMouseDown, { passive: true });

        this.loaded = true;

        this.book.locations.generate(LOCATIONS_INTERVAL).then(locations => {
          this.locations = locations;
          this.$emit('startTracking');
          this.updateContentStateInterval = setInterval(this.updateProgress, 30000);

          // Update current location, .currentLocation() can return Promise or value
          Promise.resolve()
            .then(() => this.rendition.currentLocation())
            .then(currentLocation => {
              if (currentLocation && currentLocation.start) {
                this.relocatedHandler(currentLocation);
              }
            });
        });
      },
      updateRenditionTheme(newTheme) {
        const themeName = JSON.stringify(newTheme);
        this.rendition.themes.register(themeName, newTheme);
        this.rendition.themes.select(themeName);
      },
      getIFrameView() {
        return this.rendition
{'fraction_non_alphanumeric': 0.1206686735412797, 'fraction_numerical': 0.0037789021968872094, 'mean_word_length': 2.0364644107351224, 'pattern_counts': {'":': 0, '<': 81, '<?xml version=': 0, '>': 125, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4672496', 'n_tokens_mistral': 9094, 'n_tokens_neox': 8752, 'n_words': 1529}