Plugin.js Implemantation
tinymce.PluginManager.add('multiImageUpload', function(editor, pluginUrl) {
const pluginLabel = 'Multi-Image Upload';
window.multiImageConfig = {};
const dialogUrl = window.location.origin + '/tinymce/multiImageUpload/upload_dialog.html';
multiImageConfig.uploadHandler = editor.getParam('images_upload_handler', null, 'function');
multiImageConfig.basePath = editor.getParam('images_upload_base_path', '', 'string');
multiImageConfig.allowedTypes = editor.getParam('multiImageUpload_filetype', '.png,.gif,.jpg,.jpeg', 'string');
multiImageConfig.uploadResults = [];
const launchDialog = function() {
setupMessageListener(true);
return editor.windowManager.openUrl({
title: pluginLabel,
size: 'large',
url: dialogUrl,
buttons: [
{
type: 'custom',
text: 'Cancel',
name: 'cancel',
},
{
type: 'custom',
text: 'Upload',
name: 'upload',
primary: true
},
],
onAction: function (dialogApi, buttonDetails) {
switch (buttonDetails.name) {
case 'upload':
window.postMessage({ command: 'processImageUpload' }, '*');
break;
case 'cancel':
window.postMessage({ command: 'cancelUploadProcess' }, '*');
setupMessageListener(false);
break;
}
},
});
};
const insertImages = function() {
let imageMarkup = '';
const uploadedImages = multiImageConfig.uploadResults;
const imageCount = uploadedImages.length;
for (let i = 0; i < imageCount; i++) {
if (uploadedImages[i].url) {
imageMarkup += '<img src="' + uploadedImages[i].url + '" />';
}
}
editor.insertContent(imageMarkup);
multiImageConfig.uploadResults = [];
tinymce.activeEditor.windowManager.close();
};
const handleDialogMessages = function(event){
const messageData = event.data;
if (messageData.command === 'completeImageUpload') {
insertImages();
setupMessageListener(false);
}
}
const setupMessageListener = function(enable){
if(enable){
window.addEventListener('message', handleDialogMessages, false);
} else {
window.removeEventListener('message', handleDialogMessages, false);
}
}
editor.ui.registry.getAll().icons.multiImageUpload || editor.ui.registry.addIcon('multiImageUpload', '<svg viewBox="0 0 1280 1024" xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path d="M1126.2,779.8V87.6c0-24-22.6-86.9-83.5-86.9H83.5C14.7,0.7,0,63.7,0,87.7v692c0,36.2,29.2,89.7,83.5,89.7l959.3-1.3c51.7,0,83.5-42.5,83.5-88.3zm-1044,4V86.3h961.6V783.7H82.2v0.1z" fill="#53565A"/><path d="M603,461.6L521.1,366.3,313,629.8,227.2,546.8,102.4,716.8H972.8v-170L768.2,235.2,603.1,461.6zM284.6,358.4a105.4,105.4,0,0,0,73.5-30c19.5-19.1,30.3-45,30.2-71.8,0-56.8-45.9-103-102.4-103-56.6,0-102.4,46.1-102.4,103C183.4,313.5,228,358.4,284.6,358.4z" fill="#9598A0"/><path d="M1197.7,153.6l-0.3,669.3s13.5,113.9-67.4,113.9H153.6c0,24.1,23.9,87.2,83.5,87.2h959.3c58.3,0,83.6-49.5,83.6-89.9V240.8c-0.1-41.8-44.9-87.2-82.3-87.2z" fill="#53565A"/></svg>');
editor.ui.registry.addButton('multiImageUpload', {
icon: 'multiImageUpload',
tooltip: pluginLabel,
onAction: function() {
launchDialog();
}
});
editor.ui.registry.addMenuItem('multiImageUpload', {
icon: 'multiImageUpload',
text: 'Upload Multiple Images...',
onAction: function() {
launchDialog();
}
});
return {
getMetadata: function() {
return {
name: pluginLabel,
url: "https://tinymce.com/plugins/multi-image-upload",
};
}
};
});
Dialog HTMML Strucutre
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Image Upload Dialog</title>
<style>
body {
margin: 0;
padding: 0;
background-color: #ffffff;
font-family: sans-serif;
}
.container {
padding: 15px;
}
.upload-header {
padding: 12px 0;
border-bottom: 1px solid #e0e0e0;
display: flex;
justify-content: space-between;
align-items: center;
}
.image-preview-area {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 20px;
min-height: 200px;
}
.preview-item {
width: 120px;
height: 120px;
border: 2px dashed #cccccc;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
cursor: move;
}
.preview-item.dragging {
opacity: 0.5;
border-color: #007bff;
}
.preview-item img {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
.upload-controls {
margin-top: 15px;
display: flex;
gap: 10px;
}
.drag-handle {
position: absolute;
top: 5px;
right: 5px;
width: 20px;
height: 20px;
background-color: rgba(0,0,0,0.5);
border-radius: 50%;
cursor: move;
}
</style>
</head>
<body>
<div class="container">
<div class="upload-header">
<h3>Upload and Arrange Images</h3>
</div>
<div class="image-preview-area" id="imageContainer">
<!-- Dynamic preview elements will be inserted here -->
</div>
<div class="upload-controls">
<input type="file" id="fileInput" multiple accept=".png,.gif,.jpg,.jpeg">
<button id="uploadButton">Upload Selected Images</button>
</div>
</div>
<script>
// Drag-and-drop sorting implementation
const imageContainer = document.getElementById('imageContainer');
let draggedElement = null;
imageContainer.addEventListener('dragstart', function(e) {
if (e.target.classList.contains('preview-item')) {
draggedElement = e.target;
e.target.classList.add('dragging');
}
});
imageContainer.addEventListener('dragover', function(e) {
e.preventDefault();
const afterElement = getDragAfterElement(imageContainer, e.clientY);
if (draggedElement && afterElement) {
imageContainer.insertBefore(draggedElement, afterElement);
}
});
imageContainer.addEventListener('dragend', function(e) {
if (draggedElement) {
draggedElement.classList.remove('dragging');
draggedElement = null;
}
});
function getDragAfterElement(container, y) {
const draggableElements = [...container.querySelectorAll('.preview-item:not(.dragging)')];
return draggableElements.reduce((closest, child) => {
const box = child.getBoundingClientRect();
const offset = y - box.top - box.height / 2;
if (offset < 0 && offset > closest.offset) {
return { offset: offset, element: child };
} else {
return closest;
}
}, { offset: Number.NEGATIVE_INFINITY }).element;
}
// File handling and communication with parent window
document.getElementById('fileInput').addEventListener('change', handleFileSelection);
document.getElementById('uploadButton').addEventListener('click', processUpload);
function handleFileSelection(e) {
const files = Array.from(e.target.files);
files.forEach(file => {
if (file.type.match('image.*')) {
const reader = new FileReader();
reader.onload = function(event) {
createPreviewElement(event.target.result);
};
reader.readAsDataURL(file);
}
});
}
function createPreviewElement(imageData) {
const previewDiv = document.createElement('div');
previewDiv.className = 'preview-item';
previewDiv.draggable = true;
const img = document.createElement('img');
img.src = imageData;
const handle = document.createElement('div');
handle.className = 'drag-handle';
previewDiv.appendChild(img);
previewDiv.appendChild(handle);
imageContainer.appendChild(previewDiv);
}
function processUpload() {
const previews = imageContainer.querySelectorAll('.preview-item img');
const imageUrls = Array.from(previews).map(img => ({ url: img.src }));
window.parent.postMessage({
command: 'completeImageUpload',
data: imageUrls
}, '*');
}
window.addEventListener('message', function(event) {
if (event.data.command === 'processImageUpload') {
processUpload();
}
if (event.data.command === 'cancelUploadProcess') {
window.parent.postMessage({ command: 'dialogClosed' }, '*');
}
});
</script>
</body>
</html>