Änderungen von Dokument Script
Zuletzt geändert von Daniel Herrmann am 2026/02/07 23:23
Von 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]
Auf Version
2.1
bearbeitet von Daniel Herrmann
am 2025/07/19 16:47
am 2025/07/19 16:47
Änderungskommentar:
Install extension [org.xwiki.platform:xwiki-platform-annotation-ui/17.5.0]
Zusammenfassung
Details
- XWiki.JavaScriptExtension[0]
-
- Code
-
... ... @@ -37,13 +37,11 @@ 37 37 return; 38 38 } 39 39 this.range = window.getSelection().getRangeAt(0); 40 - // ignore if the selection is notin the passed container40 + // ignore if the selection is 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'))",'') 47 47 if (this.selectionText.strip() == '') { 48 48 this.selectionText = false; 49 49 } ... ... @@ -63,15 +63,13 @@ 63 63 64 64 // these functions are here because they depend on selection 65 65 66 - highlightSelection : function() { 64 + highlightSelection : function(color) { 67 67 if (!this.range) { 68 68 // there should be some selection at this point 69 69 return; 70 70 } 71 71 // create an annotation highlight span around this content 72 - var highlightWrapperTemplate = new Element('mark', { 73 - 'class': 'selection-highlight' 74 - }); 70 + var highlightWrapperTemplate = new Element('span', {'style': 'background-color: ' + color, 'class' : 'selection-highlight'}); 75 75 // get all the text nodes of this range 76 76 var rangeTextNodes = this.getRangeTextNodes(); 77 77 // and remove all the ranges in this selection, otherwise so messed up things will happen - Inhalt parsen
-
... ... @@ -1,1 +1,1 @@ 1 - Ja1 +Nein
- 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 synchr onous and70 + // In this case the annotations are just made visible (if need be). This is synchrnous and 71 71 // we can just let the click event bubble up. 72 72 this.setAnnotationVisibility(true); 73 73 this.toggleAnnotations(this.displayingAnnotations); ... ... @@ -247,6 +247,8 @@ 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); 250 250 this.setAnnotationVisibility(visible); 251 251 if (!visible) { 252 252 // Close all open bubbles. ... ... @@ -343,7 +343,7 @@ 343 343 } 344 344 // hide message at page bottom 345 345 this._x_notification.hide(); 346 - // Load the received annotations, along with annotations highlights.348 + // Load the received annotations, along with annotations markers. 347 347 this.loadAnnotations(response.responseJSON.annotatedContent, andShow, false, force); 348 348 // store the state of the annotations 349 349 this.fetchedAnnotations = true; ... ... @@ -404,87 +404,39 @@ 404 404 if (plainTextStartOffset.value === null || plainTextEndOffset.value === null) { 405 405 return false; 406 406 } 409 + 407 407 var annDOMRange = this.getDOMRange(this.annotatedElement, plainTextStartOffset.value, plainTextEndOffset.value); 408 408 // Since the annotation could start at a specific offset, the node is splitted for not wrapping the whole text and 409 409 // a new range is created to recalculate the new ends. 410 410 var strictRange = this.fixRangeEndPoints(annDOMRange); 411 411 412 - // Wrap each text node from this range inside an annotation markup node.415 + // Wrap each text node from this range inside an annotation markup SPAN. 413 413 this.getTextNodesInRange(strictRange).forEach(textNode => this.markAnnotation(textNode, ann)); 417 + 418 + // Add the marker span after the last span of this annotation. 414 414 var allSpans = this.annotatedElement.select('[class~=ID' + ann.annotationId + ']'); 415 415 if (!allSpans.length) { 416 416 return; 417 417 } 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 - }); 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)); 459 459 }, 460 460 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 - 478 478 /** 479 - * Surround this node with a span corresponding to its annotation. 432 + * Surround this node with a span corresponding to it's annotation. 480 480 * 481 481 * @param markedNode the node that corresponds to the current annotation 482 482 * @param ann object holding information about the annotation 483 483 */ 484 484 markAnnotation: function(markedNode, ann) { 485 - var wrapper = document.createElement(' mark');438 + var wrapper = document.createElement('span'); 486 486 wrapper.addClassName('annotation'); 487 487 wrapper.addClassName('ID' + ann.annotationId); 441 + 488 488 var parentNode = markedNode.parentElement; 489 489 parentNode.replaceChild(wrapper, markedNode); 490 490 wrapper.appendChild(markedNode); ... ... @@ -640,13 +640,13 @@ 640 640 }, 641 641 642 642 /** 643 - * Remove the wrapper sfor annotations. The selection highlight is also removed in case the new annotation597 + * Remove the wrapper and marker of annotations. The selection highlight is also removed in case the new annotation 644 644 * was deleted. 645 645 */ 646 646 removeAnnotationsAndSelectionMarkups: function() { 647 - document.querySelectorAll(" mark.annotation,mark.selection-highlight")601 + document.querySelectorAll("span.annotation, span.selection-highlight") 648 648 .forEach(annotationNode => annotationNode.replaceWith(...annotationNode.childNodes)); 649 - document.querySelectorAll(" button.annotation-toggle,span[id^='annotation-toggle-description-']").forEach(description=>description.remove());603 + document.querySelectorAll("span.annotation-marker").forEach(marker => marker.remove()); 650 650 this.fetchedAnnotations = false; 651 651 }, 652 652 ... ... @@ -798,7 +798,8 @@ 798 798 } 799 799 }.bindAsEventListener(this)); 800 800 }, 801 - 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 802 802 onMarkerClick : function(event, id) { 803 803 var bubbleId = 'annotation-bubble-' + id; 804 804 var bubble = $(bubbleId); ... ... @@ -810,23 +810,9 @@ 810 810 this.hideBubble(bubble); 811 811 } else { 812 812 // Show the bubble and fetch the annotation display in it. 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); 768 + var bubble = this.displayLoadingBubble(event.element().cumulativeOffset().top, 769 + event.element().cumulativeOffset().left); 826 826 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); 830 830 this.fetchAndShowAnnotationDetails(id, bubble); 831 831 } 832 832 }, ... ... @@ -858,37 +858,29 @@ 858 858 }.bind(this)); 859 859 }, 860 860 861 - displayLoadingBubble : function( horizontalCoordinate,verticalCoordinate) {862 - // create a bubbleelement wrappingthe form802 + displayLoadingBubble : function(top, left) { 803 + // create an element with the form 863 863 var bubble = new Element('div', {'class' : 'annotation-bubble'}); 864 - // Adda nice loading panel inside805 + // and a nice loading panel inside 865 865 bubble.insert({top : new Element('div', {'class' : 'loading'})}); 866 - // Position it off-screen at first, before we measure it and compute its final position 867 - bubble.style.top = verticalCoordinate + 'px'; 868 - bubble.style.left = '-100%'; 869 - // and insert the bubble in the content 807 + // and put it in the content 870 870 document.body.insert({bottom : bubble}); 809 + // make it hidden for the moment 810 + bubble.toggleClassName('hidden'); 811 + // position it 812 + bubble.style.left = left + 'px'; 813 + bubble.style.top = top + 'px'; 814 + // make it visible 815 + bubble.toggleClassName('hidden'); 871 871 // put this bubble in the bubbles stack 872 872 this.bubbles.push(bubble); 873 - // position the bubble 874 - // By default, we position it to the right and under the coordinates 875 - // Position it horizontally. 876 - let bubbleStyles = getComputedStyle(bubble); 877 - let bubbleWidth = bubble.offsetWidth 878 - + parseInt(bubbleStyles.marginLeft) 879 - + parseInt(bubbleStyles.marginRight); 880 - if (horizontalCoordinate < bubbleWidth 881 - || horizontalCoordinate + bubbleWidth < window.innerWidth) { 882 - bubble.style.left = horizontalCoordinate + 'px'; 883 - } else { 884 - // There isn't enough space on the right of the window to fit the modal, and there is enough on the left. 885 - bubble.style.left = (horizontalCoordinate - bubbleWidth) + 'px'; 886 - bubble.toggleClassName('annotation-bubble-position-left'); 887 - } 888 888 889 889 return bubble; 890 890 }, 891 891 822 + displayAnnotationViewBubble : function(marker) { 823 + }, 824 + 892 892 /** 893 893 * Updates the container with the passed content only if the container is still displayed, and returns true if this is the case. 894 894 */ ... ... @@ -1081,11 +1081,6 @@ 1081 1081 // Cancel the edit otherwise the user will be asked for confirmation when leaving the page. 1082 1082 document.fire('xwiki:actions:cancel'); 1083 1083 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 - 1089 1089 bubble.remove(); 1090 1090 var bubbleIndex = this.bubbles.indexOf(bubble); 1091 1091 if (bubbleIndex >= 0) { ... ... @@ -1206,10 +1206,11 @@ 1206 1206 }, 1207 1207 1208 1208 displayAnnotationCreationForm : function() { 1209 - this.selectionService.highlightSelection(); 1137 + // TODO: get this color from the color theme 1138 + this.selectionService.highlightSelection('#FFEE99'); 1210 1210 // get the position and build the loading bubble 1211 1211 var position = this.selectionService.getPositionNextToSelection(); 1212 - this.createPanel = this.displayLoadingBubble(position. left, position.top, false);1141 + this.createPanel = this.displayLoadingBubble(position.top, position.left); 1213 1213 // remove the ctrl + M listeners, so that only one dialog is displayed at one moment 1214 1214 this.unregisterAddAnnotationShortcut(); 1215 1215 }, ... ... @@ -1302,6 +1302,16 @@ 1302 1302 } 1303 1303 }) 1304 1304 1234 +define('node-module', ['jquery'], function($) { 1235 + return { 1236 + load: function(name, req, onLoad, config) { 1237 + $.get(req.toUrl(name + '.js'), function(text) { 1238 + onLoad.fromText(`define(function(require, exports, module) {${text}});`); 1239 + }, 'text'); 1240 + } 1241 + } 1242 +}); 1243 + 1305 1305 define('xwiki-text-offset-updater', ['jquery', 'node-module!fast-diff'], function($, diff) { 1306 1306 /** 1307 1307 * Compute the changes between different versions of a text. ... ... @@ -1392,8 +1392,6 @@ 1392 1392 this.removeAnnotationsAndSelectionMarkups(); 1393 1393 this.updateAnnotationsOffsets(annotatedContent); 1394 1394 this.addAnnotationsMarkup(annotatedContent.annotations); 1395 - // We add the hints afterwards because they shift the ranges in the text. 1396 - this.addAnnotationsToggleHint(annotatedContent.annotations); 1397 1397 // Notify the content change. 1398 1398 $(document).trigger('xwiki:dom:updated', {'elements': [this.annotatedElement]}); 1399 1399 // Also handle the tab 'downstairs' when the annotations list changes.