diff --git a/examples/streamed/server/server-cli.inlinesourcemap.js b/examples/streamed/server/server-cli.inlinesourcemap.js
index fdac3bfe..d1966fc9 100644
--- a/examples/streamed/server/server-cli.inlinesourcemap.js
+++ b/examples/streamed/server/server-cli.inlinesourcemap.js
@@ -1701,9 +1701,10 @@ function serverPub(server, topRouter) {
const urlBookShowAll = "./manifest.json/show/all";
const urlReaderAPI = "/viewer/index_api.html?url=PREFIX" + querystring.escape(urlBook);
const urlReaderMaterial = "/viewer/index_material.html?url=PREFIX" + querystring.escape(urlBook);
+ const urlD2E = "/viewer/index.html?url=PREFIX" + querystring.escape(urlBook);
const htmlLanding = "
PATH_STR
" +
- (server.disableReaders ? "" : ("Reader API:
" + urlReaderAPI + "
Reader Material:
" + urlReaderMaterial + "
")) +
+ (server.disableReaders ? "" : ("Reader API:
" + urlReaderAPI + "
Reader Material:
" + urlReaderMaterial + "
D2 Reader:
" + urlD2E + "
")) +
"";
const routerPathBase64 = express.Router({ strict: false });
routerPathBase64.use(morgan("combined", { stream: { write: (msg) => debug(msg) } }));
diff --git a/package-lock.json b/package-lock.json
index 97b98b43..5087dfc3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "@d-i-t-a/reader",
- "version": "1.4.0",
+ "version": "1.5.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
diff --git a/package.json b/package.json
index c99f9925..e936328a 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "@d-i-t-a/reader",
- "version": "1.4.0",
+ "version": "1.5.0",
"description": "A viewer application for EPUB files.",
"repository": "https://github.com/d-i-t-a/R2D2BC",
"license": "Apache-2.0",
diff --git a/src/index.ts b/src/index.ts
index c8b24dde..170dfcc2 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -29,6 +29,8 @@ import { oc } from "ts-optchain"
import { TTSSettings } from "./modules/TTS/TTSSettings";
import SearchModule from "./modules/search/SearchModule";
import TextHighlighter from "./modules/highlight/TextHighlighter";
+import { Locator } from "./model/Locator";
+import TimelineModule from "./modules/positions/TimelineModule";
var R2Settings: UserSettings;
var R2TTSSettings: TTSSettings;
@@ -38,6 +40,7 @@ var BookmarkModuleInstance: BookmarkModule;
var AnnotationModuleInstance: AnnotationModule;
var TTSModuleInstance: TTSModule;
var SearchModuleInstance:SearchModule;
+var TimelineModuleInstance: TimelineModule;
export const IS_DEV = (process.env.NODE_ENV === "development" || process.env.NODE_ENV === "dev");
@@ -60,6 +63,9 @@ export async function unload() {
if (oc(R2Navigator.rights).enableSearch(false)) {
SearchModuleInstance.stop()
}
+ if (oc(R2Navigator.rights).enableTimeline(false)) {
+ TimelineModuleInstance.stop()
+ }
}
export function startReadAloud() {
if (IS_DEV) { console.log("startReadAloud") }
@@ -251,6 +257,18 @@ export async function scroll(value) {
R2Settings.scroll(value)
}
+export async function currentLocator() {
+ if (IS_DEV) { console.log("currentLocator") }
+ return R2Navigator.currentLocator()
+}
+export async function positions() {
+ if (IS_DEV) { console.log("positions") }
+ return R2Navigator.positions()
+}
+export async function goToPosition(value) {
+ if (IS_DEV) { console.log("goToPosition") }
+ return R2Navigator.goToPosition(value)
+}
export async function load(config: ReaderConfig): Promise {
var mainElement = document.getElementById("D2Reader-Container");
@@ -275,6 +293,53 @@ export async function load(config: ReaderConfig): Promise {
const publication: Publication = await Publication.getManifest(webpubManifestUrl, store);
+ var startPosition = 0
+ var totalContentLength = 0
+ var positions = []
+ publication.readingOrder.map(async (link, index) => {
+ var href = publication.getAbsoluteHref(link.href);
+ await fetch(href)
+ .then(async r => {
+ let length = (await r.blob()).size
+ link.contentLength = length
+ totalContentLength += length
+ let positionLength = 1024
+ let positionCount = Math.max(1, Math.ceil(length / positionLength))
+ if (IS_DEV) console.log(length + " Bytes")
+ if (IS_DEV) console.log(positionCount + " Positions")
+ Array.from(Array(positionCount).keys()).map((_, position) => {
+ const locator: Locator = {
+ href: link.href,
+ locations: {
+ progression: (position) / (positionCount),
+ position: startPosition + (position + 1),
+ },
+ type: link.type
+ };
+ if (IS_DEV) console.log(locator)
+ positions.push(locator)
+ });
+ startPosition = startPosition + positionCount
+ })
+ if (index + 1 == publication.readingOrder.length) {
+ publication.readingOrder.map(async (link) => {
+ if (IS_DEV) console.log(totalContentLength)
+ if (IS_DEV) console.log(link.contentLength)
+ link.contentWeight = 100 / totalContentLength * link.contentLength
+ if (IS_DEV) console.log(link.contentWeight)
+ })
+ positions.map((locator, _index) => {
+ let resource = positions.filter((el: Locator) => el.href === locator.href)
+ let positionIndex = Math.ceil(locator.locations.progression * (resource.length - 1))
+ locator.locations.totalProgression = (locator.locations.position - 1) / (positions.length)
+ locator.locations.remainingPositions = Math.abs((positionIndex) - (resource.length - 1))
+ locator.locations.totalRemainingPositions = Math.abs((locator.locations.position - 1) - (positions.length - 1))
+ })
+ publication.positions = positions
+ if (IS_DEV) console.log(positions)
+ }
+ });
+
// Settings
R2Settings = await UserSettings.create({
store: settingsStore,
@@ -364,6 +429,16 @@ export async function load(config: ReaderConfig): Promise {
});
}
+ // Timeline Module
+ if (oc(config.rights).enableTimeline(false)) {
+ TimelineModule.create({
+ publication: publication,
+ delegate: R2Navigator
+ }).then(function (timelineModule) {
+ TimelineModuleInstance = timelineModule
+ })
+ }
+
return new Promise(resolve => resolve(R2Navigator));
}
@@ -498,4 +573,13 @@ exports.goToSearchID = function (href, index, current) {
}
exports.clearSearch = function () {
clearSearch()
+}
+exports.currentLocator = function () {
+ return currentLocator()
+}
+exports.positions = function () {
+ return positions()
+}
+exports.goToPosition = function (value) {
+ goToPosition(value)
}
\ No newline at end of file
diff --git a/src/model/Locator.ts b/src/model/Locator.ts
index 57814a84..5eb4b0d6 100644
--- a/src/model/Locator.ts
+++ b/src/model/Locator.ts
@@ -22,9 +22,10 @@ import { IHighlight } from "../modules/highlight/common/highlight";
export interface Locator {
href: string;
type?: string;
- title: string;
+ title?: string;
locations: Locations;
text?: LocatorText;
+ displayInfo?: any;
}
@@ -39,6 +40,8 @@ export interface Locations {
progression?: number; // 3 = bookmarks
position?: number; // 4 = goto page
totalProgression?: number;
+ remainingPositions?: number;
+ totalRemainingPositions?: number;
}
export interface ReadingPosition extends Locator {
diff --git a/src/model/Publication.ts b/src/model/Publication.ts
index 55d9e87e..01432c42 100644
--- a/src/model/Publication.ts
+++ b/src/model/Publication.ts
@@ -18,6 +18,7 @@
*/
import Store from "../store/Store";
+import { Locator } from "./Locator";
export interface Metadata {
title?: string;
@@ -54,6 +55,8 @@ export interface Link {
// The MediaOverlays associated to the resource of the Link
// mediaOverlays?: MediaOverlays;
+ contentLength?: number;
+ contentWeight?: number;
}
export default class Publication {
@@ -65,6 +68,7 @@ export default class Publication {
public readonly landmarks: Array;
public readonly pageList: Array;
public readonly images: Array;
+ public positions: Array;
private readonly manifestUrl: URL;
@@ -158,7 +162,11 @@ export default class Publication {
}
public getRelativeHref(href: string): string | null {
const manifest = this.manifestUrl.href.replace("/manifest.json", ""); //new URL(this.manifestUrl.href, this.manifestUrl.href).href;
- return href.replace(manifest, "");
+ var href = href.replace(manifest, "");
+ if(href.charAt(0) === '/') {
+ href = href.substring(1);
+ }
+ return href;
}
@@ -215,4 +223,12 @@ export default class Publication {
}
return link
}
+
+ /**
+ * positionsByHref
+ */
+ public positionsByHref(href:string) {
+ return this.positions.filter((el: Locator) => el.href === href)
+ }
+
}
diff --git a/src/modules/positions/TimelineModule.ts b/src/modules/positions/TimelineModule.ts
new file mode 100644
index 00000000..b42af879
--- /dev/null
+++ b/src/modules/positions/TimelineModule.ts
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2018-2020 DITA (AM Consulting LLC)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Developed on behalf of: DITA
+ * Licensed to: Bibliotheca LLC, Bokbasen AS and CAST under one or more contributor license agreements.
+ */
+
+import { IS_DEV } from "../..";
+import { Locator } from "../../model/Locator";
+import Publication from "../../model/Publication";
+import IFrameNavigator from "../../navigator/IFrameNavigator";
+import { addEventListenerOptional } from "../../utils/EventHandler";
+import ReaderModule from "../ReaderModule";
+import * as HTMLUtilities from "../../utils/HTMLUtilities";
+import { oc } from "ts-optchain";
+
+export interface TimelineModuleConfig {
+ publication: Publication;
+ delegate: IFrameNavigator;
+}
+
+export default class TimelineModule implements ReaderModule {
+
+ private publication: Publication;
+ private delegate: IFrameNavigator;
+ private timelineContainer: HTMLDivElement;
+ private positionSlider: HTMLInputElement;
+
+ public static async create(config: TimelineModuleConfig) {
+ const timeline = new this(
+ config.delegate,
+ config.publication
+ );
+
+ await timeline.start();
+ return timeline;
+ }
+
+ private constructor(delegate: IFrameNavigator, publication: Publication) {
+ this.delegate = delegate
+ this.publication = publication
+ }
+
+ async stop() {
+ if (IS_DEV) { console.log("Timeline module stop") }
+ }
+
+ protected async start(): Promise {
+
+ this.delegate.timelineModule = this
+
+ this.timelineContainer = HTMLUtilities.findElement(document, "#container-view-timeline") as HTMLDivElement;
+ if (oc(this.delegate.rights).enableMaterial(false)) {
+ this.positionSlider = HTMLUtilities.findElement(document, "#positionSlider") as HTMLInputElement;
+ }
+ }
+
+ async initialize() {
+ return new Promise(async (resolve) => {
+ await (document as any).fonts.ready;
+
+ let locator = this.delegate.currentLocator()
+ if (oc(this.delegate.rights).enableMaterial(false)) {
+ this.positionSlider.value = locator.locations.position.toString()
+ this.positionSlider.max = (locator.locations.totalRemainingPositions + locator.locations.position).toString()
+ }
+
+ this.timelineContainer.innerHTML = ""
+ this.publication.readingOrder.forEach(link => {
+
+ console.log(link.contentWeight)
+ const linkHref = this.publication.getAbsoluteHref(link.href);
+ const tocItemAbs = this.publication.getTOCItemAbsolute(linkHref);
+ const tocHref = (tocItemAbs.href.indexOf("#") !== -1) ? tocItemAbs.href.slice(0, tocItemAbs.href.indexOf("#")) : tocItemAbs.href
+ const tocHrefAbs = this.publication.getAbsoluteHref(tocHref);
+
+ var chapterHeight
+ if (this.publication.positions) {
+ if (link.contentWeight) {
+ chapterHeight = link.contentWeight
+ } else {
+ chapterHeight = 5
+ }
+ } else {
+ chapterHeight = 100 / this.publication.readingOrder.length
+ }
+
+ var chapter = document.createElement("div")
+ chapter.style.height = chapterHeight + "%"
+ chapter.style.width = "100%"
+ chapter.className = "chapter";
+
+ if (tocItemAbs.title !== undefined) {
+ var tooltip = document.createElement("span")
+ tooltip.innerHTML = tocItemAbs.title;
+ tooltip.className = "chapter-tooltip";
+ chapter.appendChild(tooltip);
+ }
+
+ addEventListenerOptional(chapter, 'click', (event: MouseEvent) => {
+
+ event.preventDefault();
+ event.stopPropagation();
+
+ const position1 = this.publication.positions.filter((el: Locator) => el.href === link.href)[0]
+ position1.href = this.publication.getAbsoluteHref(position1.href)
+ console.log(position1)
+ this.delegate.navigate(position1)
+ });
+
+ if (tocHrefAbs === this.delegate.currentChapterLink.href) {
+ chapter.className += " active";
+ } else {
+ chapter.className = chapter.className.replace(" active", "");
+ }
+
+ // append bookmarks indicator
+ // append notes indicator
+ // append highlights indicator
+
+ this.timelineContainer.appendChild(chapter)
+
+ });
+
+ resolve()
+ });
+ }
+
+}
diff --git a/src/navigator/IFrameNavigator.ts b/src/navigator/IFrameNavigator.ts
index 530c9ffd..d2400c3e 100644
--- a/src/navigator/IFrameNavigator.ts
+++ b/src/navigator/IFrameNavigator.ts
@@ -30,12 +30,13 @@ import { UserSettingsUIConfig, UserSettings } from "../model/user-settings/UserS
import BookmarkModule from "../modules/BookmarkModule";
import AnnotationModule from "../modules/AnnotationModule";
import TTSModule, { TTSSpeechConfig } from "../modules/TTS/TTSModule";
-import { IS_DEV } from "..";
+import { goTo, IS_DEV } from "..";
import Splitting from "../modules/TTS/splitting";
import { oc } from "ts-optchain";
import ReflowableBookView from "../views/ReflowableBookView";
import SearchModule from "../modules/search/SearchModule";
import TextHighlighter from "../modules/highlight/TextHighlighter";
+import TimelineModule from "../modules/positions/TimelineModule";
export interface UpLinkConfig {
url?: URL;
@@ -85,6 +86,7 @@ export interface ReaderRights {
enableTTS?: boolean;
enableSearch?: boolean;
enableMaterial?: boolean;
+ enableTimeline?: boolean;
}
export interface ReaderUI {
@@ -121,6 +123,7 @@ export default class IFrameNavigator implements Navigator {
ttsModule?: TTSModule;
searchModule?: SearchModule;
highlighter?: TextHighlighter;
+ timelineModule?: TimelineModule;
sideNavExpanded: boolean = false
material: boolean = false
@@ -175,6 +178,7 @@ export default class IFrameNavigator implements Navigator {
private bookTitle: HTMLSpanElement;
private chapterTitle: HTMLSpanElement;
private chapterPosition: HTMLSpanElement;
+ private remainingPositions: HTMLSpanElement;
private newPosition: Locator | null;
private newElementId: string | null;
private isBeingStyled: boolean;
@@ -290,14 +294,16 @@ export default class IFrameNavigator implements Navigator {
// Main Element
this.iframe = HTMLUtilities.findRequiredElement(mainElement, "main#iframe-wrapper iframe") as HTMLIFrameElement;
-
- this.loadingMessage = HTMLUtilities.findRequiredElement(mainElement, "#reader-loading") as HTMLDivElement;
- this.loadingMessage.innerHTML = readerLoading;
- this.loadingMessage.style.display = "none";
-
- this.errorMessage = HTMLUtilities.findRequiredElement(mainElement, "#reader-error") as HTMLDivElement;
- this.errorMessage.innerHTML = readerError;
- this.errorMessage.style.display = "none";
+ this.loadingMessage = HTMLUtilities.findElement(mainElement, "#reader-loading") as HTMLDivElement;
+ if (this.loadingMessage) {
+ this.loadingMessage.innerHTML = readerLoading;
+ this.loadingMessage.style.display = "none";
+ }
+ this.errorMessage = HTMLUtilities.findElement(mainElement, "#reader-error") as HTMLDivElement;
+ if (this.errorMessage) {
+ this.errorMessage.innerHTML = readerError;
+ this.errorMessage.style.display = "none";
+ }
this.tryAgainButton = HTMLUtilities.findElement(mainElement, "button[class=try-again]") as HTMLButtonElement;
this.goBackButton = HTMLUtilities.findElement(mainElement, "button[class=go-back]") as HTMLButtonElement;
@@ -306,8 +312,9 @@ export default class IFrameNavigator implements Navigator {
if (this.headerMenu) this.bookTitle = HTMLUtilities.findElement(this.headerMenu, "#book-title") as HTMLSpanElement;
- if (this.infoBottom) this.chapterTitle = HTMLUtilities.findRequiredElement(this.infoBottom, "span[class=chapter-title]") as HTMLSpanElement;
- if (this.infoBottom) this.chapterPosition = HTMLUtilities.findRequiredElement(this.infoBottom, "span[class=chapter-position]") as HTMLSpanElement;
+ if (this.infoBottom) this.chapterTitle = HTMLUtilities.findElement(this.infoBottom, "span[class=chapter-title]") as HTMLSpanElement;
+ if (this.infoBottom) this.chapterPosition = HTMLUtilities.findElement(this.infoBottom, "span[class=chapter-position]") as HTMLSpanElement;
+ if (this.infoBottom) this.remainingPositions = HTMLUtilities.findElement(this.infoBottom, "span[class=remaining-positions]") as HTMLSpanElement;
if (this.headerMenu) this.espandMenuIcon = HTMLUtilities.findElement(this.headerMenu, "#expand-menu") as HTMLElement;
@@ -423,8 +430,10 @@ export default class IFrameNavigator implements Navigator {
self.annotationModule.drawHighlights()
// self.annotationModule.drawIndicators()
} else {
- await this.highlighter.destroyAllhighlights(this.iframe.contentDocument)
- self.searchModule.drawSearch()
+ if (oc(this.rights).enableSearch(false)) {
+ await this.highlighter.destroyAllhighlights(this.iframe.contentDocument)
+ self.searchModule.drawSearch()
+ }
}
}, 300);
@@ -559,6 +568,7 @@ export default class IFrameNavigator implements Navigator {
if (this.previousPageAnchorElement) this.previousPageAnchorElement.style.display = "unset"
if (this.chapterTitle) this.chapterTitle.style.display = "inline";
if (this.chapterPosition) this.chapterPosition.style.display = "inline";
+ if (this.remainingPositions) this.remainingPositions.style.display = "inline";
if (this.eventHandler) {
this.eventHandler.onInternalLink = this.handleInternalLink.bind(this);
this.eventHandler.onClickThrough = this.handleClickThrough.bind(this);
@@ -626,6 +636,7 @@ export default class IFrameNavigator implements Navigator {
if (this.chapterTitle) this.chapterTitle.style.display = "none";
if (this.chapterPosition) this.chapterPosition.style.display = "none";
+ if (this.remainingPositions) this.remainingPositions.style.display = "none";
if (this.eventHandler) {
this.eventHandler.onInternalLink = this.handleInternalLink.bind(this);
this.eventHandler.onClickThrough = this.handleClickThrough.bind(this);
@@ -644,10 +655,12 @@ export default class IFrameNavigator implements Navigator {
if (this.annotationModule !== undefined) {
this.annotationModule.drawHighlights()
} else {
- await this.highlighter.destroyAllhighlights(this.iframe.contentDocument)
- this.searchModule.drawSearch()
+ if (oc(this.rights).enableSearch(false)) {
+ await this.highlighter.destroyAllhighlights(this.iframe.contentDocument)
+ this.searchModule.drawSearch()
+ }
}
- }, 100);
+ }, 200);
}
@@ -872,7 +885,7 @@ export default class IFrameNavigator implements Navigator {
}
private async handleIFrameLoad(): Promise {
- this.errorMessage.style.display = "none";
+ if (this.errorMessage) this.errorMessage.style.display = "none";
this.showLoadingMessageAfterDelay();
try {
let bookViewPosition = 0;
@@ -887,7 +900,6 @@ export default class IFrameNavigator implements Navigator {
setTimeout(() => {
this.reflowable.goToPosition(bookViewPosition);
- this.updatePositionInfo();
}, 100);
setTimeout(() => {
@@ -900,7 +912,9 @@ export default class IFrameNavigator implements Navigator {
let currentLocation = this.currentChapterLink.href
- this.updatePositionInfo();
+ setTimeout(() => {
+ this.updatePositionInfo();
+ }, 200);
const previous = this.publication.getPreviousSpineItem(currentLocation);
if (previous && previous.href) {
@@ -1050,6 +1064,13 @@ export default class IFrameNavigator implements Navigator {
}, 100);
+ setTimeout(async () => {
+
+ if (this.timelineModule !== undefined) {
+ await this.timelineModule.initialize()
+ }
+
+ }, 100);
return new Promise(resolve => resolve());
@@ -1061,7 +1082,7 @@ export default class IFrameNavigator implements Navigator {
}
private abortOnError() {
- this.errorMessage.style.display = "block";
+ if (this.errorMessage) this.errorMessage.style.display = "block";
if (this.isLoading) {
this.hideLoadingMessage();
}
@@ -1150,20 +1171,13 @@ export default class IFrameNavigator implements Navigator {
private hideModal(modal: HTMLDivElement, control?: HTMLAnchorElement | HTMLButtonElement) {
// Restore the page for screen readers.
this.iframe.setAttribute("aria-hidden", "false");
- if (this.upLink) {
- this.upLink.setAttribute("aria-hidden", "false");
- }
- if (this.linksBottom) {
- this.linksBottom.setAttribute("aria-hidden", "false");
- }
- if (this.linksMiddle) {
- this.linksMiddle.setAttribute("aria-hidden", "false");
- }
- this.loadingMessage.setAttribute("aria-hidden", "false");
- this.errorMessage.setAttribute("aria-hidden", "false");
- this.infoTop.setAttribute("aria-hidden", "false");
- this.infoBottom.setAttribute("aria-hidden", "false");
-
+ if (this.upLink) this.upLink.setAttribute("aria-hidden", "false");
+ if (this.linksBottom) this.linksBottom.setAttribute("aria-hidden", "false");
+ if (this.linksMiddle) this.linksMiddle.setAttribute("aria-hidden", "false");
+ if (this.loadingMessage) this.loadingMessage.setAttribute("aria-hidden", "false");
+ if (this.errorMessage) this.errorMessage.setAttribute("aria-hidden", "false");
+ if (this.infoTop) this.infoTop.setAttribute("aria-hidden", "false");
+ if (this.infoBottom) this.infoBottom.setAttribute("aria-hidden", "false");
this.hideElement(modal, control);
}
@@ -1274,7 +1288,25 @@ export default class IFrameNavigator implements Navigator {
this.stopReadAloud();
this.navigate(position);
}
+ currentLocator():Locator {
+ let positions = this.publication.positionsByHref(this.publication.getRelativeHref(this.currentTOCRawLink));
+ let positionIndex = Math.ceil(this.reflowable.getCurrentPosition() * (positions.length - 1))
+ let position = positions[positionIndex]
+ position.locations.progression = this.reflowable.getCurrentPosition()
+ position.displayInfo = {
+ resourceScreenIndex : Math.round(this.reflowable.getCurrentPage()),
+ resourceScreenCount : Math.round(this.reflowable.getPageCount())
+ }
+ return position
+ }
+ positions():any {
+ return this.publication.positions
+ }
+ goToPosition(position:number) {
+ let locator = this.publication.positions.filter((el: Locator) => el.locations.position == position)[0]
+ goTo(locator)
+ }
private handlePreviousPageClick(event: MouseEvent | TouchEvent | KeyboardEvent): void {
this.stopReadAloud();
if(this.reflowable.isPaginated()) {
@@ -1479,18 +1511,24 @@ export default class IFrameNavigator implements Navigator {
if (this.annotationModule !== undefined) {
this.annotationModule.handleResize()
} else {
- this.searchModule.handleResize()
+ if (oc(this.rights).enableSearch(false)) {
+ this.searchModule.handleResize()
+ }
}
}, 100);
}
updatePositionInfo() {
if(this.reflowable.isPaginated()) {
- const currentPage = Math.round(this.reflowable.getCurrentPage());
- const pageCount = Math.round(this.reflowable.getPageCount());
+ const locator = this.currentLocator()
+ const currentPage = locator.displayInfo.resourceScreenIndex
+ const pageCount = locator.displayInfo.resourceScreenCount
+ const remaining = locator.locations.remainingPositions;
if (this.chapterPosition) this.chapterPosition.innerHTML = "Page " + currentPage + " of " + pageCount;
+ if (this.remainingPositions) this.remainingPositions.innerHTML = remaining + " left in chapter";
} else {
if (this.chapterPosition) this.chapterPosition.innerHTML = "";
+ if (this.remainingPositions) this.remainingPositions.innerHTML = "";
}
}
@@ -1603,8 +1641,10 @@ export default class IFrameNavigator implements Navigator {
this.annotationModule.drawHighlights()
this.annotationModule.showHighlights();
} else {
- await this.highlighter.destroyAllhighlights(this.iframe.contentDocument)
- this.searchModule.drawSearch()
+ if (oc(this.rights).enableSearch(false)) {
+ await this.highlighter.destroyAllhighlights(this.iframe.contentDocument)
+ this.searchModule.drawSearch()
+ }
}
if(this.reflowable.isScrollmode()) {
@@ -1657,7 +1697,7 @@ export default class IFrameNavigator implements Navigator {
private showLoadingMessageAfterDelay() {
this.isLoading = true;
setTimeout(() => {
- if (this.isLoading) {
+ if (this.isLoading && this.loadingMessage) {
this.loadingMessage.style.display = "block";
this.loadingMessage.classList.add("is-loading");
}
@@ -1671,8 +1711,10 @@ export default class IFrameNavigator implements Navigator {
private hideLoadingMessage() {
this.isLoading = false;
- this.loadingMessage.style.display = "none";
- this.loadingMessage.classList.remove("is-loading");
+ if (this.loadingMessage) {
+ this.loadingMessage.style.display = "none";
+ this.loadingMessage.classList.remove("is-loading");
+ }
}
private async saveCurrentReadingPosition(): Promise {
diff --git a/src/styles/sass/reader.scss b/src/styles/sass/reader.scss
index 641f2110..a6e621eb 100644
--- a/src/styles/sass/reader.scss
+++ b/src/styles/sass/reader.scss
@@ -43,3 +43,5 @@
@import "reader/toolbox";
// TTS ui tools
@import "reader/tts";
+
+@import "reader/timeline";
diff --git a/src/styles/sass/reader/_global.scss b/src/styles/sass/reader/_global.scss
index 532523ea..95f43e52 100644
--- a/src/styles/sass/reader/_global.scss
+++ b/src/styles/sass/reader/_global.scss
@@ -60,6 +60,11 @@
box-sizing: border-box;
}
+ #reader-info-bottom {
+ bottom: 0px;
+ position: fixed;
+ width: 100%;
+ }
.info {
color: $ui-dark-gray;
margin: 0;
@@ -87,7 +92,8 @@
}
.chapter-position,
- .chapter-title {
+ .chapter-title,
+ .remaining-positions {
font-size: 0.85rem;
font-variant-numeric: lining-nums tabular-nums;
}
diff --git a/src/styles/sass/reader/_timeline.scss b/src/styles/sass/reader/_timeline.scss
new file mode 100644
index 00000000..a0a8927c
--- /dev/null
+++ b/src/styles/sass/reader/_timeline.scss
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2018-2020 DITA (AM Consulting LLC)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Developed on behalf of: DITA
+ * Licensed to: Bibliotheca LLC, Bokbasen AS and CAST under one or more contributor license agreements.
+ */
+
+ .timeline {
+ position: fixed;
+ top: 1.5rem;
+ left: 2.5rem;
+ bottom: 1.5rem;
+ width: 1.25rem;
+ .chapter {
+ border-left: 6px solid transparent;
+ transition: border-color 300ms ease-out;
+ background: $ui-light-gray;
+ border: 1px solid $ui-white;
+ border-radius: 5px;
+ position: relative;
+ &:hover {
+ background-color: $ui-light-gray;
+ .chapter-tooltip {
+ display: block;
+ }
+ }
+ &.active {
+ background-color: $ui-default;
+ }
+ }
+ .chapter-tooltip {
+ display: none;
+ position: absolute;
+ left: 2rem;
+ top: 50%;
+ transform: translate(0,-50%);
+ background: $ui-light-gray;
+ padding: 0.25rem 0.5rem;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ max-width: 22rem;
+ }
+}
+
+
+
diff --git a/viewer/index.html b/viewer/index.html
new file mode 100644
index 00000000..6b0a25db
--- /dev/null
+++ b/viewer/index.html
@@ -0,0 +1,664 @@
+
+
+
+
+
+
+ D2 Reader
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/viewer/index_api.html b/viewer/index_api.html
index 8add6df7..5d2320f6 100644
--- a/viewer/index_api.html
+++ b/viewer/index_api.html
@@ -25,7 +25,8 @@
-
+
@@ -42,7 +43,7 @@
-
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
@@ -69,7 +76,16 @@
-
+
+
+
@@ -84,7 +100,8 @@
-
- - D2 Reader
+
- D2 Reader
import_contacts
unfold_more
-
-
-
+
-
-
+
@@ -139,24 +147,24 @@
-
- - Day
-
+
keyboard_arrow_up
-
+
keyboard_arrow_down
@@ -273,22 +283,46 @@
- close
+ close
- delete
+ delete