Änderungen von Dokument Script
Zuletzt geändert von Daniel Herrmann am 2026/02/07 23:23
Von Version
4.1
bearbeitet von Daniel Herrmann
am 2026/02/04 20:22
am 2026/02/04 20:22
Änderungskommentar:
Install extension [org.xwiki.platform:xwiki-platform-annotation-ui/17.10.3]
Auf Version
5.1
bearbeitet von Daniel Herrmann
am 2026/02/07 23:23
am 2026/02/07 23:23
Änderungskommentar:
Install extension [org.xwiki.platform:xwiki-platform-annotation-ui/18.0.1]
Zusammenfassung
Details
- XWiki.JavaScriptExtension[0]
-
- Code
-
... ... @@ -37,11 +37,13 @@ 37 37 return; 38 38 } 39 39 this.range = window.getSelection().getRangeAt(0); 40 - // ignore if the selection is in the passed container 40 + // ignore if the selection is not in the passed container 41 41 if (!this.isDescendantOrSelf(this.container, this.range.commonAncestorContainer)) { 42 42 return; 43 43 } 44 44 this.selectionText = this.range.toString(); 45 + this.selectionText = this.selectionText.replaceAll( 46 + "$escapetool.xml($services.localization.render('annotations.annotated.highlight.toggle.hint'))",'') 45 45 if (this.selectionText.strip() == '') { 46 46 this.selectionText = false; 47 47 } ... ... @@ -61,13 +61,15 @@ 61 61 62 62 // these functions are here because they depend on selection 63 63 64 - highlightSelection : function( color) {66 + highlightSelection : function() { 65 65 if (!this.range) { 66 66 // there should be some selection at this point 67 67 return; 68 68 } 69 69 // create an annotation highlight span around this content 70 - var highlightWrapperTemplate = new Element('span', {'style': 'background-color: ' + color, 'class' : 'selection-highlight'}); 72 + var highlightWrapperTemplate = new Element('mark', { 73 + 'class': 'selection-highlight' 74 + }); 71 71 // get all the text nodes of this range 72 72 var rangeTextNodes = this.getRangeTextNodes(); 73 73 // and remove all the ranges in this selection, otherwise so messed up things will happen - Inhalt parsen
-
... ... @@ -1,1 +1,1 @@ 1 - Nein1 +Ja
- XWiki.JavaScriptExtension[1]
-
- Code
-
... ... @@ -67,7 +67,7 @@ 67 67 require(['jquery'], ($) => { 68 68 $(document).on('click', 'blockquote.annotatedText', (event) => { 69 69 if(this.fetchedAnnotations) { 70 - // In this case the annotations are just made visible (if need be). This is synchrnous and 70 + // In this case the annotations are just made visible (if need be). This is synchronous and 71 71 // we can just let the click event bubble up. 72 72 this.setAnnotationVisibility(true); 73 73 this.toggleAnnotations(this.displayingAnnotations); ... ... @@ -247,8 +247,6 @@ 247 247 if (this.displayHighlight) { 248 248 this.annotatedElement.select('.annotation').invoke('toggleClassName', 'annotation-highlight', !!visible); 249 249 } 250 - // Toggle all annotation markers. 251 - this.annotatedElement.select('.annotation-marker').invoke('toggleClassName', 'hidden', !visible); 252 252 this.setAnnotationVisibility(visible); 253 253 if (!visible) { 254 254 // Close all open bubbles. ... ... @@ -345,7 +345,7 @@ 345 345 } 346 346 // hide message at page bottom 347 347 this._x_notification.hide(); 348 - // Load the received annotations, along with annotations markers.346 + // Load the received annotations, along with annotations highlights. 349 349 this.loadAnnotations(response.responseJSON.annotatedContent, andShow, false, force); 350 350 // store the state of the annotations 351 351 this.fetchedAnnotations = true; ... ... @@ -406,39 +406,87 @@ 406 406 if (plainTextStartOffset.value === null || plainTextEndOffset.value === null) { 407 407 return false; 408 408 } 409 - 410 410 var annDOMRange = this.getDOMRange(this.annotatedElement, plainTextStartOffset.value, plainTextEndOffset.value); 411 411 // Since the annotation could start at a specific offset, the node is splitted for not wrapping the whole text and 412 412 // a new range is created to recalculate the new ends. 413 413 var strictRange = this.fixRangeEndPoints(annDOMRange); 414 414 415 - // Wrap each text node from this range inside an annotation markup SPAN.412 + // Wrap each text node from this range inside an annotation markup node. 416 416 this.getTextNodesInRange(strictRange).forEach(textNode => this.markAnnotation(textNode, ann)); 417 - 418 - // Add the marker span after the last span of this annotation. 419 419 var allSpans = this.annotatedElement.select('[class~=ID' + ann.annotationId + ']'); 420 420 if (!allSpans.length) { 421 421 return; 422 422 } 423 - var lastSpan = allSpans[allSpans.length - 1]; 424 - // Create the annotation markers hidden by default, since annotations are added on the document hidden by default. 425 - var markerSpan = new Element('span', {'id': 'ID' + ann.annotationId, 'class' : 'hidden annotation-marker ' + ann.state}); 426 - lastSpan.insert({after: markerSpan}); 427 - // Annotations are displayed on mouseover. 428 - markerSpan.observe('click', this.onMarkerClick.bindAsEventListener(this, ann.annotationId)); 418 + // We generate the keyboard interactive button. 419 + let toggleButton = document.createElement('button'); 420 + toggleButton.id = 'ID' + ann.annotationId; 421 + toggleButton.classList.add('annotation-toggle'); 422 + toggleButton.classList.add('sr-only'); 423 + toggleButton.classList.add('btn'); 424 + toggleButton.classList.add('btn-xs'); 425 + toggleButton.classList.add('btn-warning'); 426 + toggleButton.innerHTML = "$!escapetool.javascript($services.icon.renderHTML('note'))"; 427 + // Add the button at the end of the last mark for the annotation. 428 + allSpans[allSpans.length-1].appendChild(toggleButton); 429 + // Add the onclick listener on every span of this annotation. 430 + allSpans.forEach(markNode => markNode.addEventListener('click', (event) => 431 + this.onMarkerClick(event, ann.annotationId))); 432 + // Clicking specifically on the toggle blocks any other event, such as a page reload if the parent is an anchor 433 + // The other event can still be triggered by clicking on the other text in it. 434 + toggleButton.addEventListener('click', (event) => { 435 + this.onMarkerClick(event, ann.annotationId); 436 + event.stopPropagation(); 437 + event.preventDefault(); 438 + }); 439 + let onMarkerHover = function(event, allSpans) { 440 + allSpans.forEach((span) => span.classList.add('hovered')); 441 + }; 442 + let onMarkerHoverLeft = function(event, allSpans) { 443 + allSpans.forEach((span) => span.classList.remove('hovered')); 444 + }; 445 + // Add the hover listener on every span to highlight every part of its annotation 446 + allSpans.forEach(function(markNode){ 447 + markNode.addEventListener('mouseover', (event) => onMarkerHover(event, allSpans)); 448 + markNode.addEventListener('mouseout', (event) => onMarkerHoverLeft(event, allSpans)); 449 + }); 450 + // Only the button can be focused with the keyboard. 451 + toggleButton.addEventListener('focus', (event) => { 452 + event.target.classList.remove('sr-only'); 453 + onMarkerHover(event, allSpans); 454 + }); 455 + toggleButton.addEventListener('focusout', (event) => { 456 + event.target.classList.add('sr-only'); 457 + onMarkerHoverLeft(event, allSpans); 458 + }); 429 429 }, 430 430 461 + addAnnotationsToggleHint : function(annotations) { 462 + annotations.forEach((ann) => { 463 + var plainTextStartOffset = ann.fields.find(field => field.name === 'plainTextStartOffset'); 464 + var plainTextEndOffset = ann.fields.find(field => field.name === 'plainTextEndOffset'); 465 + if(plainTextStartOffset.value === null || plainTextEndOffset.value === null) { 466 + return false; 467 + } 468 + let toggleButtonDescription = document.createElement('span'); 469 + toggleButtonDescription.addClassName('sr-only'); 470 + toggleButtonDescription.id = 'annotation-toggle-description-' + ann.annotationId; 471 + toggleButtonDescription.textContent = 472 + "$escapetool.xml($services.localization.render('annotations.annotated.highlight.toggle.hint'))"; 473 + let toggleButton = document.getElementById('ID' + ann.annotationId); 474 + toggleButton.appendChild(toggleButtonDescription); 475 + }); 476 + }, 477 + 431 431 /** 432 - * Surround this node with a span corresponding to it 's annotation.479 + * Surround this node with a span corresponding to its annotation. 433 433 * 434 434 * @param markedNode the node that corresponds to the current annotation 435 435 * @param ann object holding information about the annotation 436 436 */ 437 437 markAnnotation: function(markedNode, ann) { 438 - var wrapper = document.createElement(' span');485 + var wrapper = document.createElement('mark'); 439 439 wrapper.addClassName('annotation'); 440 440 wrapper.addClassName('ID' + ann.annotationId); 441 - 442 442 var parentNode = markedNode.parentElement; 443 443 parentNode.replaceChild(wrapper, markedNode); 444 444 wrapper.appendChild(markedNode); ... ... @@ -594,13 +594,13 @@ 594 594 }, 595 595 596 596 /** 597 - * Remove the wrapper and markerofannotations. The selection highlight is also removed in case the new annotation643 + * Remove the wrappers for annotations. The selection highlight is also removed in case the new annotation 598 598 * was deleted. 599 599 */ 600 600 removeAnnotationsAndSelectionMarkups: function() { 601 - document.querySelectorAll(" span.annotation,span.selection-highlight")647 + document.querySelectorAll("mark.annotation, mark.selection-highlight") 602 602 .forEach(annotationNode => annotationNode.replaceWith(...annotationNode.childNodes)); 603 - document.querySelectorAll("span .annotation-marker").forEach(marker =>marker.remove());649 + document.querySelectorAll("button.annotation-toggle, span[id^='annotation-toggle-description-']").forEach(description => description.remove()); 604 604 this.fetchedAnnotations = false; 605 605 }, 606 606 ... ... @@ -752,8 +752,7 @@ 752 752 } 753 753 }.bindAsEventListener(this)); 754 754 }, 755 - 756 - // maybe this should be moved in a function to display a bubble from an address, to call for all dialogs for different parameters 801 + 757 757 onMarkerClick : function(event, id) { 758 758 var bubbleId = 'annotation-bubble-' + id; 759 759 var bubble = $(bubbleId); ... ... @@ -765,9 +765,23 @@ 765 765 this.hideBubble(bubble); 766 766 } else { 767 767 // Show the bubble and fetch the annotation display in it. 768 - var bubble = this.displayLoadingBubble(event.element().cumulativeOffset().top, 769 - event.element().cumulativeOffset().left, true); 813 + // If the event is triggered via keyboard, we use the annotation highlight position instead of the event position 814 + let bubbleCoordX,bubbleCoordY ; 815 + if (event.pageX === 0 && event.pageY === 0) { 816 + let highlight = event.target; 817 + let rect = highlight.getBoundingClientRect(); 818 + bubbleCoordX = rect.left + window.scrollX + highlight.offsetWidth / 2; 819 + bubbleCoordY = rect.top + window.scrollY + highlight.offsetHeight / 2; 820 + } 821 + else { 822 + bubbleCoordX = event.pageX; 823 + bubbleCoordY = event.pageY; 824 + } 825 + var bubble = this.displayLoadingBubble(bubbleCoordX, bubbleCoordY, true); 770 770 bubble.writeAttribute('id', bubbleId); 827 + // Link semantically the clicked mark to the generated bubble 828 + // Adding this info earlier is useless since the element is not even loaded before the click. 829 + event.target.writeAttribute('aria-details', bubbleId); 771 771 this.fetchAndShowAnnotationDetails(id, bubble); 772 772 } 773 773 }, ... ... @@ -799,7 +799,7 @@ 799 799 }.bind(this)); 800 800 }, 801 801 802 - displayLoadingBubble : function( verticalCoordinate,horizontalCoordinate, hasMarker) {861 + displayLoadingBubble : function(horizontalCoordinate, verticalCoordinate) { 803 803 // create a bubble element wrapping the form 804 804 var bubble = new Element('div', {'class' : 'annotation-bubble'}); 805 805 // Add a nice loading panel inside ... ... @@ -815,18 +815,15 @@ 815 815 // By default, we position it to the right and under the coordinates 816 816 // Position it horizontally. 817 817 let bubbleStyles = getComputedStyle(bubble); 818 - // The width in pixels of the annotation marker added in the text. 819 - const markerWidth = 10; 820 - let markerOffset = hasMarker ? -markerWidth : 0; 821 - let bubbleWidth = bubble.offsetWidth 877 + let bubbleWidth = bubble.offsetWidth 822 822 + parseInt(bubbleStyles.marginLeft) 823 823 + parseInt(bubbleStyles.marginRight); 824 824 if (horizontalCoordinate < bubbleWidth 825 - || horizontalCoordinate + markerOffset +bubbleWidth < window.innerWidth) {881 + || horizontalCoordinate + bubbleWidth < window.innerWidth) { 826 826 bubble.style.left = horizontalCoordinate + 'px'; 827 827 } else { 828 828 // There isn't enough space on the right of the window to fit the modal, and there is enough on the left. 829 - bubble.style.left = (horizontalCoordinate + markerOffset- bubbleWidth) + 'px';885 + bubble.style.left = (horizontalCoordinate - bubbleWidth) + 'px'; 830 830 bubble.toggleClassName('annotation-bubble-position-left'); 831 831 } 832 832 ... ... @@ -833,9 +833,6 @@ 833 833 return bubble; 834 834 }, 835 835 836 - displayAnnotationViewBubble : function(marker) { 837 - }, 838 - 839 839 /** 840 840 * Updates the container with the passed content only if the container is still displayed, and returns true if this is the case. 841 841 */ ... ... @@ -1028,6 +1028,11 @@ 1028 1028 // Cancel the edit otherwise the user will be asked for confirmation when leaving the page. 1029 1029 document.fire('xwiki:actions:cancel'); 1030 1030 1084 + // Remove all the places where the bubble id is used as an attribute on other elements. 1085 + document.querySelectorAll('[aria-details="' + bubble.id + '"]').forEach((mark) => { 1086 + mark.removeAttribute('aria-details'); 1087 + }); 1088 + 1031 1031 bubble.remove(); 1032 1032 var bubbleIndex = this.bubbles.indexOf(bubble); 1033 1033 if (bubbleIndex >= 0) { ... ... @@ -1148,11 +1148,10 @@ 1148 1148 }, 1149 1149 1150 1150 displayAnnotationCreationForm : function() { 1151 - // TODO: get this color from the color theme 1152 - this.selectionService.highlightSelection('#FFEE99'); 1209 + this.selectionService.highlightSelection(); 1153 1153 // get the position and build the loading bubble 1154 1154 var position = this.selectionService.getPositionNextToSelection(); 1155 - this.createPanel = this.displayLoadingBubble(position.t op, position.left, false);1212 + this.createPanel = this.displayLoadingBubble(position.left, position.top, false); 1156 1156 // remove the ctrl + M listeners, so that only one dialog is displayed at one moment 1157 1157 this.unregisterAddAnnotationShortcut(); 1158 1158 }, ... ... @@ -1335,6 +1335,8 @@ 1335 1335 this.removeAnnotationsAndSelectionMarkups(); 1336 1336 this.updateAnnotationsOffsets(annotatedContent); 1337 1337 this.addAnnotationsMarkup(annotatedContent.annotations); 1395 + // We add the hints afterwards because they shift the ranges in the text. 1396 + this.addAnnotationsToggleHint(annotatedContent.annotations); 1338 1338 // Notify the content change. 1339 1339 $(document).trigger('xwiki:dom:updated', {'elements': [this.annotatedElement]}); 1340 1340 // Also handle the tab 'downstairs' when the annotations list changes.