/** * @file Basic_functions.js * @brief Основной JavaScript-файл проекта, содержит базовые функции, переменные и настройки */ /** @brief Действие менеджера */ window.managerDataAction = ""; /** @brief Текущий путь */ window.currentPath = ''; /** @brief История путей в менеджере */ window.managerHistoryPaths = [window.currentPath]; /** @brief Индекс текущего пути в истории */ window.managerHistoryIndex = 0; /** @brief Путь к файлу для таблицы менеджера */ window.managerTableDivFilePath = ""; /** @brief Имя файла для таблицы менеджера */ window.managerTableDivFileName = ""; /** @brief Последнее сохранённое имя страницы */ window.saveHowNameLast = "index.page.php"; /** @brief Путь для кнопки открытия страницы */ window.openPageButPath = "no/Select"; /** @brief Флаг, определяющий, запущено ли на телефоне */ window.isPhone = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); /** @brief Координаты касания по X (для мобильных устройств) */ window.touchX = 0; /** @brief Координаты касания по Y (для мобильных устройств) */ window.touchY = 0; /** * @brief Подключает плагин на страницу * @param String plugin Имя подключаемого плагина * @return Promise Разрешается после загрузки всех скриптов плагина */ function includePlugin(plugin) { return jsonrpcRequest("includePlugin", { plugin }) .then(html => { const div = document.createElement('div'); div.innerHTML = html; const scripts = []; Array.from(div.childNodes).forEach(node => { if (node.nodeType === 1) node.setAttribute('plugin', plugin); if (node.tagName === 'SCRIPT') { const s = document.createElement('script'); if (node.src) { s.src = node.src; s.async = false; scripts.push(new Promise(res => s.onload = res)); } else { s.textContent = node.textContent; } document.body.appendChild(s); } else { document.body.appendChild(node); } }); return Promise.all(scripts); }) .then(() => { const event = new Event("Load" + plugin + "Js"); document.dispatchEvent(event); window.dispatchEvent(event); }); } window.includePlugin = includePlugin; /** * @brief Удаляет DOM-элементы и скрипты плагина * @param String plugin Имя удаляемого плагина * @return Promise Разрешается после удаления всех файлов и элементов плагина */ function removePluginDom(plugin) { return jsonrpcRequest("removePluginDom", { plugin }) .then(files => { files.forEach(f => { if (f.startsWith('#')) { const el = document.getElementById(f.slice(1)); if (el) el.remove(); } else if (f.endsWith('.css')) { document.querySelectorAll(`link[href="${f}"]`).forEach(el => el.remove()); } else if (/\.js(\.php)?(\?.*)?$/.test(f)) { const nameJs = plugin + "Js"; delete window[nameJs]; const event = new Event('Load' + nameJs); console.log(document.dispatchEvent(event)); document.querySelectorAll('script').forEach(el => { if (el.getAttribute('plugin') === plugin || (el.src && el.src.includes(f))) { el.remove(); } }); } }); document.querySelectorAll(`[plugin="${plugin}"]`).forEach(el => el.remove()); }); } window.removePluginDom = removePluginDom; document.addEventListener('DOMContentLoaded', () => { const newsPlaceholder = document.getElementById('news-placeholder'); const centerFloat = document.querySelector('.center-float'); if (newsPlaceholder && centerFloat) { centerFloat.appendChild(newsPlaceholder); } else if (newsPlaceholder && !centerFloat) { newsPlaceholder.remove(); } }); (function(){ const hbody = document.getElementById('hbody') const menuBtn = document.querySelector('.menu-btn.open') const sideMenu = document.querySelector('.side-menu') const smenu = document.getElementById('smenu') const overlay = document.getElementById('overlay') let rafId function update() { const elems = [ document.querySelector('.menu-btn.open'), document.getElementById('shome'), document.getElementById('smenu'), document.getElementById('slng'), document.getElementById('authorizationButton') ].filter(Boolean) const originalDisplay = new Map() elems.forEach(el => { originalDisplay.set(el, el.style.display) el.style.display = '' }) const totalW = elems.reduce((sum, el) => { const style = getComputedStyle(el) return sum + el.offsetWidth + parseFloat(style.marginLeft) + parseFloat(style.marginRight) }, 0) const baseTop = elems[0].offsetTop const wrapped = elems.some(el => el.offsetTop > baseTop) elems.forEach(el => { el.style.display = originalDisplay.get(el) }) if (totalW > hbody.clientWidth || wrapped) { menuBtn.style.display = '' sideMenu.style.display = '' overlay.style.display = '' smenu.style.display = 'none' } else { menuBtn.style.display = 'none' sideMenu.style.display = 'none' overlay.style.display = 'none' smenu.style.display = '' } } function onResize() { cancelAnimationFrame(rafId) rafId = requestAnimationFrame(update) } window.addEventListener('resize', onResize) window.addEventListener('load', onResize); })() ;(function(){ const menuBtnOpen = document.querySelector('.menu-btn.open'); const menuBtnClose = document.querySelector('.menu-btn.close'); const checkbox = document.getElementById('menu-toggle'); const overlay = document.getElementById('overlay'); const sideMenu = document.querySelector('.side-menu'); menuBtnOpen.addEventListener('click', e => { e.stopPropagation(); e.preventDefault(); checkbox.checked = true; overlay.classList.add('active') }); menuBtnClose.addEventListener('click', e => { e.stopPropagation(); e.preventDefault(); checkbox.checked = false; overlay.classList.remove('active') }); checkbox.addEventListener('click', e => { e.stopPropagation(); }); document.addEventListener('click', e => { if (!sideMenu.contains(e.target) && !menuBtnOpen.contains(e.target)) { checkbox.checked = false; overlay.classList.remove('active') } }); })(); /** * @brief Получает значение cookie по имени * @param String name Имя cookie * @return String|null Значение cookie или null, если не найдено */ function getCookie(name) { const cookies = document.cookie.split(';'); for (let c of cookies) { let [key, ...val] = c.trim().split('='); if (key === name) return val.join('='); } return null; } /** * @brief Обновляет позицию фона и размер элементов редактирования */ function updateEditElements() { const scale = window.innerWidth <= 549 ? 1.15 : 1; document.querySelectorAll('.editib, .editimc, .sym').forEach(el => { if (!el.dataset.origBgX) { const [origX, origY] = getComputedStyle(el) .backgroundPosition .split(' ') .map(v => parseFloat(v)); el.dataset.origBgX = origX; el.dataset.origBgY = origY; } const origX = parseFloat(el.dataset.origBgX); const origY = parseFloat(el.dataset.origBgY); const newX = (origX * scale).toFixed(2) + 'px'; const newY = (origY * scale).toFixed(2) + 'px'; el.style.backgroundPosition = `${newX} ${newY}`; if (scale > 1) { el.style.width = '30px'; el.style.height = '30px'; el.style.backgroundSize = 'calc(1122px* 1.15)'; } else { el.style.width = ''; el.style.height = ''; el.style.backgroundSize = ''; } }); } window.addEventListener('load', updateEditElements); window.addEventListener('resize', updateEditElements); /** * @brief Настраивает обработчик движения меню по элементу * @param String id Идентификатор основного элемента меню * @param String extraId (опционально) Идентификатор дополнительного элемента для обработки */ function movementMenu(id, extraId = "") { const el = document.getElementById(id); const extraEl = extraId ? document.getElementById(extraId) : document.getElementById(id); if (!el) return; extraEl.addEventListener("pointerdown", e => { if (!isPhone) { coor(e, id); } }); } window.movementMenu = movementMenu; window.addEventListener('load', function(){ const container=document.querySelector('.toolbar-container') const panel=document.getElementById('panel') const arrowLeft=document.getElementById('arrow-left') if(!container||!panel||!arrowLeft||!('ontouchstart' in window))return let startX=0 let currentTranslate=0 container.addEventListener('touchstart',onTouchStart,{passive:false}) container.addEventListener('touchmove',onTouchMove,{passive:false}) container.addEventListener('touchend',onTouchEnd) function getBounds(){ const cw=container.scrollWidth const pw=panel.clientWidth const aw=arrowLeft.clientWidth const extra=aw const maxOffset=24 const minOffset=pw-cw-extra return{maxOffset,minOffset} } function getCurrentTranslate(){ const m=window.getComputedStyle(container).transform return m&&m!=='none'?parseFloat(m.split(',')[4]):0 } function onTouchStart(e){ currentTranslate=getCurrentTranslate() startX=e.touches[0].clientX container.style.transition='none' } function onTouchMove(e){ e.preventDefault() const deltaX=e.touches[0].clientX-startX const{maxOffset,minOffset}=getBounds() let nextTranslate=currentTranslate+deltaX nextTranslate=Math.min(maxOffset,nextTranslate) nextTranslate=Math.max(minOffset,nextTranslate) container.style.transform=`translateX(${nextTranslate}px)` } function onTouchEnd(){ currentTranslate=getCurrentTranslate() } }) addEventListener("pointerup", stco); addEventListener("touchend", stco); /** @brief Вертикальное смещение при перемещении элемента */ let dvV = 0; /** @brief Горизонтальное смещение при перемещении элемента */ let dvH = 0; /** @brief Таймер для удержания перед началом перемещения */ let holdTimer = null; /** @brief ID элемента, который в данный момент перемещается */ let targetId = ""; /** * @brief Запускает процесс захвата координат элемента для перемещения * @param Event event Событие указателя или касания * @param String id Идентификатор элемента для перемещения */ function coor(event, id) { if (event.type === "pointerdown" && event.button !== 0) return; if (event.target.closest('[contenteditable], input, textarea, select, option, ul, ol')) return; const el = document.getElementById(id); if (!el) return; el.style.touchAction = 'none'; el.style.overscrollBehavior = 'contain'; const rect = el.getBoundingClientRect(); el.style.top = rect.top + "px"; el.style.left = rect.left + "px"; removeTranslateOnly(el); const clientY = event.touches ? event.touches[0].clientY : event.clientY; const clientX = event.touches ? event.touches[0].clientX : event.clientX; dvV = clientY - rect.top; dvH = clientX - rect.left; targetId = id; clearTimeout(holdTimer); holdTimer = setTimeout(function() { if (targetId) { addEventListener("pointermove", neco, { passive: false }); addEventListener("touchmove", neco, { passive: false }); } }, 100); } /** * @brief Обновляет позицию перемещаемого элемента при движении указателя или касания * @param Event event Событие указателя или касания */ function neco(event) { if (!targetId) return; event.preventDefault(); const el = document.getElementById(targetId); const clientY = event.touches ? event.touches[0].clientY : event.clientY; const clientX = event.touches ? event.touches[0].clientX : event.clientX; const fvV = clientY - dvV; const fvH = clientX - dvH; el.style.top = fvV + "px"; el.style.left = fvH + "px"; if (targetId === "basis3" && fvH < 0) { el.style.left = "0px"; } } /** * @brief Завершает перемещение элемента и убирает обработчики событий */ function stco() { clearTimeout(holdTimer); removeEventListener("pointermove", neco, { passive: false }); removeEventListener("touchmove", neco, { passive: false }); targetId = ""; } /** * @brief Удаляет из transform только translate-преобразования * @param HTMLElement el Элемент, у которого нужно очистить трансформацию */ function removeTranslateOnly(el) { const tf = el.style.transform.trim(); if (!tf || tf === 'none') return; const parts = tf .split(/\)\s*/) .map(p => p.trim()) .filter(p => p && !p.startsWith('translate(') && !p.startsWith('translateX(') && !p.startsWith('translateY(')) .map(p => p + ')'); el.style.transform = parts.length > 0 ? parts.join(' ') : ''; } /** * @brief Создает AJAX-запрос с помощью jQuery * @param Object data Данные для отправки * @param Function successCallback Колбэк при успешном ответе */ function createAjaxRequest(data, successCallback) { $.ajax({ url: window.location.href, type: "POST", data: data, dataType: "json", success: successCallback, error: function(xhr, status, error) { console.error('Ошибка:', status, error); messageFunction("{{error}}"); } }); /* console.log(data + " " + successCallback); */ } window.createAjaxRequest = createAjaxRequest; /** * @brief Создает объект XMLHttpRequest с преднастроенным POST-запросом * @param Function callback Функция, вызываемая при завершении запроса * @return XMLHttpRequest Объект XHR */ function createXHR(callback) { let xhr = new XMLHttpRequest(); xhr.open("POST", window.location.href, true); xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.onload = callback; return xhr; } window.createXHR = createXHR; /** @brief Счетчик идентификаторов для JSON-RPC */ let rpcId = 0; /** @brief Флаг блокировки генерации идентификатора */ let idLock = false; /** * @brief Генерирует следующий уникальный идентификатор для RPC-запроса * @return Number Уникальный идентификатор */ async function getNextId() { while (idLock) await new Promise(r => setTimeout(r, 1)) idLock = true const id = ++rpcId idLock = false return id } /** * @brief Отправляет JSON-RPC запрос на сервер * @param String method Имя удаленного метода * @param Object params Параметры вызова метода * @return Promise Результат выполнения RPC */ async function jsonrpcRequest(method, params) { const id = await getNextId() const payload = { jsonrpc: "2.0", method, params, id } return fetch(window.location.href, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) }) .then(res => { if (!res.ok) { messageFunction("HTTP error: " + res.status) throw new Error(`HTTP ${res.status}`) } return res.json() }) .then(r => { if (r.error) { messageFunction("Error: " + r.error.message) throw new Error(`RPC function ${method} (id ${r.id}): ${r.error.code} ${r.error.message}`) } if (r.id !== id) { messageFunction("Error: mismatched function id") throw new Error(`Mismatched id: expected ${id}, got ${r.id}`) } return r.result }) } /** * @brief Долгое нажатие на телефоне * @param HTMLElement el Элемент, на который вешается обработчик * @param Function fn Функция, вызываемая при удержании */ function touchLong(el, fn) { let timer el.addEventListener('touchstart', function(e) { timer = setTimeout(function() { fn(e) }, 500) }, { passive: false }) el.addEventListener('touchend', function() { clearTimeout(timer) }, { passive: true }) el.addEventListener('touchmove', function() { clearTimeout(timer) }, { passive: true }) } document.addEventListener('pointerdown', function(e){ window.touchX = e.clientX window.touchY = e.clientY }, false) document.addEventListener('touchstart', function(e){ window.touchX = e.touches[0].clientX window.touchY = e.touches[0].clientY }, { passive: true }) /* двойный клик для телефонов */ if(isPhone){ document.querySelectorAll('td[ondblclick]').forEach(td=>{ let lastTap=0 td.addEventListener('touchend',function(e){ const now=Date.now() if(now-lastTap<300){ const js=td.getAttribute('ondblclick').replace(/;$/,'') new Function('event',js)(e) } lastTap=now },{passive:true}) }) } /** @brief Флаг, указывающий, были ли изменения в контенте */ window.contentIsEdit = false; if (document.getElementById("content")) { document.getElementById("content").addEventListener("input", function() { window.contentIsEdit = true; }); const observer = new MutationObserver(() => window.contentIsEdit = true); observer.observe(document.getElementById("content"), { childList: true, subtree: true }); } /** @brief Очередь сообщений для отображения */ let messageQueue = []; window.messageQueue = messageQueue; /** @brief Флаг, указывающий, создается ли в данный момент сообщение */ let messageIsCreating = false; /** * @brief Добавляет сообщение в очередь и инициирует его показ * @param String message Текст сообщения */ function messageFunction(message) { messageQueue.push(message); if (!messageIsCreating) { messageCreate(); } } window.messageFunction = messageFunction; /** * @brief Создает и отображает сообщение пользователю */ function messageCreate() { if (messageQueue.length === 0) return; messageIsCreating = true; const messageBlock = document.createElement("div"); messageBlock.classList.add('messageBlock', 'borderStyle'); document.body.appendChild(messageBlock); requestAnimationFrame(() => { messageBlock.classList.add('show'); }); const messageBasicText = document.createElement('div'); messageBasicText.classList.add('messageBasicText', 'borderStyle'); messageBasicText.textContent = "{{message}}"; const messageText = document.createElement('div'); messageText.classList.add('messageText'); messageText.textContent = messageQueue.shift(); const messageButton = document.createElement('div'); messageButton.classList.add('messageButton', 'borderStyle'); messageButton.textContent = "{{ok}}"; messageButton.onclick = messageRemove; messageBlock.appendChild(messageBasicText); messageBlock.appendChild(messageText); messageBlock.appendChild(messageButton); setTimeout(messageRemove, 4000); function messageRemove() { if (messageBlock.parentElement) { messageBlock.classList.remove('show'); setTimeout(() => { messageBlock.remove(); messageIsCreating = false; messageCreate(); }, 150); } } } /** @brief Флаг, подтвержден ли пользователь в сообщении-вопросе */ let userConfirmed = false; window.messageCreateQuestion = messageCreateQuestion; /** * @brief Создает сообщение-вопрос (да/нет) * @return Promise true если пользователь нажал "Да", иначе false */ function messageCreateQuestion() { return new Promise((resolve, reject) => { if (messageQueue.length === 0) return resolve(false); messageIsCreating = true; let messageBlock = document.createElement("div"); messageBlock.className = 'messageBlock borderStyle'; document.body.appendChild(messageBlock); requestAnimationFrame(() => { messageBlock.classList.add('show'); }); let messageBasicText = document.createElement("div"); messageBasicText.classList.add('messageBasicText', 'borderStyle'); messageBasicText.textContent = "{{message}}"; let messageText = document.createElement("div"); messageText.className = 'messageText'; messageText.textContent = messageQueue.shift(); let messageButtonNo = document.createElement("div"); messageButtonNo.classList.add('messageButton', 'borderStyle'); messageButtonNo.textContent = "{{no}}"; messageButtonNo.style.float = "right"; messageButtonNo.onclick = function() { messageBlock.classList.remove('show'); setTimeout(() => { messageBlock.remove(); messageIsCreating = false; reject(false); }, 150); }; let messageButton = document.createElement("div"); messageButton.classList.add('messageButton', 'borderStyle'); messageButton.textContent = "{{yes}}"; messageButton.style.float = "left"; messageButton.onclick = function() { messageBlock.classList.remove('show'); setTimeout(() => { messageBlock.remove(); messageIsCreating = false; resolve(true); }, 0); }; messageBlock.appendChild(messageBasicText); messageBlock.appendChild(messageText); messageBlock.appendChild(messageButtonNo); messageBlock.appendChild(messageButton); }); } /** * @brief Создает сообщение с полем ввода текста * @param String initial Начальное значение для ввода * @return Promise Введенный пользователем текст или null при отмене */ function messageCreateInput(initial = '') { return new Promise((resolve, reject) => { if (messageQueue.length === 0) return resolve(false); messageIsCreating = true; const messageBlock = document.createElement("div"); messageBlock.classList.add("messageBlock", "borderStyle"); document.body.appendChild(messageBlock); requestAnimationFrame(() => { messageBlock.classList.add("show"); }); const messageBasicText = document.createElement("div"); messageBasicText.classList.add("messageBasicText", "borderStyle"); messageBasicText.style.fontSize = '1.4em'; messageBasicText.textContent = messageQueue.shift(); const messageInput = document.createElement("input"); messageInput.classList.add("messageInput", "borderStyle"); messageInput.value = initial; messageInput.type = "text"; const messageButtonOk = document.createElement("div"); messageButtonOk.classList.add("messageButton", "borderStyle"); messageButtonOk.textContent = "{{ok}}"; messageButtonOk.onclick = function() { const value = messageInput.value; messageBlock.classList.remove("show"); setTimeout(() => { messageBlock.remove(); messageIsCreating = false; resolve(value); messageCreate(); }, 150); }; const messageButtonCancel = document.createElement("div"); messageButtonCancel.classList.add("messageButton", "borderStyle"); messageButtonCancel.textContent = "{{cancel}}"; messageButtonCancel.style.width = 'auto'; messageButtonCancel.style.float = 'right'; messageButtonCancel.onclick = function() { messageBlock.classList.remove("show"); setTimeout(() => { messageBlock.remove(); messageIsCreating = false; resolve(null); messageCreate(); }, 150); }; messageBlock.appendChild(messageBasicText); messageBlock.appendChild(messageInput); messageBlock.appendChild(messageButtonOk); messageBlock.appendChild(messageButtonCancel); }); } /** @brief Счётчик для переключения режима отображения HTML */ let cou=1; /** * @brief Показать/скрыть HTML-код страницы в textarea */ function showHtmlCode() { if (cou==1) { document.getElementById("tex").value=document.getElementById("content").innerHTML; document.getElementById("tex").value = decodeHtmlEntities(document.getElementById("tex").value); document.getElementById("tex").value = formatHTML(document.getElementById("tex").value); let sbe=document.getElementsByClassName("sb"); for(let i=0; i { if (ans == 'true') { console.log("{{main_block_saved}}"); } else { messageFunction("{{main_block_not_saved}}"); } }); } /** * @brief Сохраняет данные подключённых плагинов (левой и правой колонок) */ function savePlugins() { let pluginsFloatsId = ["left-float", "right-float"]; let floatsBlockKeys = ["lblock", "rblock"]; let requestsData = { left: [], right: [] }; pluginsFloatsId.forEach((pluginsFloatId, i) => { let plugins = document.getElementById(pluginsFloatId).querySelectorAll('.plugin-url'); plugins.forEach(plugin => { let pluginData = { pluginUrl: plugin.pluginUrl, title: "", tclass: "", bclass: "" }; let tElem = plugin.querySelector('[tclass]'); pluginData.title = tElem ? tElem.textContent.trim() : ""; pluginData.tclass = tElem ? tElem.getAttribute('class') : ""; let bElem = plugin.querySelector('[bclass]'); pluginData.bclass = bElem ? bElem.getAttribute('class') : ""; let blockKey = floatsBlockKeys[i]; let blockArray = (blockKey === "lblock") ? requestsData.left : requestsData.right; blockArray.push(pluginData); }); }); let data = `floatsBlock=${encodeURIComponent(JSON.stringify(requestsData))}`; jsonrpcRequest("savePageSideBlocks", { floatsBlock: JSON.stringify(requestsData) }).then(response => { console.log(response); }); } /** * @brief Сохраняет заголовок страницы */ function saveHeading() { document.querySelectorAll('#mainTitle').forEach((editTitle) => { if (functionOpenPage === true) { messageFunction("{{open_page}}"); } else { let newTitle = editTitle.innerHTML.trim(); if (newTitle === "") { messageFunction("{{plugin_title_empty_error}}"); } else { jsonrpcRequest("savePageTitle", { newTitle }).then(response => { console.log(response); }); } } }); } /** * @brief Декодирует HTML-сущности (&...;) * @param String str Строка для декодирования * @return String Декодированная строка */ function decodeHtmlEntities(str) { var textarea = document.createElement('textarea'); textarea.innerHTML = str; return textarea.value; } /** * @brief Форматирует HTML-код (переносы строк и отступы) * @param String html Исходный HTML * @return String Отформатированный HTML */ function formatHTML(html) { html = html.replace(/<([^\/][^>\s]*)[^>]*>/g, "\n$&\n"); html = html.replace(/<\/([^>\s]*)>/g, "\n$&\n"); html = html.replace(/^\s*[\r\n]/gm, ''); let lines = html.split('\n'); let indentLevel = 0; let indentSize = 2; for (let i = 0; i < lines.length; i++) { let line = lines[i]; if (line.match(/^<\/[^>]+>/)) indentLevel--; if (line.match(/^<\/[^>]+>/) || line.match(/^<[^\/>]+[^>]*>$/)) lines[i] = ' '.repeat(indentLevel * indentSize) + line; if (line.match(/^<[^\/>]+[^>]*>$/)) indentLevel++; } html = lines.join('\n'); return html; } /** * @brief Сохраняет изменения в новый файл * @param String currentPath Текущий путь * @return Promise */ window.saveContentIdHow = async function (currentPath) { if (cou == 1) { document.getElementById("content").innerHTML = decodeHtmlEntities(document.getElementById("content").innerHTML); document.getElementById("tex").value = document.getElementById("content").innerHTML; } document.getElementById("tex").value = decodeHtmlEntities(document.getElementById("tex").value); let saveContentIdData = document.getElementById("tex").value.trim(); let nameFile = document.getElementById('saveHowName').value; if (!nameFile.endsWith('.page.php')) { nameFile += '.page.php'; messageQueue.push('{{page_must_end_with_page_php}}'); return; } messageQueue.push('{{save_file_as}} ' + nameFile + '?'); if (!(await messageCreateQuestion())) { return; } let ans = await jsonrpcRequest("checkFile", { path: currentPath, nameFile: nameFile }); if (ans == 'true') { messageQueue.push(`{{file}} ${nameFile} {{exists_overwrite_prompt}}`); if (!(await messageCreateQuestion())) { return; } sendSaveRequest(currentPath, nameFile, saveContentIdData, true); window.newPageFunValue = ""; document.getElementById("mainTitle").innerHTML = "{{new_file}}"; } else if (ans == 'false') { createNewFile(currentPath, nameFile, saveContentIdData); window.newPageFunValue = ""; document.getElementById("mainTitle").innerHTML = "{{new_file}}"; } else { messageQueue.push("{{file_save_failed}}"); } managerData(currentPath); document.getElementById("managerDiv").style.visibility = "hidden"; }; /** * @brief Отправляет запрос на сохранение страницы * @param String currentPath Текущий путь * @param String nameFile Имя файла * @param String saveContentIdData Содержимое * @param Boolean overwrite Перезаписать существующий файл */ function sendSaveRequest(currentPath, nameFile, saveContentIdData, overwrite = false) { let pluginsFloatsId = ["left-float", "right-float"]; let floatsBlockKeys = ["lblock", "rblock"]; let requestsData = { floatsBlock: [], title: [], pluginUrl: [], tclass: [], bclass: [] }; pluginsFloatsId.forEach((id, i) => { document.getElementById(id).querySelectorAll('.plugin-url').forEach(plugin => { requestsData.floatsBlock.push(floatsBlockKeys[i]); let tElem = plugin.querySelector('[tclass]'); requestsData.title.push(tElem ? tElem.textContent.trim() : ""); requestsData.pluginUrl.push(plugin.pluginUrl); requestsData.tclass.push(tElem ? tElem.getAttribute('class') : ""); let bElem = plugin.querySelector('[bclass]'); requestsData.bclass.push(bElem.getAttribute('class')); }); }); let params = { page_url: currentPath, nameFile: nameFile, overwrite: overwrite, saveContentIdData: saveContentIdData, floatsBlock: requestsData.floatsBlock, title: requestsData.title, pluginUrl: requestsData.pluginUrl, tclass: requestsData.tclass, bclass: requestsData.bclass, }; jsonrpcRequest("saveHowPageContent", params).then(ans => { messageFunction("{{changes_saved_successfully}}"); managerData(currentPath); document.getElementById("managerDiv").style.visibility = "hidden"; }); } /** * @brief Создаёт новый файл страницы * @param String currentPath Текущий путь * @param String nameFile Имя файла * @param String saveContentIdData Содержимое */ function createNewFile(currentPath, nameFile, saveContentIdData) { jsonrpcRequest("createNewPage", { saveContentIdData, page_url: currentPath, nameFile }).then(ans => { if (ans == 'true') { messageFunction(`{{file}} ${nameFile} {{created_successfully}}!`); } managerData(currentPath); document.getElementById("managerDiv").style.visibility = "hidden"; }); } /** * @brief Формирует строку запроса для XMLHttpRequest * @param Array dataNames Массив имён параметров * @param Array dataValues Массив значений параметров * @return String Строка запроса */ function createQueryString(dataNames, dataValues) { let data = ""; for (let i = 0; i < dataNames.length; i++) { data += `${dataNames[i]}=${encodeURIComponent(dataValues[i])}&`; } return data.slice(0, -1); } /** * @brief Переключает отображение меню настроек сайта */ window.toggleMenu = function() { document.getElementById('siteSettings').style.display = document.getElementById('siteSettings').style.display === 'none' || document.getElementById('siteSettings').style.display === '' ? 'block' : 'none'; }; /** * @brief Скрывает меню настроек сайта при клике вне кнопки */ window.onclick = function(event) { var btn = document.getElementById('siteSettingsButton'); var settings = document.getElementById('siteSettings'); if (btn && !btn.contains(event.target)) { settings && (settings.style.display = 'none'); } }; /** @brief Режим настроек древа сайта */ window.treeSettingsMode = ""; /** * @brief Открывает/закрывает древо сайта */ function basisVisSiteTree() { siteTreeGeneration(); window.treeSettingsMode = ""; let treeDiv = document.getElementById("treeDiv"); if (treeDiv.style.visibility=="hidden") { treeDiv.style.visibility = "visible"; if(isPhone) closeWindows("treeDiv"); } else { treeDiv.style.visibility = "hidden"; document.getElementById("treeProperties").style.visibility = "hidden"; } document.getElementById('treeCloseFun').onclick = function() { treeDiv.style.visibility = "hidden"; document.getElementById("treeProperties").style.visibility = "hidden"; }; treeSettings(); } window.basisVisSiteTree = basisVisSiteTree; /** * @brief Включает режим выбора ссылки из древа сайта */ function linkFromPage() { siteTreeGeneration(); window.treeSettingsMode = 'linkFromPage'; let treeDiv = document.getElementById("treeDiv"); treeDiv.style.visibility = "visible"; document.getElementById('treeCloseFun').onclick = function() { treeDiv.style.visibility = "hidden"; document.getElementById("treeProperties").style.visibility = "hidden"; }; treeSettings(); } window.linkFromPage = linkFromPage; /** * @brief Переключает отображение дочерних элементов в древе * @param HTMLElement el Элемент, по которому кликнули */ function toggleChildren(el) { let details = el.closest('li').querySelector('.details'); if (!details) return; details.style.display = details.style.display === 'none' ? 'block' : 'none'; el.querySelector('.tree-marker').textContent = details.style.display === 'block' ? '▼' : '►'; } window.toggleChildren = toggleChildren; /** * @brief Открывает/закрывает менеджер файлов и папок */ function basisVisManager() { let managerDiv = document.getElementById('managerDiv'); if (managerDiv.style.visibility === "hidden") { managerDiv.style.visibility = "visible"; window.managerDataAction = ""; managerData(currentPath); if(isPhone) closeWindows("managerDiv"); } else { managerDiv.style.visibility = "hidden"; document.getElementById("managerProperties").style.visibility = "hidden"; } } window.basisVisManager = basisVisManager; /** * @brief Закрывает одно окно при открытии другого (managerDiv/treeDiv) * @param String div Идентификатор открываемого окна */ function closeWindows(div) { if (div == "managerDiv") { document.getElementById("treeDiv").style.visibility = "hidden"; document.getElementById("treeProperties").style.visibility = "hidden"; } else if (div == "treeDiv") { document.getElementById("managerDiv").style.visibility = "hidden"; document.getElementById("managerProperties").style.visibility = "hidden"; } } /** * @brief Обновляет данные менеджера файлов для указанного пути * @param String path Путь к папке */ function managerData(path) { currentPath = path if (managerHistoryPaths[managerHistoryIndex] !== currentPath) { managerHistoryPaths = managerHistoryPaths.slice(0, managerHistoryIndex + 1) managerHistoryPaths.push(currentPath) managerHistoryIndex++ } jsonrpcRequest("getFolderContents", { managerPathFolder: path }).then(data => { let rows = '' for (let i = 0; i < data.length; i++) { if (data[i].name) { let newPath = `${path}/${data[i].name}` let attr = 'style="cursor: pointer; display: flex; align-items: center;" ' if (data[i].type === '{{file}}') { let dbl = 'ondblclick="managerData(\'' + newPath + '\');"' attr += isPhone ? 'onclick="managerData(\'' + newPath + '\');" ' + dbl : dbl } else if (data[i].name.endsWith('.page.php')) { let page = newPath.replace('.page.php', '') let dbl = 'ondblclick="getPage(\'' + page + '\');"' attr += isPhone ? 'onclick="getPage(\'' + page + '\');" ' + dbl : dbl } let icon = data[i].type === '{{file}}' ? '
' : '
' let unit = data[i].type === '{{file}}' ? ' files' : ' bytes' let sizeText = data[i].size + unit let nameCell = isPhone ? `
${icon}${data[i].name}
${sizeText}
` : `${icon}${data[i].name}` rows += ` ${nameCell} ${isPhone ? '' : `${sizeText}`} ${data[i].creationTime} ` } } let actionButton = '' if (document.getElementById('saveHowName')) { if (document.getElementById('saveHowName').value === '{{select_file}}') { document.getElementById('saveHowName').value = 'index.page.php' } saveHowNameLast = document.getElementById('saveHowName').value document.getElementById('saveHowButton').removeEventListener('click', saveHow) document.getElementById('saveHowButton').removeEventListener('click', openPageBut) } let managerTopTitleName = '{{file_manager_title}}' if (window.managerDataAction === 'saveHow') { actionButton = `
{{name}}: {{save}}
` managerTopTitleName = '{{save}} как' } else if (window.managerDataAction === 'getPage') { actionButton = `
{{name}}: {{open}}
` managerTopTitleName = '{{open}}' } else if (window.managerDataAction === 'propertiesUrl') { actionButton = `
{{name}}: Выбрать
` managerTopTitleName = '{{open}}' } else if (window.managerDataAction === 'selectImgForm') { actionButton = `
{{name}}: {{open}}
` managerTopTitleName = '{{choose}}' } else if (window.managerDataAction === 'selectImgFormToForm') { actionButton = `
{{name}}: {{open}}
` managerTopTitleName = '{{choose}}' } let sizeHeader = isPhone ? '' : `{{column_size_bytes}}` let dateWidth = isPhone ? '55%' : '23%' managerDiv.innerHTML = `
${managerTopTitleName}
 /  ${currentPath.split('/').filter(Boolean).map((seg, idx) => { let segPath = '/' + currentPath.split('/').slice(1, idx + 2).join('/') return ` ${seg} /` }).join('')}
${sizeHeader} ${rows}
{{name}}{{column_creation_date}}
${actionButton} ` if (window.managerDataAction === 'saveHow') { document.getElementById('saveHowButton').addEventListener('click', saveHow) } else if (window.managerDataAction === 'getPage') { document.getElementById('saveHowButton').addEventListener('click', openPageBut) } else if (window.managerDataAction === 'propertiesUrl') { document.getElementById('saveHowButton').addEventListener('click', propertiesUrlFun) } else if (window.managerDataAction === 'selectImgForm') { document.getElementById('saveHowButton').addEventListener('click', selectImgFormButFun) } else if (window.managerDataAction === 'selectImgFormToForm') { document.getElementById('saveHowButton').addEventListener('click', selectImgFormToFormButFun) } openPageButPath = 'no/Select' if (window.managerDataAction === 'getPage') { document.querySelectorAll('.managerTableDivFile').forEach(el => { el.addEventListener('click', function() { openPageButPath = 'no/Select' const td = this.querySelector('td') const dbl = td.getAttribute('ondblclick') || '' if (dbl.includes('getPage')) { const m = dbl.match(/getPage\(['"]([^'"]+)['"]\)/) if (m) openPageButPath = m[1] const inp = document.getElementById('saveHowName') if (inp) inp.value = td.innerText } }) }) } else if (window.managerDataAction === 'selectImgForm') { document.querySelectorAll('.managerTableDivFile').forEach(el => { el.addEventListener('click', function() { openPageButPath = 'no/Select' const td = this.querySelector('td') if (td) { openPageButPath = td.getAttribute('data-path') || openPageButPath const inp = document.getElementById('saveHowName') if (inp) inp.value = td.innerText } }) }) } else if (window.managerDataAction === 'selectImgFormToForm') { document.querySelectorAll('.managerTableDivFile').forEach(el => { el.addEventListener('click', function() { openPageButPath = 'no/Select' const td = this.querySelector('td') if (td) { openPageButPath = td.getAttribute('data-path') || openPageButPath const inp = document.getElementById('saveHowName') if (inp) inp.value = td.innerText } }) }) } else if (window.managerDataAction === 'saveHow' || window.managerDataAction === 'propertiesUrl') { document.querySelectorAll('.managerTableDivFile').forEach(el => { el.addEventListener('click', function() { openPageButPath = 'no/Select' const td = this.querySelector('td') if (!td) return let dbl = td.getAttribute('ondblclick') || '' if (dbl.includes('getPage')) { let nd = dbl.replace(/getPage\(['"][^'"]+['"]\)/, '') if (!nd.trim()) td.removeAttribute('ondblclick') else td.setAttribute('ondblclick', nd) } const inp = document.getElementById('saveHowName') if (inp) inp.value = td.innerText }) }) } managerSettings() managerFun() }) } /** @brief Флаг открытия страницы */ window.functionOpenPage = false; /** * @brief Загружает страницу по указанному пути * @param String newPath Путь к новой странице */ function getPage(newPath) { window.functionOpenPage = true; jsonrpcRequest("getPage", { newPath }).then(page => { document.getElementById("right-float").innerHTML = page.right; document.getElementById("left-float").innerHTML = page.left; document.getElementById("content").innerHTML = page.content; document.getElementById("managerDiv").style.visibility = "hidden"; // history.pushState(null, '', page.pagePath); document.getElementById("mainTitle").innerHTML = "{{new_file}}!"; }); window.newPageFunValue = ""; } //обьявление функции для того, чтобы обращатся к ней из других файлов window.getPage = getPage; window.managerData = managerData; /* Функция z-index элементов */ document.addEventListener("DOMContentLoaded", function() { var selectors = [ '#basis3 .cust', '#managerDiv #managerSettings', '#managerProperties', '#treeDiv #treeSettings', '#treeProperties', '#authorizationDiv' ]; var groups = selectors.map(function(sel) { var parts = sel.split(' '); var elements = []; parts.forEach(function(part) { if (part.startsWith('#')) { var el = document.getElementById(part.slice(1)); if (el) elements.push(el); } if (part.startsWith('.')) { elements.push(...document.getElementsByClassName(part.slice(1))); } }); return elements; }); var queue = []; document.addEventListener('pointerdown', function(e) { var clickedGroup = -1; groups.forEach(function(group, idx) { if (group.some(el => el.contains(e.target))) { clickedGroup = idx; } }); if (clickedGroup !== -1) { var i = queue.indexOf(clickedGroup); if (i !== -1) queue.splice(i, 1); queue.unshift(clickedGroup); } var result = selectors.map((_, idx) => { var pos = queue.indexOf(idx); return pos === -1 ? 100 : 199 - pos; }); groups.forEach(function(group, idx) { group.forEach(function(el) { el.style.zIndex = result[idx]; }); }); }); var selectorsElements = [ '#basis3', '.cust', '#managerDiv', '#managerSettings', '#managerProperties', '#treeDiv', '#treeSettings', '#treeProperties', '#authorizationDiv' ]; var observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { if (mutation.attributeName === 'style') { var el = mutation.target; var oldVal = mutation.oldValue || ''; var newVis = el.style.visibility; if (newVis === 'visible' && !/visibility\s*:\s*visible/.test(oldVal)) { selectorsElements.forEach(function(sel) { document.querySelectorAll(sel).forEach(function(item) { if (item === el) { item.style.zIndex = 199; } else { let z = parseInt(item.style.zIndex, 10) || 100; item.style.zIndex = z > 100 ? z - 1 : 100; } }); }); } } }); }); selectorsElements.forEach(function(sel) { document.querySelectorAll(sel).forEach(function(el) { observer.observe(el, { attributes: true, attributeFilter: ['style'], attributeOldValue: true }); }); }); document.querySelectorAll('.cust').forEach(el=>{ let hideTimer, watchTimer, watching=false const observer = new MutationObserver(muts=>{ muts.forEach(m=>{ if(m.attributeName==='style' && getComputedStyle(el).visibility==='visible'){ watching = false clearTimeout(watchTimer) watchTimer = setTimeout(()=> watching = true, 0) } }) }) observer.observe(el, { attributes: true, attributeFilter: ['style'] }) document.addEventListener('pointerdown', e=>{ if(watching && getComputedStyle(el).visibility==='visible' && !el.contains(e.target)){ clearTimeout(hideTimer) hideTimer = setTimeout(()=> el.style.visibility = 'hidden', 0) } }) el.addEventListener('pointerdown', e=>{ e.stopPropagation() clearTimeout(hideTimer) }) }) function updateZIndices() { const result = selectors.map((_, idx) => { const pos = queue.indexOf(idx); return pos === -1 ? 100 : 199 - pos; }); groups.forEach((group, idx) => { group.forEach(el => el.style.zIndex = result[idx]); }); } var observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { if (mutation.attributeName === 'style') { const el = mutation.target; const oldVal = mutation.oldValue || ''; const newVis = getComputedStyle(el).visibility; if (newVis === 'visible' && !/visibility\s*:\s*visible/.test(oldVal)) { selectors.forEach((sel, idx) => { groups[idx].forEach(member => { if (member === el) { const i = queue.indexOf(idx); if (i !== -1) queue.splice(i, 1); queue.unshift(idx); updateZIndices(); } }); }); } } }); }); }); /* путь элементов */ document.querySelectorAll('#left-float [plugin-url], #right-float [plugin-url]').forEach((el) => { el.pluginUrl = el.getAttribute('plugin-url'); el.classList.add('plugin-url'); el.removeAttribute('plugin-url'); }); /* редактирования заголовков */ window.addEventListener('load', function() { const editable = document.querySelector('#mainTitle'); const observer = new MutationObserver(() => { const txt = editable.textContent.replace(/\u00A0/g, ' '); if (editable.innerHTML !== txt) { const sel = window.getSelection(); const anchorNode = sel.anchorNode; const anchorOffset = sel.anchorOffset; observer.disconnect(); editable.textContent = txt; const range = document.createRange(); let node = editable.firstChild || editable; const offset = Math.min(anchorOffset, node.textContent.length); range.setStart(node, offset); range.collapse(true); sel.removeAllRanges(); sel.addRange(range); observer.observe(editable, { childList: true, subtree: true, characterData: true }); } }); if(editable && editable.nodeType === 1){ observer.observe(editable, { childList: true, subtree: true, characterData: true }); } });