Files
PortalSettings/sharepoint/assets/PortalSettings.aspx

110 lines
68 KiB
Plaintext

<%@ Page language="C#" MasterPageFile="~masterurl/default.master" Inherits="Microsoft.SharePoint.WebPartPages.WebPartPage, Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register Tagprefix="WebPartPages" Namespace="Microsoft.SharePoint.WebPartPages" Assembly="Microsoft.SharePoint, Version=15.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<asp:Content ContentPlaceHolderID="PlaceHolderAdditionalPageHead" runat="server">
<SharePoint:ScriptLink language="javascript" name="sp.js" runat="server" Defer="true" LoadAfterUI="true" Localizable="false" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type="text/css">
:root{--ps-bg:#f4f7fb;--ps-surface:#fff;--ps-border:#d8e1ec;--ps-ink:#16263d;--ps-muted:#607086;--ps-accent:#0f6cbd;--ps-soft:#eaf2fb;--ps-success:#107c10;--ps-warning:#a15c00;--ps-danger:#b42318;--ps-shadow:0 16px 34px rgba(20,35,59,.08);--ps-radius:18px}
.ps-page{background:linear-gradient(180deg,#f9fbfe 0%,var(--ps-bg) 100%);padding:28px 0 40px}.ps-shell{width:min(1240px,calc(100% - 32px));margin:0 auto}.ps-hero{background:linear-gradient(135deg,#123660 0%,#0f6cbd 60%,#58a3e0 100%);color:#fff;border-radius:24px;padding:28px 30px;box-shadow:var(--ps-shadow)}.ps-hero h1{margin:0 0 10px;font-size:34px;line-height:1.1}.ps-hero p{margin:0;max-width:860px;line-height:1.6;color:rgba(255,255,255,.92)}.ps-eyebrow{font-size:12px;text-transform:uppercase;letter-spacing:.16em;opacity:.85;margin-bottom:12px}.ps-status-host{min-height:56px;margin-bottom:18px}.ps-status{padding:14px 16px;border-radius:12px;border:1px solid transparent;background:#fff;box-shadow:0 10px 24px rgba(20,35,59,.06)}.ps-status-info{color:var(--ps-accent);background:#f3f8fe;border-color:rgba(15,108,189,.2)}.ps-status-success{color:var(--ps-success);background:#eff8ef;border-color:rgba(16,124,16,.2)}.ps-status-warning{color:var(--ps-warning);background:#fff6eb;border-color:rgba(161,92,0,.2)}.ps-status-error{color:var(--ps-danger);background:#fff1f0;border-color:rgba(180,35,24,.2)}.ps-metrics,.ps-grid{display:grid;gap:16px;margin-top:18px}.ps-metrics{grid-template-columns:repeat(4,minmax(0,1fr))}.ps-grid{grid-template-columns:repeat(12,minmax(0,1fr))}.ps-card,.ps-panel,.ps-metric,.ps-empty{background:var(--ps-surface);border:1px solid var(--ps-border);border-radius:var(--ps-radius);box-shadow:0 10px 24px rgba(20,35,59,.05)}.ps-metric{padding:16px 18px}.ps-metric label{display:block;font-size:12px;text-transform:uppercase;letter-spacing:.08em;color:var(--ps-muted);margin-bottom:8px}.ps-metric strong{display:block;font-size:18px;line-height:1.45;word-break:break-word}.ps-tabs{display:flex;flex-wrap:wrap;gap:10px;margin-top:22px}.ps-tab,.ps-btn{height:42px;padding:0 18px;border-radius:999px;border:1px solid var(--ps-border);background:#fff;color:var(--ps-ink);font-size:14px;font-weight:600;cursor:pointer}.ps-tab.is-active{border-color:var(--ps-accent);background:var(--ps-soft);color:var(--ps-accent)}.ps-empty{margin-top:18px;padding:24px;color:var(--ps-muted);line-height:1.7}.ps-panel{margin-top:18px;padding:22px}.ps-panel-head{display:flex;justify-content:space-between;align-items:flex-start;gap:14px;margin-bottom:18px}.ps-panel-head h2,.ps-card h3{margin:0 0 8px}.ps-panel-head p,.ps-card p,.ps-note,.ps-help{margin:0;color:var(--ps-muted);line-height:1.6}.ps-chip{display:inline-flex;align-items:center;justify-content:center;min-width:36px;height:28px;padding:0 10px;border-radius:999px;background:var(--ps-soft);color:var(--ps-accent);font-size:12px;font-weight:700}.ps-card{padding:20px}.ps-span-4{grid-column:span 4}.ps-span-6{grid-column:span 6}.ps-span-8{grid-column:span 8}.ps-span-12{grid-column:span 12}.ps-meta{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px;margin:14px 0}.ps-meta div{padding:12px;border:1px solid var(--ps-border);border-radius:12px;background:#f8fbff}.ps-meta label{display:block;font-size:12px;text-transform:uppercase;letter-spacing:.08em;color:var(--ps-muted);margin-bottom:6px}.ps-meta strong{display:block;font-size:14px;line-height:1.55;word-break:break-word}.ps-label{display:block;margin:0 0 8px;font-size:13px;font-weight:600}.ps-input,.ps-textarea,.ps-preview{width:100%;box-sizing:border-box;border:1px solid var(--ps-border);border-radius:12px;background:#fbfdff;color:var(--ps-ink)}.ps-input{height:44px;padding:0 14px;font-size:14px}.ps-textarea,.ps-preview{min-height:260px;padding:14px;font:13px/1.6 Consolas,'Courier New',monospace;white-space:pre-wrap;overflow:auto}.ps-inline{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:10px;align-items:center}.ps-list{display:grid;gap:10px;margin-top:14px}.ps-item,.ps-list-empty{border:1px solid var(--ps-border);border-radius:12px;background:#fbfdff}.ps-item{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:14px}.ps-list-empty{padding:18px;text-align:center;color:var(--ps-muted)}.ps-path{font:13px/1.6 Consolas,'Courier New',monospace;word-break:break-all}.ps-actions{display:flex;gap:10px;flex-wrap:wrap}.ps-actions-bar{position:sticky;bottom:16px;z-index:4;display:flex;justify-content:space-between;align-items:center;gap:14px;margin-top:18px;padding:16px 18px;border:1px solid rgba(15,108,189,.14);border-radius:20px;background:rgba(255,255,255,.92);box-shadow:0 16px 32px rgba(20,35,59,.12)}.ps-btn-primary{background:linear-gradient(135deg,var(--ps-accent) 0%,#1b86e0 100%);border-color:var(--ps-accent);color:#fff}.ps-btn-quiet{background:#f7faff}.ps-btn-danger{background:#fff6f5;border-color:rgba(180,35,24,.18);color:var(--ps-danger)}.ps-check{display:flex;align-items:center;gap:10px;min-height:44px}.ps-check input{width:18px;height:18px}.ps-note{margin-top:14px;padding:14px 16px;border-radius:12px;background:#f8fbff;border:1px solid var(--ps-border)}
.ps-hero-actions{margin-top:18px}.ps-modal-backdrop{position:fixed;inset:0;z-index:10000;display:flex;align-items:center;justify-content:center;padding:20px;background:rgba(10,25,45,.58)}.ps-modal-backdrop[hidden]{display:none}.ps-modal{width:min(620px,100%);max-height:calc(100vh - 40px);overflow:auto;padding:24px;border-radius:20px;background:#fff;box-shadow:0 28px 70px rgba(0,0,0,.28)}.ps-modal h2{margin:0 0 8px}.ps-modal p{margin:0 0 18px;color:var(--ps-muted);line-height:1.6}.ps-modal .ps-actions{justify-content:flex-end;margin-top:22px}
@media (max-width:1080px){.ps-metrics{grid-template-columns:repeat(2,minmax(0,1fr))}.ps-span-4,.ps-span-6,.ps-span-8{grid-column:span 12}.ps-meta{grid-template-columns:minmax(0,1fr)}}@media (max-width:720px){.ps-shell{width:calc(100% - 20px)}.ps-metrics{grid-template-columns:minmax(0,1fr)}.ps-inline,.ps-actions-bar{display:grid;grid-template-columns:minmax(0,1fr)}.ps-actions .ps-btn,.ps-inline .ps-btn,.ps-tab{width:100%}.ps-hero h1{font-size:28px}}
</style>
</asp:Content>
<asp:Content ContentPlaceHolderID="PlaceHolderMain" runat="server">
<div class="ps-page"><div class="ps-shell"><div id="statusMessage" class="ps-status-host"></div><section class="ps-hero"><div class="ps-eyebrow">Portal Settings</div><h1>Zentrale Verwaltung fuer aktive Portal-Erweiterungen</h1><p>Diese Seite erkennt installierte Einstellungsanbieter automatisch und zeigt nur deren Tabs. Konfigurationen werden in UserCustomActions, Feld-Properties oder im Property Bag des Root Webs gespeichert.</p><div class="ps-hero-actions"><button type="button" class="ps-btn" onclick="openExpiryBindingDialog()">ExpiryIndicator anbinden</button></div></section><section class="ps-metrics"><div class="ps-metric"><label>Aktive Erweiterungen</label><strong id="activeSolutionCount">0</strong></div><div class="ps-metric"><label>Aktiver Tab</label><strong id="activeTabValue">-</strong></div><div class="ps-metric"><label>Site Collection</label><strong id="siteUrlValue">-</strong></div><div class="ps-metric"><label>Erkannte Erweiterungen</label><strong id="detectedSolutionsValue">Keine</strong></div></section><div id="tabsHost" class="ps-tabs" role="tablist" aria-label="Portal Settings Tabs"></div><section id="emptyState" class="ps-empty" hidden>Keine unterstuetzten Erweiterungen wurden aktiv gefunden. Aktuell werden <strong>Custom Branding</strong>, <strong>Mega Menu</strong> und <strong>ExpiryIndicator</strong> unterstuetzt.</section>
<section id="panel-branding" class="ps-panel" hidden><div class="ps-panel-head"><div><h2>Custom Branding</h2><p>Pflegt die ClientSideComponentProperties des aktiven Custom Branding Application Customizers.</p></div><span id="brandingChip" class="ps-chip">0</span></div><div class="ps-grid"><div class="ps-card ps-span-4"><h3>CSS-Dateien</h3><p>Lokale SiteAssets-Pfade oder externe Stylesheets.</p><div class="ps-meta"><div><label>Component Id</label><strong id="brandingComponentId">-</strong></div><div><label>Registrierung</label><strong id="brandingLocation">-</strong></div></div><label class="ps-label" for="brandingCssPath">Neue CSS-Datei</label><div class="ps-inline"><input type="text" id="brandingCssPath" class="ps-input" placeholder="/sites/root/SiteAssets/custom-branding.css oder https://cdn.example.com/style.css" /><button type="button" class="ps-btn ps-btn-primary" onclick="addBrandingCssFile()">Hinzufuegen</button></div><div id="brandingCssList" class="ps-list"></div></div><div class="ps-card ps-span-4"><h3>Placeholder Top</h3><p>JSON-Array fuer den oberen Placeholder.</p><label class="ps-label" for="brandingTopJson">Top JSON</label><textarea id="brandingTopJson" class="ps-textarea" spellcheck="false"></textarea></div><div class="ps-card ps-span-4"><h3>Placeholder Bottom</h3><p>JSON-Array fuer den unteren Placeholder.</p><label class="ps-label" for="brandingBottomJson">Bottom JSON</label><textarea id="brandingBottomJson" class="ps-textarea" spellcheck="false"></textarea></div><div class="ps-card ps-span-12"><h3>ClientSideComponentProperties Preview</h3><p>Dieses JSON wird direkt in die aktive UserCustomAction fuer Custom Branding geschrieben.</p><pre id="brandingPreview" class="ps-preview"></pre><div class="ps-note"><strong>Schema:</strong> <code>{ cssfiles, placeholdertop: { elements: [] }, placeholderbottom: { elements: [] } }</code></div></div></div><div class="ps-actions-bar"><div class="ps-actions"><button type="button" class="ps-btn ps-btn-quiet" onclick="reloadSolutions('branding')">Neu laden</button><button type="button" class="ps-btn ps-btn-quiet" onclick="validateBrandingEditors()">JSON validieren</button><button type="button" class="ps-btn ps-btn-quiet" onclick="formatBrandingEditors()">JSON formatieren</button></div><div class="ps-actions"><button type="button" class="ps-btn ps-btn-primary" onclick="applyBrandingConfig()">Konfiguration anwenden</button></div></div></section>
<section id="panel-megamenu" class="ps-panel" hidden><div class="ps-panel-head"><div><h2>Mega Menu</h2><p>Pflegt die ClientSideComponentProperties des aktiven Mega Menu Application Customizers.</p></div><span id="megaMenuChip" class="ps-chip">OK</span></div><div class="ps-grid"><div class="ps-card ps-span-6"><h3>Navigation</h3><p>Waehlt das Termset aus dem Default Site Collection Term Store und pflegt Cache sowie Debug-Verhalten. Individuelle Styles werden ueber Custom Branding eingebunden.</p><div class="ps-meta"><div><label>Component Id</label><strong id="megaMenuComponentId">-</strong></div><div><label>Registrierung</label><strong id="megaMenuLocation">-</strong></div></div><input type="hidden" id="megaMenuTermSetId" /><input type="hidden" id="megaMenuTermSetName" /><label class="ps-label">Ausgewaehltes Termset</label><div class="ps-item"><div><strong id="megaMenuSelectedTermSetName">Kein Termset ausgewaehlt</strong><div id="megaMenuSelectedTermSetMeta" class="ps-path">-</div></div><div class="ps-actions"><button type="button" class="ps-btn" onclick="openMegaMenuTermSetDialog()">Auswaehlen</button><button type="button" class="ps-btn ps-btn-quiet" onclick="clearMegaMenuTermSet()">Leeren</button></div></div><label class="ps-label" for="megaMenuCacheMinutes" style="margin-top:16px;">Cache-Dauer in Minuten</label><input type="number" id="megaMenuCacheMinutes" class="ps-input" min="1" max="1440" value="15" /><label class="ps-label" style="margin-top:16px;">Debug-Modus</label><div class="ps-check"><input type="checkbox" id="megaMenuDebug" /><label for="megaMenuDebug" style="margin:0;font-weight:600;">Debug-Ausgaben in der Browser-Konsole aktivieren</label></div></div><div class="ps-card ps-span-6"><h3>ClientSideComponentProperties Preview</h3><p>Dieses JSON wird direkt in die aktive UserCustomAction fuer Mega Menu geschrieben.</p><pre id="megaMenuPreview" class="ps-preview"></pre><div class="ps-note"><strong>Schema:</strong> <code>{ termSetId, termSetName, cacheMinutes, debug }</code></div></div></div><div class="ps-actions-bar"><div class="ps-actions"><button type="button" class="ps-btn ps-btn-quiet" onclick="reloadSolutions('megamenu')">Neu laden</button></div><div class="ps-actions"><button type="button" class="ps-btn ps-btn-primary" onclick="applyMegaMenuConfig()">Konfiguration anwenden</button></div></div></section>
<section id="panel-expiryindicator" class="ps-panel" hidden><div class="ps-panel-head"><div><h2>ExpiryIndicator 2.1</h2><p>Zentrale Standards fuer die Site Collection und lokale Ausnahmen fuer einzelne Listen oder Bibliotheken.</p></div><span id="expiryIndicatorChip" class="ps-chip">V2.1</span></div><div class="ps-grid"><div class="ps-card ps-span-4"><h3>Site-Collection-Standard</h3><p>Gilt im Root Web und in allen Subwebs, sofern eine Bindung auf Vererbung steht.</p><div class="ps-actions" style="margin-top:16px;"><button type="button" class="ps-btn ps-btn-quiet" onclick="loadExpirySiteDefaults()">Standard laden</button><button type="button" class="ps-btn ps-btn-primary" onclick="saveExpirySiteDefaults()">Standard speichern</button></div><div class="ps-note"><strong>Speicherort:</strong> Property Bag des Root Webs. Es wird keine Konfigurationsliste verwendet.</div></div><div class="ps-card ps-span-8"><h3>Registrierte Bindungen</h3><p>Listen und Bibliotheken, die per PortalSettings oder Aktivierungsskript registriert wurden.</p><div id="expiryBindingsList" class="ps-list"><div class="ps-list-empty">Keine Bindungen registriert.</div></div><div class="ps-actions" style="margin-top:16px;"><button type="button" class="ps-btn ps-btn-quiet" onclick="openExpiryBindingDialog()">Bindung hinzufuegen</button><button type="button" class="ps-btn ps-btn-quiet" onclick="setAllExpiryBindingsToInheritance()">Alle auf Vererbung stellen</button></div></div><div class="ps-card ps-span-4"><h3>Ausgewaehltes Ziel</h3><p>Web, Liste und interner Name des gebundenen Ablaufdatumsfeldes.</p><label class="ps-label" for="expiryWebUrl" style="margin-top:16px;">Web URL</label><input type="text" id="expiryWebUrl" class="ps-input" /><label class="ps-label" for="expiryListTitle" style="margin-top:16px;">Listentitel</label><input type="text" id="expiryListTitle" class="ps-input" placeholder="Dokumente" /><label class="ps-label" for="expiryFieldName" style="margin-top:16px;">Interner Feldname</label><input type="text" id="expiryFieldName" class="ps-input" placeholder="ExpiryDate" /><div class="ps-actions" style="margin-top:16px;"><button type="button" class="ps-btn ps-btn-primary" onclick="loadExpiryIndicatorConfig()">Lokale Konfiguration laden</button><button type="button" class="ps-btn ps-btn-quiet" onclick="setSelectedExpiryBindingToInheritance()">Standard erben</button></div></div><div class="ps-card ps-span-8"><h3>Konfigurationseditor</h3><p>Unterstuetzt V1-Regeln sowie verschachtelte V2-<code>condition</code>-Gruppen mit <code>and</code>/<code>or</code>.</p><textarea id="expiryIndicatorJson" class="ps-textarea" style="min-height:430px" spellcheck="false"></textarea><div class="ps-note"><strong>Prioritaet:</strong> lokale Feldkonfiguration &gt; Subweb-Override &gt; Site-Collection-Standard &gt; eingebauter Standard.</div></div></div><div class="ps-actions-bar"><div class="ps-actions"><button type="button" class="ps-btn ps-btn-quiet" onclick="validateExpiryIndicatorConfig()">JSON validieren</button><button type="button" class="ps-btn ps-btn-quiet" onclick="formatExpiryIndicatorConfig()">JSON formatieren</button></div><div class="ps-actions"><button type="button" class="ps-btn ps-btn-primary" onclick="applyExpiryIndicatorConfig()">Als lokale Ausnahme speichern</button></div></div></section>
<div id="expiryBindingDialog" class="ps-modal-backdrop" hidden><div class="ps-modal" role="dialog" aria-modal="true" aria-labelledby="expiryBindingDialogTitle"><h2 id="expiryBindingDialogTitle">ExpiryIndicator anbinden</h2><p>Verbindet ein bereits vorhandenes Datumsfeld mit dem Field Customizer und registriert die Liste zentral.</p><div id="bindExpiryStatus"></div><label class="ps-label" for="bindExpiryWebUrl">Web URL</label><input type="text" id="bindExpiryWebUrl" class="ps-input" /><label class="ps-label" for="bindExpiryListTitle" style="margin-top:16px;">Listentitel</label><input type="text" id="bindExpiryListTitle" class="ps-input" placeholder="Documents" /><label class="ps-label" for="bindExpiryFieldName" style="margin-top:16px;">Interner Name des vorhandenen Datumsfeldes</label><input type="text" id="bindExpiryFieldName" class="ps-input" placeholder="ExpiryDate" /><div class="ps-check" style="margin-top:14px;"><input type="checkbox" id="bindExpiryClassic" checked="checked" /><label for="bindExpiryClassic" style="margin:0;font-weight:600;">Classic ScriptLink und Ribbon-Befehl registrieren</label></div><div class="ps-note">Die Solution legt keine Spalte an. Bei bereits vorhandener lokaler Konfiguration bleiben die Component Properties erhalten.</div><div class="ps-actions"><button type="button" class="ps-btn ps-btn-quiet" onclick="closeExpiryBindingDialog()">Abbrechen</button><button type="button" id="bindExpirySubmit" class="ps-btn ps-btn-primary" onclick="bindExpiryIndicatorFromDialog()">Bindung anlegen</button></div></div></div>
<div id="megaMenuTermSetDialog" class="ps-modal-backdrop" hidden><div class="ps-modal" role="dialog" aria-modal="true" aria-labelledby="megaMenuTermSetDialogTitle"><h2 id="megaMenuTermSetDialogTitle">Termset auswaehlen</h2><p>Termsets aus dem Default Site Collection Term Store.</p><div id="megaMenuTermSetStatus"></div><label class="ps-label" for="megaMenuTermSetSearch">Suche</label><input type="text" id="megaMenuTermSetSearch" class="ps-input" placeholder="Gruppe oder Termset" oninput="renderMegaMenuTermSets()" /><div id="megaMenuTermSetList" style="max-height:420px;overflow:auto;margin-top:16px;"></div><div class="ps-actions" style="margin-top:16px;"><button type="button" class="ps-btn ps-btn-quiet" onclick="closeMegaMenuTermSetDialog()">Abbrechen</button></div></div></div>
<script type="text/javascript">
var CUSTOM_SOLUTION_TAG='MSFT-Custom-Solution';var EXPIRY_FIELD_COMPONENT_ID='a555f4fc-d6a6-4421-8189-457449d9bbde';var EXPIRY_DESCRIPTOR_PROPERTY='PortalSettings.Solutions.ExpiryIndicator';var EXPIRY_DEFAULTS_PROPERTY='PortalSettings.ExpiryIndicator.Defaults';var EXPIRY_BINDINGS_PROPERTY='PortalSettings.ExpiryIndicator.Bindings';var SUPPORTED_SOLUTIONS={branding:{key:'branding',label:'Custom Branding',componentId:'035ba968-6488-4d42-86b3-0470ffcc95b9',panelId:'panel-branding',descriptionTag:'MSFT-Custom-Solution:CustomBranding',providerType:'applicationCustomizer'},megamenu:{key:'megamenu',label:'Mega Menu',componentId:'abc3361f-bb2d-491f-aba3-cd51c19a299b',panelId:'panel-megamenu',descriptionTag:'MSFT-Custom-Solution:MegaMenu',providerType:'applicationCustomizer'},expiryindicator:{key:'expiryindicator',label:'ExpiryIndicator',componentId:EXPIRY_FIELD_COMPONENT_ID,panelId:'panel-expiryindicator',providerType:'rootPropertyBag'}};
var state={booted:false,activeKey:'',solutions:{},megaMenuTermSets:[],megaMenuTermSetsLoaded:false};
document.addEventListener('DOMContentLoaded',function(){bootPage();});
function bootPage(){if(state.booted){return;}if(window.SP&&SP.ClientContext){state.booted=true;bindPage();reloadSolutions();return;}window.setTimeout(bootPage,150);}
function bindPage(){document.getElementById('siteUrlValue').textContent=getSiteUrl();document.getElementById('expiryWebUrl').value=getWebUrl();document.getElementById('expiryIndicatorJson').value=JSON.stringify(defaultExpiryIndicatorConfig('ExpiryDate'),null,2);document.getElementById('brandingCssPath').addEventListener('keypress',function(event){if(event.key==='Enter'){event.preventDefault();addBrandingCssFile();}});document.getElementById('brandingTopJson').addEventListener('input',updateBrandingPreview);document.getElementById('brandingBottomJson').addEventListener('input',updateBrandingPreview);document.getElementById('megaMenuTermSetId').addEventListener('input',updateMegaMenuPreview);document.getElementById('megaMenuTermSetName').addEventListener('input',updateMegaMenuPreview);document.getElementById('megaMenuCacheMinutes').addEventListener('input',updateMegaMenuPreview);document.getElementById('megaMenuDebug').addEventListener('change',updateMegaMenuPreview);}
function reloadSolutions(preferredKey){showStatus('Lade aktive Einstellungsanbieter...','info',true);state.solutions={};loadSolutionsSequentially(Object.keys(SUPPORTED_SOLUTIONS),0,preferredKey||state.activeKey,[],[],function(labels,errors,preferred){var keys=getAvailableSolutionKeys();if(!keys.length){state.activeKey='';renderTabs();renderPanels();updateOverview();showStatus('Keine unterstuetzten Erweiterungen wurden aktiv registriert gefunden.','warning',true);return;}state.activeKey=preferred&&state.solutions[preferred]?preferred:keys[0];hydratePanels();renderTabs();renderPanels();updateOverview();var message='Aktive Erweiterungen geladen: '+labels.join(', ')+'.';if(errors.length){showStatus(message+' Hinweise: '+errors.join(' | '),'warning',true);return;}showStatus(message,'success');});}
function loadSolutionsSequentially(keys,index,preferred,labels,errors,done){if(index>=keys.length){done(labels,errors,preferred);return;}var key=keys[index];var definition=SUPPORTED_SOLUTIONS[key];if(definition.providerType==='rootPropertyBag'){readExpiryProviderInfo(function(info){if(info){state.solutions[key]={definition:definition,info:info,config:info.siteDefaults};labels.push(definition.label);}loadSolutionsSequentially(keys,index+1,preferred,labels,errors,done);},function(message){errors.push(definition.label+': '+message);loadSolutionsSequentially(keys,index+1,preferred,labels,errors,done);});return;}readCustomizerActionInfo(definition,function(info){if(info&&info.action){state.solutions[key]={definition:definition,info:info,config:parseSolutionConfig(key,info.action.ClientSideComponentProperties)};labels.push(definition.label);}loadSolutionsSequentially(keys,index+1,preferred,labels,errors,done);},function(message){errors.push(definition.label+': '+message);loadSolutionsSequentially(keys,index+1,preferred,labels,errors,done);});}
function hydratePanels(){if(state.solutions.branding){populateBrandingPanel(state.solutions.branding);}if(state.solutions.megamenu){populateMegaMenuPanel(state.solutions.megamenu);}if(state.solutions.expiryindicator){renderExpiryBindings();}}
function renderTabs(){var host=document.getElementById('tabsHost');var keys=getAvailableSolutionKeys();if(!keys.length){host.innerHTML='';host.hidden=true;document.getElementById('emptyState').hidden=false;return;}host.hidden=false;document.getElementById('emptyState').hidden=true;var html='';for(var i=0;i<keys.length;i++){var key=keys[i];var def=SUPPORTED_SOLUTIONS[key];var isActive=state.activeKey===key;html+='<button type="button" class="ps-tab'+(isActive?' is-active':'')+'" role="tab" aria-selected="'+(isActive?'true':'false')+'" aria-controls="'+def.panelId+'" onclick="switchTab(\''+key+'\')">'+escapeHtml(def.label)+'</button>'; }host.innerHTML=html;}
function renderPanels(){document.getElementById('panel-branding').hidden=!(state.activeKey==='branding'&&state.solutions.branding);document.getElementById('panel-megamenu').hidden=!(state.activeKey==='megamenu'&&state.solutions.megamenu);document.getElementById('panel-expiryindicator').hidden=!(state.activeKey==='expiryindicator'&&state.solutions.expiryindicator);}
function switchTab(key){if(!state.solutions[key]){return;}state.activeKey=key;renderTabs();renderPanels();updateOverview();}
function updateOverview(){var keys=getAvailableSolutionKeys();document.getElementById('activeSolutionCount').textContent=String(keys.length);document.getElementById('activeTabValue').textContent=state.activeKey&&SUPPORTED_SOLUTIONS[state.activeKey]?SUPPORTED_SOLUTIONS[state.activeKey].label:'-';document.getElementById('detectedSolutionsValue').textContent=keys.length?keys.map(function(key){return SUPPORTED_SOLUTIONS[key].label;}).join(', '):'Keine';}
function populateBrandingPanel(solution){document.getElementById('brandingComponentId').textContent=solution.definition.componentId;document.getElementById('brandingLocation').textContent=getActionLocationText(solution.info);document.getElementById('brandingCssPath').value='';document.getElementById('brandingTopJson').value=JSON.stringify(solution.config.placeholdertop.elements,null,2);document.getElementById('brandingBottomJson').value=JSON.stringify(solution.config.placeholderbottom.elements,null,2);renderBrandingCssFiles();updateBrandingPreview();}
function renderBrandingCssFiles(){var host=document.getElementById('brandingCssList');var solution=state.solutions.branding;var cssFiles=solution?solution.config.cssfiles:[];if(!cssFiles||!cssFiles.length){host.innerHTML='<div class="ps-list-empty">Keine CSS-Dateien konfiguriert.</div>';document.getElementById('brandingChip').textContent='0';return;}var html='';for(var i=0;i<cssFiles.length;i++){html+='<div class="ps-item"><div class="ps-path">'+escapeHtml(cssFiles[i].path)+'</div><button type="button" class="ps-btn ps-btn-danger" onclick="removeBrandingCssFile('+i+')">Entfernen</button></div>'; }host.innerHTML=html;document.getElementById('brandingChip').textContent=String(cssFiles.length);}
function addBrandingCssFile(){var solution=state.solutions.branding;if(!solution){showStatus('Custom Branding ist aktuell nicht aktiv konfiguriert.','warning');return;}var input=document.getElementById('brandingCssPath');var path=input.value.trim();if(!path){showStatus('Bitte geben Sie einen CSS-Pfad ein.','warning');return;}var exists=solution.config.cssfiles.some(function(item){return item.path===path;});if(exists){showStatus('Diese CSS-Datei ist bereits vorhanden.','warning');return;}solution.config.cssfiles.push({path:path});input.value='';renderBrandingCssFiles();updateBrandingPreview();showStatus('CSS-Datei hinzugefuegt.','success');}
function removeBrandingCssFile(index){var solution=state.solutions.branding;if(!solution){return;}solution.config.cssfiles.splice(index,1);renderBrandingCssFiles();updateBrandingPreview();showStatus('CSS-Datei entfernt.','success');}
function buildBrandingConfig(){var solution=state.solutions.branding;if(!solution){throw new Error('Custom Branding ist aktuell nicht aktiv konfiguriert.');}return{cssfiles:solution.config.cssfiles.slice(),placeholdertop:{elements:parseJsonArray('brandingTopJson','Placeholder Top')},placeholderbottom:{elements:parseJsonArray('brandingBottomJson','Placeholder Bottom')}};}
function validateBrandingEditors(){try{state.solutions.branding.config=normalizeBrandingConfig(buildBrandingConfig());updateBrandingPreview();showStatus('Custom Branding JSON ist gueltig.','success');}catch(error){showStatus(error.message,'error');}}
function formatBrandingEditors(){try{state.solutions.branding.config=normalizeBrandingConfig(buildBrandingConfig());document.getElementById('brandingTopJson').value=JSON.stringify(state.solutions.branding.config.placeholdertop.elements,null,2);document.getElementById('brandingBottomJson').value=JSON.stringify(state.solutions.branding.config.placeholderbottom.elements,null,2);updateBrandingPreview();showStatus('Custom Branding JSON wurde formatiert.','success');}catch(error){showStatus(error.message,'error');}}
function updateBrandingPreview(){var preview=document.getElementById('brandingPreview');try{var config=buildBrandingConfig();preview.textContent=JSON.stringify(config,null,2);document.getElementById('brandingChip').textContent=String(config.cssfiles.length);}catch(error){preview.textContent='Fehler: '+error.message;}}
function applyBrandingConfig(){try{var config=buildBrandingConfig();showStatus('Aktualisiere Custom Branding...','info',true);updateSolutionAction('branding',config,function(){state.solutions.branding.config=normalizeBrandingConfig(config);populateBrandingPanel(state.solutions.branding);showStatus('Custom Branding wurde erfolgreich aktualisiert.','success');},function(message){showStatus('Custom Branding konnte nicht aktualisiert werden: '+message,'error',true);});}catch(error){showStatus(error.message,'error');}}
function populateMegaMenuPanel(solution){document.getElementById('megaMenuComponentId').textContent=solution.definition.componentId;document.getElementById('megaMenuLocation').textContent=getActionLocationText(solution.info);document.getElementById('megaMenuTermSetId').value=solution.config.termSetId||'';document.getElementById('megaMenuTermSetName').value=solution.config.termSetName||'';document.getElementById('megaMenuCacheMinutes').value=String(solution.config.cacheMinutes||15);document.getElementById('megaMenuDebug').checked=solution.config.debug===true;updateMegaMenuSelectedTermSet();updateMegaMenuPreview();}
function buildMegaMenuConfig(){var solution=state.solutions.megamenu;if(!solution){throw new Error('Mega Menu ist aktuell nicht aktiv konfiguriert.');}var termSetId=document.getElementById('megaMenuTermSetId').value.trim();var termSetName=document.getElementById('megaMenuTermSetName').value.trim();var cacheMinutes=Number(document.getElementById('megaMenuCacheMinutes').value);if(termSetId&&!/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(termSetId)){throw new Error('Die Term Set ID ist keine gueltige GUID.');}if(!termSetId&&!termSetName){throw new Error('Bitte eine Term Set ID oder einen Term Set Name angeben.');}if(!isFinite(cacheMinutes)||cacheMinutes<1||cacheMinutes>1440){throw new Error('Die Cache-Dauer muss zwischen 1 und 1440 Minuten liegen.');}return{termSetId:termSetId,termSetName:termSetName,cacheMinutes:Math.floor(cacheMinutes),debug:document.getElementById('megaMenuDebug').checked===true};}
function updateMegaMenuPreview(){var preview=document.getElementById('megaMenuPreview');try{var config=buildMegaMenuConfig();preview.textContent=JSON.stringify(config,null,2);document.getElementById('megaMenuChip').textContent=config.debug?'DBG':'OK';}catch(error){preview.textContent='Fehler: '+error.message;document.getElementById('megaMenuChip').textContent='!';}}
function applyMegaMenuConfig(){try{var config=buildMegaMenuConfig();showStatus('Aktualisiere Mega Menu...','info',true);updateSolutionAction('megamenu',config,function(){state.solutions.megamenu.config=normalizeMegaMenuConfig(config);populateMegaMenuPanel(state.solutions.megamenu);showStatus('Mega Menu wurde erfolgreich aktualisiert.','success');},function(message){showStatus('Mega Menu konnte nicht aktualisiert werden: '+message,'error',true);});}catch(error){showStatus(error.message,'error');}}
function updateMegaMenuSelectedTermSet(){var id=document.getElementById('megaMenuTermSetId').value.trim();var name=document.getElementById('megaMenuTermSetName').value.trim();document.getElementById('megaMenuSelectedTermSetName').textContent=name||'Kein Termset ausgewaehlt';document.getElementById('megaMenuSelectedTermSetMeta').textContent=id||'-';}
function clearMegaMenuTermSet(){document.getElementById('megaMenuTermSetId').value='';document.getElementById('megaMenuTermSetName').value='';updateMegaMenuSelectedTermSet();updateMegaMenuPreview();}
function openMegaMenuTermSetDialog(){document.getElementById('megaMenuTermSetDialog').hidden=false;document.getElementById('megaMenuTermSetSearch').value='';document.getElementById('megaMenuTermSetList').innerHTML='';setMegaMenuTermSetStatus('Lade Termsets...','info');if(state.megaMenuTermSetsLoaded){setMegaMenuTermSetStatus('','info');renderMegaMenuTermSets();window.setTimeout(function(){document.getElementById('megaMenuTermSetSearch').focus();},0);return;}loadMegaMenuTermSets();}
function closeMegaMenuTermSetDialog(){document.getElementById('megaMenuTermSetDialog').hidden=true;}
function setMegaMenuTermSetStatus(message,type){var host=document.getElementById('megaMenuTermSetStatus');host.innerHTML=message?'<div class="ps-status ps-status-'+type+'">'+escapeHtml(message)+'</div>':'';}
function loadMegaMenuTermSets(){var requestXml='<Request AddExpandoFieldTypeSuffix="true" SchemaVersion="15.0.0.0" LibraryVersion="16.0.0.0" ApplicationName="PortalSettings" xmlns="http://schemas.microsoft.com/sharepoint/clientquery/2009"><Actions><ObjectPath Id="2" ObjectPathId="1" /><ObjectIdentityQuery Id="3" ObjectPathId="1" /><ObjectPath Id="5" ObjectPathId="4" /><ObjectIdentityQuery Id="6" ObjectPathId="4" /><Query Id="7" ObjectPathId="4"><Query SelectAllProperties="false"><Properties><Property Name="Id" ScalarProperty="true" /><Property Name="Name" ScalarProperty="true" /><Property Name="Groups"><Query SelectAllProperties="false"><Properties /></Query><ChildItemQuery SelectAllProperties="false"><Properties><Property Name="Name" ScalarProperty="true" /><Property Name="Id" ScalarProperty="true" /><Property Name="IsSystemGroup" ScalarProperty="true" /><Property Name="TermSets"><Query SelectAllProperties="false"><Properties /></Query><ChildItemQuery SelectAllProperties="false"><Properties><Property Name="Name" ScalarProperty="true" /><Property Name="Id" ScalarProperty="true" /><Property Name="Description" ScalarProperty="true" /></Properties></ChildItemQuery></Property></Properties></ChildItemQuery></Property></Properties></Query></Query></Actions><ObjectPaths><StaticMethod Id="1" Name="GetTaxonomySession" TypeId="{981cbc68-9edc-4f8d-872f-71146fcbb84f}" /><Method Id="4" ParentId="1" Name="GetDefaultSiteCollectionTermStore" /></ObjectPaths></Request>';try{var request=new XMLHttpRequest();request.open('POST',getSiteUrl().replace(/\/$/,'')+'/_vti_bin/client.svc/ProcessQuery',true);request.setRequestHeader('Accept','application/json');request.setRequestHeader('Content-Type','application/xml');var digest=getRequestDigest();if(digest){request.setRequestHeader('X-RequestDigest',digest);}request.onreadystatechange=function(){if(request.readyState!==4){return;}if(request.status<200||request.status>=300){setMegaMenuTermSetStatus('Termsets konnten nicht geladen werden: '+buildRequestErrorMessage(request),'error');return;}try{var payload=JSON.parse(request.responseText||'[]');var errorInfo=payload&&payload.length?payload[0]:null;if(errorInfo&&errorInfo.ErrorInfo){throw new Error(errorInfo.ErrorInfo.ErrorMessage||'Taxonomy-Abfrage fehlgeschlagen.');}state.megaMenuTermSets=parseMegaMenuTermSets(payload);state.megaMenuTermSetsLoaded=true;setMegaMenuTermSetStatus(state.megaMenuTermSets.length?'':'Keine lesbaren Termsets gefunden.',state.megaMenuTermSets.length?'info':'warning');renderMegaMenuTermSets();window.setTimeout(function(){document.getElementById('megaMenuTermSetSearch').focus();},0);}catch(error){setMegaMenuTermSetStatus('Termsets konnten nicht gelesen werden: '+(error.message||String(error)),'error');}};request.onerror=function(){setMegaMenuTermSetStatus('Netzwerkfehler beim Laden der Termsets.','error');};request.send(requestXml);}catch(error){setMegaMenuTermSetStatus(error.message||String(error),'error');}}
function parseMegaMenuTermSets(payload){var result=[];if(!Array.isArray(payload)){return result;}var store=null;for(var i=0;i<payload.length;i++){if(payload[i]&&payload[i]._ObjectType_==='SP.Taxonomy.TermStore'){store=payload[i];break;}}var groups=store&&store.Groups&&Array.isArray(store.Groups._Child_Items_)?store.Groups._Child_Items_:[];for(var g=0;g<groups.length;g++){var group=groups[g];if(!group||group.IsSystemGroup===true){continue;}var sets=group.TermSets&&Array.isArray(group.TermSets._Child_Items_)?group.TermSets._Child_Items_:[];for(var s=0;s<sets.length;s++){var termSet=sets[s];if(!termSet){continue;}var id=cleanTaxonomyGuid(termSet.Id);if(id){result.push({id:id,name:termSet.Name||id,groupName:group.Name||'Ohne Gruppe',description:termSet.Description||''});}}}result.sort(function(left,right){var a=(left.groupName+' '+left.name).toLowerCase();var b=(right.groupName+' '+right.name).toLowerCase();return a<b?-1:(a>b?1:0);});return result;}
function cleanTaxonomyGuid(value){return String(value||'').replace('/Guid(','').replace(')/','').replace(')','').replace(/[{}]/g,'').toLowerCase();}
function renderMegaMenuTermSets(){var host=document.getElementById('megaMenuTermSetList');var search=document.getElementById('megaMenuTermSetSearch').value.trim().toLowerCase();var html='';for(var i=0;i<state.megaMenuTermSets.length;i++){var item=state.megaMenuTermSets[i];var haystack=(item.groupName+' '+item.name+' '+item.description+' '+item.id).toLowerCase();if(search&&haystack.indexOf(search)===-1){continue;}html+='<div class="ps-item"><div><strong>'+escapeHtml(item.name)+'</strong><div class="ps-path">'+escapeHtml(item.groupName)+' | '+escapeHtml(item.id)+'</div>'+(item.description?'<div>'+escapeHtml(item.description)+'</div>':'')+'</div><button type="button" class="ps-btn" onclick="selectMegaMenuTermSet('+i+')">Auswaehlen</button></div>'; }host.innerHTML=html||'<div class="ps-list-empty">Keine passenden Termsets gefunden.</div>';}
function selectMegaMenuTermSet(index){var item=state.megaMenuTermSets[index];if(!item){return;}document.getElementById('megaMenuTermSetId').value=item.id;document.getElementById('megaMenuTermSetName').value=item.name;updateMegaMenuSelectedTermSet();updateMegaMenuPreview();closeMegaMenuTermSetDialog();showStatus('Termset ausgewaehlt: '+item.name+'.','success');}
function defaultExpiryIndicatorConfig(expiryField){return{baseField:'Created',expiryField:expiryField||'ExpiryDate',default:{lifeTime:{value:2,unit:'years'},columnRule:[{daysUntilExpiry:0,operator:'lessOrEqual',backgroundColor:'#a4262c',textColor:'#ffffff',label:'Abgelaufen'},{daysUntilExpiry:30,operator:'lessOrEqual',backgroundColor:'#ffaa44',textColor:'#000000',label:'Laeuft bald ab'},{daysUntilExpiry:90,operator:'lessOrEqual',backgroundColor:'#fff4ce',textColor:'#000000',label:'Beobachten'}]},rules:[],nullText:'Kein Ablaufdatum',confirmExtension:true};}
function openExpiryBindingDialog(){document.getElementById('bindExpiryWebUrl').value=getWebUrl();document.getElementById('bindExpiryListTitle').value='';document.getElementById('bindExpiryFieldName').value='ExpiryDate';document.getElementById('bindExpiryStatus').innerHTML='';document.getElementById('bindExpirySubmit').disabled=false;document.getElementById('expiryBindingDialog').hidden=false;window.setTimeout(function(){document.getElementById('bindExpiryListTitle').focus();},0);}
function closeExpiryBindingDialog(){document.getElementById('expiryBindingDialog').hidden=true;}
function getExpiryBindingInput(){var webUrl=document.getElementById('bindExpiryWebUrl').value.trim().replace(/\/$/,'');var listTitle=document.getElementById('bindExpiryListTitle').value.trim();var fieldName=document.getElementById('bindExpiryFieldName').value.trim();if(!webUrl){throw new Error('Bitte eine Web URL angeben.');}if(!listTitle){throw new Error('Bitte einen Listentitel angeben.');}if(!/^[A-Za-z_][A-Za-z0-9_]*$/.test(fieldName)){throw new Error('Der interne Feldname ist ungueltig.');}return{webUrl:webUrl,listTitle:listTitle,fieldName:fieldName,classic:document.getElementById('bindExpiryClassic').checked===true};}
function bindingListUrl(target){return target.webUrl+"/_api/web/lists/getbytitle('"+target.listTitle.replace(/'/g,"''")+"')";}
function bindExpiryIndicatorFromDialog(){var target;try{target=getExpiryBindingInput();}catch(error){showStatus(error.message,'error',true);return;}var button=document.getElementById('bindExpirySubmit');button.disabled=true;showStatus('Pruefe Liste und Ablaufdatumsfeld...','info',true);var listUrl=bindingListUrl(target);getJson(listUrl+'?$select=Id,Title,BaseType',function(listPayload){var list=listPayload.d||listPayload;getJson(listUrl+"/fields/getbyinternalnameortitle('"+target.fieldName+"')?$select=Id,InternalName,TypeAsString,ClientSideComponentId,ClientSideComponentProperties",function(fieldPayload){var field=fieldPayload.d||fieldPayload;var currentComponent=normalizeGuid(field.ClientSideComponentId);if(currentComponent&&currentComponent!==normalizeGuid(EXPIRY_FIELD_COMPONENT_ID)){button.disabled=false;showStatus('Das Feld ist bereits mit einem anderen Field Customizer verbunden. Die Bindung wurde nicht geaendert.','error',true);return;}if(field.TypeAsString&&field.TypeAsString!=='DateTime'){button.disabled=false;showStatus('Das angegebene Feld ist kein DateTime-Feld.','error',true);return;}var properties=field.ClientSideComponentProperties;if(!properties||!String(properties).trim()){properties=JSON.stringify({schemaVersion:2,expiryField:field.InternalName,inheritSiteDefaults:true});}var payload={__metadata:{type:field.__metadata&&field.__metadata.type?field.__metadata.type:'SP.Field'},ClientSideComponentId:EXPIRY_FIELD_COMPONENT_ID,ClientSideComponentProperties:properties};postJsonMerge(listUrl+"/fields(guid'"+normalizeGuid(field.Id)+"')",payload,function(){var info={target:target,list:list,field:field,listUrl:listUrl};function finish(classicWarning){registerExpiryBinding(info,function(){button.disabled=false;closeExpiryBindingDialog();reloadSolutions('expiryindicator');if(classicWarning){showStatus('Die moderne Bindung wurde angelegt. Classic konnte nicht vollstaendig registriert werden: '+classicWarning,'warning',true);}else{showStatus('ExpiryIndicator wurde erfolgreich angebunden und zentral registriert.','success');}},function(message){button.disabled=false;showStatus('Das Feld wurde gebunden, aber das zentrale Inventar konnte nicht aktualisiert werden: '+message,'warning',true);});}if(target.classic){ensureExpiryClassicRegistration(info,function(warning){finish(warning);});}else{finish('');}},function(message){button.disabled=false;showStatus('Das Feld konnte nicht mit dem ExpiryIndicator verbunden werden: '+message,'error',true);});},function(message){button.disabled=false;showStatus('Das Ablaufdatumsfeld konnte nicht gelesen werden: '+message,'error',true);});},function(message){button.disabled=false;showStatus('Die Liste oder Bibliothek konnte nicht gelesen werden: '+message,'error',true);});}
function registerExpiryBinding(info,onSuccess,onError){getJson(getSiteUrl().replace(/\/$/,'')+'/_api/web/AllProperties',function(payload){var properties=payload.d||payload||{};var bindings=normalizeExpiryBindings(propertyBagJson(properties,EXPIRY_BINDINGS_PROPERTY,[]));var webUrl=info.target.webUrl.replace(/\/$/,'');var listId=normalizeGuid(info.list.Id);bindings=bindings.filter(function(item){return !(String(item.webUrl||'').replace(/\/$/,'').toLowerCase()===webUrl.toLowerCase()&&normalizeGuid(item.listId)===listId);});bindings.push({webUrl:webUrl,listId:listId,listTitle:info.list.Title,fieldInternalName:info.field.InternalName,fieldId:normalizeGuid(info.field.Id)});var descriptor={key:'ExpiryIndicator',displayName:'Expiry Indicator',version:'2.1.1',provider:'expiryIndicator',componentIds:[EXPIRY_FIELD_COMPONENT_ID,'cd58f5d9-ffc8-4df7-a910-3054842d7abf','95691218-bcb1-4fad-adda-02bb12014d15'],settingsScope:['siteCollection','web','list']};setRootPropertyBagValue(EXPIRY_DESCRIPTOR_PROPERTY,JSON.stringify(descriptor),function(){setRootPropertyBagValue(EXPIRY_BINDINGS_PROPERTY,JSON.stringify(bindings),onSuccess,onError);},onError);},onError);}
function ensureExpiryClassicRegistration(info,onComplete){var webActionsUrl=info.target.webUrl+'/_api/web/UserCustomActions';getJson(webActionsUrl,function(webPayload){var webActions=getResultArray(webPayload);var hasScript=webActions.some(function(action){return action.Name==='ExpiryIndicator.Classic.ScriptLink';});function ensureRibbon(){var listActionsUrl=info.listUrl+'/UserCustomActions';getJson(listActionsUrl,function(listPayload){var listActions=getResultArray(listPayload);var ribbonAction=null;for(var i=0;i<listActions.length;i++){if(listActions[i].Name==='ExpiryIndicator.Classic.Ribbon'){ribbonAction=listActions[i];break;}}var ribbonLocation=Number(info.list.BaseType)===1?'Ribbon.Documents.Manage.Controls._children':'Ribbon.ListItem.Actions.Controls._children';var ribbonXml='<CommandUIExtension xmlns="http://schemas.microsoft.com/sharepoint/"><CommandUIDefinitions><CommandUIDefinition Location="'+ribbonLocation+'"><Button Id="ExpiryIndicator.Classic.ExtendOneYear.Button" Command="ExpiryIndicator.Classic.ExtendOneYear" Sequence="90" LabelText="Ablaufdatum +1 Jahr" Description="Verlaengert das Ablaufdatum der ausgewaehlten Elemente um ein Kalenderjahr." TemplateAlias="o1" /></CommandUIDefinition></CommandUIDefinitions><CommandUIHandlers><CommandUIHandler Command="ExpiryIndicator.Classic.ExtendOneYear" CommandAction="javascript:ExpiryIndicatorClassic.extendSelected();" EnabledScript="javascript:ExpiryIndicatorClassic.canExtend();" /></CommandUIHandlers></CommandUIExtension>';var payload={__metadata:{type:'SP.UserCustomAction'},Name:'ExpiryIndicator.Classic.Ribbon',Title:'ExpiryIndicator Classic ribbon command',Location:'CommandUI.Ribbon',CommandUIExtension:ribbonXml,Sequence:651};if(ribbonAction){if(String(ribbonAction.CommandUIExtension||'').indexOf(ribbonLocation)>=0){onComplete('');return;}postJsonMerge(listActionsUrl+"(guid'"+normalizeGuid(ribbonAction.Id)+"')",payload,function(){onComplete('');},onComplete);return;}postJson(listActionsUrl,payload,function(){onComplete('');},onComplete);},onComplete);}if(hasScript){ensureRibbon();return;}postJson(webActionsUrl,{__metadata:{type:'SP.UserCustomAction'},Name:'ExpiryIndicator.Classic.ScriptLink',Title:'ExpiryIndicator Classic runtime',Location:'ScriptLink',ScriptSrc:'~sitecollection/SiteAssets/ExpiryIndicator/ExpiryIndicatorClassic.js',Sequence:650},ensureRibbon,onComplete);},onComplete);}
function normalizePropertyBagKey(value){return String(value||'').replace(/_x002e_/gi,'.').toLowerCase();}
function propertyBagJson(properties,key,fallback){if(!properties){return fallback;}var value;var expected=normalizePropertyBagKey(key);Object.keys(properties).some(function(name){if(normalizePropertyBagKey(name)===expected){value=properties[name];return true;}return false;});if(typeof value==='undefined'||value===null||value===''){return fallback;}if(typeof value==='object'){return value;}try{return JSON.parse(value);}catch(ignore){return fallback;}}
function normalizeExpiryBindings(value){var result=[];function visit(item){if(Array.isArray(item)){item.forEach(visit);return;}if(!item||typeof item!=='object'){return;}if(item.webUrl&&item.listId&&item.fieldInternalName){result.push(item);}}visit(value);return result;}
function readExpiryProviderInfo(onSuccess,onError){getJson(getSiteUrl().replace(/\/$/,'')+'/_api/web/AllProperties',function(payload){var properties=payload.d||payload||{};var descriptor=propertyBagJson(properties,EXPIRY_DESCRIPTOR_PROPERTY,null);var bindings=normalizeExpiryBindings(propertyBagJson(properties,EXPIRY_BINDINGS_PROPERTY,[]));var siteDefaults=propertyBagJson(properties,EXPIRY_DEFAULTS_PROPERTY,null);if(!descriptor&&!siteDefaults&&!bindings.length){onSuccess(null);return;}onSuccess({descriptor:descriptor,bindings:bindings,siteDefaults:siteDefaults||defaultExpiryIndicatorConfig('ExpiryDate'),rootProperties:properties,selected:null});},onError);}
function renderExpiryBindings(){var solution=state.solutions.expiryindicator;var host=document.getElementById('expiryBindingsList');if(!solution||!solution.info||!solution.info.bindings.length){host.innerHTML='<div class="ps-list-empty">Keine Bindungen registriert. Aktivieren Sie ExpiryIndicator 2.1 erneut an den gewuenschten Listen.</div>';return;}var html='';for(var i=0;i<solution.info.bindings.length;i++){var binding=solution.info.bindings[i];html+='<div class="ps-item"><div><strong>'+escapeHtml(binding.listTitle||binding.listId)+'</strong><div class="ps-path">'+escapeHtml(binding.webUrl)+' | '+escapeHtml(binding.fieldInternalName)+'</div></div><button type="button" class="ps-btn ps-btn-quiet" onclick="selectExpiryBinding('+i+')">Auswaehlen</button></div>';}host.innerHTML=html;document.getElementById('expiryIndicatorChip').textContent=String(solution.info.bindings.length);}
function selectExpiryBinding(index){var solution=state.solutions.expiryindicator;if(!solution||!solution.info||!solution.info.bindings[index]){return;}var binding=solution.info.bindings[index];document.getElementById('expiryWebUrl').value=binding.webUrl||getWebUrl();document.getElementById('expiryListTitle').value=binding.listTitle||'';document.getElementById('expiryFieldName').value=binding.fieldInternalName||'ExpiryDate';loadExpiryIndicatorConfig();}
function loadExpirySiteDefaults(){var solution=state.solutions.expiryindicator;if(!solution||!solution.info){return;}var config=solution.info.siteDefaults||defaultExpiryIndicatorConfig('ExpiryDate');document.getElementById('expiryIndicatorJson').value=JSON.stringify(config,null,2);showStatus('Site-Collection-Standard wurde in den Editor geladen.','success');}
function saveExpirySiteDefaults(){var config;try{config=parseExpiryIndicatorEditor();}catch(error){showStatus(error.message,'error',true);return;}showStatus('Speichere Site-Collection-Standard...','info',true);setRootPropertyBagValue(EXPIRY_DEFAULTS_PROPERTY,JSON.stringify(config),function(){state.solutions.expiryindicator.info.siteDefaults=config;state.solutions.expiryindicator.config=config;showStatus('Site-Collection-Standard wurde gespeichert. Vererbende Bindungen verwenden ihn beim naechsten Laden.','success');},function(message){showStatus('Site-Collection-Standard konnte nicht gespeichert werden: '+message,'error',true);});}
function setRootPropertyBagValue(key,value,onSuccess,onError){try{var context=new SP.ClientContext(getSiteUrl());var web=context.get_web();var properties=web.get_allProperties();context.load(properties);context.executeQueryAsync(function(){try{properties.set_item(key,value);web.update();context.executeQueryAsync(onSuccess,function(sender,args){onError(args.get_message());});}catch(error){onError(error.message||String(error));}},function(sender,args){onError(args.get_message());});}catch(error){onError(error.message||String(error));}}
function inheritancePayload(fieldName){return{__metadata:{type:'SP.Field'},ClientSideComponentProperties:JSON.stringify({schemaVersion:2,expiryField:fieldName,inheritSiteDefaults:true})};}
function setSelectedExpiryBindingToInheritance(){var target;try{target=getExpiryTarget();}catch(error){showStatus(error.message,'error');return;}showStatus('Aktiviere Vererbung fuer die ausgewaehlte Bindung...','info',true);postJsonMerge(expiryFieldUrl(target),inheritancePayload(target.fieldName),function(){if(state.solutions.expiryindicator.info){state.solutions.expiryindicator.info.selected=null;}loadExpirySiteDefaults();showStatus('Die Bindung erbt jetzt den Site-Collection-Standard.','success');},function(message){showStatus('Vererbung konnte nicht aktiviert werden: '+message,'error',true);});}
function expiryBindingFieldUrl(binding){return String(binding.webUrl||'').replace(/\/$/,'')+"/_api/web/lists(guid'"+normalizeGuid(binding.listId)+"')/fields/getbyinternalnameortitle('"+String(binding.fieldInternalName||'ExpiryDate').replace(/'/g,"''")+"')";}
function setAllExpiryBindingsToInheritance(){var solution=state.solutions.expiryindicator;var bindings=solution&&solution.info?solution.info.bindings:[];if(!bindings.length){showStatus('Es sind keine Bindungen registriert.','warning');return;}if(!window.confirm('Lokale Konfigurationen von '+bindings.length+' Bindung(en) durch Vererbung ersetzen?')){return;}showStatus('Aktiviere Vererbung fuer '+bindings.length+' Bindung(en)...','info',true);var errors=[];function next(index){if(index>=bindings.length){if(errors.length){showStatus('Vererbung wurde mit Hinweisen abgeschlossen: '+errors.join(' | '),'warning',true);}else{showStatus('Alle registrierten Bindungen erben jetzt den Site-Collection-Standard.','success');}return;}var binding=bindings[index];postJsonMerge(expiryBindingFieldUrl(binding),inheritancePayload(binding.fieldInternalName),function(){next(index+1);},function(message){errors.push((binding.listTitle||binding.listId)+': '+message);next(index+1);});}next(0);}
function getExpiryTarget(){var webUrl=document.getElementById('expiryWebUrl').value.trim().replace(/\/$/,'');var listTitle=document.getElementById('expiryListTitle').value.trim();var fieldName=document.getElementById('expiryFieldName').value.trim();if(!webUrl){throw new Error('Bitte eine Web URL angeben.');}if(!listTitle){throw new Error('Bitte einen Listentitel angeben.');}if(!/^[A-Za-z_][A-Za-z0-9_]*$/.test(fieldName)){throw new Error('Der interne Feldname ist ungueltig.');}return{webUrl:webUrl,listTitle:listTitle,fieldName:fieldName};}
function expiryFieldUrl(target){return target.webUrl+"/_api/web/lists/getbytitle('"+target.listTitle.replace(/'/g,"''")+"')/fields/getbyinternalnameortitle('"+target.fieldName+"')";}
function loadExpiryIndicatorConfig(){var target;try{target=getExpiryTarget();}catch(error){showStatus(error.message,'error');return;}showStatus('Lade ExpiryIndicator-Konfiguration...','info',true);getJson(expiryFieldUrl(target)+'?$select=Id,InternalName,ClientSideComponentId,ClientSideComponentProperties',function(payload){var field=payload.d||payload;if(normalizeGuid(field.ClientSideComponentId)!==normalizeGuid(EXPIRY_FIELD_COMPONENT_ID)){showStatus('Das Feld ist nicht mit dem ExpiryIndicator Field Customizer verbunden.','error',true);return;}var config=defaultExpiryIndicatorConfig(field.InternalName);var inherited=false;if(field.ClientSideComponentProperties&&field.ClientSideComponentProperties.trim()){try{var stored=JSON.parse(field.ClientSideComponentProperties);if(stored.inheritSiteDefaults===true){inherited=true;config=JSON.parse(JSON.stringify(state.solutions.expiryindicator.info.siteDefaults||config));}else{config=stored;}}catch(error){showStatus('Die gespeicherten Component Properties enthalten ungueltiges JSON: '+error.message,'error',true);return;}}config.expiryField=field.InternalName;state.solutions.expiryindicator.info.selected={target:target,field:field,updateUrl:expiryFieldUrl(target),actionType:field.__metadata&&field.__metadata.type?field.__metadata.type:'SP.Field'};state.solutions.expiryindicator.config=config;document.getElementById('expiryFieldName').value=field.InternalName;document.getElementById('expiryIndicatorJson').value=JSON.stringify(config,null,2);document.getElementById('expiryIndicatorChip').textContent=inherited?'ERBT':'LOKAL';showStatus(inherited?'Die Bindung erbt den Site-Collection-Standard.':'Lokale ExpiryIndicator-Konfiguration wurde geladen.','success');},function(message){showStatus('ExpiryIndicator-Konfiguration konnte nicht geladen werden: '+message,'error',true);});}
function parseExpiryIndicatorEditor(){var raw=document.getElementById('expiryIndicatorJson').value.trim();if(!raw){throw new Error('Die ExpiryIndicator-Konfiguration darf nicht leer sein.');}var config;try{config=JSON.parse(raw);}catch(error){throw new Error('Ungueltiges JSON: '+error.message);}var errors=validateExpiryIndicatorObject(config);if(errors.length){throw new Error(errors.join(' | '));}return config;}
function validateExpiryIndicatorObject(config){var errors=[];function safeName(value){return/^[A-Za-z_][A-Za-z0-9_]*$/.test(value||'');}function validDuration(value,path){if(!value||typeof value.value!=='number'||!isFinite(value.value)||['days','months','years'].indexOf(value.unit)<0){errors.push(path+' muss value und unit days/months/years enthalten.');}}function validColorRules(value,path){if(!Array.isArray(value)){errors.push(path+' muss ein Array sein.');return;}for(var i=0;i<value.length;i++){var item=value[i];if(!item||typeof item.daysUntilExpiry!=='number'||['lessThan','lessOrEqual','equal','greaterOrEqual','greaterThan'].indexOf(item.operator)<0||typeof item.backgroundColor!=='string'||typeof item.textColor!=='string'){errors.push(path+'['+i+'] ist ungueltig.');}}}function validGroup(group,path,depth){if(depth>10){errors.push(path+' ist tiefer als 10 Ebenen.');return;}if(!group||(group.operator!=='and'&&group.operator!=='or')){errors.push(path+'.operator muss and oder or sein.');}if(!group||!Array.isArray(group.conditions)||!group.conditions.length){errors.push(path+'.conditions muss mindestens einen Eintrag enthalten.');return;}for(var i=0;i<group.conditions.length;i++){var condition=group.conditions[i];var child=path+'.conditions['+i+']';if(condition&&typeof condition.operator!=='undefined'){validGroup(condition,child,depth+1);}else if(!condition||!safeName(condition.columnName)||typeof condition.columnValue==='undefined'){errors.push(child+' benoetigt columnName und columnValue.');}}}if(!config||typeof config!=='object'){return['Konfiguration muss ein Objekt sein.'];}if(!safeName(config.baseField)){errors.push('baseField ist ungueltig.');}if(!safeName(config.expiryField)){errors.push('expiryField ist ungueltig.');}if(!config.default){errors.push('default fehlt.');}else{validDuration(config.default.lifeTime,'default.lifeTime');validColorRules(config.default.columnRule,'default.columnRule');}if(!Array.isArray(config.rules)){errors.push('rules muss ein Array sein.');}else{for(var r=0;r<config.rules.length;r++){var rule=config.rules[r];var path='rules['+r+']';if(rule&&rule.condition){validGroup(rule.condition,path+'.condition',0);}else if(!rule||!safeName(rule.columnName)||typeof rule.columnValue==='undefined'){errors.push(path+' benoetigt condition oder columnName/columnValue.');}if(rule){validDuration(rule.lifeTime,path+'.lifeTime');validColorRules(rule.columnRule,path+'.columnRule');}}}return errors;}
function validateExpiryIndicatorConfig(){try{parseExpiryIndicatorEditor();showStatus('ExpiryIndicator V2 JSON ist gueltig.','success');document.getElementById('expiryIndicatorChip').textContent='OK';}catch(error){showStatus(error.message,'error',true);document.getElementById('expiryIndicatorChip').textContent='!';}}
function formatExpiryIndicatorConfig(){try{var config=parseExpiryIndicatorEditor();document.getElementById('expiryIndicatorJson').value=JSON.stringify(config,null,2);showStatus('ExpiryIndicator JSON wurde formatiert.','success');}catch(error){showStatus(error.message,'error',true);}}
function applyExpiryIndicatorConfig(){var solution=state.solutions.expiryindicator;var selected=solution&&solution.info?solution.info.selected:null;if(!selected){showStatus('Bitte zuerst eine lokale ExpiryIndicator-Konfiguration laden.','warning',true);return;}var config;try{config=parseExpiryIndicatorEditor();}catch(error){showStatus(error.message,'error',true);return;}config.expiryField=selected.field.InternalName;var payload={__metadata:{type:selected.actionType},ClientSideComponentProperties:JSON.stringify(config)};showStatus('Aktualisiere lokale ExpiryIndicator-Ausnahme...','info',true);postJsonMerge(selected.updateUrl,payload,function(){solution.config=config;document.getElementById('expiryIndicatorJson').value=JSON.stringify(config,null,2);document.getElementById('expiryIndicatorChip').textContent='LOKAL';showStatus('Lokale ExpiryIndicator-Konfiguration wurde erfolgreich aktualisiert.','success');},function(message){showStatus('ExpiryIndicator konnte nicht aktualisiert werden: '+message,'error',true);});}
function parseSolutionConfig(key,rawValue){var parsed={};if(typeof rawValue==='string'&&rawValue.trim().length>0){try{parsed=JSON.parse(rawValue);}catch(error){parsed={};}}if(key==='branding'){return normalizeBrandingConfig(parsed);}if(key==='megamenu'){return normalizeMegaMenuConfig(parsed);}return parsed||{};}
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 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<actions.length;i++){var action=actions[i];if(normalizeGuid(action&&action.ClientSideComponentId)!==expected){continue;}if(!componentFallback){componentFallback=action;}var isApplicationCustomizer=typeof action.Location==='string'&&action.Location.indexOf('ClientSideExtension.ApplicationCustomizer')!==-1;if(isApplicationCustomizer&&!applicationCustomizer){applicationCustomizer=action;}if(matchesSolutionTag(action,definition)){if(isApplicationCustomizer){return action;}if(!tagged){tagged=action;}}}return tagged||applicationCustomizer||componentFallback;}function matchesSolutionTag(action,definition){var description=action&&typeof action.Description==='string'?action.Description:'';if(description.indexOf(CUSTOM_SOLUTION_TAG)===-1){return false;}if(definition&&typeof definition.descriptionTag==='string'&&definition.descriptionTag){return description.indexOf(definition.descriptionTag)!==-1||description===CUSTOM_SOLUTION_TAG;}return true;}
function getJson(url,onSuccess,onError){try{var request=new XMLHttpRequest();request.open('GET',url,true);request.setRequestHeader('Accept','application/json;odata=verbose');request.onreadystatechange=function(){if(request.readyState!==4){return;}if(request.status>=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 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;}
function buildRequestErrorMessage(request){var message=String(request.status||0);if(request.statusText){message+=' '+request.statusText;}if(request.responseText){try{var payload=JSON.parse(request.responseText);if(payload&&payload.error&&payload.error.message){message+=': '+(payload.error.message.value||payload.error.message);}}catch(ignoreError){}}return message;}
function getAvailableSolutionKeys(){return Object.keys(SUPPORTED_SOLUTIONS).filter(function(key){return !!state.solutions[key];});}
function getActionLocationText(info){if(!info||!info.action){return '-';}var scopeLabel=info.scopeLabel?info.scopeLabel+' | ':'';return scopeLabel+(info.action.Location||'ClientSideExtension.ApplicationCustomizer');}
function getWebUrl(){if(window._spPageContextInfo&&_spPageContextInfo.webAbsoluteUrl){return _spPageContextInfo.webAbsoluteUrl;}return window.location.origin||'';}
function getSiteUrl(){if(window._spPageContextInfo&&_spPageContextInfo.siteAbsoluteUrl){return _spPageContextInfo.siteAbsoluteUrl;}return getWebUrl();}
function showStatus(message,type,keepVisible){var html='<div class="ps-status ps-status-'+type+'">'+escapeHtml(message)+'</div>';var host=document.getElementById('statusMessage');host.innerHTML=html;var dialog=document.getElementById('expiryBindingDialog');var dialogHost=document.getElementById('bindExpiryStatus');if(dialog&&dialog.hidden===false&&dialogHost){dialogHost.innerHTML=html;}if(!keepVisible){window.clearTimeout(showStatus._timer);showStatus._timer=window.setTimeout(function(){host.innerHTML='';if(dialogHost){dialogHost.innerHTML='';}},4200);}}
function escapeHtml(text){var div=document.createElement('div');div.textContent=text===null||text===undefined?'':String(text);return div.innerHTML;}
</script></div></div>
</asp:Content>