diff --git a/src/menu/menu.controller.ts b/src/menu/menu.controller.ts index ddc69a9..e92a6f9 100644 --- a/src/menu/menu.controller.ts +++ b/src/menu/menu.controller.ts @@ -25,11 +25,15 @@ module tagIt { this.webPageService = WebPageService; this.tagStorageService = TagStorageService; - // Wire up clicklistener - this.webPageService.wireUpListener( - this.onWordSelected, - this.onWordDeSelected - ); + this.$scope.$on('wordWasSelected', (event, selectedWord) => { + this.$log.debug('a word was selected' + selectedWord); + this.onWordSelectedEvent(selectedWord); + }); + + this.$scope.$on('wordWasSelected', (event, selectedWord) => { + this.$log.debug('a word was selected' + selectedWord); + this.onWordSelectedEvent(selectedWord); + }); // Reload existing tags var tagsToLoad = this.tagStorageService.loadTags(); @@ -40,6 +44,10 @@ module tagIt { this.webPageService.readdTagsToPage(tagsToLoad); } + /** + * Fired when user selects a sense amongst the senses + * returned from the backend. + */ onSenseSelect(sense: ISense) { //remove all tags so that new tag range is serialized //based on a document without any highlights @@ -57,11 +65,10 @@ module tagIt { }); } - onWordSelected = (newWord: string) => { - function countWords(wordWithUnderscores: string) { - return wordWithUnderscores.split("_").length; - } - + /** + * + */ + onWordSelectedEvent = (newWord: string) => { if (countWords(newWord) > 2) { this.selectedWord = "Wops! Plugin can't handle more than two words."; this.senses = []; @@ -72,24 +79,20 @@ module tagIt { else { this.selectedWord = newWord; this.backendService.callServer(newWord) - .then((synsets: Object) => { + .then((synsets: any) => { this.$log.debug(synsets); - this.senses = this.backendService.processSynsets(synsets); + this.senses = synsets.data.senses;; }); } - //call for a digest because clicklistener that triggers this - //function is unknown to Angular. - this.$scope.$apply(); + function countWords(wordWithUnderscores: string) { + return wordWithUnderscores.split("_").length; + } } - onWordDeSelected = () => { + onWordDeSelectedEvent = () => { this.$log.debug("onWordDeSelected"); this.clearMenuVariables() - // since the click did not originate from - // an ng-click or the like we need to - // do an explicit view refresh - this.$scope.$digest; } clearMenuVariables() { diff --git a/src/plugin-specific/manifest.json b/src/plugin-specific/manifest.json index 5b9e76f..8bf27d9 100644 --- a/src/plugin-specific/manifest.json +++ b/src/plugin-specific/manifest.json @@ -1,7 +1,7 @@ { "name": "Tag you're it", "description": "A browser tool for tagging words with exact definitions.", - "version": "0.0.4", + "version": "0.0.5", "manifest_version": 2, "icons": { "16": "icon16.png", diff --git a/src/services/backend.service.ts b/src/services/backend.service.ts index 02df4c3..d4563d7 100644 --- a/src/services/backend.service.ts +++ b/src/services/backend.service.ts @@ -25,10 +25,6 @@ module tagIt { return this.$http.get(this.serverUrl + this.createQuery(word)); } - processSynsets (synsets: ISynset) : string[] { - return synsets.data.senses; - } - sendTaggedDataToServer (senseTag: ISenseTag) { this.$log.debug('sendTaggedDataToServer() was called'); diff --git a/src/services/webpage.service.ts b/src/services/webpage.service.ts index c0206f8..50365f4 100644 --- a/src/services/webpage.service.ts +++ b/src/services/webpage.service.ts @@ -1,70 +1,60 @@ 'use strict'; module tagIt { - /** - * Takes care of figuring out what word - * is selected. - */ declare var rangy: any; declare var uuid: any; + /** + * This service is responsible for interfacing with the content + * that we want to tag. It injects/removes tags and listens for clicks. + */ export class WebPageService { $log: ng.ILogService; // when clicking the menu to select a synset // we need to remember what the currently selected word was tagStorageService: TagStorageService; + rootScopeService: ng.IRootScopeService; savedSelection: Object; + savedText: string; listOfFramesWithContent: (HTMLFrameElement | HTMLIFrameElement)[] = []; /* @ngInject */ - constructor($log: ng.ILogService, TagStorageService: TagStorageService) { + constructor($log: ng.ILogService, TagStorageService: TagStorageService, $rootScope: ng.IRootScopeService) { this.$log = $log; this.tagStorageService = TagStorageService; + this.rootScopeService = $rootScope; + this.wireUpListener(); rangy.init(); } - //this allows the menu controller to bind to the service so that it may - //notify the menu. TODO refactor to use events on $rootScope instead. - wireUpListener(callbackOnSelectFunc: (result: Object) => void, - callbackOnDeSelectFunc: () => void) { - const tagitBodyIframe = parent.document.getElementById('tagit-body'); - const tagitBodyIframeDoc = tagitBodyIframe.contentDocument; - - if (tagitBodyIframeDoc.getElementsByTagName('frameset').length > 0) { - _.map(tagitBodyIframeDoc.getElementsByTagName('frame'), - (frame) => { this.listOfFramesWithContent.push(frame) }); - } else { - this.listOfFramesWithContent.push(tagitBodyIframe); + /** + * The click listening function placed on each of the iframes + * we capture. We then listen for clicks and check if a word + * was selected. + */ + clickhandler = (evt: Event) => { + this.$log.debug('A click happened!'); + var documentThatWasClicked = evt.srcElement.ownerDocument; + if (!documentThatWasClicked.hasFocus()) { + return true; } - - _.map(this.listOfFramesWithContent, (frame: HTMLIFrameElement) => { - frame.contentDocument.addEventListener('click', (evt: Event) => { - this.$log.debug('A click happened!'); - var documentThatWasClicked = evt.srcElement.ownerDocument; - if (!documentThatWasClicked.hasFocus()) { - return true; - } - else if (wasRemoveTagButtonClicked(evt)) { - this.$log.debug('remove tag button was clicked'); - removeTagFromWebAndStorage(evt, this.tagStorageService); - } - else if (documentThatWasClicked.getSelection().toString() !== '') { - resetSavedSelection(); - this.savedSelection = rangy.saveSelection(documentThatWasClicked); - - callbackOnSelectFunc( - joinLongWords( - documentThatWasClicked.getSelection().toString() - ) - ); - } else { - callbackOnDeSelectFunc(); - } - }, false); - }); - + else if (wasRemoveTagButtonClicked(evt)) { + this.$log.debug('remove tag button was clicked'); + removeTagFromWebAndStorage(evt, this.tagStorageService); + } + else if (documentThatWasClicked.getSelection().toString() !== '') { + resetSavedSelection(); + this.savedSelection = rangy.saveSelection(documentThatWasClicked); + this.savedText = joinLongWords(documentThatWasClicked.getSelection().toString()); + this.rootScopeService.$broadcast('wordWasSelected', this.savedText); + } else { + resetSavedSelection(); + this.savedText = ''; + this.rootScopeService.$broadcast('wordWasDeSelected', null); + } + /** * Remove markers to ensure a clean page before * adding markers for a new page. @@ -78,9 +68,11 @@ module tagIt { function joinLongWords(possiblyLongWord: string) { return possiblyLongWord.trim().split(" ").join("_"); } + function wasRemoveTagButtonClicked(evt: any) { return evt.target.className === 'js-tagit-remove-tag'; } + function removeTagFromWebAndStorage(evt: Event, tagStorageService: TagStorageService) { var target = evt.target; var theOriginalTextNode = target.previousSibling; @@ -91,14 +83,24 @@ module tagIt { } } - findSelectedText(evt: Event) { - var selectedText = evt.srcElement.ownerDocument.getSelection().toString(); - if (selectedText) { - this.$log.debug('text that was selected: ' + selectedText); - return selectedText; + /** + * Find one or more iframes in which content has been captured. + * We then place click listeners on each of the frames. + */ + wireUpListener() { + const tagitBodyIframe = parent.document.getElementById('tagit-body'); + const tagitBodyIframeDoc = tagitBodyIframe.contentDocument; + + if (tagitBodyIframeDoc.getElementsByTagName('frameset').length > 0) { + _.map(tagitBodyIframeDoc.getElementsByTagName('frame'), + (frame) => { this.listOfFramesWithContent.push(frame) }); } else { - return; + this.listOfFramesWithContent.push(tagitBodyIframe); } + + _.map(this.listOfFramesWithContent, (frame: HTMLIFrameElement) => { + frame.contentDocument.addEventListener('click', this.clickhandler, false); + }); } /** @@ -109,32 +111,49 @@ module tagIt { removeAllTagsFromPage(callback: () => void) { //loop that will keep asking for spans to remove //until they are all gone. - const done = _.after(this.listOfFramesWithContent.length, callback); + const done = _.after(this.listOfFramesWithContent.length, () => { + callback(); + }); _.map(this.listOfFramesWithContent, (iframe) => { removeTagsFromIframe(iframe, done); }); - function removeTagsFromIframe(iframe: any, callback: () => void) { - while (iframe.getElementsByClassName('tagit-tag').length > 0) { - var spanElement = this.iframeDocument.getElementsByClassName('tagit-tag')[0]; + function removeTagsFromIframe(iframe: HTMLFrameElement | HTMLIFrameElement, + callback: () => void) { + while (iframe.contentDocument.getElementsByClassName('tagit-tag').length > 0) { + var spanElement = iframe.contentDocument.getElementsByClassName('tagit-tag')[0]; var spanElementParent = spanElement.parentNode; spanElementParent.replaceChild( spanElement.firstChild, spanElement); + /** + * call normalize on the parent element so that it will join up + * any text nodes that might have become chopped up by + * tags and selection markers. + * */ spanElementParent.normalize(); } callback(); } } + /** + * Handles adding tags to page which needs to happen in a + * bottom up order, so that all the tags find their right place. + */ readdTagsToPage(tagsToLoad: ISenseTag[]) { this.$log.debug('readdTagsToPage()'); //first deselect all places before we go to work this.removeAllRanges(); - //deserialize ranges, remove the ones that fail. + /** + * The tags need to be loaded (deserialized) so that + * they can be inserted properly. Deserialization might + * fail if the page has changed since it was tagged. Thus + * we remove tags that fail to load. + */ tagsToLoad = _.filter(tagsToLoad, (tagToLoad) => { try { tagToLoad.deserializedRange = rangy.deserializeRange( @@ -156,13 +175,18 @@ module tagIt { this.$log.debug('finished deserializing tags'); - //sort tags by ascending so that they can be properly inserted + /** + * sort tags descending (highest number = furthest down on page). + */ tagsToLoad = _.sortBy(tagsToLoad, (tag: ISenseTag) => { return tag.deserializedRange.startOffset; }); this.$log.debug('finished sorting tags'); + /** + * Loop through loaded tags and insert them to the page. + */ _.map(tagsToLoad, (tag: ISenseTag) => { if (tag.deserializedRange) { this.surroundRangeWithSpan(