mirror of
				https://github.com/Theodor-Springmann-Stiftung/kgpz_web.git
				synced 2025-10-31 09:55:30 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			145 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			145 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| //==========================================================
 | |
| // head-support.js
 | |
| //
 | |
| // An extension to add head tag merging.
 | |
| //==========================================================
 | |
| (function(){
 | |
| 
 | |
|     var api = null;
 | |
| 
 | |
|     function log() {
 | |
|         //console.log(arguments);
 | |
|     }
 | |
| 
 | |
|     function mergeHead(newContent, defaultMergeStrategy) {
 | |
| 
 | |
|         if (newContent && newContent.indexOf('<head') > -1) {
 | |
|             const htmlDoc = document.createElement("html");
 | |
|             // remove svgs to avoid conflicts
 | |
|             var contentWithSvgsRemoved = newContent.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim, '');
 | |
|             // extract head tag
 | |
|             var headTag = contentWithSvgsRemoved.match(/(<head(\s[^>]*>|>)([\s\S]*?)<\/head>)/im);
 | |
| 
 | |
|             // if the  head tag exists...
 | |
|             if (headTag) {
 | |
| 
 | |
|                 var added = []
 | |
|                 var removed = []
 | |
|                 var preserved = []
 | |
|                 var nodesToAppend = []
 | |
| 
 | |
|                 htmlDoc.innerHTML = headTag;
 | |
|                 var newHeadTag = htmlDoc.querySelector("head");
 | |
|                 var currentHead = document.head;
 | |
| 
 | |
|                 if (newHeadTag == null) {
 | |
|                     return;
 | |
|                 } else {
 | |
|                     // put all new head elements into a Map, by their outerHTML
 | |
|                     var srcToNewHeadNodes = new Map();
 | |
|                     for (const newHeadChild of newHeadTag.children) {
 | |
|                         srcToNewHeadNodes.set(newHeadChild.outerHTML, newHeadChild);
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
| 
 | |
| 
 | |
|                 // determine merge strategy
 | |
|                 var mergeStrategy = api.getAttributeValue(newHeadTag, "hx-head") || defaultMergeStrategy;
 | |
| 
 | |
|                 // get the current head
 | |
|                 for (const currentHeadElt of currentHead.children) {
 | |
| 
 | |
|                     // If the current head element is in the map
 | |
|                     var inNewContent = srcToNewHeadNodes.has(currentHeadElt.outerHTML);
 | |
|                     var isReAppended = currentHeadElt.getAttribute("hx-head") === "re-eval";
 | |
|                     var isPreserved = api.getAttributeValue(currentHeadElt, "hx-preserve") === "true";
 | |
|                     if (inNewContent || isPreserved) {
 | |
|                         if (isReAppended) {
 | |
|                             // remove the current version and let the new version replace it and re-execute
 | |
|                             removed.push(currentHeadElt);
 | |
|                         } else {
 | |
|                             // this element already exists and should not be re-appended, so remove it from
 | |
|                             // the new content map, preserving it in the DOM
 | |
|                             srcToNewHeadNodes.delete(currentHeadElt.outerHTML);
 | |
|                             preserved.push(currentHeadElt);
 | |
|                         }
 | |
|                     } else {
 | |
|                         if (mergeStrategy === "append") {
 | |
|                             // we are appending and this existing element is not new content
 | |
|                             // so if and only if it is marked for re-append do we do anything
 | |
|                             if (isReAppended) {
 | |
|                                 removed.push(currentHeadElt);
 | |
|                                 nodesToAppend.push(currentHeadElt);
 | |
|                             }
 | |
|                         } else {
 | |
|                             // if this is a merge, we remove this content since it is not in the new head
 | |
|                             if (api.triggerEvent(document.body, "htmx:removingHeadElement", {headElement: currentHeadElt}) !== false) {
 | |
|                                 removed.push(currentHeadElt);
 | |
|                             }
 | |
|                         }
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 // Push the tremaining new head elements in the Map into the
 | |
|                 // nodes to append to the head tag
 | |
|                 nodesToAppend.push(...srcToNewHeadNodes.values());
 | |
|                 log("to append: ", nodesToAppend);
 | |
| 
 | |
|                 for (const newNode of nodesToAppend) {
 | |
|                     log("adding: ", newNode);
 | |
|                     var newElt = document.createRange().createContextualFragment(newNode.outerHTML);
 | |
|                     log(newElt);
 | |
|                     if (api.triggerEvent(document.body, "htmx:addingHeadElement", {headElement: newElt}) !== false) {
 | |
|                         currentHead.appendChild(newElt);
 | |
|                         added.push(newElt);
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 // remove all removed elements, after we have appended the new elements to avoid
 | |
|                 // additional network requests for things like style sheets
 | |
|                 for (const removedElement of removed) {
 | |
|                     if (api.triggerEvent(document.body, "htmx:removingHeadElement", {headElement: removedElement}) !== false) {
 | |
|                         currentHead.removeChild(removedElement);
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 api.triggerEvent(document.body, "htmx:afterHeadMerge", {added: added, kept: preserved, removed: removed});
 | |
|             }
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     htmx.defineExtension("head-support", {
 | |
|         init: function(apiRef) {
 | |
|             // store a reference to the internal API.
 | |
|             api = apiRef;
 | |
| 
 | |
|             htmx.on('htmx:afterSwap', function(evt){
 | |
|                 let xhr = evt.detail.xhr;
 | |
|                 if (xhr) {
 | |
|                     var serverResponse = xhr.response;
 | |
|                     if (api.triggerEvent(document.body, "htmx:beforeHeadMerge", evt.detail)) {
 | |
|                         mergeHead(serverResponse, evt.detail.boosted ? "merge" : "append");
 | |
|                     }
 | |
|                 }
 | |
|             })
 | |
| 
 | |
|             htmx.on('htmx:historyRestore', function(evt){
 | |
|                 if (api.triggerEvent(document.body, "htmx:beforeHeadMerge", evt.detail)) {
 | |
|                     if (evt.detail.cacheMiss) {
 | |
|                         mergeHead(evt.detail.serverResponse, "merge");
 | |
|                     } else {
 | |
|                         mergeHead(evt.detail.item.head, "merge");
 | |
|                     }
 | |
|                 }
 | |
|             })
 | |
| 
 | |
|             htmx.on('htmx:historyItemCreated', function(evt){
 | |
|                 var historyItem = evt.detail.item;
 | |
|                 historyItem.head = document.head.outerHTML;
 | |
|             })
 | |
|         }
 | |
|     });
 | |
| 
 | |
| })()
 | 
