media_duplicate_check-1.0.0/js/duplicate-check.js
js/duplicate-check.js
/**
* @file
* JavaScript for media duplicate checking functionality.
*/
(function ($, Drupal, drupalSettings) {
'use strict';
/**
* Get translated string from drupalSettings or fallback to English.
*/
function getTranslation(key, fallback) {
if (drupalSettings.media_duplicate_check &&
drupalSettings.media_duplicate_check.translations &&
drupalSettings.media_duplicate_check.translations[key]) {
return drupalSettings.media_duplicate_check.translations[key];
}
return fallback || key;
}
/**
* Store warning state globally to persist through AJAX updates
*/
window.duplicateWarningState = {
hasWarning: false,
warningHtml: '',
filename: '',
fileInput: null
};
/**
* Media duplicate check behaviors.
*/
Drupal.behaviors.mediaDuplicateCheck = {
attach: function (context, settings) {
// Initialize the duplicate check handler.
if (!window.mediaDuplicateCheck) {
window.mediaDuplicateCheck = new MediaDuplicateCheck();
}
// Restore warning state if it exists
if (window.duplicateWarningState.hasWarning && window.duplicateWarningState.warningHtml) {
var $container = $('#duplicate-check-wrapper');
if ($container.length > 0 && $container.html().trim() === '') {
$container.html(window.duplicateWarningState.warningHtml).addClass('has-duplicate-warning');
// Re-attach event handlers
window.mediaDuplicateCheck.attachWarningHandlers($container);
}
}
// Re-check file inputs that may have been added via AJAX
var $fileInputs = $('input[type="file"]', context);
$fileInputs.each(function() {
if (this.files && this.files.length > 0 && !$(this).data('duplicate-checked')) {
var filename = this.files[0].name;
window.mediaDuplicateCheck.checkOriginalFilename(filename, this);
$(this).data('duplicate-checked', true);
}
});
}
};
/**
* MediaDuplicateCheck class.
*/
function MediaDuplicateCheck() {
this.fileInput = null;
this.confirmed = false;
}
/**
* Confirm the upload and close modal.
*/
MediaDuplicateCheck.prototype.confirmUpload = function() {
// Set the confirmation flag.
$('#duplicate-confirmed').val('1');
this.confirmed = true;
// Send AJAX request to set session confirmation
var filename = $('.duplicate-media-preview').data('filename') || '';
if (filename) {
$.post('/admin/config/media/duplicate-check/confirm', {
filename: filename,
confirmed: '1'
});
}
// Close the modal.
$('.ui-dialog-content').dialog('close');
// Clear the warning message.
$('#duplicate-check-wrapper').html('');
// Show a confirmation message using proper Drupal markup.
var confirmMessage = '<div class="messages messages--status">' +
'<h2 class="visually-hidden">' + getTranslation('status_message', 'Status message') + '</h2>' +
'<p>' + getTranslation('upload_confirmed_save', 'Upload confirmed. You may now save the media item.') + '</p>' +
'</div>';
$('#duplicate-check-wrapper').html(confirmMessage);
// Enable form submission buttons.
$('form').find('input[type="submit"], button[type="submit"]').prop('disabled', false);
};
/**
* Cancel the upload and reset the file input.
*/
MediaDuplicateCheck.prototype.cancelUpload = function() {
// Reset the confirmation flag.
$('#duplicate-confirmed').val('0');
this.confirmed = false;
// Close the modal.
$('.ui-dialog-content').dialog('close');
// Find and click the remove button to properly clear the uploaded file
var $removeBtn = $('input[name*="remove_button"], button[name*="remove_button"]').filter(':visible').first();
if ($removeBtn.length > 0) {
// Click the remove button to trigger proper AJAX removal
$removeBtn.trigger('click');
// Clear the warning message.
$('#duplicate-check-wrapper').html('');
} else {
// Fallback: manually clear file inputs if no remove button found
var fileInputs = $('input[type="file"]');
fileInputs.each(function() {
$(this).val('');
// Trigger change event to clear any file preview.
$(this).trigger('change');
});
// Clear the warning message.
$('#duplicate-check-wrapper').html('');
// Show a cancellation message.
var cancelMessage = '<div class="messages messages--info">' +
'<h2 class="visually-hidden">' + getTranslation('information_message', 'Information message') + '</h2>' +
getTranslation('upload_cancelled', 'Upload cancelled. Please select a different file.') +
'</div>';
$('#duplicate-check-wrapper').html(cancelMessage);
// Fade out the message after 3 seconds.
setTimeout(function() {
$('#duplicate-check-wrapper').fadeOut('slow', function() {
$(this).html('').show();
});
}, 3000);
}
};
/**
* Attach event handlers to warning buttons
*/
MediaDuplicateCheck.prototype.attachWarningHandlers = function($container) {
var self = this;
$('.duplicate-proceed-btn', $container).off('click').on('click', function() {
var confirmedFilename = $(this).data('filename');
$('#duplicate-confirmed').val('1');
self.confirmed = true;
// Clear warning state
window.duplicateWarningState.hasWarning = false;
window.duplicateWarningState.warningHtml = '';
// Set session confirmation
$.post('/admin/config/media/duplicate-check/confirm', {
filename: confirmedFilename,
confirmed: '1'
});
// Clear warning
$container.html('').removeClass('has-duplicate-warning');
// Show confirmation message using proper Drupal markup
var confirmMessage = '<div class="messages messages--status">' +
'<h2 class="visually-hidden">' + getTranslation('status_message', 'Status message') + '</h2>' +
'<p>' + getTranslation('upload_confirmed_auto_name', 'Upload confirmed. The file will be uploaded with an automatically generated name to avoid conflicts.') + '</p>' +
'</div>';
$container.html(confirmMessage);
});
$('.duplicate-cancel-btn', $container).off('click').on('click', function() {
// Clear warning state
window.duplicateWarningState.hasWarning = false;
window.duplicateWarningState.warningHtml = '';
// Clear warning first
$container.html('').removeClass('has-duplicate-warning');
// Look for remove button with more specific selectors
var $removeBtn = $('input[value="Remove"], button[value="Remove"], input[id*="remove-button"], button[id*="remove-button"]').filter(':visible');
if ($removeBtn.length > 0) {
// Get the form and trigger submit with the remove button
var $form = $removeBtn.closest('form');
var $removeButton = $removeBtn.first();
// Create a temporary submit button click event
var formData = new FormData($form[0]);
formData.append($removeButton.attr('name'), $removeButton.val());
// Try to trigger the AJAX request manually
if (Drupal.ajax && Drupal.ajax.instances) {
// Find the AJAX instance for this button
var ajaxInstance = null;
for (var i in Drupal.ajax.instances) {
if (Drupal.ajax.instances[i] && Drupal.ajax.instances[i].element === $removeButton[0]) {
ajaxInstance = Drupal.ajax.instances[i];
break;
}
}
if (ajaxInstance) {
try {
ajaxInstance.eventResponse(ajaxInstance, null);
} catch (e) {
// Fallback to click events
$removeButton.trigger('mousedown').trigger('mouseup').trigger('click');
if ($removeButton[0].click) {
$removeButton[0].click();
}
}
} else {
// Fallback to click events
$removeButton.trigger('mousedown').trigger('mouseup').trigger('click');
if ($removeButton[0].click) {
$removeButton[0].click();
}
}
} else {
// Fallback to click events
$removeButton.trigger('mousedown').trigger('mouseup').trigger('click');
if ($removeButton[0].click) {
$removeButton[0].click();
}
}
} else {
// More aggressive fallback: look for any upload button or file input and try to reset
var $form = $container.closest('form');
var $fileInputs = $form.find('input[type="file"]');
// Clear file inputs
$fileInputs.each(function() {
$(this).val('');
$(this).trigger('change');
});
// Try to find and trigger any "Remove" or upload-related buttons
var $anyRemoveBtn = $form.find('input[type="submit"], button[type="submit"]').filter(function() {
var value = $(this).val() || $(this).text();
return value && (value.toLowerCase().includes('remove') || value.toLowerCase().includes('fjern'));
});
if ($anyRemoveBtn.length > 0) {
$anyRemoveBtn.first().click();
} else {
// Show cancellation message using proper Drupal markup
var cancelMessage = '<div class="messages messages--info">' +
'<h2 class="visually-hidden">' + getTranslation('information_message', 'Information message') + '</h2>' +
'<p>' + getTranslation('upload_cancelled', 'Upload cancelled. Please select a different file.') + '</p>' +
'</div>';
$container.html(cancelMessage);
// Fade out the message after 3 seconds
setTimeout(function() {
$container.fadeOut('slow', function() {
$(this).html('').show();
});
}, 3000);
}
}
});
};
/**
* Reset the duplicate check state.
*/
MediaDuplicateCheck.prototype.reset = function() {
$('#duplicate-confirmed').val('0');
this.confirmed = false;
// Clear global warning state
window.duplicateWarningState = {
hasWarning: false,
warningHtml: '',
filename: '',
fileInput: null
};
// Clear all possible warning containers
$('#duplicate-check-wrapper').html('').removeClass('has-duplicate-warning');
$('.media-library-add-form #duplicate-check-wrapper').html('').removeClass('has-duplicate-warning');
$('[data-drupal-selector*="media-library"] #duplicate-check-wrapper').html('').removeClass('has-duplicate-warning');
};
/**
* Check for duplicates using the original filename (before upload).
*/
MediaDuplicateCheck.prototype.checkOriginalFilename = function(filename, fileInput) {
var self = this;
// Skip check if already confirmed for this file
if (self.confirmed) {
return;
}
// Send AJAX request to check for duplicates
$.ajax({
url: '/admin/config/media/duplicate-check/check-original',
method: 'POST',
data: {
filename: filename
},
success: function(response) {
if (response.duplicates && response.duplicates.length > 0) {
// Show warning and modal
self.showPreUploadWarning(response.duplicates, filename, fileInput);
} else {
// Clear any previous warnings
$('#duplicate-check-wrapper').html('');
// Allow normal upload to proceed
self.confirmed = true;
}
},
error: function(xhr, status, error) {
// Allow upload to proceed on error
self.confirmed = true;
}
});
};
/**
* Show warning before upload when duplicates are detected.
*/
MediaDuplicateCheck.prototype.showPreUploadWarning = function(duplicates, filename, fileInput) {
var self = this;
var count = duplicates.length;
var message = Drupal.formatPlural(count,
'@count media item with the filename "@filename" already exists. Are you sure you want to upload this file with a new filename?',
'@count media items with the filename "@filename" already exist. Are you sure you want to upload this file with a new filename?',
{'@count': count, '@filename': filename}
);
// Build list of existing media links using proper Drupal markup
var existingMediaLinks = '';
if (duplicates.length > 0) {
existingMediaLinks = '<div class="duplicate-warning__existing-media">' +
'<p class="existing-media-links__header">' + getTranslation('existing_media', 'Existing media:') + '</p>' +
'<ul class="existing-media-links__list">';
duplicates.forEach(function(media) {
existingMediaLinks += '<li class="existing-media-links__item">' +
'<a href="' + media.url + '" target="_blank" class="existing-media-link">' +
media.name + '</a> (' + media.type + ', ' + media.created_formatted + ')' +
'</li>';
});
existingMediaLinks += '</ul></div>';
}
// Show warning message using details/summary structure matching Claro pattern
var warningHtml = '<details class="duplicate-warning claro-details" open data-once="details">' +
'<summary class="claro-details__summary" role="button" aria-expanded="true">' +
getTranslation('duplicate_file_detected', 'Duplicate file detected: @filename').replace('@filename', filename) +
'</summary>' +
'<div class="claro-details__wrapper details-wrapper">' +
'<p class="duplicate-warning__message">' + message + '</p>' +
existingMediaLinks +
'<div class="duplicate-warning-actions form-actions">' +
'<button type="button" class="button button--danger duplicate-proceed-btn" data-filename="' + filename + '">' +
getTranslation('yes_upload_anyway', 'Yes, Upload Anyway') + '</button>' +
'<button type="button" class="button button--primary duplicate-cancel-btn">' +
getTranslation('cancel', 'Cancel') + '</button>' +
'</div>' +
'</div>' +
'</details>';
// Store warning state globally
window.duplicateWarningState = {
hasWarning: true,
warningHtml: warningHtml,
filename: filename,
fileInput: fileInput
};
// Find the appropriate container for the warning
var $warningContainer = $('#duplicate-check-wrapper');
// If not found, try to find it in media library context
if ($warningContainer.length === 0) {
var $mediaLibraryForm = $(fileInput).closest('.media-library-add-form, [data-drupal-selector*="media-library"]');
if ($mediaLibraryForm.length > 0) {
// Create a warning container if it doesn't exist
if ($mediaLibraryForm.find('#duplicate-check-wrapper').length === 0) {
$mediaLibraryForm.prepend('<div id="duplicate-check-wrapper"></div>');
}
$warningContainer = $mediaLibraryForm.find('#duplicate-check-wrapper');
}
}
// Fallback: create container at the top of the form
if ($warningContainer.length === 0) {
var $form = $(fileInput).closest('form');
if ($form.find('#duplicate-check-wrapper').length === 0) {
$form.prepend('<div id="duplicate-check-wrapper"></div>');
}
$warningContainer = $form.find('#duplicate-check-wrapper');
}
// Store the warning HTML as data to persist through AJAX updates
$warningContainer.html(warningHtml).data('duplicate-warning', warningHtml);
// Add a class to the container for CSS styling
$warningContainer.addClass('has-duplicate-warning');
// Don't disable the file input - just show the warning
// Users can still proceed with normal upload process
// Attach event handlers
this.attachWarningHandlers($warningContainer);
};
// Make the MediaDuplicateCheck instance globally available.
window.mediaDuplicateCheck = new MediaDuplicateCheck();
// Listen for file input changes to check for duplicates BEFORE upload.
$(document).on('change', 'input[type="file"]', function(e) {
// Skip if this is triggered by our duplicate check process
if (e.namespace === 'duplicateCheck') {
return;
}
var fileInput = this;
var files = fileInput.files;
// Check if this is in a media library context
var isMediaLibrary = $(fileInput).closest('.media-library-add-form').length > 0 ||
$(fileInput).closest('[data-drupal-selector*="media-library"]').length > 0;
if (files && files.length > 0) {
var file = files[0];
var filename = file.name;
// Check if this file was already confirmed
if (window.mediaDuplicateCheck && window.mediaDuplicateCheck.confirmed) {
// Reset confirmation for new file
window.mediaDuplicateCheck.confirmed = false;
$('#duplicate-confirmed').val('0');
return;
}
// Check for duplicates using the original filename
if (window.mediaDuplicateCheck) {
window.mediaDuplicateCheck.checkOriginalFilename(filename, fileInput);
}
}
if (window.mediaDuplicateCheck && !window.mediaDuplicateCheck.confirmed) {
window.mediaDuplicateCheck.reset();
}
});
// Listen for AJAX events to restore warnings if needed
$(document).ajaxComplete(function(event, xhr, settings) {
// Check if this was a media library related AJAX call
if (settings.url && (settings.url.indexOf('media-library') !== -1 || settings.url.indexOf('ajax_form') !== -1)) {
// Restore warnings for any file inputs with data
setTimeout(function() {
if (window.duplicateWarningState.hasWarning && window.duplicateWarningState.warningHtml) {
var $container = $('#duplicate-check-wrapper');
// If container doesn't exist, create it
if ($container.length === 0) {
var $form = $('.media-library-add-form, form[data-drupal-selector*="media-library"]').first();
if ($form.length > 0) {
$form.prepend('<div id="duplicate-check-wrapper"></div>');
$container = $('#duplicate-check-wrapper');
}
}
if ($container.length > 0 && ($container.html().trim() === '' || !$container.hasClass('has-duplicate-warning'))) {
$container.html(window.duplicateWarningState.warningHtml).addClass('has-duplicate-warning');
// Re-attach event handlers
if (window.mediaDuplicateCheck) {
window.mediaDuplicateCheck.attachWarningHandlers($container);
}
}
}
}, 100);
}
});
// Monitor DOM mutations to catch dynamic form updates
if (window.MutationObserver) {
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.type === 'childList' && window.duplicateWarningState.hasWarning) {
// Check if the duplicate-check-wrapper was removed or emptied
var $container = $('#duplicate-check-wrapper');
if ($container.length === 0 || ($container.html().trim() === '' && window.duplicateWarningState.warningHtml)) {
// Find or create container
if ($container.length === 0) {
var $form = $('.media-library-add-form, form[data-drupal-selector*="media-library"]').first();
if ($form.length > 0) {
$form.prepend('<div id="duplicate-check-wrapper"></div>');
$container = $('#duplicate-check-wrapper');
}
}
if ($container.length > 0) {
$container.html(window.duplicateWarningState.warningHtml).addClass('has-duplicate-warning');
// Re-attach event handlers
if (window.mediaDuplicateCheck) {
window.mediaDuplicateCheck.attachWarningHandlers($container);
}
}
}
}
});
});
// Start observing
observer.observe(document.body, {
childList: true,
subtree: true
});
}
// Add visual feedback when hovering over existing media items.
$(document).on('mouseenter', '.existing-media-item', function() {
$(this).addClass('hover');
}).on('mouseleave', '.existing-media-item', function() {
$(this).removeClass('hover');
});
})(jQuery, Drupal, drupalSettings);
