Remove user group and welcome page templates from the OTS Signs theme
This commit is contained in:
@@ -62,6 +62,13 @@
|
||||
</script>
|
||||
|
||||
<style nonce="{{ cspNonce }}">
|
||||
/* ── Ensure editor pop-ups appear above our fixed action bars ───────────── */
|
||||
/* Our #ots-editor-bar sits at z-index 1300. Bootstrap modals default to
|
||||
backdrop:1040 / modal:1050, so they render behind it. Raise them to
|
||||
1302/1303 so the Checkout / welcome dialogs always appear on top. */
|
||||
.modal-backdrop { z-index: 1302 !important; }
|
||||
.modal { z-index: 1303 !important; }
|
||||
|
||||
/* ── Embed mode styles ──────────────────────────────────── */
|
||||
|
||||
/* Hide Back/Exit button area */
|
||||
@@ -74,11 +81,6 @@
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Hide floating page actions (notification bell, user menu) */
|
||||
.ots-embed-mode .ots-page-actions {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Remove content wrapper padding/margins for full-bleed editor */
|
||||
.ots-embed-mode #content-wrapper {
|
||||
padding: 0 !important;
|
||||
@@ -126,11 +128,6 @@
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Hide floating page actions (notification bell, user menu) */
|
||||
.ots-deeplink-mode .ots-page-actions {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Remove content wrapper padding/margins for full-bleed editor */
|
||||
.ots-deeplink-mode #content-wrapper {
|
||||
padding: 0 !important;
|
||||
@@ -156,16 +153,81 @@
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
/* Push body content down to clear the fixed OTS back bar (44px) */
|
||||
.ots-deeplink-mode body {
|
||||
padding-top: 44px !important;
|
||||
/* ── Editor positioning fix ─────────────────────────────────────────────
|
||||
Xibo's editor internal structure (from layout-editor.hbs + layout-editor.scss):
|
||||
|
||||
#layout-editor
|
||||
.editor-top-bar ← always min-height ~40px in normal flow;
|
||||
its children are position:fixed top:0 (covered by our bar)
|
||||
.container-designer { height: calc(100vh - 50px) }
|
||||
|
||||
Keeping #layout-editor in normal flow and hiding .editor-top-bar removes
|
||||
the 40px gap. We then push .container-designer down with margin-top and
|
||||
resize it so it fills exactly from below our bar to the viewport bottom.
|
||||
─────────────────────────────────────────────────────────────────────── */
|
||||
|
||||
/* Restore #layout-editor to normal document flow */
|
||||
#layout-editor {
|
||||
position: static !important;
|
||||
top: auto !important;
|
||||
left: auto !important;
|
||||
right: auto !important;
|
||||
bottom: auto !important;
|
||||
width: auto !important;
|
||||
height: auto !important;
|
||||
/* Preserve Xibo's horizontal negative margins (Bootstrap column gutter) */
|
||||
margin: 0 -15px -15px -15px !important;
|
||||
}
|
||||
|
||||
/* ── OTS branded back bar ──────────────────────────────── */
|
||||
/* Xibo's .editor-top-bar inner items use position:fixed top:0 so they
|
||||
already render behind our #ots-editor-bar (z-index 1300). Hiding the
|
||||
container removes the dead 40px normal-flow space it leaves behind. */
|
||||
.editor-top-bar {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Hidden by default; revealed only in deep-link mode */
|
||||
#ots-deeplink-backbar {
|
||||
display: none;
|
||||
/* Reposition the playlist-editor "Back to Layout" button below our bar
|
||||
instead of hiding it — it's shown by lD.openPlaylistEditor() */
|
||||
.back-button-playlist {
|
||||
top: 50px !important;
|
||||
z-index: 1290 !important;
|
||||
}
|
||||
|
||||
/* Push the canvas below our 44px bar and fill the remaining viewport height */
|
||||
body.editor-opened .container-designer {
|
||||
margin-top: 44px !important;
|
||||
height: calc(100vh - 44px) !important;
|
||||
}
|
||||
|
||||
/* Loading spinner while editor data loads — same offset */
|
||||
body.editor-opened #layout-editor .loading-container {
|
||||
margin-top: 44px !important;
|
||||
height: calc(100vh - 44px) !important;
|
||||
}
|
||||
|
||||
/* Embed mode: no bar — canvas fills the full viewport */
|
||||
.ots-embed-mode .container-designer,
|
||||
.ots-embed-mode #layout-editor .loading-container {
|
||||
margin-top: 0 !important;
|
||||
height: 100vh !important;
|
||||
}
|
||||
|
||||
/* Remove body-level padding (harmless safety reset) */
|
||||
body {
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
|
||||
/* Ensure no stray padding above the editor from page-wrapper */
|
||||
#page-wrapper,
|
||||
#content-wrapper {
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
|
||||
/* ── OTS combined editor bar ──────────────────────────── */
|
||||
|
||||
/* Single fixed bar: back button + layout name + action buttons */
|
||||
#ots-editor-bar {
|
||||
display: flex;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
@@ -176,14 +238,16 @@
|
||||
z-index: 1300;
|
||||
align-items: center;
|
||||
padding: 0 12px;
|
||||
gap: 8px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.ots-deeplink-mode #ots-deeplink-backbar {
|
||||
display: flex;
|
||||
/* Hide bar inside an iframe embed */
|
||||
.ots-embed-mode #ots-editor-bar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#ots-deeplink-back {
|
||||
#ots-bar-back {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
@@ -197,97 +261,305 @@
|
||||
border-radius: 4px;
|
||||
line-height: 1;
|
||||
transition: background 0.15s;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
#ots-deeplink-back:hover {
|
||||
#ots-bar-back:hover {
|
||||
background: rgba(232, 120, 0, 0.12);
|
||||
}
|
||||
|
||||
#ots-deeplink-back:focus-visible {
|
||||
#ots-bar-back:focus-visible {
|
||||
outline: 2px solid #e87800;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
#ots-deeplink-back svg {
|
||||
#ots-bar-back svg {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.ots-deeplink-wordmark {
|
||||
margin-left: auto;
|
||||
color: #64748b;
|
||||
font-size: 12px;
|
||||
/* Vertical divider between back button and layout name */
|
||||
#ots-bar-divider {
|
||||
width: 1px;
|
||||
height: 20px;
|
||||
background: #334155;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* ── Hide stock Xibo header bar on the layout designer page ── */
|
||||
.row.header.header-side {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* ── Remove sidebar offset so editor fills the full viewport ── */
|
||||
#page-wrapper,
|
||||
#content-wrapper {
|
||||
margin-left: 0 !important;
|
||||
padding-left: 0 !important;
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
.page-content,
|
||||
.page-content > .row,
|
||||
.page-content > .row > .col-sm-12 {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
/* Layout name / status label */
|
||||
#ots-bar-label {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
font-size: 13px;
|
||||
color: #94a3b8;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.ots-bar-status {
|
||||
display: inline-block;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.05em;
|
||||
text-transform: uppercase;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
margin-left: 8px;
|
||||
vertical-align: middle;
|
||||
background: rgba(232, 120, 0, 0.15);
|
||||
color: #e87800;
|
||||
}
|
||||
|
||||
.ots-bar-status.ots-status-published {
|
||||
background: rgba(74, 222, 128, 0.15);
|
||||
color: #4ade80;
|
||||
}
|
||||
|
||||
/* Shared button base */
|
||||
.ots-editor-bar-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
height: 30px;
|
||||
padding: 0 12px;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.04em;
|
||||
cursor: pointer;
|
||||
border: 1px solid transparent;
|
||||
line-height: 1;
|
||||
background: none;
|
||||
font-family: inherit;
|
||||
transition: background 0.15s, opacity 0.15s, border-color 0.15s;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Save — filled orange */
|
||||
#ots-editor-save-btn {
|
||||
background: #e87800;
|
||||
color: #fff;
|
||||
border-color: #e87800;
|
||||
}
|
||||
|
||||
#ots-editor-save-btn:hover {
|
||||
background: #d16b00;
|
||||
border-color: #d16b00;
|
||||
}
|
||||
|
||||
/* Brief green flash after a successful save AJAX round-trip */
|
||||
#ots-editor-save-btn.ots-btn-saved {
|
||||
background: #16a34a !important;
|
||||
border-color: #16a34a !important;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
/* Publish — orange outline */
|
||||
#ots-editor-publish-btn {
|
||||
color: #e87800;
|
||||
border-color: #e87800;
|
||||
}
|
||||
|
||||
#ots-editor-publish-btn:hover {
|
||||
background: rgba(232, 120, 0, 0.1);
|
||||
}
|
||||
|
||||
/* Checkout — muted outline */
|
||||
#ots-editor-checkout-btn {
|
||||
color: #94a3b8;
|
||||
border-color: #334155;
|
||||
}
|
||||
|
||||
#ots-editor-checkout-btn:hover {
|
||||
background: rgba(148, 163, 184, 0.1);
|
||||
border-color: #64748b;
|
||||
color: #cbd5e1;
|
||||
}
|
||||
|
||||
/* Draft state: show Save + Publish; hide Checkout */
|
||||
#ots-editor-bar .ots-draft-only { display: inline-flex; }
|
||||
#ots-editor-bar .ots-readonly-only { display: none; }
|
||||
|
||||
/* Read-only/published state: hide Save + Publish; show Checkout */
|
||||
#ots-editor-bar.ots-state-readonly .ots-draft-only { display: none !important; }
|
||||
#ots-editor-bar.ots-state-readonly .ots-readonly-only { display: inline-flex !important; }
|
||||
|
||||
.ots-editor-bar-btn:disabled,
|
||||
.ots-editor-bar-btn.ots-btn-loading {
|
||||
opacity: 0.45;
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.ots-editor-bar-btn:focus-visible {
|
||||
outline: 2px solid #e87800;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
/* ── Interactive mode toggle ──────────────────────── */
|
||||
#ots-editor-interactive-btn {
|
||||
color: #94a3b8;
|
||||
border-color: #334155;
|
||||
}
|
||||
#ots-editor-interactive-btn:hover {
|
||||
background: rgba(148, 163, 184, 0.1);
|
||||
border-color: #64748b;
|
||||
color: #cbd5e1;
|
||||
}
|
||||
/* Lit up when interactive mode is active */
|
||||
#ots-editor-interactive-btn.ots-btn-active {
|
||||
background: rgba(232, 120, 0, 0.15);
|
||||
border-color: #e87800;
|
||||
color: #e87800;
|
||||
}
|
||||
/* Not useful in embed mode */
|
||||
.ots-embed-mode #ots-editor-interactive-btn { display: none !important; }
|
||||
|
||||
/* Secondary divider between interactive toggle and primary action buttons */
|
||||
#ots-bar-actions-divider {
|
||||
width: 1px;
|
||||
height: 20px;
|
||||
background: #334155;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* ── Discard — destructive/red outline, draft-only ── */
|
||||
#ots-editor-discard-btn {
|
||||
color: #f87171;
|
||||
border-color: #7f1d1d;
|
||||
}
|
||||
#ots-editor-discard-btn:hover {
|
||||
background: rgba(248, 113, 113, 0.1);
|
||||
border-color: #f87171;
|
||||
}
|
||||
|
||||
/* ── Schedule — muted outline, readonly-only ─────── */
|
||||
#ots-editor-schedule-btn {
|
||||
color: #94a3b8;
|
||||
border-color: #334155;
|
||||
}
|
||||
#ots-editor-schedule-btn:hover {
|
||||
background: rgba(148, 163, 184, 0.1);
|
||||
border-color: #64748b;
|
||||
color: #cbd5e1;
|
||||
}
|
||||
|
||||
/* ── Unlock — amber, hidden until layout is locked ── */
|
||||
#ots-editor-unlock-btn {
|
||||
display: none;
|
||||
color: #fbbf24;
|
||||
border-color: #78350f;
|
||||
}
|
||||
#ots-editor-unlock-btn:hover {
|
||||
background: rgba(251, 191, 36, 0.1);
|
||||
border-color: #fbbf24;
|
||||
}
|
||||
/* .ots-bar-locked is added via JS when lD detects locked-for-user */
|
||||
#ots-editor-bar.ots-bar-locked #ots-editor-unlock-btn {
|
||||
display: inline-flex;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block pageContent %}
|
||||
|
||||
{# Deep-link mode: branded back bar (position:fixed — shown only when ots-deeplink-mode is active) #}
|
||||
<div id="ots-deeplink-backbar" role="navigation" aria-label="{{ "Back navigation"|trans }}">
|
||||
<button id="ots-deeplink-back" type="button">
|
||||
{# OTS combined editor bar — back button + layout name + action buttons #}
|
||||
<div id="ots-editor-bar" role="banner" aria-label="{{ "Layout editor"|trans }}">
|
||||
<button id="ots-bar-back" type="button" aria-label="{{ "Back"|trans }}">
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false">
|
||||
<path d="M10 12L6 8l4-4" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
{{ "Back"|trans }}
|
||||
</button>
|
||||
<span class="ots-deeplink-wordmark">OTS Signs</span>
|
||||
</div>
|
||||
<div id="ots-bar-divider" aria-hidden="true"></div>
|
||||
<span id="ots-bar-label">
|
||||
{{ layout.layout }}
|
||||
<span class="ots-bar-status" id="ots-bar-status-badge">{{ "Draft"|trans }}</span>
|
||||
</span>
|
||||
{# Interactive mode toggle — available in both draft and read-only #}
|
||||
<button id="ots-editor-interactive-btn" type="button" class="ots-editor-bar-btn"
|
||||
title="{{ "Toggle interactive mode to link regions with actions"|trans }}">
|
||||
<svg width="13" height="13" viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false">
|
||||
<path d="M4 2l2 9 2.5-3H12L4 2z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
{{ "Interactive"|trans }}
|
||||
</button>
|
||||
<div id="ots-bar-actions-divider" aria-hidden="true"></div>
|
||||
{# Draft state: Save + Publish + Discard #}
|
||||
<button id="ots-editor-save-btn" type="button" class="ots-editor-bar-btn ots-draft-only"
|
||||
title="{{ "Save current changes"|trans }}">
|
||||
<svg width="13" height="13" viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false">
|
||||
<path d="M8 2v7m0 0L5 6m3 3l3-3M3 13h10" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
{{ "Save"|trans }}
|
||||
</button>
|
||||
<button id="ots-editor-publish-btn" type="button" class="ots-editor-bar-btn ots-draft-only"
|
||||
title="{{ "Publish layout to displays"|trans }}">
|
||||
<svg width="13" height="13" viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false">
|
||||
<path d="M8 11V4m0 0L5 7m3-3l3 3M3 13h10" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
{{ "Publish"|trans }}
|
||||
</button>
|
||||
<button id="ots-editor-discard-btn" type="button" class="ots-editor-bar-btn ots-draft-only"
|
||||
title="{{ "Discard this draft and revert to the published version"|trans }}">
|
||||
<svg width="12" height="12" viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false">
|
||||
<path d="M4 4l8 8M12 4l-8 8" stroke="currentColor" stroke-width="1.75" stroke-linecap="round"/>
|
||||
</svg>
|
||||
{{ "Discard"|trans }}
|
||||
</button>
|
||||
{# Read-only/published state: Checkout + Schedule #}
|
||||
<button id="ots-editor-checkout-btn" type="button" class="ots-editor-bar-btn ots-readonly-only"
|
||||
title="{{ "Checkout this layout for editing"|trans }}">
|
||||
<svg width="13" height="13" viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false">
|
||||
<path d="M10.5 2.5l3 3L5 14H2v-3L10.5 2.5z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9 4l3 3" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
{{ "Checkout"|trans }}
|
||||
</button>
|
||||
<button id="ots-editor-schedule-btn" type="button" class="ots-editor-bar-btn ots-readonly-only"
|
||||
title="{{ "Schedule this layout on displays"|trans }}">
|
||||
<svg width="13" height="13" viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false">
|
||||
<circle cx="8" cy="8" r="6" stroke="currentColor" stroke-width="1.5"/>
|
||||
<path d="M8 5v3.5l2.5 2" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
{{ "Schedule"|trans }}
|
||||
</button>
|
||||
{# Unlock: hidden by default, revealed via JS when lD marks the layout as locked-for-user #}
|
||||
<button id="ots-editor-unlock-btn" type="button" class="ots-editor-bar-btn"
|
||||
title="{{ "Unlock this layout"|trans }}">
|
||||
<svg width="13" height="13" viewBox="0 0 16 16" fill="none" aria-hidden="true" focusable="false">
|
||||
<rect x="3" y="8" width="10" height="6" rx="1.5" stroke="currentColor" stroke-width="1.5"/>
|
||||
<path d="M6 8V5a3 3 0 016 0v2" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
{{ "Unlock"|trans }}
|
||||
</button>
|
||||
</div> {# /ots-editor-bar #}
|
||||
|
||||
<!-- Editor structure -->
|
||||
<div id="layout-editor" data-published-layout-id="{{ publishedLayoutId }}" data-layout-id="{{ layout.layoutId }}" data-layout-help={{ help }}></div>
|
||||
|
||||
{# Skeleton loading screen — replaced by editor once editor-opened class fires on body #}
|
||||
<div id="ots-editor-skeleton" aria-hidden="true">
|
||||
<div class="ots-skeleton-topbar">
|
||||
<div class="ots-skeleton-topbar-left">
|
||||
<div class="ots-skeleton-chip"></div>
|
||||
<div class="ots-skeleton-chip" style="width:80px"></div>
|
||||
</div>
|
||||
<div class="ots-skeleton-topbar-right">
|
||||
<div class="ots-skeleton-chip" style="width:150px"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ots-skeleton-body">
|
||||
<div class="ots-skeleton-left-rail">
|
||||
<div class="ots-skeleton-icon-dot"></div>
|
||||
<div class="ots-skeleton-icon-dot"></div>
|
||||
<div class="ots-skeleton-icon-dot"></div>
|
||||
<div class="ots-skeleton-icon-dot"></div>
|
||||
</div>
|
||||
<div class="ots-skeleton-canvas-area">
|
||||
<div class="ots-skeleton-canvas-box"></div>
|
||||
</div>
|
||||
<div class="ots-skeleton-props">
|
||||
<div class="ots-skeleton-prop-group">
|
||||
<div class="ots-skeleton-prop-label"></div>
|
||||
<div class="ots-skeleton-prop-swatch-row">
|
||||
<div class="ots-skeleton-prop-swatch"></div>
|
||||
<div class="ots-skeleton-prop-field"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ots-skeleton-prop-group">
|
||||
<div class="ots-skeleton-prop-label"></div>
|
||||
<div class="ots-skeleton-prop-field" style="height:70px"></div>
|
||||
</div>
|
||||
<div class="ots-skeleton-prop-group">
|
||||
<div class="ots-skeleton-prop-label"></div>
|
||||
<div class="ots-skeleton-prop-field"></div>
|
||||
</div>
|
||||
<div class="ots-skeleton-prop-group">
|
||||
<div class="ots-skeleton-prop-label" style="width:35%"></div>
|
||||
<div class="ots-skeleton-prop-field"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ots-skeleton-bottombar">
|
||||
<div class="ots-skeleton-chip" style="width:120px; opacity:.5"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block javaScript %}
|
||||
@@ -633,57 +905,6 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
{# ── Skeleton dismiss + keyboard shortcut hints ──────────── #}
|
||||
<script type="text/javascript" nonce="{{ cspNonce }}">
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
// ── Skeleton dismiss ─────────────────────────────────────
|
||||
var skeleton = document.getElementById('ots-editor-skeleton');
|
||||
if (skeleton) {
|
||||
if (document.body.classList.contains('editor-opened')) {
|
||||
skeleton.parentNode.removeChild(skeleton);
|
||||
} else {
|
||||
var skeletonObs = new MutationObserver(function() {
|
||||
if (document.body.classList.contains('editor-opened')) {
|
||||
skeleton.classList.add('ots-skeleton-done');
|
||||
setTimeout(function() {
|
||||
if (skeleton.parentNode) skeleton.parentNode.removeChild(skeleton);
|
||||
}, 380);
|
||||
skeletonObs.disconnect();
|
||||
}
|
||||
});
|
||||
skeletonObs.observe(document.body, { attributes: true, attributeFilter: ['class'] });
|
||||
}
|
||||
}
|
||||
|
||||
// ── Keyboard shortcut hints (title attrs on toolbar buttons) ──
|
||||
var hints = [
|
||||
{ sel: '#undoBtn', hint: 'Undo (Ctrl+Z)' },
|
||||
{ sel: '#fullscreenBtn', hint: 'Toggle Fullscreen (F)' },
|
||||
{ sel: '#layerManagerBtn', hint: 'Layer Manager (L)' }
|
||||
];
|
||||
|
||||
function applyKbHints() {
|
||||
hints.forEach(function(h) {
|
||||
try {
|
||||
document.querySelectorAll(h.sel).forEach(function(el) {
|
||||
if (!el.title) el.title = h.hint;
|
||||
});
|
||||
} catch(ignore) {}
|
||||
});
|
||||
}
|
||||
|
||||
var kbObs = new MutationObserver(function() {
|
||||
if (document.body.classList.contains('editor-opened')) {
|
||||
setTimeout(applyKbHints, 1500);
|
||||
kbObs.disconnect();
|
||||
}
|
||||
});
|
||||
kbObs.observe(document.body, { attributes: true, attributeFilter: ['class'] });
|
||||
})();
|
||||
</script>
|
||||
|
||||
{# ── Embed mode: postMessage bridge ──────────────────────── #}
|
||||
<script type="text/javascript" nonce="{{ cspNonce }}">
|
||||
(function() {
|
||||
@@ -829,16 +1050,17 @@
|
||||
})();
|
||||
</script>
|
||||
|
||||
{# ── Deep-link mode: returnUrl back navigation ────────────── #}
|
||||
{# ── Back bar: returnUrl navigation (always active) ────────── #}
|
||||
<script type="text/javascript" nonce="{{ cspNonce }}">
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
var params = new URLSearchParams(window.location.search);
|
||||
if (params.get('deeplink') !== '1') return;
|
||||
|
||||
// Mirror the html-level class onto body (needed for body-targeted CSS rules).
|
||||
document.body.classList.add('ots-deeplink-mode');
|
||||
// Still mirror deeplink class for any other deeplink-specific behaviour.
|
||||
if (params.get('deeplink') === '1') {
|
||||
document.body.classList.add('ots-deeplink-mode');
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow only safe same-origin relative paths.
|
||||
@@ -857,7 +1079,7 @@
|
||||
var rawReturn = params.get('returnUrl');
|
||||
var safeReturn = validateReturnUrl(rawReturn) ? rawReturn : null;
|
||||
|
||||
var backBtn = document.getElementById('ots-deeplink-back');
|
||||
var backBtn = document.getElementById('ots-bar-back');
|
||||
if (backBtn) {
|
||||
backBtn.addEventListener('click', function() {
|
||||
if (safeReturn) {
|
||||
@@ -869,4 +1091,234 @@
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
|
||||
{# ── OTS Editor Action Bar: state management & button wiring ── #}
|
||||
<script type="text/javascript" nonce="{{ cspNonce }}">
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
var bar = document.getElementById('ots-editor-bar');
|
||||
var saveBtn = document.getElementById('ots-editor-save-btn');
|
||||
var publishBtn = document.getElementById('ots-editor-publish-btn');
|
||||
var checkoutBtn = document.getElementById('ots-editor-checkout-btn');
|
||||
var discardBtn = document.getElementById('ots-editor-discard-btn');
|
||||
var interactiveBtn = document.getElementById('ots-editor-interactive-btn');
|
||||
var scheduleBtn = document.getElementById('ots-editor-schedule-btn');
|
||||
var unlockBtn = document.getElementById('ots-editor-unlock-btn');
|
||||
var statusBadge = document.getElementById('ots-bar-status-badge');
|
||||
|
||||
if (!bar || !saveBtn || !publishBtn || !checkoutBtn) return;
|
||||
|
||||
// ── State management ──────────────────────────────────────
|
||||
// 'draft' → Save + Publish visible, Checkout hidden
|
||||
// 'readonly' → Checkout visible, Save + Publish hidden
|
||||
function setBarState(state) {
|
||||
if (state === 'readonly') {
|
||||
bar.classList.add('ots-state-readonly');
|
||||
if (statusBadge) {
|
||||
statusBadge.textContent = '{{ "Published"|trans }}';
|
||||
statusBadge.classList.add('ots-status-published');
|
||||
}
|
||||
} else {
|
||||
bar.classList.remove('ots-state-readonly');
|
||||
if (statusBadge) {
|
||||
statusBadge.textContent = '{{ "Draft"|trans }}';
|
||||
statusBadge.classList.remove('ots-status-published');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Detect initial state from data attributes ─────────────
|
||||
// publishedLayoutId === layoutId → viewing the published version → read-only
|
||||
// publishedLayoutId !== layoutId → viewing a draft copy → editable
|
||||
function detectInitialState() {
|
||||
var editorEl = document.getElementById('layout-editor');
|
||||
if (!editorEl) return;
|
||||
var layoutId = editorEl.getAttribute('data-layout-id');
|
||||
var publishedId = editorEl.getAttribute('data-published-layout-id');
|
||||
if (publishedId && publishedId !== '0' && publishedId === layoutId) {
|
||||
setBarState('readonly');
|
||||
} else {
|
||||
setBarState('draft');
|
||||
}
|
||||
}
|
||||
|
||||
// ── Wait for the React editor to signal it's ready ────────
|
||||
if (document.body.classList.contains('editor-opened')) {
|
||||
detectInitialState();
|
||||
} else {
|
||||
var initObs = new MutationObserver(function() {
|
||||
if (document.body.classList.contains('editor-opened')) {
|
||||
initObs.disconnect();
|
||||
detectInitialState();
|
||||
}
|
||||
});
|
||||
initObs.observe(document.body, { attributes: true, attributeFilter: ['class'] });
|
||||
}
|
||||
|
||||
// ── Button: Save ──────────────────────────────────────────
|
||||
// Xibo auto-saves widget/region changes via the history manager.
|
||||
// The save button's job is to flush any pending properties panel form.
|
||||
saveBtn.addEventListener('click', function() {
|
||||
if (saveBtn.classList.contains('ots-btn-loading')) return;
|
||||
try {
|
||||
if (typeof lD !== 'undefined' && lD.propertiesPanel) {
|
||||
lD.propertiesPanel.save({ target: lD.selectedObject });
|
||||
}
|
||||
} catch(e) { /* silent */ }
|
||||
});
|
||||
|
||||
// ── Button: Publish ───────────────────────────────────────
|
||||
publishBtn.addEventListener('click', function() {
|
||||
if (publishBtn.classList.contains('ots-btn-loading')) return;
|
||||
try {
|
||||
if (typeof lD !== 'undefined' && typeof lD.showPublishScreen === 'function') {
|
||||
lD.showPublishScreen();
|
||||
}
|
||||
} catch(e) { /* silent */ }
|
||||
});
|
||||
|
||||
// ── Button: Checkout ──────────────────────────────────────
|
||||
// Use urlsForApi (set by editorVars.twig) so the URL is always correct
|
||||
// regardless of any sub-path the CMS is hosted under.
|
||||
checkoutBtn.addEventListener('click', function() {
|
||||
// Delegate to Xibo's own checkout implementation, which handles
|
||||
// the AJAX call and redirect internally using urlsForApi.
|
||||
if (typeof lD === 'undefined' || !lD.layout ||
|
||||
typeof lD.layout.checkout !== 'function') {
|
||||
toastr.error('Editor not ready. Please refresh and try again.');
|
||||
return;
|
||||
}
|
||||
lD.layout.checkout();
|
||||
});
|
||||
|
||||
// ── Button: Discard ───────────────────────────────────────
|
||||
// We handle this manually so we can redirect back to the custom app
|
||||
// (returnUrl) instead of letting Xibo redirect to its own layouts page.
|
||||
if (discardBtn) {
|
||||
discardBtn.addEventListener('click', function() {
|
||||
if (discardBtn.classList.contains('ots-btn-loading')) return;
|
||||
|
||||
if (!confirm('{{ "Discard this draft and revert to the published version?"|trans }}')) return;
|
||||
|
||||
var editorEl = document.getElementById('layout-editor');
|
||||
if (!editorEl) return;
|
||||
// Discard endpoint takes the published (parent) layout ID.
|
||||
var publishedId = editorEl.getAttribute('data-published-layout-id');
|
||||
if (!publishedId) return;
|
||||
|
||||
if (typeof urlsForApi === 'undefined' ||
|
||||
!urlsForApi.layout || !urlsForApi.layout.discard) return;
|
||||
|
||||
discardBtn.classList.add('ots-btn-loading');
|
||||
discardBtn.disabled = true;
|
||||
|
||||
$.ajax({
|
||||
type: urlsForApi.layout.discard.type || 'PUT',
|
||||
url: urlsForApi.layout.discard.url.replace(':id', publishedId),
|
||||
success: function() {
|
||||
// Redirect to returnUrl (same validation as Back button)
|
||||
var rawReturn = new URLSearchParams(window.location.search).get('returnUrl');
|
||||
var safeReturn = (rawReturn &&
|
||||
rawReturn.charAt(0) === '/' &&
|
||||
rawReturn.charAt(1) !== '/' &&
|
||||
rawReturn.indexOf('://') === -1) ? rawReturn : null;
|
||||
window.location.href = safeReturn || '/';
|
||||
},
|
||||
error: function(xhr) {
|
||||
discardBtn.classList.remove('ots-btn-loading');
|
||||
discardBtn.disabled = false;
|
||||
try {
|
||||
var err = xhr.responseJSON || JSON.parse(xhr.responseText);
|
||||
toastr.error(err.message || '{{ "Discard failed"|trans }}');
|
||||
} catch(e) {
|
||||
toastr.error('{{ "Discard failed"|trans }}');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ── Button: Interactive mode toggle ───────────────────────
|
||||
if (interactiveBtn) {
|
||||
interactiveBtn.addEventListener('click', function() {
|
||||
try {
|
||||
if (typeof lD !== 'undefined' && typeof lD.toggleInteractiveMode === 'function') {
|
||||
lD.toggleInteractiveMode(!lD.interactiveMode);
|
||||
}
|
||||
} catch(e) { /* silent */ }
|
||||
});
|
||||
}
|
||||
|
||||
// ── Button: Schedule ──────────────────────────────────────
|
||||
if (scheduleBtn) {
|
||||
scheduleBtn.addEventListener('click', function() {
|
||||
if (scheduleBtn.classList.contains('ots-btn-loading')) return;
|
||||
try {
|
||||
if (typeof lD !== 'undefined' && typeof lD.showScheduleScreen === 'function') {
|
||||
lD.showScheduleScreen();
|
||||
}
|
||||
} catch(e) { /* silent */ }
|
||||
});
|
||||
}
|
||||
|
||||
// ── Button: Unlock ────────────────────────────────────────
|
||||
if (unlockBtn) {
|
||||
unlockBtn.addEventListener('click', function() {
|
||||
try {
|
||||
if (typeof lD !== 'undefined' && typeof lD.showUnlockScreen === 'function') {
|
||||
lD.showUnlockScreen();
|
||||
}
|
||||
} catch(e) { /* silent */ }
|
||||
});
|
||||
}
|
||||
|
||||
// ── Watch #layout-editor classList for interactive-mode and locked state ──
|
||||
(function() {
|
||||
var editorEl = document.getElementById('layout-editor');
|
||||
if (!editorEl) return;
|
||||
new MutationObserver(function() {
|
||||
// Sync interactive button visual toggle state
|
||||
if (interactiveBtn) {
|
||||
interactiveBtn.classList.toggle(
|
||||
'ots-btn-active',
|
||||
editorEl.classList.contains('interactive-mode') ||
|
||||
editorEl.classList.contains('interactive-edit-widget-mode')
|
||||
);
|
||||
}
|
||||
// Show Unlock button when lD adds 'locked' + 'locked-for-user' classes
|
||||
bar.classList.toggle('ots-bar-locked', editorEl.classList.contains('locked-for-user'));
|
||||
}).observe(editorEl, { attributes: true, attributeFilter: ['class'] });
|
||||
})();
|
||||
|
||||
// ── AJAX intercepts: auto-update bar state ────────────────
|
||||
$(document).ajaxComplete(function(event, xhr, settings) {
|
||||
if (!settings || !settings.url) return;
|
||||
try {
|
||||
var response = xhr.responseJSON || {};
|
||||
if (!response.success) return;
|
||||
var url = settings.url;
|
||||
|
||||
// Layout draft save (PUT /layout/{id}) → flash Save button
|
||||
if (settings.type === 'PUT' && /\/layout\/\d+(?:$|\?|\/(?!publish|checkout|discard|delete))/.test(url)) {
|
||||
saveBtn.classList.add('ots-btn-saved');
|
||||
setTimeout(function() {
|
||||
saveBtn.classList.remove('ots-btn-saved');
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// Publish → layout is now the published version → read-only
|
||||
if (/\/layout\/publish\/\d+/.test(url)) {
|
||||
setBarState('readonly');
|
||||
}
|
||||
|
||||
// Checkout → a new draft is now active → editable
|
||||
if (/\/layout\/checkout\/\d+/.test(url)) {
|
||||
setBarState('draft');
|
||||
}
|
||||
} catch(e) { /* silent */ }
|
||||
});
|
||||
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user