diff --git a/package-lock.json b/package-lock.json index 63c6eaae..98246904 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@d-i-t-a/reader", - "version": "1.0.6", + "version": "1.0.7", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 4eed0e27..9daaaa0d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@d-i-t-a/reader", - "version": "1.0.6", + "version": "1.0.7", "description": "A viewer application for EPUB files.", "repository": "https://github.com/d-i-t-a/R2D2BC", "main": "src/index.js", diff --git a/src/index.ts b/src/index.ts index 4afbc278..218fc518 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,11 +14,13 @@ import Publication from "./model/Publication"; import BookmarkModule from "./modules/BookmarkModule"; import { UserSettings } from "./model/user-settings/UserSettings"; import AnnotationModule from "./modules/AnnotationModule"; +import TTSModule from "./modules/TTSModule"; var R2Settings: UserSettings; var R2Navigator: IFrameNavigator; var BookmarkModuleInstance: BookmarkModule; var AnnotationModuleInstance: AnnotationModule; +var TTSModuleInstance: TTSModule; export const IS_DEV = (process.env.NODE_ENV === "development" || process.env.NODE_ENV === "dev"); @@ -30,6 +32,7 @@ export async function unload() { R2Settings.stop() BookmarkModuleInstance.stop() AnnotationModuleInstance.stop() + TTSModuleInstance.stop() } export async function saveBookmark() { @@ -177,7 +180,10 @@ export async function load(config: ReaderConfig): Promise { delegate: R2Navigator, initialAnnotations: config.annotations }) - } + TTSModuleInstance = await TTSModule.create({ + annotationModule: AnnotationModuleInstance + }) + } return new Promise(resolve => resolve(R2Navigator)); } diff --git a/src/modules/AnnotationModule.ts b/src/modules/AnnotationModule.ts index 8f7c8eca..5d6214a5 100644 --- a/src/modules/AnnotationModule.ts +++ b/src/modules/AnnotationModule.ts @@ -47,7 +47,7 @@ export default class AnnotationModule implements ReaderModule { private highlightsView: HTMLDivElement; private headerMenu: HTMLElement; - private highlighter: TextHighlighter; + highlighter: TextHighlighter; private initialAnnotations: any; diff --git a/src/modules/TTSModule.ts b/src/modules/TTSModule.ts new file mode 100644 index 00000000..9cb86297 --- /dev/null +++ b/src/modules/TTSModule.ts @@ -0,0 +1,61 @@ +/* + * Project: R2D2BC - Web Reader + * Developers: Aferdita Muriqi + * Copyright (c) 2020. DITA. All rights reserved. + * Developed on behalf of: CAST (http://www.cast.org) + * Licensed to: Bokbasen AS and CAST under one or more contributor license agreements. + * Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. + */ + +import ReaderModule from "./ReaderModule"; +import AnnotationModule from "./AnnotationModule"; +import { IS_DEV } from ".."; +import { ISelectionInfo } from "../model/Locator"; + + +export interface TTSModuleConfig { + annotationModule: AnnotationModule; +} + +export default class TTSModule implements ReaderModule { + + annotationModule: AnnotationModule; + synth = window.speechSynthesis + + initialize() { + this.annotationModule.highlighter.ttsDelegate = this + } + + speak(selectionInfo: ISelectionInfo | undefined ): any { + console.log(selectionInfo.cleanText) + var self = this + var utterance = new SpeechSynthesisUtterance(selectionInfo.cleanText); + this.synth.cancel() + this.synth.speak(utterance); + utterance.onend = function () { + console.log("utterance ended"); + self.annotationModule.highlighter.doneSpeaking() + } + } + + public static async create(config: TTSModuleConfig) { + const annotations = new this( + config.annotationModule, + ); + await annotations.start(); + return annotations; + } + + public constructor(annotationModule: AnnotationModule) { + this.annotationModule = annotationModule + } + + protected async start(): Promise { + this.annotationModule.delegate.ttsModule = this + } + + async stop() { + if (IS_DEV) { console.log("TTS module stop")} + } + +} \ No newline at end of file diff --git a/src/modules/highlight/TextHighlighter.ts b/src/modules/highlight/TextHighlighter.ts index 8b8a438a..234ae45f 100644 --- a/src/modules/highlight/TextHighlighter.ts +++ b/src/modules/highlight/TextHighlighter.ts @@ -31,6 +31,7 @@ import AnnotationModule from "../AnnotationModule"; import { Annotation, AnnotationMarker } from "../../model/Locator"; import { IS_DEV } from "../.."; import { icons } from "../../utils/IconLib"; +import TTSModule from "../TTSModule"; export const ID_HIGHLIGHTS_CONTAINER = "R2_ID_HIGHLIGHTS_CONTAINER"; @@ -85,6 +86,7 @@ export default class TextHighlighter { el: HTMLElement; options: any; delegate:AnnotationModule; + ttsDelegate:TTSModule; lastSelectedHighlight:number = undefined; public constructor(delegate: AnnotationModule, element: HTMLElement, options: any) { @@ -625,6 +627,7 @@ export default class TextHighlighter { self.toolboxMode('add'); var highlightIcon = document.getElementById("highlightIcon"); var underlineIcon = document.getElementById("underlineIcon"); + var speakIcon = document.getElementById("speakIcon"); var colorIcon = document.getElementById("colorIcon"); highlightIcon.style.display = "unset"; @@ -640,6 +643,8 @@ export default class TextHighlighter { var colorIconSymbol = colorIcon.lastChild as HTMLElement; colorIconSymbol.style.backgroundColor = this.getColor(); + (speakIcon.getElementsByTagName("span")[0] as HTMLSpanElement).innerHTML = icons.speak; + // speaker_notes // add_comment // file_copy @@ -663,6 +668,13 @@ export default class TextHighlighter { } underlineIcon.addEventListener("click", commentEvent); + function speakEvent(){ + // self.doHighlight(false, AnnotationMarker.Underline); + speakIcon.removeEventListener("click", speakEvent); + self.speak(); + } + speakIcon.addEventListener("click", speakEvent); + var backdropButton = document.getElementById("toolbox-backdrop"); function backdropEvent(){ @@ -720,6 +732,40 @@ export default class TextHighlighter { } }; + speak() { + var self = this + function getCssSelector(element: Element): string { + const options = { + className: (str: string) => { + return _blacklistIdClassForCssSelectors.indexOf(str) < 0; + }, + idName: (str: string) => { + return _blacklistIdClassForCssSelectors.indexOf(str) < 0; + }, + }; + return uniqueCssSelector(element, self.dom(self.el).getDocument(), options); + } + + const selectionInfo = getCurrentSelectionInfo(this.dom(this.el).getWindow(), getCssSelector) + if (selectionInfo) { + // if (this.options.onBeforeHighlight(selectionInfo) === true) { + // var highlight = this.createHighlight(self.dom(self.el).getWindow(), selectionInfo, TextHighlighter.hexToColor(this.getColor()),true, marker) + // this.options.onAfterHighlight(highlight, marker); + // } + this.ttsDelegate.speak(selectionInfo as any); + + } + }; + + doneSpeaking() { + var toolbox = document.getElementById("highlight-toolbox"); + var backdrop = document.getElementById("toolbox-backdrop"); + + toolbox.style.display = "none"; + backdrop.style.display = "none"; + this.dom(this.el).removeAllRanges(); + } + /** * Normalizes highlights. Ensures that highlighting is done with use of the smallest possible number of * wrapping HTML elements. diff --git a/src/navigator/IFrameNavigator.ts b/src/navigator/IFrameNavigator.ts index 5027e49f..f2e9a7b7 100644 --- a/src/navigator/IFrameNavigator.ts +++ b/src/navigator/IFrameNavigator.ts @@ -22,6 +22,7 @@ import { UserSettingsUIConfig, UserSettings } from "../model/user-settings/UserS import BookmarkModule from "../modules/BookmarkModule"; import { IS_DEV } from ".."; import AnnotationModule from "../modules/AnnotationModule"; +import TTSModule from "../modules/TTSModule"; export interface UpLinkConfig { url?: URL; @@ -88,6 +89,7 @@ export default class IFrameNavigator implements Navigator { bookmarkModule?: BookmarkModule; annotationModule?: AnnotationModule; + ttsModule?: TTSModule; sideNavExanded: boolean = false material: boolean = false @@ -834,7 +836,12 @@ export default class IFrameNavigator implements Navigator { if (this.annotationModule !== undefined) { this.annotationModule.initialize() } - + setTimeout(() => { + if (this.ttsModule !== undefined) { + this.ttsModule.initialize() + } + }, 200); + }, 100); return new Promise(resolve => resolve()); @@ -1012,9 +1019,28 @@ export default class IFrameNavigator implements Navigator { this.handleNextChapterClick(null) } goTo(locator: Locator): any { + let locations: Locations = { + progression: 0 + } + if (locator.href.indexOf("#") !== -1) { + const elementId = locator.href.slice(locator.href.indexOf("#") + 1); + if (elementId !== null) { + locations = { + fragment: elementId + } + } + } + const position: Locator = { + href: locator.href, + locations: locations, + type: locator.type, + title: locator.title + }; const linkHref = this.publication.getAbsoluteHref(locator.href); - locator.href = linkHref - this.navigate(locator); + console.log(locator.href) + console.log(linkHref) + position.href = linkHref + this.navigate(position); } private handlePreviousPageClick(event: MouseEvent | TouchEvent | KeyboardEvent): void { diff --git a/src/utils/IconLib.ts b/src/utils/IconLib.ts index 52e2f522..7923f889 100644 --- a/src/utils/IconLib.ts +++ b/src/utils/IconLib.ts @@ -30,6 +30,7 @@ export const icons = { "delete" : iconTemplate(`delete-icon`, `Delete`, ``, `icon open`), "close" : iconTemplate(`close-icon`, `Close`, ``, `icon open`), "text" : iconTemplate(`text-icon`, `Text`, ``, `icon open`), + "speak" : iconTemplate(`speak-icon`, `Speak`, ``, `icon open`), "note": iconTemplate(`note-icon`, `Note`,``, `icon open`), "highlight": iconTemplate(`highlight-icon`, `Highlight`,``, `icon open`) } diff --git a/viewer/index_api.html b/viewer/index_api.html index 7f411626..686f9c0e 100644 --- a/viewer/index_api.html +++ b/viewer/index_api.html @@ -180,9 +180,10 @@
- + + + +
- + + + +