Änderungen von Dokument Script

Zuletzt geändert von Daniel Herrmann am 2026/02/07 23:23

Von Version Icon 5.1
bearbeitet von Daniel Herrmann
am 2026/02/07 23:23
Änderungskommentar: Install extension [org.xwiki.platform:xwiki-platform-annotation-ui/18.0.1]
Auf Version Icon 2.1 Icon
bearbeitet von Daniel Herrmann
am 2025/07/19 16:47
Änderungskommentar: Install extension [org.xwiki.platform:xwiki-platform-annotation-ui/17.5.0]

Zusammenfassung

Details

Icon 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 not in the passed container
40 + // 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 -Ja
1 +Nein
Icon 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 synchronous and
70 + // 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 wrappers for annotations. The selection highlight is also removed in case the new annotation
597 + * 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 bubble element wrapping the form
802 + displayLoadingBubble : function(top, left) {
803 + // create an element with the form
863 863   var bubble = new Element('div', {'class' : 'annotation-bubble'});
864 - // Add a nice loading panel inside
805 + // 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.