diff --git a/src/index.interfaces.ts b/src/index.interfaces.ts index 739e221..6d20015 100644 --- a/src/index.interfaces.ts +++ b/src/index.interfaces.ts @@ -31,6 +31,7 @@ module tagIt { userEmail: string; sense: ISense; context: string; + iframeIndex: number; wordThatWasTagged: string; serializedSelectionRange: string; deserializedRange?: Range; diff --git a/src/services/webpage.service.ts b/src/services/webpage.service.ts index 2f99ffa..b26d5ab 100644 --- a/src/services/webpage.service.ts +++ b/src/services/webpage.service.ts @@ -16,6 +16,7 @@ module tagIt { // we need to remember what the currently selected word was tagStorageService: TagStorageService; savedSelection: Object; + listOfFramesWithContent: HTMLIFrameElement[] = []; /* @ngInject */ constructor($log: ng.ILogService, TagStorageService: TagStorageService) { @@ -29,16 +30,17 @@ module tagIt { wireUpListener(callbackOnSelectFunc: (result: Object) => void, callbackOnDeSelectFunc: () => void) { var that = this; - var listOfFramesToListenTo: any[] = []; if ($('#tagit-body').find('frameset').length > 0) { $('#tagit-body').find('frame').each((index, elem) => { - listOfFramesToListenTo.push(elem); + this.listOfFramesWithContent.push(elem); }); } else { - listOfFramesToListenTo.push(parent.document.getElementById('tagit-body')); + this.listOfFramesWithContent.push( + parent.document.getElementById('tagit-body') + ); } - _.map(listOfFramesToListenTo, (frame: HTMLIFrameElement) => { + _.map(this.listOfFramesWithContent, (frame: HTMLIFrameElement) => { frame.contentDocument.addEventListener('click', (evt: Event) => { var documentThatWasClicked = evt.srcElement.ownerDocument; if (!documentThatWasClicked.hasFocus()) { @@ -46,30 +48,47 @@ module tagIt { } else if (wasRemoveTagButtonClicked(evt)) { this.$log.debug('remove tag button was clicked'); - removeTagFromWebpage(evt); - var elementClicked = evt.target; - this.tagStorageService.deleteTagById(elementClicked.parentElement.id); + removeTagFromWebAndStorage(evt); } - else if (this.findSelectedText()) { - this.updateSavedSelection(); - callbackOnSelectFunc(joinLongWords(this.findSelectedText())); + else if (documentThatWasClicked.getSelection()) { + resetSavedSelection(); + this.savedSelection = rangy.saveSelection(documentThatWasClicked); + callbackOnSelectFunc( + joinLongWords( + documentThatWasClicked.getSelection().toString() + ) + ); } else { callbackOnDeSelectFunc(); } }, false); }); + /** + * Remove markers to ensure a clean page before + * adding markers for a new page. + */ + function resetSavedSelection() { + if (this.savedSelection) { + rangy.removeMarkers(this.savedSelection); + } + } + function joinLongWords(possiblyLongWord: string) { return possiblyLongWord.trim().split(" ").join("_"); } function wasRemoveTagButtonClicked(evt: any) { return evt.target.className === 'js-tagit-remove-tag'; } - function removeTagFromWebpage(evt: any) { + function removeTagFromWebAndStorage(evt: any) { var theOriginalTextNode = evt.target.previousSibling; var theSurroundingSpanElement = evt.target.parentElement; theSurroundingSpanElement.parentNode .replaceChild(theOriginalTextNode, theSurroundingSpanElement); + + //need to remove tag + var elementClicked = evt.target; + this.tagStorageService.deleteTagById(elementClicked.parentElement.id); } } @@ -83,33 +102,46 @@ module tagIt { } } + /** + * Will loop through all content iframes and empty them for + * tags added by us. This is because we need to position a + * new tag in relation to a clean DOM. + * */ removeAllTagsFromPage(callback: () => void) { //loop that will keep asking for spans to remove //until they are all gone. - while (this.iframeDocument.getElementsByClassName('tagit-tag').length > 0) { - var spanElement = this.iframeDocument.getElementsByClassName('tagit-tag')[0]; - var spanElementParent = spanElement.parentNode; - spanElementParent.replaceChild( - spanElement.firstChild, - spanElement); - spanElementParent.normalize(); + 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]; + var spanElementParent = spanElement.parentNode; + spanElementParent.replaceChild( + spanElement.firstChild, + spanElement); + spanElementParent.normalize(); + } + callback(); } - callback(); } readdTagsToPage(tagsToLoad: ISenseTag[]) { this.$log.debug('readdTagsToPage()'); - //first deselect before we go to work - this.iframeDocument.getSelection().removeAllRanges(); + //first deselect all places before we go to work + this.removeAllRanges(); //deserialize ranges, remove the ones that fail. tagsToLoad = _.filter(tagsToLoad, (tagToLoad) => { try { tagToLoad.deserializedRange = rangy.deserializeRange( tagToLoad.serializedSelectionRange, - this.iframeDocument.documentElement, - this.iframeWindow + this.listOfFramesWithContent[tagToLoad.iframeIndex].ownerDocument, + this.listOfFramesWithContent[tagToLoad.iframeIndex] ); return true; } catch (e) { @@ -134,21 +166,30 @@ module tagIt { _.map(tagsToLoad, (tag: ISenseTag) => { if (tag.deserializedRange) { - this.surroundRangeWithSpan(tag.sense, - tag.deserializedRange, tag.id); + this.surroundRangeWithSpan( + this.listOfFramesWithContent[tag.iframeIndex].ownerDocument, + tag.sense, + tag.deserializedRange, + tag.id); } }); this.$log.debug('finished adding tags to page'); - this.iframeDocument.getSelection().removeAllRanges(); + this.removeAllRanges(); } initializeNewTag = (sense: ISense): ISenseTag => { this.$log.debug('initializeNewTag'); - rangy.restoreSelection(this.savedSelection); + + /** + * first eliminate all selections to avoid confusion + * with other iframes. + */ + this.removeAllRanges(); + var selection: Selection = rangy.restoreSelection(this.savedSelection); - var range: Range = rangy.getSelection(this.iframeDocument).getRangeAt(0); - var serializedRange = rangy.serializeRange(range, true, this.iframeDocument.documentElement); + var range: Range = selection.getRangeAt(0); + var serializedRange = rangy.serializeRange(range, true, selection.anchorNode.ownerDocument); var generatedUuid: string = uuid.v4(); var parentElement = range.commonAncestorContainer; @@ -156,23 +197,38 @@ module tagIt { id: generatedUuid, userEmail: 'testEmail', sense: sense, - wordThatWasTagged: this.findSelectedText(), + wordThatWasTagged: selection.toString(), + iframeIndex: getIframeIndex(this.listOfFramesWithContent, selection), context: parentElement.innerText, serializedSelectionRange: serializedRange } + + /** + * Some pages might have many frames with content. Thus we + * need to loop through them, identify which document contains the current + * selection and then save its index in our list. + */ + function getIframeIndex(iframeList: HTMLIFrameElement[], selection: Selection) { + for (var i = 0; i < iframeList.length; i++) { + var iframe = iframeList[i]; + if (iframe.ownerDocument === selection.anchorNode.ownerDocument) { + return i; + } + } + return 0; + } }; - private updateSavedSelection() { - if (this.savedSelection) { - rangy.removeMarkers(this.savedSelection); - } - this.savedSelection = rangy.saveSelection(this.iframeWindow); + private removeAllRanges = () => { + _.map(this.listOfFramesWithContent, (iframe) => { + iframe.contentDocument.getSelection().removeAllRanges(); + }); } - private surroundRangeWithSpan(sense: ISense, range: Range, uuid: string) { + private surroundRangeWithSpan(documentToManipulate: HTMLDocument, sense: ISense, range: Range, uuid: string) { // add span around content - var span: HTMLSpanElement = this.iframeDocument.createElement('span'); + var span: HTMLSpanElement = documentToManipulate.createElement('span'); span.id = uuid; span.title = sense.explanation; span.className = 'tagit-tag'; @@ -180,9 +236,9 @@ module tagIt { range.surroundContents(span); // add a button for removing the tag. - var btn = this.iframeDocument.createElement("BUTTON"); + var btn = documentToManipulate.createElement("BUTTON"); btn.className = 'js-tagit-remove-tag'; - btn.appendChild(this.iframeDocument.createTextNode("X")); + btn.appendChild(documentToManipulate.createTextNode("X")); span.appendChild(btn); } }