diff options
| author | erdgeist <erdgeist@erdgeist.org> | 2026-07-14 17:06:59 +0200 |
|---|---|---|
| committer | erdgeist <erdgeist@erdgeist.org> | 2026-07-14 18:30:40 +0200 |
| commit | 8847db139bef9dd8f9465c31438f16af13593267 (patch) | |
| tree | d192bd396ea74f89cd84aac22d79ef126aec6976 /public/javascripts | |
| parent | ffb3c9987c2f820b99e8ad0ad56db8ae0324671c (diff) | |
Add inline image insertion for page bodies, plus a headline-conflict badge
Custom TinyMCE toolbar button opens a picker scoped to a page's own
attached images. Selecting one and a placement (full width, left,
right) inserts <a class="glightbox" data-gallery="page-N">
wrapping an <img>, sharing the existing headline-image lightbox
gallery. No native TinyMCE image dialog or resize handles --
placement is fixed to the three options via CSS classes only.
extended_valid_elements defines a and img explicitly and
symmetrically; valid_classes allows the placement classes and
glightbox specifically. content_style renders placement inside the
editing iframe, since ccc.css doesn't load there.
A pre-save pass removes any a.glightbox left without an img,
covering in-editor deletion regardless of how it happened.
Alt text comes from the asset's name, escaped.
A badge marks whichever attached image sits at position 0, since
_headline_image.html.erb always renders that position as the
page's automatic headline. Shown in the sidebar Images list and in
the picker grid.
Diffstat (limited to 'public/javascripts')
| -rw-r--r-- | public/javascripts/admin_interface.js | 114 | ||||
| -rw-r--r-- | public/javascripts/related_assets.js | 19 |
2 files changed, 130 insertions, 3 deletions
diff --git a/public/javascripts/admin_interface.js b/public/javascripts/admin_interface.js index 9da5d28..0c9ec66 100644 --- a/public/javascripts/admin_interface.js +++ b/public/javascripts/admin_interface.js | |||
| @@ -19,15 +19,29 @@ $(document).ready(function () { | |||
| 19 | promotion: false, | 19 | promotion: false, |
| 20 | menubar: false, | 20 | menubar: false, |
| 21 | plugins: 'code link lists visualblocks', | 21 | plugins: 'code link lists visualblocks', |
| 22 | toolbar: 'bold italic underline | bullist numlist | link unlink | blocks | code', | 22 | toolbar: 'bold italic underline | bullist numlist | link unlink | insertpageimage | blocks | code', |
| 23 | extended_valid_elements: 'aggregate[children|tags|limit|order_by|order_direction|partial|conditions]', | 23 | extended_valid_elements: 'aggregate[children|tags|limit|order_by|order_direction|partial|conditions],a[href|target|rel|class|data-gallery],img[class|src|alt|title|width|height|style|border]', |
| 24 | relative_urls: false, | 24 | relative_urls: false, |
| 25 | entity_encoding: 'raw', | 25 | entity_encoding: 'raw', |
| 26 | valid_classes: { '*': '' }, | 26 | content_style: '.inline-image--full { width: 100%; margin: 1rem 0; } .inline-image--half { width: 48%; margin-bottom: 1rem; } .inline-image--left { float: left; margin-right: 1rem; } .inline-image--right { float: right; margin-left: 1rem; }', |
| 27 | valid_classes: { | ||
| 28 | '*': '', | ||
| 29 | 'img': 'inline-image inline-image--full inline-image--half inline-image--left inline-image--right', | ||
| 30 | 'a': 'glightbox' | ||
| 31 | }, | ||
| 27 | setup: function(editor) { | 32 | setup: function(editor) { |
| 28 | editor.on('init', function() { | 33 | editor.on('init', function() { |
| 29 | cccms.setup_autosave(); | 34 | cccms.setup_autosave(); |
| 30 | }); | 35 | }); |
| 36 | |||
| 37 | editor.ui.registry.addButton('insertpageimage', { | ||
| 38 | icon: 'image', | ||
| 39 | text: 'Insert image', | ||
| 40 | tooltip: "Insert one of this page's attached images", | ||
| 41 | onAction: function() { | ||
| 42 | cccms.inline_images.open(editor); | ||
| 43 | } | ||
| 44 | }); | ||
| 31 | } | 45 | } |
| 32 | }); | 46 | }); |
| 33 | 47 | ||
| @@ -172,6 +186,10 @@ cccms = { | |||
| 172 | options = options || {}; | 186 | options = options || {}; |
| 173 | if (options.force || page.title_has_changed() || page.abstract_has_changed() || page.body_has_changed()) { | 187 | if (options.force || page.title_has_changed() || page.abstract_has_changed() || page.body_has_changed()) { |
| 174 | 188 | ||
| 189 | elements.body.find('a.glightbox').each(function() { | ||
| 190 | if ($(this).find('img').length === 0) { $(this).remove(); } | ||
| 191 | }); | ||
| 192 | |||
| 175 | tinymce.triggerSave(); | 193 | tinymce.triggerSave(); |
| 176 | page.cached_title = elements.title.val(); | 194 | page.cached_title = elements.title.val(); |
| 177 | page.cached_abstract = elements.abstract.val(); | 195 | page.cached_abstract = elements.abstract.val(); |
| @@ -252,6 +270,96 @@ cccms = { | |||
| 252 | refresh_if_open : function() { | 270 | refresh_if_open : function() { |
| 253 | if (cccms.preview.is_open) cccms.preview.refresh(); | 271 | if (cccms.preview.is_open) cccms.preview.refresh(); |
| 254 | } | 272 | } |
| 273 | }, | ||
| 274 | |||
| 275 | inline_images: { | ||
| 276 | ensure_overlay: function() { | ||
| 277 | if ($('#inline_image_picker').length) return; | ||
| 278 | |||
| 279 | var overlay = $( | ||
| 280 | '<div id="inline_image_picker" style="display:none">' + | ||
| 281 | '<div id="inline_image_picker_grid"></div>' + | ||
| 282 | '<div id="inline_image_picker_actions">' + | ||
| 283 | '<button type="button" data-placement="full">Insert full width</button>' + | ||
| 284 | '<button type="button" data-placement="left">Insert, align left</button>' + | ||
| 285 | '<button type="button" data-placement="right">Insert, align right</button>' + | ||
| 286 | '<button type="button" id="inline_image_picker_cancel">Cancel</button>' + | ||
| 287 | '</div>' + | ||
| 288 | '</div>' | ||
| 289 | ); | ||
| 290 | $('body').append(overlay); | ||
| 291 | |||
| 292 | overlay.on('click', '#inline_image_picker_grid img', function() { | ||
| 293 | overlay.find('#inline_image_picker_grid img').removeClass('selected'); | ||
| 294 | $(this).addClass('selected'); | ||
| 295 | cccms.inline_images.selected_index = $(this).data('index'); | ||
| 296 | }); | ||
| 297 | |||
| 298 | overlay.on('click', '[data-placement]', function() { | ||
| 299 | cccms.inline_images.insert($(this).data('placement')); | ||
| 300 | }); | ||
| 301 | |||
| 302 | overlay.on('click', '#inline_image_picker_cancel', function() { | ||
| 303 | overlay.hide(); | ||
| 304 | }); | ||
| 305 | }, | ||
| 306 | |||
| 307 | open: function(editor) { | ||
| 308 | cccms.inline_images.ensure_overlay(); | ||
| 309 | cccms.inline_images.editor = editor; | ||
| 310 | |||
| 311 | var showUrl = $("#page_editor > form").attr("data-show-url") || ""; | ||
| 312 | var match = showUrl.match(/(\d+)$/); | ||
| 313 | cccms.inline_images.node_id = match ? match[1] : ""; | ||
| 314 | |||
| 315 | var items = $('#related_asset_list li').map(function() { | ||
| 316 | return { | ||
| 317 | thumb: $(this).find('img').attr('src'), | ||
| 318 | large: $(this).data('large-url'), | ||
| 319 | original: $(this).data('original-url'), | ||
| 320 | name: $(this).data('name') | ||
| 321 | }; | ||
| 322 | }).get(); | ||
| 323 | cccms.inline_images.items = items; | ||
| 324 | |||
| 325 | var grid = $('#inline_image_picker_grid').empty(); | ||
| 326 | if (items.length === 0) { | ||
| 327 | grid.append('<p>No images are attached to this page yet -- attach one from the Images section first.</p>'); | ||
| 328 | cccms.inline_images.selected_index = null; | ||
| 329 | } else { | ||
| 330 | items.forEach(function(item, i) { | ||
| 331 | var wrapper = $('<div class="inline_image_picker_item"></div>'); | ||
| 332 | wrapper.append($('<img>').attr('src', item.thumb).attr('data-index', i)); | ||
| 333 | if (i === 0) { | ||
| 334 | wrapper.append($('<span>', { "class": "inline_image_picker_headline_badge" }).text("Headline")); | ||
| 335 | } | ||
| 336 | grid.append(wrapper); | ||
| 337 | }); | ||
| 338 | cccms.inline_images.selected_index = 0; | ||
| 339 | grid.find('img').first().addClass('selected'); | ||
| 340 | } | ||
| 341 | |||
| 342 | $('#inline_image_picker').show(); | ||
| 343 | }, | ||
| 344 | |||
| 345 | escape_attr: function(str) { | ||
| 346 | return String(str || '').replace(/&/g, '&').replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>'); | ||
| 347 | }, | ||
| 348 | |||
| 349 | insert: function(placement) { | ||
| 350 | var item = cccms.inline_images.items[cccms.inline_images.selected_index]; | ||
| 351 | if (!item) return; | ||
| 352 | |||
| 353 | var classes = placement === 'full' | ||
| 354 | ? 'inline-image inline-image--full' | ||
| 355 | : 'inline-image inline-image--half inline-image--' + placement; | ||
| 356 | |||
| 357 | var html = '<a href="' + item.original + '" class="glightbox" data-gallery="page-' + cccms.inline_images.node_id + '">' + | ||
| 358 | '<img src="' + item.large + '" class="' + classes + '" alt="' + cccms.inline_images.escape_attr(item.name) + '"></a>'; | ||
| 359 | |||
| 360 | cccms.inline_images.editor.insertContent(html); | ||
| 361 | $('#inline_image_picker').hide(); | ||
| 362 | } | ||
| 255 | } | 363 | } |
| 256 | } | 364 | } |
| 257 | 365 | ||
diff --git a/public/javascripts/related_assets.js b/public/javascripts/related_assets.js index 2955cbf..803332f 100644 --- a/public/javascripts/related_assets.js +++ b/public/javascripts/related_assets.js | |||
| @@ -5,6 +5,8 @@ related_assets = { | |||
| 5 | var createUrl = container.data("create-url"); | 5 | var createUrl = container.data("create-url"); |
| 6 | var list = $("#related_asset_list"); | 6 | var list = $("#related_asset_list"); |
| 7 | 7 | ||
| 8 | related_assets.update_headline_badge(list); | ||
| 9 | |||
| 8 | initSearchPicker({ | 10 | initSearchPicker({ |
| 9 | inputSelector: "#related_asset_search_term", | 11 | inputSelector: "#related_asset_search_term", |
| 10 | resultsSelector: "#related_asset_search_results", | 12 | resultsSelector: "#related_asset_search_results", |
| @@ -45,6 +47,7 @@ related_assets = { | |||
| 45 | url: ui.item.data("url"), | 47 | url: ui.item.data("url"), |
| 46 | data: "position=" + newPosition | 48 | data: "position=" + newPosition |
| 47 | }); | 49 | }); |
| 50 | related_assets.update_headline_badge(list); | ||
| 48 | } | 51 | } |
| 49 | }); | 52 | }); |
| 50 | 53 | ||
| @@ -55,11 +58,23 @@ related_assets = { | |||
| 55 | url: item.data("url"), | 58 | url: item.data("url"), |
| 56 | success: function() { | 59 | success: function() { |
| 57 | item.remove(); | 60 | item.remove(); |
| 61 | related_assets.update_headline_badge(list); | ||
| 58 | } | 62 | } |
| 59 | }); | 63 | }); |
| 60 | }); | 64 | }); |
| 61 | }, | 65 | }, |
| 62 | 66 | ||
| 67 | update_headline_badge: function(list) { | ||
| 68 | list.find(".related_asset_headline_badge").remove(); | ||
| 69 | var first = list.children("li").first(); | ||
| 70 | if (first.length) { | ||
| 71 | $("<span>", { | ||
| 72 | "class": "related_asset_headline_badge", | ||
| 73 | "title": "Shown automatically as this page's headline image" | ||
| 74 | }).text("Headline").insertBefore(first.find(".related_asset_remove")); | ||
| 75 | } | ||
| 76 | }, | ||
| 77 | |||
| 63 | attach: function(assetId, createUrl, list) { | 78 | attach: function(assetId, createUrl, list) { |
| 64 | $.ajax({ | 79 | $.ajax({ |
| 65 | type: "POST", | 80 | type: "POST", |
| @@ -69,9 +84,13 @@ related_assets = { | |||
| 69 | success: function(related) { | 84 | success: function(related) { |
| 70 | var item = $($("#related_asset_template").html().trim()); | 85 | var item = $($("#related_asset_template").html().trim()); |
| 71 | item.attr("data-url", related.url); | 86 | item.attr("data-url", related.url); |
| 87 | item.attr("data-large-url", related.large_url); | ||
| 88 | item.attr("data-original-url", related.original_url); | ||
| 89 | item.attr("data-name", related.name); | ||
| 72 | item.find("img").attr("src", related.thumb_url); | 90 | item.find("img").attr("src", related.thumb_url); |
| 73 | list.append(item); | 91 | list.append(item); |
| 74 | list.sortable("refresh"); | 92 | list.sortable("refresh"); |
| 93 | related_assets.update_headline_badge(list); | ||
| 75 | $("#related_asset_search_term").val(""); | 94 | $("#related_asset_search_term").val(""); |
| 76 | $("#related_asset_search_results").slideUp().empty(); | 95 | $("#related_asset_search_results").slideUp().empty(); |
| 77 | } | 96 | } |
