Hello, the IMGUR extension was giving me a lot of problems with performance and compatibility. The latest issue I've encountered is that the API simply doesn't work, and I can't create a new APP for a new API.
So I created this extender to use IMG-Chest instead of IMGUR. It's more stable, transparent, and works well. I know that some people will advocate using the official FOF/Upload extension, but there are several reasons why many of us choose not to use it:
- The cost of image storage can be high if you have a lot of traffic and active users.
- Hosting certain images on your own servers can be a legal headache.
I'm too bad at making extensions, and all the comments in my code are in Spanish, but you can edit them (especially the notices and alerts). If anyone wants to create an extension with this code, go ahead, it will be good for the community.
Just copy and paste this into your extend.php and enter your imgchest API KEY, and that's it! A new button will appear at the bottom of the Flarum editor.
IMGCHEST: https://imgchest.com/profile/api
// Función para usar IMGChest
(new Extend\Frontend('forum'))
->content(function ($document) {
// IMPORTANTE: Reemplaza con tu API key de IMGChest
$apiKey = 'YORR_API_KEY';
$document->head[] = '
<style>
/* Notificación de subida flotante */
.imgchest-notification {
position: fixed;
bottom: 20px;
right: 20px;
background: white;
padding: 15px 20px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
z-index: 9999;
min-width: 300px;
max-width: 400px;
animation: slideIn 0.3s ease-out;
}
@keyframes slideIn {
from {
transform: translateX(400px);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
.imgchest-notification.success {
border-left: 4px solid #28a745;
}
.imgchest-notification.error {
border-left: 4px solid #dc3545;
}
.imgchest-notification.loading {
border-left: 4px solid #007bff;
}
.imgchest-notification-title {
font-weight: bold;
margin-bottom: 5px;
color: #333;
}
.imgchest-notification-message {
color: #666;
font-size: 14px;
}
.imgchest-progress {
width: 100%;
height: 4px;
background: #e9ecef;
border-radius: 2px;
margin-top: 10px;
overflow: hidden;
}
.imgchest-progress-bar {
height: 100%;
background: #007bff;
width: 0%;
transition: width 0.3s;
}
.imgchest-notification-close {
position: absolute;
top: 10px;
right: 10px;
background: none;
border: none;
font-size: 18px;
cursor: pointer;
color: #999;
padding: 0;
width: 20px;
height: 20px;
line-height: 1;
}
.imgchest-notification-close:hover {
color: #333;
}
/* Ocultar el input file */
#imgchest-file-input {
display: none;
}
</style>
<script>
(function() {
var currentEditor = null;
var API_KEY = "' . $apiKey . '";
var notificationElement = null;
var pasteListenerAttached = false;
// Crear input de archivo oculto
function createFileInput() {
if (document.getElementById("imgchest-file-input")) return;
var fileInput = document.createElement("input");
fileInput.type = "file";
fileInput.id = "imgchest-file-input";
fileInput.accept = "image/*";
fileInput.multiple = true;
fileInput.addEventListener("change", function(e) {
if (e.target.files.length > 0) {
uploadImages(e.target.files);
}
});
document.body.appendChild(fileInput);
}
// Crear notificación
function showNotification(type, title, message, showProgress) {
// Eliminar notificación anterior si existe
if (notificationElement) {
notificationElement.remove();
}
notificationElement = document.createElement("div");
notificationElement.className = "imgchest-notification " + type;
var html = `
<button class="imgchest-notification-close">×</button>
<div class="imgchest-notification-title">${title}</div>
<div class="imgchest-notification-message">${message}</div>
`;
if (showProgress) {
html += `
<div class="imgchest-progress">
<div class="imgchest-progress-bar" id="imgchest-progress-bar"></div>
</div>
`;
}
notificationElement.innerHTML = html;
document.body.appendChild(notificationElement);
// Botón cerrar
notificationElement.querySelector(".imgchest-notification-close").addEventListener("click", function() {
notificationElement.remove();
notificationElement = null;
});
return notificationElement;
}
// Subir imágenes
function uploadImages(files) {
if (files.length > 20) {
showNotification("error", "Error", "Máximo 20 imágenes permitidas", false);
return;
}
var fileCount = files.length;
var fileText = fileCount === 1 ? "imagen" : fileCount + " imágenes";
showNotification("loading", "Subiendo...", "Subiendo " + fileText + " a IMGChest", true);
var formData = new FormData();
for (var i = 0; i < files.length; i++) {
formData.append("images[]", files[i], files[i].name);
}
formData.append("privacy", "hidden");
var xhr = new XMLHttpRequest();
// Progreso de subida
xhr.upload.addEventListener("progress", function(e) {
if (e.lengthComputable) {
var percentComplete = (e.loaded / e.total) * 100;
var progressBar = document.getElementById("imgchest-progress-bar");
if (progressBar) {
progressBar.style.width = percentComplete + "%";
}
}
});
// Subida completada
xhr.addEventListener("load", function() {
if (xhr.status === 200 || xhr.status === 201) {
try {
var data = JSON.parse(xhr.responseText);
if (data.data && data.data.images && data.data.images.length > 0) {
showNotification("success", "¡Listo!", fileText + " subida(s) correctamente", false);
// Insertar en el editor
if (currentEditor) {
var markdown = "";
data.data.images.forEach(function(image) {
markdown += "\n";
});
currentEditor.insertAtCursor(markdown);
}
// Cerrar notificación después de 3 segundos
setTimeout(function() {
if (notificationElement) {
notificationElement.remove();
notificationElement = null;
}
}, 3000);
} else {
throw new Error("No se recibió URL de imagen");
}
} catch (e) {
console.error("Error parsing response:", e);
showNotification("error", "Error", "No se pudo procesar la respuesta del servidor", false);
}
} else {
var errorMsg = "Error al subir (código " + xhr.status + ")";
try {
var errorData = JSON.parse(xhr.responseText);
if (errorData.message) {
errorMsg = errorData.message;
}
} catch (e) {}
console.error("Upload failed:", xhr.status, xhr.responseText);
showNotification("error", "Error de subida", errorMsg, false);
}
// Resetear el input
var fileInput = document.getElementById("imgchest-file-input");
if (fileInput) {
fileInput.value = "";
}
});
xhr.addEventListener("error", function() {
console.error("Network error");
showNotification("error", "Error de red", "Verifica tu conexión a internet", false);
var fileInput = document.getElementById("imgchest-file-input");
if (fileInput) {
fileInput.value = "";
}
});
xhr.open("POST", "https://api.imgchest.com/v1/post", true);
xhr.setRequestHeader("Authorization", "Bearer " + API_KEY);
xhr.send(formData);
}
// Detectar pegado de imágenes
function attachPasteListener() {
if (pasteListenerAttached) return;
document.addEventListener("paste", function(e) {
// Verificar si el compositor está activo
if (!window.app || !window.app.composer || !window.app.composer.isVisible()) {
return;
}
// Actualizar referencia al editor
if (window.app.composer.editor) {
currentEditor = window.app.composer.editor;
}
// Obtener items del portapapeles
var items = e.clipboardData.items;
var imageFiles = [];
for (var i = 0; i < items.length; i++) {
var item = items[i];
// Verificar si es una imagen
if (item.type.indexOf("image") !== -1) {
var blob = item.getAsFile();
if (blob) {
// Crear un nombre único para la imagen pegada
var timestamp = new Date().getTime();
var file = new File([blob], "imagen-pegada-" + timestamp + ".png", {
type: blob.type
});
imageFiles.push(file);
}
}
}
// Si hay imágenes, subirlas
if (imageFiles.length > 0) {
e.preventDefault(); // Evitar pegado por defecto
uploadImages(imageFiles);
}
});
pasteListenerAttached = true;
console.log("IMGChest: Listener de pegado de imágenes activado");
}
// Inicializar
var checkInterval = setInterval(function() {
if (window.app && window.app.composer) {
clearInterval(checkInterval);
// Crear input oculto
createFileInput();
// Activar listener de pegado
attachPasteListener();
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
mutation.addedNodes.forEach(function(node) {
if (node.nodeType === 1) {
var toolbar = node.querySelector(".TextEditor-toolbar, .Composer-controls");
if (toolbar && !toolbar.querySelector(".imgchest-upload-btn")) {
var btn = document.createElement("button");
btn.className = "Button Button--icon Button-icon imgchest-upload-btn";
btn.type = "button";
btn.title = "Subir imagen a IMGChest";
btn.innerHTML = \'<i class="icon fas fa-image"></i>\';
btn.style.marginLeft = "5px";
btn.onclick = function(e) {
e.preventDefault();
// Guardar referencia al editor actual
if (window.app && window.app.composer && window.app.composer.editor) {
currentEditor = window.app.composer.editor;
}
// Abrir selector de archivos directamente
var fileInput = document.getElementById("imgchest-file-input");
if (fileInput) {
fileInput.click();
}
};
toolbar.appendChild(btn);
}
}
});
});
});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
}, 500);
})();
</script>
';
}),