diff --git a/README.md b/README.md index 76652f2..0dce264 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,8 @@ PortalSettings bleibt fuer den Betrieb des ExpiryIndicators optional. - Custom Branding und Mega Menu werden anhand ihrer aktiven, markierten Application-Customizer-UserCustomAction erkannt. - ExpiryIndicator registriert einen Solution-Descriptor und sein Bindungsinventar im Property Bag des Root Webs. +Beim Speichern einer nur web-scoped registrierten Custom-Branding- oder MegaMenu-Konfiguration erstellt PortalSettings automatisch die site-scoped UserCustomAction und entfernt den alten Web-Eintrag. Das ist fuer die verlaessliche Ausfuehrung auf allen modernen Seiten und Subwebs der Site Collection erforderlich. + Neue eigene Solutions werden zentral im Provider-Register in `PortalSettings.aspx` hinterlegt. Ein Provider definiert Kennung, Anzeigename, Component-ID, Panel und Erkennungsart. So bleibt PortalSettings die gemeinsame Admin-Oberflaeche, ohne dass nicht installierte Solutions leere Tabs erzeugen. @@ -47,7 +49,7 @@ Das Paket liegt danach unter `sharepoint/solution/portal-settings.sppkg`. ## Installation und Upgrade -1. `portal-settings.sppkg` im App Catalog durch Version `2.1.7.0` ersetzen. +1. `portal-settings.sppkg` im App Catalog durch Version `2.1.8.0` ersetzen. 2. PortalSettings im Root Web der Site Collection installieren beziehungsweise aktualisieren. 3. `expiry-indicator.sppkg` durch Version `2.1.0.0` ersetzen und die App aktualisieren. 4. `/SitePages/PortalSettings.aspx` mit geleertem Browsercache laden. diff --git a/config/package-solution.json b/config/package-solution.json index eca7fee..396227c 100644 --- a/config/package-solution.json +++ b/config/package-solution.json @@ -3,7 +3,7 @@ "solution": { "name": "portal-settings-client-side-solution", "id": "1c98e32d-bb7b-43b7-9625-074d6e4ea286", - "version": "2.1.7.0", + "version": "2.1.8.0", "includeClientSideAssets": true, "skipFeatureDeployment": false, "features": [ @@ -11,7 +11,7 @@ "title": "Portal Settings", "description": "Provisioniert die zentrale Settings-Seite fuer aktive Portal-Erweiterungen.", "id": "57bd9daf-48f9-49e2-aebd-11b15a0b5e93", - "version": "2.1.7.0", + "version": "2.1.8.0", "assets": { "elementManifests": [ "elements-v2.xml" diff --git a/package-lock.json b/package-lock.json index 7b81ea4..35692ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "portal-settings", - "version": "2.1.7", + "version": "2.1.8", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index b85751a..28b64bb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "portal-settings", - "version": "2.1.7", + "version": "2.1.8", "private": true, "main": "lib/index.js", "engines": { diff --git a/sharepoint/assets/PortalSettings.aspx b/sharepoint/assets/PortalSettings.aspx index 4bae553..caf68b5 100644 --- a/sharepoint/assets/PortalSettings.aspx +++ b/sharepoint/assets/PortalSettings.aspx @@ -88,13 +88,16 @@ function parseSolutionConfig(key,rawValue){var parsed={};if(typeof rawValue==='s function normalizeBrandingConfig(config){var normalized={cssfiles:[],placeholdertop:{elements:[]},placeholderbottom:{elements:[]}};if(config&&Array.isArray(config.cssfiles)){normalized.cssfiles=config.cssfiles.filter(function(item){return item&&typeof item.path==='string'&&item.path.trim().length>0;}).map(function(item){return{path:item.path.trim()};});}if(config&&config.placeholdertop&&Array.isArray(config.placeholdertop.elements)){normalized.placeholdertop.elements=config.placeholdertop.elements;}else if(config&&Array.isArray(config.elements)){normalized.placeholdertop.elements=config.elements;}if(config&&config.placeholderbottom&&Array.isArray(config.placeholderbottom.elements)){normalized.placeholderbottom.elements=config.placeholderbottom.elements;}return normalized;} function normalizeMegaMenuConfig(config){var cacheMinutes=config?Number(config.cacheMinutes):15;return{termSetId:config&&typeof config.termSetId==='string'?config.termSetId.trim():'',termSetName:config&&typeof config.termSetName==='string'?config.termSetName.trim():'',cacheMinutes:isFinite(cacheMinutes)?Math.min(Math.max(Math.floor(cacheMinutes),1),1440):15,debug:!!(config&&config.debug===true)};} function parseJsonArray(textareaId,label){var textarea=document.getElementById(textareaId);var rawValue=textarea.value.trim();if(!rawValue){return [];}var parsedValue;try{parsedValue=JSON.parse(rawValue);}catch(error){throw new Error(label+' enthaelt kein gueltiges JSON: '+error.message);}if(!Array.isArray(parsedValue)){throw new Error(label+' muss ein JSON-Array sein.');}return parsedValue;} -function updateSolutionAction(key,config,onSuccess,onError){var solution=state.solutions[key];if(!solution||!solution.info||!solution.info.action){onError('Die passende UserCustomAction wurde nicht gefunden.');return;}var actionId=solution.info.action.Id;if(!actionId){onError('Die UserCustomAction hat keine gueltige Id.');return;}var updateUrl=solution.info.updateBaseUrl+'(guid\''+actionId+'\')';var payload={__metadata:{type:solution.info.actionType||'SP.UserCustomAction'},ClientSideComponentProperties:JSON.stringify(config)};postJsonMerge(updateUrl,payload,onSuccess,onError);} +function updateSolutionAction(key,config,onSuccess,onError){var solution=state.solutions[key];if(!solution||!solution.info||!solution.info.action){onError('Die passende UserCustomAction wurde nicht gefunden.');return;}if(solution.info.scopeLabel!=='Site Collection'){promoteSolutionActionToSiteCollection(key,config,onSuccess,onError);return;}var actionId=solution.info.action.Id;if(!actionId){onError('Die UserCustomAction hat keine gueltige Id.');return;}var updateUrl=solution.info.updateBaseUrl+'(guid\''+actionId+'\')';var payload={__metadata:{type:solution.info.actionType||'SP.UserCustomAction'},ClientSideComponentProperties:JSON.stringify(config)};postJsonMerge(updateUrl,payload,onSuccess,onError);} +function promoteSolutionActionToSiteCollection(key,config,onSuccess,onError){var solution=state.solutions[key];var definition=solution.definition;var oldInfo=solution.info;var oldAction=oldInfo.action;var siteActionsUrl=getSiteUrl().replace(/\/$/,'')+'/_api/site/UserCustomActions';var payload={__metadata:{type:'SP.UserCustomAction'},Name:oldAction.Name||definition.key,Title:oldAction.Title||definition.label,Description:definition.descriptionTag||CUSTOM_SOLUTION_TAG,Location:'ClientSideExtension.ApplicationCustomizer',ClientSideComponentId:definition.componentId,ClientSideComponentProperties:JSON.stringify(config)};postJsonResult(siteActionsUrl,payload,function(createdPayload){var createdAction=createdPayload&&createdPayload.d?createdPayload.d:createdPayload;var oldActionId=oldAction.Id;function finish(){solution.info={action:createdAction&&createdAction.Id?createdAction:{Id:createdAction&&createdAction.Id?createdAction.Id:'',Title:payload.Title,Name:payload.Name,Description:payload.Description,Location:payload.Location,ClientSideComponentId:payload.ClientSideComponentId,ClientSideComponentProperties:payload.ClientSideComponentProperties},updateBaseUrl:siteActionsUrl,actionType:'SP.UserCustomAction',scopeLabel:'Site Collection'};onSuccess();}if(!oldActionId||!oldInfo.updateBaseUrl){finish();return;}var oldUrl=oldInfo.updateBaseUrl+'(guid\''+oldActionId+'\')';postJsonDelete(oldUrl,finish,function(message){if(window.console&&console.warn){console.warn('Die site-scoped Registrierung wurde erstellt, der alte Web-Eintrag konnte aber nicht entfernt werden: '+message);}finish();});},function(message){onError('Die site-scoped UserCustomAction konnte nicht erstellt werden: '+message);});} function readCustomizerActionInfo(definition,onSuccess,onError){var baseSiteUrl=getSiteUrl().replace(/\/$/,'');var endpoints=[{scopeLabel:'Site Collection',readUrl:baseSiteUrl+'/_api/site/UserCustomActions',updateBaseUrl:baseSiteUrl+'/_api/site/UserCustomActions'},{scopeLabel:'Web',readUrl:baseSiteUrl+'/_api/site/rootweb/UserCustomActions',updateBaseUrl:baseSiteUrl+'/_api/site/rootweb/UserCustomActions'}];readCustomizerActionInfoFromEndpoints(definition,endpoints,0,[],onSuccess,onError);} function readCustomizerActionInfoFromEndpoints(definition,endpoints,index,errors,onSuccess,onError){if(index>=endpoints.length){if(errors.length){onError(errors.join(' | '));return;}onSuccess(null);return;}var endpoint=endpoints[index];getJson(endpoint.readUrl,function(payload){try{var actions=getResultArray(payload);var action=findActiveCustomizerAction(actions,definition);if(action){onSuccess({action:action,updateBaseUrl:endpoint.updateBaseUrl,actionType:action.__metadata&&action.__metadata.type?action.__metadata.type:'SP.UserCustomAction',scopeLabel:endpoint.scopeLabel});return;}readCustomizerActionInfoFromEndpoints(definition,endpoints,index+1,errors,onSuccess,onError);}catch(error){errors.push(error.message||String(error));readCustomizerActionInfoFromEndpoints(definition,endpoints,index+1,errors,onSuccess,onError);}},function(message){errors.push(message);readCustomizerActionInfoFromEndpoints(definition,endpoints,index+1,errors,onSuccess,onError);});} function findActiveCustomizerAction(actions,definition){if(!Array.isArray(actions)||!definition){return null;}var expected=normalizeGuid(definition.componentId);var tagged=null;var applicationCustomizer=null;var componentFallback=null;for(var i=0;i=200&&request.status<300){try{onSuccess(JSON.parse(request.responseText||'{}'));}catch(error){onError('Antwort konnte nicht gelesen werden: '+(error.message||String(error)));}return;}onError(buildRequestErrorMessage(request));};request.onerror=function(){onError('Netzwerkfehler beim Lesen von '+url);};request.send();}catch(error){onError(error.message||String(error));}} function postJson(url,payload,onSuccess,onError){try{var digest=getRequestDigest();if(!digest){onError('Kein Request Digest gefunden. Laden Sie die Seite neu und versuchen Sie es erneut.');return;}var request=new XMLHttpRequest();request.open('POST',url,true);request.setRequestHeader('Accept','application/json;odata=verbose');request.setRequestHeader('Content-Type','application/json;odata=verbose');request.setRequestHeader('X-RequestDigest',digest);request.onreadystatechange=function(){if(request.readyState!==4){return;}if(request.status>=200&&request.status<300){onSuccess();return;}onError(buildRequestErrorMessage(request));};request.onerror=function(){onError('Netzwerkfehler beim Erstellen unter '+url);};request.send(JSON.stringify(payload));}catch(error){onError(error.message||String(error));}} function postJsonMerge(url,payload,onSuccess,onError){try{var digest=getRequestDigest();if(!digest){onError('Kein Request Digest gefunden. Laden Sie die Seite neu und versuchen Sie es erneut.');return;}var request=new XMLHttpRequest();request.open('POST',url,true);request.setRequestHeader('Accept','application/json;odata=verbose');request.setRequestHeader('Content-Type','application/json;odata=verbose');request.setRequestHeader('X-RequestDigest',digest);request.setRequestHeader('IF-MATCH','*');request.setRequestHeader('X-HTTP-Method','MERGE');request.onreadystatechange=function(){if(request.readyState!==4){return;}if(request.status>=200&&request.status<300){onSuccess();return;}onError(buildRequestErrorMessage(request));};request.onerror=function(){onError('Netzwerkfehler beim Aktualisieren von '+url);};request.send(JSON.stringify(payload));}catch(error){onError(error.message||String(error));}} +function postJsonResult(url,payload,onSuccess,onError){try{var digest=getRequestDigest();if(!digest){onError('Kein Request Digest gefunden. Laden Sie die Seite neu und versuchen Sie es erneut.');return;}var request=new XMLHttpRequest();request.open('POST',url,true);request.setRequestHeader('Accept','application/json;odata=verbose');request.setRequestHeader('Content-Type','application/json;odata=verbose');request.setRequestHeader('X-RequestDigest',digest);request.onreadystatechange=function(){if(request.readyState!==4){return;}if(request.status>=200&&request.status<300){try{onSuccess(request.responseText?JSON.parse(request.responseText):{});}catch(error){onError('Erstellte UserCustomAction konnte nicht gelesen werden: '+(error.message||String(error)));}return;}onError(buildRequestErrorMessage(request));};request.onerror=function(){onError('Netzwerkfehler beim Erstellen unter '+url);};request.send(JSON.stringify(payload));}catch(error){onError(error.message||String(error));}} +function postJsonDelete(url,onSuccess,onError){try{var digest=getRequestDigest();if(!digest){onError('Kein Request Digest gefunden.');return;}var request=new XMLHttpRequest();request.open('POST',url,true);request.setRequestHeader('Accept','application/json;odata=verbose');request.setRequestHeader('X-RequestDigest',digest);request.setRequestHeader('IF-MATCH','*');request.setRequestHeader('X-HTTP-Method','DELETE');request.onreadystatechange=function(){if(request.readyState!==4){return;}if(request.status>=200&&request.status<300){onSuccess();return;}onError(buildRequestErrorMessage(request));};request.onerror=function(){onError('Netzwerkfehler beim Entfernen von '+url);};request.send();}catch(error){onError(error.message||String(error));}} function getRequestDigest(){var digestField=document.getElementById('__REQUESTDIGEST');return digestField&&digestField.value?digestField.value:'';} function getResultArray(payload){if(payload&&payload.d&&Array.isArray(payload.d.results)){return payload.d.results;}if(payload&&Array.isArray(payload.value)){return payload.value;}return [];} function normalizeGuid(value){if(typeof value!=='string'){return '';}var normalized=value.replace(/[{}]/g,'').toLowerCase();return normalized==='00000000-0000-0000-0000-000000000000'?'':normalized;} diff --git a/src/assets/PortalSettings.aspx b/src/assets/PortalSettings.aspx index 4bae553..caf68b5 100644 --- a/src/assets/PortalSettings.aspx +++ b/src/assets/PortalSettings.aspx @@ -88,13 +88,16 @@ function parseSolutionConfig(key,rawValue){var parsed={};if(typeof rawValue==='s function normalizeBrandingConfig(config){var normalized={cssfiles:[],placeholdertop:{elements:[]},placeholderbottom:{elements:[]}};if(config&&Array.isArray(config.cssfiles)){normalized.cssfiles=config.cssfiles.filter(function(item){return item&&typeof item.path==='string'&&item.path.trim().length>0;}).map(function(item){return{path:item.path.trim()};});}if(config&&config.placeholdertop&&Array.isArray(config.placeholdertop.elements)){normalized.placeholdertop.elements=config.placeholdertop.elements;}else if(config&&Array.isArray(config.elements)){normalized.placeholdertop.elements=config.elements;}if(config&&config.placeholderbottom&&Array.isArray(config.placeholderbottom.elements)){normalized.placeholderbottom.elements=config.placeholderbottom.elements;}return normalized;} function normalizeMegaMenuConfig(config){var cacheMinutes=config?Number(config.cacheMinutes):15;return{termSetId:config&&typeof config.termSetId==='string'?config.termSetId.trim():'',termSetName:config&&typeof config.termSetName==='string'?config.termSetName.trim():'',cacheMinutes:isFinite(cacheMinutes)?Math.min(Math.max(Math.floor(cacheMinutes),1),1440):15,debug:!!(config&&config.debug===true)};} function parseJsonArray(textareaId,label){var textarea=document.getElementById(textareaId);var rawValue=textarea.value.trim();if(!rawValue){return [];}var parsedValue;try{parsedValue=JSON.parse(rawValue);}catch(error){throw new Error(label+' enthaelt kein gueltiges JSON: '+error.message);}if(!Array.isArray(parsedValue)){throw new Error(label+' muss ein JSON-Array sein.');}return parsedValue;} -function updateSolutionAction(key,config,onSuccess,onError){var solution=state.solutions[key];if(!solution||!solution.info||!solution.info.action){onError('Die passende UserCustomAction wurde nicht gefunden.');return;}var actionId=solution.info.action.Id;if(!actionId){onError('Die UserCustomAction hat keine gueltige Id.');return;}var updateUrl=solution.info.updateBaseUrl+'(guid\''+actionId+'\')';var payload={__metadata:{type:solution.info.actionType||'SP.UserCustomAction'},ClientSideComponentProperties:JSON.stringify(config)};postJsonMerge(updateUrl,payload,onSuccess,onError);} +function updateSolutionAction(key,config,onSuccess,onError){var solution=state.solutions[key];if(!solution||!solution.info||!solution.info.action){onError('Die passende UserCustomAction wurde nicht gefunden.');return;}if(solution.info.scopeLabel!=='Site Collection'){promoteSolutionActionToSiteCollection(key,config,onSuccess,onError);return;}var actionId=solution.info.action.Id;if(!actionId){onError('Die UserCustomAction hat keine gueltige Id.');return;}var updateUrl=solution.info.updateBaseUrl+'(guid\''+actionId+'\')';var payload={__metadata:{type:solution.info.actionType||'SP.UserCustomAction'},ClientSideComponentProperties:JSON.stringify(config)};postJsonMerge(updateUrl,payload,onSuccess,onError);} +function promoteSolutionActionToSiteCollection(key,config,onSuccess,onError){var solution=state.solutions[key];var definition=solution.definition;var oldInfo=solution.info;var oldAction=oldInfo.action;var siteActionsUrl=getSiteUrl().replace(/\/$/,'')+'/_api/site/UserCustomActions';var payload={__metadata:{type:'SP.UserCustomAction'},Name:oldAction.Name||definition.key,Title:oldAction.Title||definition.label,Description:definition.descriptionTag||CUSTOM_SOLUTION_TAG,Location:'ClientSideExtension.ApplicationCustomizer',ClientSideComponentId:definition.componentId,ClientSideComponentProperties:JSON.stringify(config)};postJsonResult(siteActionsUrl,payload,function(createdPayload){var createdAction=createdPayload&&createdPayload.d?createdPayload.d:createdPayload;var oldActionId=oldAction.Id;function finish(){solution.info={action:createdAction&&createdAction.Id?createdAction:{Id:createdAction&&createdAction.Id?createdAction.Id:'',Title:payload.Title,Name:payload.Name,Description:payload.Description,Location:payload.Location,ClientSideComponentId:payload.ClientSideComponentId,ClientSideComponentProperties:payload.ClientSideComponentProperties},updateBaseUrl:siteActionsUrl,actionType:'SP.UserCustomAction',scopeLabel:'Site Collection'};onSuccess();}if(!oldActionId||!oldInfo.updateBaseUrl){finish();return;}var oldUrl=oldInfo.updateBaseUrl+'(guid\''+oldActionId+'\')';postJsonDelete(oldUrl,finish,function(message){if(window.console&&console.warn){console.warn('Die site-scoped Registrierung wurde erstellt, der alte Web-Eintrag konnte aber nicht entfernt werden: '+message);}finish();});},function(message){onError('Die site-scoped UserCustomAction konnte nicht erstellt werden: '+message);});} function readCustomizerActionInfo(definition,onSuccess,onError){var baseSiteUrl=getSiteUrl().replace(/\/$/,'');var endpoints=[{scopeLabel:'Site Collection',readUrl:baseSiteUrl+'/_api/site/UserCustomActions',updateBaseUrl:baseSiteUrl+'/_api/site/UserCustomActions'},{scopeLabel:'Web',readUrl:baseSiteUrl+'/_api/site/rootweb/UserCustomActions',updateBaseUrl:baseSiteUrl+'/_api/site/rootweb/UserCustomActions'}];readCustomizerActionInfoFromEndpoints(definition,endpoints,0,[],onSuccess,onError);} function readCustomizerActionInfoFromEndpoints(definition,endpoints,index,errors,onSuccess,onError){if(index>=endpoints.length){if(errors.length){onError(errors.join(' | '));return;}onSuccess(null);return;}var endpoint=endpoints[index];getJson(endpoint.readUrl,function(payload){try{var actions=getResultArray(payload);var action=findActiveCustomizerAction(actions,definition);if(action){onSuccess({action:action,updateBaseUrl:endpoint.updateBaseUrl,actionType:action.__metadata&&action.__metadata.type?action.__metadata.type:'SP.UserCustomAction',scopeLabel:endpoint.scopeLabel});return;}readCustomizerActionInfoFromEndpoints(definition,endpoints,index+1,errors,onSuccess,onError);}catch(error){errors.push(error.message||String(error));readCustomizerActionInfoFromEndpoints(definition,endpoints,index+1,errors,onSuccess,onError);}},function(message){errors.push(message);readCustomizerActionInfoFromEndpoints(definition,endpoints,index+1,errors,onSuccess,onError);});} function findActiveCustomizerAction(actions,definition){if(!Array.isArray(actions)||!definition){return null;}var expected=normalizeGuid(definition.componentId);var tagged=null;var applicationCustomizer=null;var componentFallback=null;for(var i=0;i=200&&request.status<300){try{onSuccess(JSON.parse(request.responseText||'{}'));}catch(error){onError('Antwort konnte nicht gelesen werden: '+(error.message||String(error)));}return;}onError(buildRequestErrorMessage(request));};request.onerror=function(){onError('Netzwerkfehler beim Lesen von '+url);};request.send();}catch(error){onError(error.message||String(error));}} function postJson(url,payload,onSuccess,onError){try{var digest=getRequestDigest();if(!digest){onError('Kein Request Digest gefunden. Laden Sie die Seite neu und versuchen Sie es erneut.');return;}var request=new XMLHttpRequest();request.open('POST',url,true);request.setRequestHeader('Accept','application/json;odata=verbose');request.setRequestHeader('Content-Type','application/json;odata=verbose');request.setRequestHeader('X-RequestDigest',digest);request.onreadystatechange=function(){if(request.readyState!==4){return;}if(request.status>=200&&request.status<300){onSuccess();return;}onError(buildRequestErrorMessage(request));};request.onerror=function(){onError('Netzwerkfehler beim Erstellen unter '+url);};request.send(JSON.stringify(payload));}catch(error){onError(error.message||String(error));}} function postJsonMerge(url,payload,onSuccess,onError){try{var digest=getRequestDigest();if(!digest){onError('Kein Request Digest gefunden. Laden Sie die Seite neu und versuchen Sie es erneut.');return;}var request=new XMLHttpRequest();request.open('POST',url,true);request.setRequestHeader('Accept','application/json;odata=verbose');request.setRequestHeader('Content-Type','application/json;odata=verbose');request.setRequestHeader('X-RequestDigest',digest);request.setRequestHeader('IF-MATCH','*');request.setRequestHeader('X-HTTP-Method','MERGE');request.onreadystatechange=function(){if(request.readyState!==4){return;}if(request.status>=200&&request.status<300){onSuccess();return;}onError(buildRequestErrorMessage(request));};request.onerror=function(){onError('Netzwerkfehler beim Aktualisieren von '+url);};request.send(JSON.stringify(payload));}catch(error){onError(error.message||String(error));}} +function postJsonResult(url,payload,onSuccess,onError){try{var digest=getRequestDigest();if(!digest){onError('Kein Request Digest gefunden. Laden Sie die Seite neu und versuchen Sie es erneut.');return;}var request=new XMLHttpRequest();request.open('POST',url,true);request.setRequestHeader('Accept','application/json;odata=verbose');request.setRequestHeader('Content-Type','application/json;odata=verbose');request.setRequestHeader('X-RequestDigest',digest);request.onreadystatechange=function(){if(request.readyState!==4){return;}if(request.status>=200&&request.status<300){try{onSuccess(request.responseText?JSON.parse(request.responseText):{});}catch(error){onError('Erstellte UserCustomAction konnte nicht gelesen werden: '+(error.message||String(error)));}return;}onError(buildRequestErrorMessage(request));};request.onerror=function(){onError('Netzwerkfehler beim Erstellen unter '+url);};request.send(JSON.stringify(payload));}catch(error){onError(error.message||String(error));}} +function postJsonDelete(url,onSuccess,onError){try{var digest=getRequestDigest();if(!digest){onError('Kein Request Digest gefunden.');return;}var request=new XMLHttpRequest();request.open('POST',url,true);request.setRequestHeader('Accept','application/json;odata=verbose');request.setRequestHeader('X-RequestDigest',digest);request.setRequestHeader('IF-MATCH','*');request.setRequestHeader('X-HTTP-Method','DELETE');request.onreadystatechange=function(){if(request.readyState!==4){return;}if(request.status>=200&&request.status<300){onSuccess();return;}onError(buildRequestErrorMessage(request));};request.onerror=function(){onError('Netzwerkfehler beim Entfernen von '+url);};request.send();}catch(error){onError(error.message||String(error));}} function getRequestDigest(){var digestField=document.getElementById('__REQUESTDIGEST');return digestField&&digestField.value?digestField.value:'';} function getResultArray(payload){if(payload&&payload.d&&Array.isArray(payload.d.results)){return payload.d.results;}if(payload&&Array.isArray(payload.value)){return payload.value;}return [];} function normalizeGuid(value){if(typeof value!=='string'){return '';}var normalized=value.replace(/[{}]/g,'').toLowerCase();return normalized==='00000000-0000-0000-0000-000000000000'?'':normalized;}