Files
HotLunches/summernote.images.php
2026-10-19 15:22:52 -07:00

236 lines
9.5 KiB
PHP

<?php
if (!isset($windowHeight)) $windowHeight = 350;
if (!isset($fieldName)) $fieldName = 'content';
?>
<style>
/* Summernote's dialogs (Insert Image / Link / Video / Help) open while the record is
being edited inside a jQuery UI modal dialog. Two separate things break there:
1. STACKING. jQuery UI 1.12's _moveToTop() (jquery-ui.js:12163) reads every visible
.ui-front sibling and sets its own dialog to max+1. A fixed high z-index on
.note-modal therefore does NOT win -- jQuery UI simply climbs above it. The
reliable fix is to keep the Summernote modal out of that comparison and give it
a z-index above the overlay, while the overlay itself keeps its own value.
2. HIT TESTING. .ui-widget-overlay is painted over the whole viewport. Even when the
Summernote dialog is drawn on top, the overlay still captures the clicks unless
pointer events are disabled on it while a Summernote dialog is open. */
.note-modal { z-index: 100050 !important; }
.note-modal-backdrop { z-index: 100040 !important; }
.note-popover.popover{ z-index: 100060 !important; }
/* The file input and URL field live inside .note-modal-content -- make sure nothing
inherited from the dialog CSS leaves them inert. */
.note-modal, .note-modal-content, .note-modal * { pointer-events: auto !important; }
/* While a Summernote dialog is open the jQuery UI overlay must stop intercepting
clicks, otherwise "Choose Files" and "Image URL" stay dead even though they are
visible on top of it. */
body.note-dialog-open .ui-widget-overlay,
body:has(.note-modal.open) .ui-widget-overlay { pointer-events: none !important; }
</style>
<script>
// Must run at parse time, not in $(document).ready. form.js.php registers its ready
// handler first and creates #data-dialog there; a ready-time patch here is too late
// and that dialog keeps the original focus trap.
if ($.ui && $.ui.dialog && !window._noteDialogAllowInteraction) {
window._noteDialogAllowInteraction = true;
$.widget("ui.dialog", $.ui.dialog, {
_allowInteraction: function (event) {
return !!$(event.target).closest(".note-modal, .note-popover, .note-editor").length ||
this._super(event);
}
});
}
$(document).ready(function() {
<?php
// NOTE: include/ajax.Setup.php calls exit() if session_start() returns false
// (ajax.Setup.php:5-7). By the time this file runs, config.php:217 has already
// started the session, so that second session_start() fails and exit() aborts
// the whole page render -- silently discarding this <script> block and the
// gallery markup below it. Emit the same $.ajaxSetup here instead, guarded.
if (!isset($_SESSION)) {
if (session_status() != PHP_SESSION_ACTIVE) {
session_name('hotlunch');
@session_start();
}
}
if (is_array($_SESSION) && !array_key_exists('csrf_token', $_SESSION)) {
$_SESSION['csrf_token'] = str_replace('+','.',base64_encode(openssl_random_pseudo_bytes(32)));
}
$csrfToken = (is_array($_SESSION) && array_key_exists('csrf_token', $_SESSION)) ? $_SESSION['csrf_token'] : '';
?>
$.ajaxSetup({
data: window.csrf,
cache: false,
headers: {
'X-CSRF-TOKEN': '<?php echo($csrfToken); ?>'
}
});
var $noteTarget = $('#<?php echo($fieldName) ?>');
// Flag the body while a Summernote dialog is open so the CSS above can drop the
// jQuery UI overlay's pointer capture. summernote-lite fires these two events on
// the .note-modal element (verified in summernote-lite.min.js).
$(document).on("note.modal.show", ".note-modal", function () {
$("body").addClass("note-dialog-open");
});
$(document).on("note.modal.hide", ".note-modal", function () {
$("body").removeClass("note-dialog-open");
});
if ($.ui && $.ui.dialog) {
$("#image-gallery-dialog").dialog({
autoOpen: false,
minWidth: 950,
minHeight: 400,
modal: true,
position: { my: 'center', at: 'center' }
});
}
$("#close-gallery").on("click", function () {
$("#image-gallery-dialog").dialog("close");
});
var Gallery = function (context) {
var ui = $.summernote.ui;
// create button. Font Awesome is not loaded on the table-edit dialog pages, so
// use Summernote's own icon font and keep a text label as the visible fallback.
var button = ui.button({
contents: '<i class="note-icon-picture"></i> Gallery',
tooltip: '<?php echo(addslashes(getLanguageText("TEXT-IMAGE-GALLERY"))) ?>',
click: function () {
loadGallery();
$("#image-gallery-dialog").dialog("open");
}
});
return button.render(); // return button as jquery object
}
$noteTarget.summernote({
height: <?php echo($windowHeight) ?>,
// THE FIX for the greyed-out, unclickable "Insert Image" popup.
// summernote-lite always appends its backdrop to document.body
// this.$backdrop.appendTo(document.body)
// but appends the dialog itself to (dialogsInBody ? $body : options.container),
// which defaults to the editor. Here the editor lives inside a jQuery UI modal
// dialog, and that dialog has its own z-index (~101) forming a stacking context.
// The dialog is therefore TRAPPED in that context no matter how high its
// z-index is, while the backdrop sits at the document root above it -- so the
// backdrop paints over the popup and eats every click. Putting the dialog in
// <body> makes it a sibling of the backdrop again, where z-index actually works.
dialogsInBody: true,
toolbar: [
['style', ['style']],
['font', ['bold', 'underline', 'clear']],
['fontname', ['fontname']],
['fontSize', ['fontsize']],
['color', ['color']],
['para', ['ul', 'ol', 'paragraph']],
['table', ['table']],
['mybutton', ['Gallery']],
['insert', ['link', 'picture', 'video']],
['view', ['fullscreen', 'codeview', 'help']],
],
buttons: {
Gallery
},
callbacks: {
onImageUpload: function(files, editor, $editable) {
uploadImageFile(files[0],editor,$editable);
}
},
imageAttributes: {
// The plugin ignores the top-level dialogsInBody and reads its own copy
// (summernote-image-attributes.js:60); without it the "Change Image Attributes"
// popup is trapped under the backdrop exactly like the Insert Image one was.
dialogsInBody: true,
icon: '<i class="note-icon-pencil">',
figureClass: 'figureClass',
figcaptionClass: 'captionClass',
captionText: 'Caption Goes Here.',
manageAspectRatio: true // true = Lock the Image Width/Height, Default to true
},
lang: 'en-US',
popover: {
image: [
['image', ['resizeFull', 'resizeHalf', 'resizeQuarter', 'resizeNone']],
['float', ['floatLeft', 'floatRight', 'floatNone']],
['custom', ['imageAttributes']],
['remove', ['removeMedia']],
],
}
});
$(document).on('click', '.gallery-image', function() {
var image = $(this);
var imageUrl = image.attr('filename');
// Create an <img> element with the image URL
var imgElement = $("<img>").attr("src", imageUrl);
// Insert the <img> element into the editor
$noteTarget.summernote("pasteHTML", imgElement[0].outerHTML);
$("#image-gallery-dialog").dialog("close");
});
function uploadImageFile(file,editor,welEditable) {
data = new FormData();
data.append("file", file);
$.ajax({
url: "ajax.imageUploader.php",
data: data,
cache: false,
contentType: false,
processData: false,
type: 'POST',
success: function(response){
var imageUrl = response.trim(); // Trim any leading/trailing whitespace
// Create an <img> element with the image URL
var imgElement = $("<img>").attr("src", imageUrl);
// Insert the <img> element into the editor
$noteTarget.summernote("pasteHTML", imgElement[0].outerHTML);
},
error: function(jqXHR, textStatus, errorThrown) {
console.log(textStatus+" "+errorThrown);
alert(textStatus+" "+errorThrown);
}
});
}
function loadGallery() {
var data = "table_name=images"+
"&sequence_flag=N"+
"&action=load-image-gallery"+
"&table_id=";
$.getJSON( "ajax.DataTable.Fetch.php",data,
function(results) {
$("#image-gallery-div").html(results.content);
}
);
}
});
</script>
<div id='image-gallery-dialog' title="<?php displayLanguageText('TEXT-IMAGE-GALLERY'); ?>">
<br>
<div id='image-gallery-div' style='height: 600px; overflow-y: auto'></div>
<br>
<center>
<button type='button' id='close-gallery' class='btn btn--outline btn--xsmall'><?php displayLanguageText('TEXT-CLOSE'); ?></button>
</center>
</div>