Add ExpiryIndicator binding functionality and UI enhancements
- Introduced a new button in the portal settings to bind the ExpiryIndicator to existing date fields. - Added a modal dialog for binding configuration, including fields for web URL, list title, and internal field name. - Enhanced the ExpiryIndicator panel to include options for loading and saving site collection defaults. - Updated the JavaScript functions to handle binding logic and validation for the ExpiryIndicator. - Improved the user interface for better usability and clarity in the portal settings page.
This commit is contained in:
@@ -8,14 +8,16 @@
|
||||
<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></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>
|
||||
<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>Pflegt Term Set, optionales CSS und Debug-Verhalten.</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><label class="ps-label" for="megaMenuTermSetName">Term Set Name</label><input type="text" id="megaMenuTermSetName" class="ps-input" placeholder="Global Navigation" /><label class="ps-label" for="megaMenuCssUrl" style="margin-top:16px;">Zusatz-CSS URL</label><input type="text" id="megaMenuCssUrl" class="ps-input" placeholder="/sites/root/SiteAssets/megamenu.css oder leer lassen" /><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>{ termSetName, cssUrl, 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 mit dem 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="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 > Subweb-Override > Site-Collection-Standard > 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>
|
||||
<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 > Subweb-Override > Site-Collection-Standard > 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><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>
|
||||
<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:{}};
|
||||
@@ -43,6 +45,13 @@ function buildMegaMenuConfig(){var solution=state.solutions.megamenu;if(!solutio
|
||||
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 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('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&¤tComponent!==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=propertyBagJson(properties,EXPIRY_BINDINGS_PROPERTY,[]);if(!Array.isArray(bindings)){bindings=[];}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.0',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 hasRibbon=listActions.some(function(action){return action.Name==='ExpiryIndicator.Classic.Ribbon';});if(hasRibbon){onComplete('');return;}var ribbonLocation=Number(info.list.BaseType)===1?'Ribbon.Library.Actions.Controls._children':'Ribbon.List.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>';postJson(listActionsUrl,{__metadata:{type:'SP.UserCustomAction'},Name:'ExpiryIndicator.Classic.Ribbon',Title:'ExpiryIndicator Classic ribbon command',Location:'CommandUI.Ribbon',CommandUIExtension:ribbonXml,Sequence:651},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 propertyBagJson(properties,key,fallback){if(!properties){return fallback;}var value;Object.keys(properties).some(function(name){if(name.toLowerCase()===key.toLowerCase()){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 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=propertyBagJson(properties,EXPIRY_BINDINGS_PROPERTY,[]);var siteDefaults=propertyBagJson(properties,EXPIRY_DEFAULTS_PROPERTY,null);if(!Array.isArray(bindings)){bindings=[];}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);}
|
||||
@@ -71,6 +80,7 @@ function readCustomizerActionInfo(definition,onSuccess,onError){var baseSiteUrl=
|
||||
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 fallback=null;for(var i=0;i<actions.length;i++){var action=actions[i];if(normalizeGuid(action&&action.ClientSideComponentId)!==expected){continue;}if(!matchesSolutionTag(action,definition)){continue;}if(!fallback){fallback=action;}if(typeof action.Location==='string'&&action.Location.indexOf('ClientSideExtension.ApplicationCustomizer')!==-1){return action;}}return fallback;}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 [];}
|
||||
|
||||
Reference in New Issue
Block a user