From e0f2394fd0816ef03bf4b928ab9fa5a18c32ffda Mon Sep 17 00:00:00 2001 From: erdgeist Date: Tue, 14 Jul 2026 04:10:01 +0200 Subject: Add a toggleable live preview panel to both editors Hidden by default, shares horizontal space with the body editor once opened rather than a separate section, and only requests the iframe's src on first toggle -- no preview traffic at all until someone actually wants to see it. Refreshes automatically on every successful autosave; a separate force-render button re-attempts a save-and- refresh cycle immediately, for when the normal 7-second cycle has been interrupted (a lock-lost error, a transient failure) and waiting for the next tick isn't good enough. --- public/javascripts/admin_interface.js | 43 +++++++++++++++++++++- public/stylesheets/admin.css | 68 +++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 1 deletion(-) (limited to 'public') diff --git a/public/javascripts/admin_interface.js b/public/javascripts/admin_interface.js index e3d4ff1..9da5d28 100644 --- a/public/javascripts/admin_interface.js +++ b/public/javascripts/admin_interface.js @@ -67,6 +67,10 @@ $(document).ready(function () { rrule_builder.initialize(); } + if ($("#preview_panel").length != 0) { + cccms.preview.initialize(); + } + if ($('#recent_changes_toggle').length != 0) { hide_all(); $('#recent_changes_toggle').attr("class", "selected"); @@ -165,7 +169,8 @@ cccms = { } jQuery.fn.submitWithAjax = function(options) { - if (page.title_has_changed() || page.abstract_has_changed() || page.body_has_changed()) { + options = options || {}; + if (options.force || page.title_has_changed() || page.abstract_has_changed() || page.body_has_changed()) { tinymce.triggerSave(); page.cached_title = elements.title.val(); @@ -181,6 +186,9 @@ cccms = { type: "POST", url: this.attr("data-autosave-url"), data: data, + success: function() { + cccms.preview.refresh_if_open(); + }, error: function(xhr) { if (xhr.status === 423) { clearInterval(cccms.autosave_timer); @@ -211,6 +219,39 @@ cccms = { $banner.append("."); $flash.html($banner); + }, + + preview : { + is_open : false, + + initialize : function() { + var $panel = $('#preview_panel'); + var $toggle = $('#preview_toggle'); + var $force = $('#preview_force_render'); + + $toggle.on('click', function() { + cccms.preview.is_open = !cccms.preview.is_open; + $panel.toggle(cccms.preview.is_open); + $force.toggle(cccms.preview.is_open); + $toggle.attr('aria-pressed', cccms.preview.is_open); + if (cccms.preview.is_open) cccms.preview.refresh(); + }); + + $force.on('click', function() { + $("#page_editor > form").submitWithAjax({ force: true }); + }); + }, + + refresh : function() { + var $iframe = $('#live_preview_iframe'); + if ($iframe.length === 0) return; + var base = $iframe.data('src'); + $iframe.attr('src', base + (base.indexOf('?') === -1 ? '?' : '&') + '_t=' + Date.now()); + }, + + refresh_if_open : function() { + if (cccms.preview.is_open) cccms.preview.refresh(); + } } } diff --git a/public/stylesheets/admin.css b/public/stylesheets/admin.css index 93cc3f5..5f7723b 100644 --- a/public/stylesheets/admin.css +++ b/public/stylesheets/admin.css @@ -683,6 +683,10 @@ table.user_table td.user_login { display: block; margin-bottom: 1rem; } + + .body_toolbar_row { + margin-left: 120px; + } } @media(max-width:1015px) { div.node_description { @@ -1324,3 +1328,67 @@ div#image_browser ul li { border-radius: 4px; } +/* ============================================================ + Live edit preview + ============================================================ */ + +#editor_and_preview { + display: flex; + flex-direction: column; + gap: 1.5rem; +} + +@media (min-width: 1016px) { + #editor_and_preview { + flex-direction: row; + align-items: flex-start; + } + + #editor_and_preview .preview_content { + flex: 1; + min-width: 0; + } + + #preview_panel { + flex: 1; + min-width: 0; + } +} + +#preview_panel iframe { + width: 100%; + height: 612px; + border: 0.5px solid var(--border); + border-radius: var(--radius); +} + +.body_toolbar_row { + min-height: 2rem; + margin-bottom: 1rem; + display: flex; + gap: 8px; +} + +.view_toggle { + background: #f7f7f7; + border: 1px solid #ddd; + border-radius: 4px; + padding: 4px 8px; + cursor: pointer; + color: #555; + display: flex; + align-items: center; + gap: 1em; +} + +.view_toggle:hover { + background: #ececec; + border-color: #ccc; + color: #222; +} + +.view_toggle[aria-pressed="true"] { + background: #dde8f5; + border-color: #a9c6e8; + color: #1a4d8f; +} -- cgit v1.3