Unfiltered / admin.js
abdullahalioo's picture
Upload 9 files
6390fb3 verified
// ===== Mubashra Studio - Advanced Drag & Drop Editor =====
const CORRECT_PASSWORD = "inshaaAllah";
// DOM Elements
const authOverlay = document.getElementById('authOverlay');
const adminWorkspace = document.getElementById('adminWorkspace');
const passwordInput = document.getElementById('passwordInput');
const loginBtn = document.getElementById('loginBtn');
const errorMsg = document.getElementById('errorMsg');
const blogCanvas = document.getElementById('blogCanvas');
const canvasWrapper = document.getElementById('canvasWrapper');
const canvasArea = document.getElementById('canvasArea');
const canvasPlaceholder = document.getElementById('canvasPlaceholder');
const blogTitleInput = document.getElementById('blogTitle');
const postBtn = document.getElementById('postBtn');
const previewBtn = document.getElementById('previewBtn');
// Sidebars
const sidebarLeft = document.getElementById('sidebarLeft');
const sidebarRight = document.getElementById('sidebarRight');
const sidebarOverlay = document.getElementById('sidebarOverlay');
const menuToggle = document.getElementById('menuToggle');
const closeSidebarLeft = document.getElementById('closeSidebarLeft');
const closeSidebarRight = document.getElementById('closeSidebarRight');
const mobilePropsBtn = document.getElementById('mobilePropsBtn');
// Zoom Controls
const zoomIn = document.getElementById('zoomIn');
const zoomOut = document.getElementById('zoomOut');
const zoomReset = document.getElementById('zoomReset');
const zoomLevelDisplay = document.getElementById('zoomLevel');
// Modal Elements
const publishModal = document.getElementById('publishModal');
const closeModal = document.getElementById('closeModal');
const cancelPublish = document.getElementById('cancelPublish');
const confirmPublish = document.getElementById('confirmPublish');
const modalBlogTitle = document.getElementById('modalBlogTitle');
const coverUploadArea = document.getElementById('coverUploadArea');
const coverImageInput = document.getElementById('coverImageInput');
const uploadPlaceholder = document.getElementById('uploadPlaceholder');
const uploadPreview = document.getElementById('uploadPreview');
const coverPreviewImg = document.getElementById('coverPreviewImg');
const removeCover = document.getElementById('removeCover');
// Element buttons (both sidebar and mobile)
const elementBtns = document.querySelectorAll('.element-btn');
const mobileToolBtns = document.querySelectorAll('.mobile-tool-btn[data-type]');
const layersList = document.getElementById('layersList');
const propertiesPanel = document.getElementById('propertiesPanel');
// Property Panels
const textProperties = document.getElementById('textProperties');
const imageProperties = document.getElementById('imageProperties');
const positionProperties = document.getElementById('positionProperties');
// Property Inputs
const fontFamily = document.getElementById('fontFamily');
const fontSize = document.getElementById('fontSize');
const fontWeight = document.getElementById('fontWeight');
const textColor = document.getElementById('textColor');
const textColorHex = document.getElementById('textColorHex');
const letterSpacing = document.getElementById('letterSpacing');
const lineHeight = document.getElementById('lineHeight');
const posX = document.getElementById('posX');
const posY = document.getElementById('posY');
const elWidth = document.getElementById('elWidth');
const elHeight = document.getElementById('elHeight');
const elRotation = document.getElementById('elRotation');
const rotationValue = document.getElementById('rotationValue');
const deleteElement = document.getElementById('deleteElement');
// Image Properties
const replaceImage = document.getElementById('replaceImage');
const imageBorderRadius = document.getElementById('imageBorderRadius');
const borderRadiusValue = document.getElementById('borderRadiusValue');
const imageOpacity = document.getElementById('imageOpacity');
const opacityValue = document.getElementById('opacityValue');
// Image Upload Input
const imageUpload = document.getElementById('imageUpload');
// State
let selectedElement = null;
let isDragging = false;
let isResizing = false;
let resizeDirection = '';
let dragStartX = 0;
let dragStartY = 0;
let initialX = 0;
let initialY = 0;
let initialWidth = 0;
let initialHeight = 0;
let elementCounter = 0;
let currentZoom = 1;
let coverImageData = null;
const ZOOM_STEP = 0.1;
const MIN_ZOOM = 0.25;
const MAX_ZOOM = 2;
// ===== Authentication =====
loginBtn.addEventListener('click', checkPassword);
passwordInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') checkPassword();
});
function checkPassword() {
if (passwordInput.value === CORRECT_PASSWORD) {
authOverlay.classList.add('hidden');
adminWorkspace.classList.remove('hidden');
} else {
errorMsg.textContent = "Incorrect password. Please try again.";
passwordInput.value = "";
passwordInput.focus();
}
}
// ===== Mobile Sidebar Controls =====
menuToggle?.addEventListener('click', () => {
sidebarLeft.classList.add('open');
sidebarOverlay.classList.add('visible');
});
closeSidebarLeft?.addEventListener('click', closeSidebars);
closeSidebarRight?.addEventListener('click', closeSidebars);
sidebarOverlay?.addEventListener('click', closeSidebars);
mobilePropsBtn?.addEventListener('click', () => {
sidebarRight.classList.add('open');
sidebarOverlay.classList.add('visible');
});
function closeSidebars() {
sidebarLeft.classList.remove('open');
sidebarRight.classList.remove('open');
sidebarOverlay.classList.remove('visible');
}
// ===== Zoom Controls =====
zoomIn?.addEventListener('click', () => {
if (currentZoom < MAX_ZOOM) {
currentZoom = Math.min(currentZoom + ZOOM_STEP, MAX_ZOOM);
applyZoom();
}
});
zoomOut?.addEventListener('click', () => {
if (currentZoom > MIN_ZOOM) {
currentZoom = Math.max(currentZoom - ZOOM_STEP, MIN_ZOOM);
applyZoom();
}
});
zoomReset?.addEventListener('click', () => {
currentZoom = 1;
applyZoom();
});
function applyZoom() {
canvasWrapper.style.transform = `scale(${currentZoom})`;
if (zoomLevelDisplay) {
zoomLevelDisplay.textContent = `${Math.round(currentZoom * 100)}%`;
}
}
// Mouse wheel zoom
canvasArea?.addEventListener('wheel', (e) => {
if (e.ctrlKey) {
e.preventDefault();
if (e.deltaY < 0 && currentZoom < MAX_ZOOM) {
currentZoom = Math.min(currentZoom + ZOOM_STEP, MAX_ZOOM);
} else if (e.deltaY > 0 && currentZoom > MIN_ZOOM) {
currentZoom = Math.max(currentZoom - ZOOM_STEP, MIN_ZOOM);
}
applyZoom();
}
}, { passive: false });
// ===== Element Creation =====
elementBtns.forEach(btn => {
btn.addEventListener('click', () => {
const type = btn.dataset.type;
createElement(type);
closeSidebars();
});
});
mobileToolBtns.forEach(btn => {
btn.addEventListener('click', () => {
const type = btn.dataset.type;
if (type) createElement(type);
});
});
function createElement(type, x = 50, y = 50) {
elementCounter++;
const element = document.createElement('div');
element.classList.add('draggable-element');
element.id = `element-${elementCounter}`;
element.dataset.type = type;
element.style.left = `${x}px`;
element.style.top = `${y}px`;
// Create resize handles
const handles = ['nw', 'ne', 'sw', 'se', 'n', 's', 'e', 'w'];
handles.forEach(dir => {
const handle = document.createElement('div');
handle.classList.add('resize-handle', dir);
handle.dataset.direction = dir;
element.appendChild(handle);
});
// Create content based on type
const content = document.createElement('div');
content.classList.add('element-content');
switch (type) {
case 'h1':
content.innerHTML = '<h1 contenteditable="true">Heading 1</h1>';
element.style.width = '300px';
element.style.minHeight = '60px';
break;
case 'h2':
content.innerHTML = '<h2 contenteditable="true">Heading 2</h2>';
element.style.width = '280px';
element.style.minHeight = '50px';
break;
case 'h3':
content.innerHTML = '<h3 contenteditable="true">Heading 3</h3>';
element.style.width = '250px';
element.style.minHeight = '40px';
break;
case 'p':
content.innerHTML = '<p contenteditable="true">Enter your paragraph text here. You can type anything you want.</p>';
element.style.width = '350px';
element.style.minHeight = '80px';
break;
case 'image':
// Trigger file upload
const uploadHandler = (e) => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (event) => {
content.innerHTML = `<img src="${event.target.result}" alt="Uploaded Image" draggable="false">`;
element.style.width = '300px';
element.style.height = '200px';
element.appendChild(content);
blogCanvas.appendChild(element);
setupElementEvents(element);
selectElement(element);
updateLayersList();
hidePlaceholder();
};
reader.readAsDataURL(file);
}
imageUpload.value = '';
imageUpload.removeEventListener('change', uploadHandler);
};
imageUpload.addEventListener('change', uploadHandler);
imageUpload.click();
return;
case 'quote':
content.innerHTML = '<blockquote contenteditable="true">"Add your inspiring quote here." - Author</blockquote>';
element.style.width = '350px';
element.style.minHeight = '100px';
break;
case 'divider':
content.innerHTML = '<div class="element-divider"></div>';
element.style.width = '300px';
element.style.height = '30px';
break;
case 'button':
content.innerHTML = '<span class="element-button" contenteditable="true">Click Me</span>';
element.style.width = '120px';
element.style.height = '50px';
break;
}
element.appendChild(content);
blogCanvas.appendChild(element);
setupElementEvents(element);
selectElement(element);
updateLayersList();
hidePlaceholder();
}
function setupElementEvents(element) {
const type = element.dataset.type;
// Helper function to get pointer position
const getPointerPos = (e) => {
if (e.touches && e.touches.length > 0) {
return { x: e.touches[0].clientX, y: e.touches[0].clientY };
}
return { x: e.clientX, y: e.clientY };
};
// Mouse/Touch down for dragging
const handlePointerDown = (e) => {
// Check if it's a resize handle
if (e.target.classList.contains('resize-handle')) {
startResize(e);
return;
}
selectElement(element);
// For text elements, check if we should allow editing
const editableEl = e.target.closest('[contenteditable="true"]');
if (editableEl && document.activeElement === editableEl) {
return; // Allow text editing
}
// Start dragging
startDrag(e, element);
};
element.addEventListener('mousedown', handlePointerDown);
element.addEventListener('touchstart', handlePointerDown, { passive: false });
// Double click/tap to edit text
element.addEventListener('dblclick', () => {
const editable = element.querySelector('[contenteditable="true"]');
if (editable) {
editable.focus();
const selection = window.getSelection();
const range = document.createRange();
range.selectNodeContents(editable);
selection.removeAllRanges();
selection.addRange(range);
}
});
// Resize handles - both mouse and touch
const handles = element.querySelectorAll('.resize-handle');
handles.forEach(handle => {
handle.addEventListener('mousedown', startResize);
handle.addEventListener('touchstart', startResize, { passive: false });
});
// Update layers on text input
const editables = element.querySelectorAll('[contenteditable="true"]');
editables.forEach(ed => {
ed.addEventListener('input', () => {
updateLayersList();
});
});
}
// ===== Drag Functionality =====
function startDrag(e, element) {
if (isResizing) return;
e.preventDefault();
isDragging = true;
selectedElement = element;
element.classList.add('dragging');
const pos = getEventPos(e);
dragStartX = pos.x;
dragStartY = pos.y;
initialX = element.offsetLeft;
initialY = element.offsetTop;
document.addEventListener('mousemove', drag);
document.addEventListener('mouseup', stopDrag);
document.addEventListener('touchmove', drag, { passive: false });
document.addEventListener('touchend', stopDrag);
}
function drag(e) {
if (!isDragging || !selectedElement) return;
e.preventDefault();
const pos = getEventPos(e);
const dx = (pos.x - dragStartX) / currentZoom;
const dy = (pos.y - dragStartY) / currentZoom;
let newX = initialX + dx;
let newY = initialY + dy;
// Keep element within canvas bounds
newX = Math.max(0, newX);
newY = Math.max(0, newY);
selectedElement.style.left = `${newX}px`;
selectedElement.style.top = `${newY}px`;
// Update position inputs
if (posX) posX.value = Math.round(newX);
if (posY) posY.value = Math.round(newY);
}
function stopDrag() {
if (selectedElement) {
selectedElement.classList.remove('dragging');
}
isDragging = false;
document.removeEventListener('mousemove', drag);
document.removeEventListener('mouseup', stopDrag);
document.removeEventListener('touchmove', drag);
document.removeEventListener('touchend', stopDrag);
}
// ===== Resize Functionality =====
function startResize(e) {
e.stopPropagation();
e.preventDefault();
isResizing = true;
resizeDirection = e.target.dataset.direction;
const element = e.target.closest('.draggable-element');
selectedElement = element;
const pos = getEventPos(e);
dragStartX = pos.x;
dragStartY = pos.y;
initialX = element.offsetLeft;
initialY = element.offsetTop;
initialWidth = element.offsetWidth;
initialHeight = element.offsetHeight;
document.addEventListener('mousemove', resize);
document.addEventListener('mouseup', stopResize);
document.addEventListener('touchmove', resize, { passive: false });
document.addEventListener('touchend', stopResize);
}
function resize(e) {
if (!isResizing || !selectedElement) return;
e.preventDefault();
const pos = getEventPos(e);
const dx = (pos.x - dragStartX) / currentZoom;
const dy = (pos.y - dragStartY) / currentZoom;
let newWidth = initialWidth;
let newHeight = initialHeight;
let newX = initialX;
let newY = initialY;
if (resizeDirection.includes('e')) {
newWidth = Math.max(50, initialWidth + dx);
}
if (resizeDirection.includes('w')) {
newWidth = Math.max(50, initialWidth - dx);
newX = initialX + (initialWidth - newWidth);
}
if (resizeDirection.includes('s')) {
newHeight = Math.max(30, initialHeight + dy);
}
if (resizeDirection.includes('n')) {
newHeight = Math.max(30, initialHeight - dy);
newY = initialY + (initialHeight - newHeight);
}
selectedElement.style.width = `${newWidth}px`;
selectedElement.style.height = `${newHeight}px`;
selectedElement.style.left = `${newX}px`;
selectedElement.style.top = `${newY}px`;
if (elWidth) elWidth.value = Math.round(newWidth);
if (elHeight) elHeight.value = Math.round(newHeight);
if (posX) posX.value = Math.round(newX);
if (posY) posY.value = Math.round(newY);
}
function stopResize() {
isResizing = false;
resizeDirection = '';
document.removeEventListener('mousemove', resize);
document.removeEventListener('mouseup', stopResize);
document.removeEventListener('touchmove', resize);
document.removeEventListener('touchend', stopResize);
}
// Helper to get event position for both mouse and touch
function getEventPos(e) {
if (e.touches && e.touches.length > 0) {
return { x: e.touches[0].clientX, y: e.touches[0].clientY };
}
return { x: e.clientX, y: e.clientY };
}
// ===== Selection =====
function selectElement(element) {
if (selectedElement && selectedElement !== element) {
selectedElement.classList.remove('selected');
}
selectedElement = element;
element.classList.add('selected');
showPropertiesFor(element);
updateLayersList();
}
function deselectAll() {
if (selectedElement) {
selectedElement.classList.remove('selected');
selectedElement = null;
}
hideAllPropertyPanels();
}
blogCanvas.addEventListener('click', (e) => {
if (e.target === blogCanvas || e.target === canvasPlaceholder) {
deselectAll();
}
});
// ===== Properties Panel =====
function showPropertiesFor(element) {
const type = element.dataset.type;
hideAllPropertyPanels();
if (positionProperties) positionProperties.classList.remove('hidden');
if (posX) posX.value = Math.round(element.offsetLeft);
if (posY) posY.value = Math.round(element.offsetTop);
if (elWidth) elWidth.value = Math.round(element.offsetWidth);
if (elHeight) elHeight.value = Math.round(element.offsetHeight);
const transform = element.style.transform;
const rotateMatch = transform.match(/rotate\((\d+)deg\)/);
if (elRotation) elRotation.value = rotateMatch ? parseInt(rotateMatch[1]) : 0;
if (rotationValue) rotationValue.textContent = `${elRotation?.value || 0}°`;
if (['h1', 'h2', 'h3', 'p', 'quote', 'button'].includes(type)) {
if (textProperties) textProperties.classList.remove('hidden');
const editable = element.querySelector('[contenteditable="true"]') ||
element.querySelector('.element-button');
if (editable) {
const style = window.getComputedStyle(editable);
if (fontSize) fontSize.value = parseInt(style.fontSize);
if (fontWeight) fontWeight.value = style.fontWeight;
const color = rgbToHex(style.color);
if (textColor) textColor.value = color;
if (textColorHex) textColorHex.value = color;
const ls = parseFloat(style.letterSpacing);
if (letterSpacing) letterSpacing.value = isNaN(ls) ? 0 : ls;
const lh = parseFloat(style.lineHeight) / parseFloat(style.fontSize);
if (lineHeight) lineHeight.value = isNaN(lh) ? 1.5 : lh.toFixed(1);
}
} else if (type === 'image') {
if (imageProperties) imageProperties.classList.remove('hidden');
const br = parseInt(element.style.borderRadius) || 0;
if (imageBorderRadius) imageBorderRadius.value = br;
if (borderRadiusValue) borderRadiusValue.textContent = `${br}px`;
const op = parseFloat(element.style.opacity);
const opacityPercent = isNaN(op) ? 100 : op * 100;
if (imageOpacity) imageOpacity.value = opacityPercent;
if (opacityValue) opacityValue.textContent = `${Math.round(opacityPercent)}%`;
}
if (propertiesPanel) {
const emptyMsg = propertiesPanel.querySelector('.empty-properties');
if (emptyMsg) emptyMsg.style.display = 'none';
}
}
function hideAllPropertyPanels() {
if (textProperties) textProperties.classList.add('hidden');
if (imageProperties) imageProperties.classList.add('hidden');
if (positionProperties) positionProperties.classList.add('hidden');
if (propertiesPanel) {
const emptyMsg = propertiesPanel.querySelector('.empty-properties');
if (emptyMsg) emptyMsg.style.display = 'block';
}
}
// ===== Property Change Handlers =====
fontFamily?.addEventListener('change', () => {
if (!selectedElement) return;
const editable = getEditableElement();
if (editable) editable.style.fontFamily = fontFamily.value;
});
fontSize?.addEventListener('input', () => {
if (!selectedElement) return;
const editable = getEditableElement();
if (editable) editable.style.fontSize = `${fontSize.value}px`;
});
fontWeight?.addEventListener('change', () => {
if (!selectedElement) return;
const editable = getEditableElement();
if (editable) editable.style.fontWeight = fontWeight.value;
});
textColor?.addEventListener('input', () => {
if (!selectedElement) return;
const editable = getEditableElement();
if (editable) editable.style.color = textColor.value;
if (textColorHex) textColorHex.value = textColor.value;
});
textColorHex?.addEventListener('input', () => {
if (!selectedElement) return;
const hex = textColorHex.value;
if (/^#[0-9A-Fa-f]{6}$/.test(hex)) {
if (textColor) textColor.value = hex;
const editable = getEditableElement();
if (editable) editable.style.color = hex;
}
});
letterSpacing?.addEventListener('input', () => {
if (!selectedElement) return;
const editable = getEditableElement();
if (editable) editable.style.letterSpacing = `${letterSpacing.value}px`;
});
lineHeight?.addEventListener('input', () => {
if (!selectedElement) return;
const editable = getEditableElement();
if (editable) editable.style.lineHeight = lineHeight.value;
});
document.querySelectorAll('[data-align]').forEach(btn => {
btn.addEventListener('click', () => {
if (!selectedElement) return;
const editable = getEditableElement();
if (editable) editable.style.textAlign = btn.dataset.align;
document.querySelectorAll('[data-align]').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
});
});
posX?.addEventListener('input', () => {
if (selectedElement) selectedElement.style.left = `${posX.value}px`;
});
posY?.addEventListener('input', () => {
if (selectedElement) selectedElement.style.top = `${posY.value}px`;
});
elWidth?.addEventListener('input', () => {
if (selectedElement) selectedElement.style.width = `${elWidth.value}px`;
});
elHeight?.addEventListener('input', () => {
if (selectedElement) selectedElement.style.height = `${elHeight.value}px`;
});
elRotation?.addEventListener('input', () => {
if (selectedElement) {
selectedElement.style.transform = `rotate(${elRotation.value}deg)`;
if (rotationValue) rotationValue.textContent = `${elRotation.value}°`;
}
});
imageBorderRadius?.addEventListener('input', () => {
if (selectedElement) {
selectedElement.style.borderRadius = `${imageBorderRadius.value}px`;
selectedElement.style.overflow = 'hidden';
if (borderRadiusValue) borderRadiusValue.textContent = `${imageBorderRadius.value}px`;
}
});
imageOpacity?.addEventListener('input', () => {
if (selectedElement) {
selectedElement.style.opacity = imageOpacity.value / 100;
if (opacityValue) opacityValue.textContent = `${imageOpacity.value}%`;
}
});
replaceImage?.addEventListener('change', (e) => {
if (!selectedElement || selectedElement.dataset.type !== 'image') return;
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (event) => {
const img = selectedElement.querySelector('img');
if (img) img.src = event.target.result;
};
reader.readAsDataURL(file);
}
replaceImage.value = '';
});
deleteElement?.addEventListener('click', () => {
if (selectedElement) {
selectedElement.remove();
selectedElement = null;
hideAllPropertyPanels();
updateLayersList();
checkPlaceholder();
}
});
// ===== Layers List =====
function updateLayersList() {
const elements = blogCanvas.querySelectorAll('.draggable-element');
if (elements.length === 0) {
layersList.innerHTML = '<p class="empty-layers">No elements yet</p>';
return;
}
layersList.innerHTML = '';
elements.forEach((el) => {
const type = el.dataset.type;
const icon = getIconForType(type);
const label = getLabelForElement(el, type);
const layerItem = document.createElement('div');
layerItem.classList.add('layer-item');
if (el === selectedElement) {
layerItem.classList.add('active');
}
layerItem.innerHTML = `
<span class="material-symbols-outlined">${icon}</span>
<span>${label}</span>
`;
layerItem.addEventListener('click', () => {
selectElement(el);
closeSidebars();
});
layersList.appendChild(layerItem);
});
}
function getIconForType(type) {
const icons = {
'h1': 'title', 'h2': 'format_h2', 'h3': 'format_h3',
'p': 'notes', 'image': 'image', 'quote': 'format_quote',
'divider': 'horizontal_rule', 'button': 'smart_button'
};
return icons[type] || 'crop_square';
}
function getLabelForElement(el, type) {
const labels = {
'h1': 'Heading 1', 'h2': 'Heading 2', 'h3': 'Heading 3',
'p': 'Paragraph', 'image': 'Image', 'quote': 'Quote',
'divider': 'Divider', 'button': 'Button'
};
const editable = el.querySelector('[contenteditable="true"]');
if (editable) {
const text = editable.textContent.trim();
if (text.length > 15) return text.substring(0, 15) + '...';
return text || labels[type];
}
return labels[type] || 'Element';
}
// ===== Helper Functions =====
function getEditableElement() {
if (!selectedElement) return null;
return selectedElement.querySelector('[contenteditable="true"]') ||
selectedElement.querySelector('.element-button') ||
selectedElement.querySelector('blockquote');
}
function rgbToHex(rgb) {
if (!rgb) return '#000000';
if (rgb.startsWith('#')) return rgb;
const match = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
if (!match) return '#000000';
const r = parseInt(match[1]).toString(16).padStart(2, '0');
const g = parseInt(match[2]).toString(16).padStart(2, '0');
const b = parseInt(match[3]).toString(16).padStart(2, '0');
return `#${r}${g}${b}`;
}
function hidePlaceholder() {
if (canvasPlaceholder) canvasPlaceholder.classList.add('hidden');
}
function checkPlaceholder() {
const elements = blogCanvas.querySelectorAll('.draggable-element');
if (canvasPlaceholder) {
canvasPlaceholder.classList.toggle('hidden', elements.length > 0);
}
}
// ===== Keyboard Shortcuts =====
document.addEventListener('keydown', (e) => {
if (!selectedElement) return;
const isEditing = document.activeElement.getAttribute('contenteditable') === 'true' ||
document.activeElement.tagName === 'INPUT';
if ((e.key === 'Delete' || e.key === 'Backspace') && !isEditing) {
selectedElement.remove();
selectedElement = null;
hideAllPropertyPanels();
updateLayersList();
checkPlaceholder();
e.preventDefault();
}
if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key) && !isEditing) {
const step = e.shiftKey ? 10 : 1;
switch (e.key) {
case 'ArrowUp':
selectedElement.style.top = `${selectedElement.offsetTop - step}px`;
break;
case 'ArrowDown':
selectedElement.style.top = `${selectedElement.offsetTop + step}px`;
break;
case 'ArrowLeft':
selectedElement.style.left = `${selectedElement.offsetLeft - step}px`;
break;
case 'ArrowRight':
selectedElement.style.left = `${selectedElement.offsetLeft + step}px`;
break;
}
if (posX) posX.value = Math.round(selectedElement.offsetLeft);
if (posY) posY.value = Math.round(selectedElement.offsetTop);
e.preventDefault();
}
});
// ===== Publish Modal =====
postBtn?.addEventListener('click', () => {
const elements = blogCanvas.querySelectorAll('.draggable-element');
if (elements.length === 0) {
alert('Canvas is empty! Add some elements before publishing.');
return;
}
// Show modal
if (modalBlogTitle) modalBlogTitle.value = blogTitleInput?.value || '';
publishModal.classList.remove('hidden');
});
closeModal?.addEventListener('click', closePublishModal);
cancelPublish?.addEventListener('click', closePublishModal);
publishModal?.querySelector('.modal-backdrop')?.addEventListener('click', closePublishModal);
function closePublishModal() {
publishModal.classList.add('hidden');
coverImageData = null;
if (uploadPlaceholder) uploadPlaceholder.classList.remove('hidden');
if (uploadPreview) uploadPreview.classList.add('hidden');
}
// Cover Image Upload
coverUploadArea?.addEventListener('click', () => {
coverImageInput?.click();
});
coverImageInput?.addEventListener('change', (e) => {
const file = e.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (event) => {
coverImageData = event.target.result;
if (coverPreviewImg) coverPreviewImg.src = coverImageData;
if (uploadPlaceholder) uploadPlaceholder.classList.add('hidden');
if (uploadPreview) uploadPreview.classList.remove('hidden');
};
reader.readAsDataURL(file);
}
});
removeCover?.addEventListener('click', (e) => {
e.stopPropagation();
coverImageData = null;
if (coverImageInput) coverImageInput.value = '';
if (uploadPlaceholder) uploadPlaceholder.classList.remove('hidden');
if (uploadPreview) uploadPreview.classList.add('hidden');
});
// Confirm Publish
confirmPublish?.addEventListener('click', () => {
const title = modalBlogTitle?.value || blogTitleInput?.value || 'Untitled Blog';
// Clone canvas for saving
const clone = blogCanvas.cloneNode(true);
// Clean up cloned elements
clone.querySelectorAll('.draggable-element').forEach(el => {
el.classList.remove('selected', 'dragging');
el.querySelectorAll('.resize-handle').forEach(h => h.remove());
el.querySelectorAll('[contenteditable]').forEach(c => {
c.removeAttribute('contenteditable');
});
el.style.cursor = 'default';
});
const placeholder = clone.querySelector('.canvas-placeholder');
if (placeholder) placeholder.remove();
// Create blog data
const blogData = {
id: Date.now(),
title: title,
date: new Date().toISOString(),
content: clone.innerHTML,
coverImage: coverImageData || null,
backgroundColor: '#FFFFFF'
};
// Save to localStorage
// Save to localStorage
const blogs = JSON.parse(localStorage.getItem('mubashra_blogs_v2')) || [];
blogs.unshift(blogData);
localStorage.setItem('mubashra_blogs_v2', JSON.stringify(blogs));
alert('Blog published successfully!');
// Clear canvas
blogCanvas.querySelectorAll('.draggable-element').forEach(el => el.remove());
if (blogTitleInput) blogTitleInput.value = '';
selectedElement = null;
hideAllPropertyPanels();
updateLayersList();
checkPlaceholder();
closePublishModal();
});
// Preview
previewBtn?.addEventListener('click', () => {
const elements = blogCanvas.querySelectorAll('.draggable-element');
if (elements.length === 0) {
alert('Canvas is empty! Add some elements to preview.');
return;
}
const clone = blogCanvas.cloneNode(true);
clone.querySelectorAll('.draggable-element').forEach(el => {
el.classList.remove('selected', 'dragging');
el.querySelectorAll('.resize-handle').forEach(h => h.remove());
el.querySelectorAll('[contenteditable]').forEach(c => {
c.removeAttribute('contenteditable');
});
el.style.cursor = 'default';
});
const placeholder = clone.querySelector('.canvas-placeholder');
if (placeholder) placeholder.remove();
const previewWindow = window.open('', '_blank');
previewWindow.document.write(`
<!DOCTYPE html>
<html>
<head>
<title>Preview - ${blogTitleInput?.value || 'Untitled Blog'}</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&family=Playfair+Display:ital,wght@0,400;0,700;1,400&display=swap" rel="stylesheet">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Inter', sans-serif;
background: #F7FAF9;
min-height: 100vh;
padding: 1rem;
}
@media (min-width: 768px) {
body { padding: 2rem; display: flex; justify-content: center; }
}
.blog-content {
width: 100%;
max-width: 800px;
margin: 0 auto;
padding: 1.5rem;
background: white;
border-radius: 16px;
box-shadow: 0 10px 40px rgba(0,0,0,0.12);
}
@media (min-width: 768px) {
.blog-content { padding: 2rem; }
}
.draggable-element {
position: relative !important;
left: auto !important;
top: auto !important;
width: 100% !important;
max-width: 100% !important;
height: auto !important;
min-height: auto !important;
margin-bottom: 1.5rem;
}
.element-content { padding: 0; }
h1, h2, h3, p { margin: 0; word-wrap: break-word; }
h1 { font-size: clamp(1.75rem, 5vw, 2.5rem); font-weight: 700; font-family: 'Playfair Display', serif; margin-bottom: 1rem; line-height: 1.2; }
h2 { font-size: clamp(1.5rem, 4vw, 2rem); font-weight: 600; margin-bottom: 0.75rem; line-height: 1.3; }
h3 { font-size: clamp(1.25rem, 3vw, 1.5rem); font-weight: 600; margin-bottom: 0.5rem; line-height: 1.4; }
p { font-size: clamp(0.95rem, 2.5vw, 1.1rem); line-height: 1.8; margin-bottom: 1rem; color: #4A5568; }
blockquote {
margin: 1.5rem 0;
padding: 1rem 1.5rem;
border-left: 4px solid #2D6A4F;
background: #E8F3EE;
font-style: italic;
border-radius: 0 8px 8px 0;
}
img {
width: 100%;
max-width: 100%;
height: auto !important;
object-fit: cover;
display: block;
border-radius: 12px;
margin: 1rem 0;
}
.element-divider {
width: 100%;
height: 2px;
background: linear-gradient(90deg, transparent, #2D6A4F, transparent);
margin: 2rem 0;
}
.element-button {
display: inline-block;
padding: 0.75rem 1.5rem;
background: #2D6A4F;
color: white;
font-weight: 600;
border-radius: 8px;
}
</style>
</head>
<body>
<div class="blog-content">${clone.innerHTML}</div>
</body>
</html>
`);
previewWindow.document.close();
});
// Initialize
updateLayersList();