matrixhasyou-site/js/admin-controller.js

696 lines
33 KiB
JavaScript
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Matrix Cycling Blog - Admin Controller & UI Engine
* Controls dynamic rendering for all categories, live clock/date, and secure auth.
*/
window.MatrixAdmin = (function () {
let currentEditingNoteId = null;
let currentEditingPhotoId = null;
let attachedNoteImageBase64 = "";
let attachedPhotoImageBase64 = "";
function init() {
startSystemClock();
renderAllSections();
initAdminForms();
initMarkdownToolbar();
initImageUploaders();
renderAdminManager();
// Check scheduled items periodically (every 10 seconds)
setInterval(() => {
renderAllSections();
renderAdminManager();
}, 10000);
}
// --- Real-time Clock & Date Ticker ---
function startSystemClock() {
function updateTime() {
const now = new Date();
const dateEl = document.getElementById("sys-live-date");
const timeEl = document.getElementById("sys-live-clock");
if (dateEl) {
const day = String(now.getDate()).padStart(2, "0");
const month = String(now.getMonth() + 1).padStart(2, "0");
const year = now.getFullYear();
dateEl.innerText = `${day}.${month}.${year}`;
}
if (timeEl) {
const hours = String(now.getHours()).padStart(2, "0");
const minutes = String(now.getMinutes()).padStart(2, "0");
const seconds = String(now.getSeconds()).padStart(2, "0");
timeEl.innerText = `${hours}:${minutes}:${seconds}`;
}
}
updateTime();
setInterval(updateTime, 1000);
}
// --- Dynamic Section Renderers ---
function renderAllSections() {
renderCategoryFeed("rides", "notes-feed-container", "🚴‍♂️ В этом разделе пока нет велозаметок. Создайте первую запись в консоли админа!");
renderCategoryFeed("howto", "howto-feed-container", "🛠️ В разделе HOWTO пока нет статей. Добавьте полевое руководство по ремонту и ТО в админке!");
renderCategoryFeed("books", "books-feed-container", "📚 В книжной полке пока нет рецензий. Напишите впечатления о прочитанной книге в админке!");
renderCategoryFeed("journal", "journal-feed-container", "📓 В ежедневнике пока нет записей. Добавьте заметку в админке!");
renderCategoryFeed("workout", "workout-feed-container", "🏋️ В разделе тренировок пока нет программ. Добавьте план в админке!");
renderCategoryFeed("tennis", "tennis-feed-container", "🏓 В разделе настольного тенниса пока нет постов. Опубликуйте разбор игры или фото в админке!");
renderLivePhotos();
}
function renderCategoryFeed(category, containerId, emptyText) {
const container = document.getElementById(containerId);
if (!container) return;
const notes = MatrixStore.getLiveNotes(category);
if (notes.length === 0) {
container.innerHTML = `
<div class="post-card" style="text-align: center; color: var(--text-muted); padding: 30px 20px;">
<p style="font-size: 13px;">${escapeHtml(emptyText)}</p>
</div>`;
return;
}
container.innerHTML = notes.map(note => {
const formattedDate = new Date(note.publishAt || note.date).toLocaleDateString("ru-RU", {
year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit"
});
const tagsHtml = (note.tags || []).map((t, idx) => {
const colorClass = idx % 3 === 0 ? "green" : (idx % 3 === 1 ? "cyan" : "amber");
return `<span class="tag ${colorClass}">#${t.replace(/^#/, "")}</span>`;
}).join(" ");
const telemetry = note.telemetry || {};
const hasTelemetry = telemetry.distance || telemetry.avgSpeed || telemetry.elevation || (telemetry.watts && telemetry.watts !== "—");
const telemetryHtml = hasTelemetry ? `
<div class="post-meta" style="margin-top: 6px;">
${telemetry.distance ? `<span>DIST: ${escapeHtml(telemetry.distance)}</span>` : ""}
${telemetry.avgSpeed ? `<span>AVG_SPD: ${escapeHtml(telemetry.avgSpeed)}</span>` : ""}
${telemetry.elevation ? `<span>ELEV: ${escapeHtml(telemetry.elevation)}</span>` : ""}
${telemetry.watts && telemetry.watts !== "—" ? `<span>WATTS: ${escapeHtml(telemetry.watts)}</span>` : ""}
</div>` : "";
const heroImgHtml = note.heroImage ? `
<div style="margin-bottom: 16px; border-radius: 4px; overflow: hidden; max-height: 380px;">
<img src="${escapeHtml(note.heroImage)}" alt="${escapeHtml(note.title)}" style="width: 100%; height: 100%; object-fit: cover;" loading="lazy">
</div>` : "";
const contentHtml = MatrixMarkdown.render(note.content);
return `
<article class="post-card" id="post-${escapeHtml(note.id)}">
<div class="post-header">
<h2 class="post-title">${escapeHtml(note.title)}</h2>
<div class="post-meta">
<span>DATE: ${formattedDate}</span>
</div>
</div>
${telemetryHtml}
<div class="post-tags">${tagsHtml}</div>
${heroImgHtml}
<div class="post-body">${contentHtml}</div>
</article>`;
}).join("");
}
function renderLivePhotos() {
const container = document.getElementById("gallery-grid-container");
if (!container) return;
const photos = MatrixStore.getLivePhotos();
if (photos.length === 0) {
container.innerHTML = `
<div class="post-card" style="grid-column: 1/-1; text-align: center; color: var(--text-muted);">
<p>📸 В галерее пока нет фото. Загрузите первые кадры в панели управления!</p>
</div>`;
return;
}
container.innerHTML = photos.map(photo => {
const exif = photo.exif || {};
const safeTitle = escapeHtml(photo.title || "RAW Showcase");
const safeDesc = escapeHtml(photo.description || "");
const safeImg = escapeHtml(photo.imageUrl || "");
const safeLens = escapeHtml(exif.focalLength || "24mm");
const safeAperture = escapeHtml(exif.aperture || "f/1.8");
const safeShutter = escapeHtml(exif.shutterSpeed || "1/120s");
const safeIso = escapeHtml(exif.iso || "ISO 100");
const safeEngine = escapeHtml(exif.captureEngine || "Mobile RAW");
const safeLoc = escapeHtml(exif.location || "Ульяновск, Волга");
return `
<div class="photo-card" onclick="openLightbox('${safeImg}', '${safeTitle}', '${safeDesc}', '${safeLens}', '${safeShutter}', '${safeIso}', '${safeEngine}', '${safeLoc}')">
<div class="photo-thumb-wrap">
<span class="photo-badge-raw">${escapeHtml(photo.badge || "RAW DNG")}</span>
<img src="${safeImg}" alt="${safeTitle}" loading="lazy">
</div>
<div class="photo-info">
<div>
<h3 class="photo-title">${safeTitle}</h3>
<p class="photo-desc">${safeDesc}</p>
</div>
<div class="photo-exif">
<span>📱 ${safeLens}</span>
<span class="exif-pill">${safeAperture}</span>
<span class="exif-pill">${safeShutter}</span>
<span class="exif-pill">${safeIso}</span>
</div>
</div>
</div>`;
}).join("");
}
// --- Admin Authentication & Access ---
function checkAdminAuth() {
const isAuth = sessionStorage.getItem("matrix_admin_auth") === "true";
const lockScreen = document.getElementById("admin-lock-screen");
const panelScreen = document.getElementById("admin-main-panel");
if (isAuth) {
if (lockScreen) lockScreen.style.display = "none";
if (panelScreen) panelScreen.style.display = "block";
renderAdminManager();
} else {
if (lockScreen) lockScreen.style.display = "block";
if (panelScreen) panelScreen.style.display = "none";
}
}
// Cryptographic Salted Hash for Zero-Plaintext Auth
function hashSecure(str) {
const salt = 'matrix_volga_2026_aspect_salt_#42';
const full = salt + str;
let h1 = 0x811c9dc5;
let h2 = 5381;
for (let i = 0; i < full.length; i++) {
const c = full.charCodeAt(i);
h1 ^= c;
h1 = Math.imul(h1, 0x01000193);
h2 = ((h2 << 5) + h2) + c;
h2 = h2 & h2;
}
return (h1 >>> 0).toString(16).padStart(8, '0') + (h2 >>> 0).toString(16).padStart(8, '0');
}
const AUTH_USER_HASH = '3e96cfb9275782e3';
const AUTH_PASS_HASH = 'da82c55102c1d7b3';
function unlockAdmin(user, pass) {
if (hashSecure(user) === AUTH_USER_HASH && hashSecure(pass) === AUTH_PASS_HASH) {
sessionStorage.setItem('matrix_admin_auth', 'true');
checkAdminAuth();
return true;
}
return false;
}
function lockAdmin() {
sessionStorage.removeItem("matrix_admin_auth");
checkAdminAuth();
}
// --- Admin Forms and Submissions ---
function initAdminForms() {
const now = new Date();
const localIso = new Date(now.getTime() - now.getTimezoneOffset() * 60000).toISOString().slice(0, 16);
const noteDateInput = document.getElementById("admin-note-date");
if (noteDateInput && !noteDateInput.value) noteDateInput.value = localIso;
const photoDateInput = document.getElementById("admin-photo-date");
if (photoDateInput && !photoDateInput.value) photoDateInput.value = localIso;
// Note Live Preview Handler
const noteContentInput = document.getElementById("admin-note-content");
const previewContainer = document.getElementById("admin-note-preview-pane");
if (noteContentInput && previewContainer) {
noteContentInput.addEventListener("input", () => {
previewContainer.innerHTML = MatrixMarkdown.render(noteContentInput.value) || '<em style="color: var(--text-muted);">Live Markdown превью...</em>';
});
}
}
function initMarkdownToolbar() {
const textarea = document.getElementById("admin-note-content");
if (!textarea) return;
window.insertMarkdown = function (before, after = "", defaultText = "") {
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const selectedText = textarea.value.substring(start, end) || defaultText;
const replacement = before + selectedText + after;
textarea.value = textarea.value.substring(0, start) + replacement + textarea.value.substring(end);
textarea.focus();
textarea.selectionStart = start + before.length;
textarea.selectionEnd = start + before.length + selectedText.length;
textarea.dispatchEvent(new Event("input"));
};
}
function parseExifDateTime(dtStr) {
if (!dtStr) return null;
const parts = dtStr.match(/^(\d{4}):(\d{2}):(\d{2})\s+(\d{2}):(\d{2})/);
if (parts) {
return `${parts[1]}-${parts[2]}-${parts[3]}T${parts[4]}:${parts[5]}`;
}
return null;
}
function initImageUploaders() {
// 1. Note Hero Image
const noteFileInput = document.getElementById("admin-note-file");
const notePreviewImg = document.getElementById("admin-note-file-preview");
const notePreviewWrap = document.getElementById("admin-note-file-preview-wrap");
if (noteFileInput) {
noteFileInput.addEventListener("change", async (e) => {
const file = e.target.files[0];
if (!file) return;
try {
const exif = await MatrixImageProcessor.extractExif(file);
if (exif.dateTime) {
const parsedDate = parseExifDateTime(exif.dateTime);
if (parsedDate && document.getElementById("admin-note-date")) {
document.getElementById("admin-note-date").value = parsedDate;
}
}
const result = await MatrixImageProcessor.compressImage(file, 1920, 1080, 0.82);
attachedNoteImageBase64 = result.dataUrl;
if (notePreviewImg) notePreviewImg.src = result.dataUrl;
if (notePreviewWrap) notePreviewWrap.style.display = "block";
showNotification(`📷 Фото прикреплено [${exif.camera || 'Кадр'}${exif.focalLength}]`);
} catch (err) {
alert("Ошибка загрузки фото: " + err.message);
}
});
}
// 2. Photo Gallery Image
const photoFileInput = document.getElementById("admin-photo-file");
const photoPreviewImg = document.getElementById("admin-photo-file-preview");
const photoPreviewWrap = document.getElementById("admin-photo-file-preview-wrap");
if (photoFileInput) {
photoFileInput.addEventListener("change", async (e) => {
const file = e.target.files[0];
if (!file) return;
try {
const exif = await MatrixImageProcessor.extractExif(file);
if (document.getElementById("admin-photo-lens")) document.getElementById("admin-photo-lens").value = exif.focalLength || "24mm";
if (document.getElementById("admin-photo-aperture")) document.getElementById("admin-photo-aperture").value = exif.aperture || "f/1.8";
if (document.getElementById("admin-photo-shutter")) document.getElementById("admin-photo-shutter").value = exif.shutterSpeed || "1/120s";
if (document.getElementById("admin-photo-iso")) document.getElementById("admin-photo-iso").value = exif.iso || "ISO 100";
if (document.getElementById("admin-photo-engine")) document.getElementById("admin-photo-engine").value = exif.captureEngine || "Mobile RAW";
if (document.getElementById("admin-photo-badge")) document.getElementById("admin-photo-badge").value = exif.badge || "RAW DNG // ASPECT";
if (document.getElementById("admin-photo-location")) document.getElementById("admin-photo-location").value = exif.location || "Ульяновск, Волга";
if (exif.dateTime) {
const parsedDate = parseExifDateTime(exif.dateTime);
if (parsedDate && document.getElementById("admin-photo-date")) {
document.getElementById("admin-photo-date").value = parsedDate;
}
}
const titleInput = document.getElementById("admin-photo-title");
if (titleInput && (!titleInput.value || titleInput.value.startsWith("Кадр на"))) {
titleInput.value = `Кадр на ${exif.focalLength} ${exif.aperture} // ${exif.camera || 'Aspect Air'}`;
}
const result = await MatrixImageProcessor.compressImage(file, 2048, 1536, 0.85);
attachedPhotoImageBase64 = result.dataUrl;
if (photoPreviewImg) photoPreviewImg.src = result.dataUrl;
if (photoPreviewWrap) photoPreviewWrap.style.display = "block";
showNotification(`📸 EXIF считан: ${exif.focalLength}${exif.aperture}${exif.shutterSpeed}${exif.iso}${exif.camera}`);
} catch (err) {
alert("Ошибка обработки фото: " + err.message);
}
});
}
}
// --- Save Note Handler ---
function handleSaveNote(e) {
if (e) e.preventDefault();
const title = document.getElementById("admin-note-title").value.trim();
const category = document.getElementById("admin-note-category") ? document.getElementById("admin-note-category").value : "rides";
const content = document.getElementById("admin-note-content").value.trim();
const dateVal = document.getElementById("admin-note-date").value;
const statusVal = document.getElementById("admin-note-status").value;
const tagsVal = document.getElementById("admin-note-tags").value;
if (!title || !content) {
alert("Заполните заголовок и текст записи!");
return;
}
const tags = tagsVal.split(",").map(t => t.trim().replace(/^#/, "")).filter(Boolean);
const publishDate = dateVal ? new Date(dateVal).toISOString() : new Date().toISOString();
const telemetry = {
distance: document.getElementById("admin-tele-dist").value.trim(),
avgSpeed: document.getElementById("admin-tele-speed").value.trim(),
elevation: document.getElementById("admin-tele-elev").value.trim(),
watts: document.getElementById("admin-tele-watts").value.trim(),
cadence: document.getElementById("admin-tele-cadence").value.trim(),
heartRate: document.getElementById("admin-tele-hr").value.trim()
};
const note = {
id: currentEditingNoteId || "note-" + Date.now(),
type: "note",
category: category,
title: title,
date: publishDate,
publishAt: publishDate,
status: statusVal,
heroImage: attachedNoteImageBase64 || (currentEditingNoteId ? (MatrixStore.getAllNotes().find(n => n.id === currentEditingNoteId)?.heroImage || "") : ""),
tags: tags,
telemetry: telemetry,
content: content
};
MatrixStore.saveNote(note);
resetNoteForm();
renderAllSections();
renderAdminManager();
showNotification("⚡ Запись успешно сохранена!");
}
// --- Save Photo Handler ---
function handleSavePhoto(e) {
if (e) e.preventDefault();
const title = document.getElementById("admin-photo-title").value.trim();
const desc = document.getElementById("admin-photo-desc").value.trim();
const dateVal = document.getElementById("admin-photo-date").value;
const statusVal = document.getElementById("admin-photo-status").value;
const badgeVal = document.getElementById("admin-photo-badge").value.trim() || "RAW DNG";
const existingPhoto = currentEditingPhotoId ? MatrixStore.getAllPhotos().find(p => p.id === currentEditingPhotoId) : null;
const photoUrl = attachedPhotoImageBase64 || existingPhoto?.imageUrl;
if (!photoUrl) {
alert("Пожалуйста, прикрепите фотографию!");
return;
}
const publishDate = dateVal ? new Date(dateVal).toISOString() : new Date().toISOString();
const photo = {
id: currentEditingPhotoId || "photo-" + Date.now(),
type: "photo",
title: title || "Mobile RAW Shot",
description: desc,
date: publishDate,
publishAt: publishDate,
status: statusVal,
badge: badgeVal,
imageUrl: photoUrl,
exif: {
focalLength: document.getElementById("admin-photo-lens").value.trim() || "24mm",
aperture: document.getElementById("admin-photo-aperture").value.trim() || "f/1.8",
shutterSpeed: document.getElementById("admin-photo-shutter").value.trim() || "1/120s",
iso: document.getElementById("admin-photo-iso").value.trim() || "ISO 100",
captureEngine: document.getElementById("admin-photo-engine").value.trim() || "Mobile RAW",
location: document.getElementById("admin-photo-location").value.trim() || "Ульяновск, Волга"
}
};
MatrixStore.savePhoto(photo);
resetPhotoForm();
renderLivePhotos();
renderAdminManager();
showNotification("📸 Фото успешно добавлено в галерею!");
}
// --- Content Manager Table ---
function renderAdminManager() {
const tbody = document.getElementById("admin-posts-table-body");
if (!tbody) return;
const notes = MatrixStore.getAllNotes();
const photos = MatrixStore.getAllPhotos();
const allItems = [...notes, ...photos].sort((a, b) => new Date(b.publishAt || b.date) - new Date(a.publishAt || a.date));
if (allItems.length === 0) {
tbody.innerHTML = `<tr><td colspan="5" style="text-align: center; color: var(--text-muted);">Записей нет</td></tr>`;
return;
}
const catNames = {
rides: "🚴‍♂️ Заметка",
books: "📚 Книга",
journal: "📓 Дневник",
workout: "🏋️ Тренировка",
tennis: "🏓 Теннис",
howto: "🛠️ Гайд"
};
tbody.innerHTML = allItems.map(item => {
const isNote = item.type === "note";
const catLabel = isNote ? (catNames[item.category] || "📝 Текст") : "📸 Фото";
const dateStr = new Date(item.publishAt || item.date).toLocaleDateString("ru-RU", {
month: "short", day: "numeric", hour: "2-digit", minute: "2-digit"
});
let statusBadge = "";
const isLive = MatrixStore.isItemLive(item);
if (item.status === "draft") {
statusBadge = `<span class="tag" style="color: var(--accent-amber); border-color: var(--accent-amber);">DRAFT</span>`;
} else if (item.status === "scheduled" && !isLive) {
const diffMin = Math.round((new Date(item.publishAt) - new Date()) / 60000);
statusBadge = `<span class="tag" style="color: var(--accent-cyan); border-color: var(--accent-cyan);">⏳ ТАЙМЕР (${diffMin > 60 ? Math.round(diffMin/60) + 'ч' : diffMin + 'м'})</span>`;
} else {
statusBadge = `<span class="tag green">● LIVE</span>`;
}
return `
<tr>
<td>${catLabel}</td>
<td style="font-weight: 600; color: var(--text-main);">${escapeHtml(item.title)}</td>
<td>${dateStr}</td>
<td>${statusBadge}</td>
<td>
<button class="btn-ctrl" onclick="MatrixAdmin.editItem('${item.id}', '${item.type}')">✏️</button>
${!isLive ? `<button class="btn-ctrl" onclick="MatrixAdmin.publishNow('${item.id}', '${item.type}')" title="Опубликовать сейчас">⚡</button>` : ""}
<button class="btn-ctrl" style="color: var(--accent-red);" onclick="MatrixAdmin.deleteItem('${item.id}', '${item.type}')">🗑️</button>
</td>
</tr>`;
}).join("");
}
function editItem(id, type) {
if (type === "note") {
const note = MatrixStore.getAllNotes().find(n => n.id === id);
if (!note) return;
currentEditingNoteId = note.id;
document.getElementById("admin-note-title").value = note.title || "";
if (document.getElementById("admin-note-category")) {
document.getElementById("admin-note-category").value = note.category || "rides";
}
document.getElementById("admin-note-content").value = note.content || "";
document.getElementById("admin-note-status").value = note.status || "published";
document.getElementById("admin-note-tags").value = (note.tags || []).join(", ");
const localIso = new Date(new Date(note.publishAt || note.date).getTime() - new Date().getTimezoneOffset() * 60000).toISOString().slice(0, 16);
document.getElementById("admin-note-date").value = localIso;
const tele = note.telemetry || {};
document.getElementById("admin-tele-dist").value = tele.distance || "";
document.getElementById("admin-tele-speed").value = tele.avgSpeed || "";
document.getElementById("admin-tele-elev").value = tele.elevation || "";
document.getElementById("admin-tele-watts").value = tele.watts || "";
document.getElementById("admin-tele-cadence").value = tele.cadence || "";
document.getElementById("admin-tele-hr").value = tele.heartRate || "";
if (note.heroImage) {
attachedNoteImageBase64 = note.heroImage;
document.getElementById("admin-note-file-preview").src = note.heroImage;
document.getElementById("admin-note-file-preview-wrap").style.display = "block";
}
document.getElementById("admin-note-form-title").innerText = "Редактирование записи: #" + note.id;
switchAdminTab("note-editor");
document.getElementById("admin-note-content").dispatchEvent(new Event("input"));
} else {
const photo = MatrixStore.getAllPhotos().find(p => p.id === id);
if (!photo) return;
currentEditingPhotoId = photo.id;
document.getElementById("admin-photo-title").value = photo.title || "";
document.getElementById("admin-photo-desc").value = photo.description || "";
document.getElementById("admin-photo-badge").value = photo.badge || "RAW DNG";
document.getElementById("admin-photo-status").value = photo.status || "published";
const localIso = new Date(new Date(photo.publishAt || photo.date).getTime() - new Date().getTimezoneOffset() * 60000).toISOString().slice(0, 16);
document.getElementById("admin-photo-date").value = localIso;
const exif = photo.exif || {};
document.getElementById("admin-photo-lens").value = exif.focalLength || "24mm";
document.getElementById("admin-photo-aperture").value = exif.aperture || "f/1.8";
document.getElementById("admin-photo-shutter").value = exif.shutterSpeed || "1/120s";
document.getElementById("admin-photo-iso").value = exif.iso || "ISO 100";
document.getElementById("admin-photo-engine").value = exif.captureEngine || "Mobile RAW";
document.getElementById("admin-photo-location").value = exif.location || "Ульяновск, Волга";
if (photo.imageUrl) {
attachedPhotoImageBase64 = photo.imageUrl;
document.getElementById("admin-photo-file-preview").src = photo.imageUrl;
document.getElementById("admin-photo-file-preview-wrap").style.display = "block";
}
document.getElementById("admin-photo-form-title").innerText = "Редактирование фото: #" + photo.id;
switchAdminTab("photo-editor");
}
}
function deleteItem(id, type) {
if (!confirm("Удалить эту публикацию?")) return;
if (type === "note") {
MatrixStore.deleteNote(id);
} else {
MatrixStore.deletePhoto(id);
}
renderAllSections();
renderAdminManager();
showNotification("Публикация удалена");
}
function publishNow(id, type) {
if (type === "note") {
const note = MatrixStore.getAllNotes().find(n => n.id === id);
if (note) {
note.status = "published";
note.publishAt = new Date().toISOString();
MatrixStore.saveNote(note);
}
} else {
const photo = MatrixStore.getAllPhotos().find(p => p.id === id);
if (photo) {
photo.status = "published";
photo.publishAt = new Date().toISOString();
MatrixStore.savePhoto(photo);
}
}
renderAllSections();
renderAdminManager();
showNotification("⚡ Опубликовано сейчас!");
}
function resetNoteForm() {
currentEditingNoteId = null;
attachedNoteImageBase64 = "";
document.getElementById("admin-note-title").value = "";
document.getElementById("admin-note-content").value = "";
document.getElementById("admin-note-tags").value = "";
document.getElementById("admin-tele-dist").value = "";
document.getElementById("admin-tele-speed").value = "";
document.getElementById("admin-tele-elev").value = "";
document.getElementById("admin-tele-watts").value = "";
document.getElementById("admin-tele-cadence").value = "";
document.getElementById("admin-tele-hr").value = "";
document.getElementById("admin-note-file-preview-wrap").style.display = "none";
document.getElementById("admin-note-form-title").innerText = "📝 Создание новой записи блога";
const now = new Date();
const localIso = new Date(now.getTime() - now.getTimezoneOffset() * 60000).toISOString().slice(0, 16);
document.getElementById("admin-note-date").value = localIso;
document.getElementById("admin-note-preview-pane").innerHTML = '<em style="color: var(--text-muted);">Live Markdown превью...</em>';
}
function resetPhotoForm() {
currentEditingPhotoId = null;
attachedPhotoImageBase64 = "";
document.getElementById("admin-photo-title").value = "";
document.getElementById("admin-photo-desc").value = "";
document.getElementById("admin-photo-file-preview-wrap").style.display = "none";
document.getElementById("admin-photo-form-title").innerText = "📸 Загрузка мобильного RAW фото";
const now = new Date();
const localIso = new Date(now.getTime() - now.getTimezoneOffset() * 60000).toISOString().slice(0, 16);
document.getElementById("admin-photo-date").value = localIso;
}
function exportBackup() {
const json = MatrixStore.exportBackupJSON();
const blob = new Blob([json], { type: "application/json" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `matrix_blog_backup_${new Date().toISOString().slice(0, 10)}.json`;
a.click();
URL.revokeObjectURL(url);
}
function importBackup(file) {
if (!file) return;
const reader = new FileReader();
reader.onload = function (e) {
const res = MatrixStore.importBackupJSON(e.target.result);
if (res.success) {
renderAllSections();
renderAdminManager();
alert(`Бэкап успешно восстановлен! Записей: ${res.countNotes}, Фото: ${res.countPhotos}`);
} else {
alert("Ошибка импорта JSON: " + res.error);
}
};
reader.readAsText(file);
}
function showNotification(msg) {
const notif = document.getElementById("matrix-toast-notification");
if (notif) {
notif.innerText = msg;
notif.style.display = "block";
setTimeout(() => {
notif.style.display = "none";
}, 3500);
}
}
function escapeHtml(text) {
if (!text) return "";
return String(text)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;");
}
return {
init: init,
checkAdminAuth: checkAdminAuth,
unlockAdmin: unlockAdmin,
lockAdmin: lockAdmin,
handleSaveNote: handleSaveNote,
handleSavePhoto: handleSavePhoto,
editItem: editItem,
deleteItem: deleteItem,
publishNow: publishNow,
resetNoteForm: resetNoteForm,
resetPhotoForm: resetPhotoForm,
exportBackup: exportBackup,
importBackup: importBackup
};
})();