1511 lines
58 KiB
JavaScript
Executable File
1511 lines
58 KiB
JavaScript
Executable File
/**
|
||
* @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<sbe.length; i++) {
|
||
if (sbe[i] != document.getElementById("tex"))
|
||
sbe[i].style.visibility="hidden";
|
||
}
|
||
if (document.getElementById("tex").style.visibility=="hidden") {
|
||
document.getElementById("tex").style.visibility="visible";
|
||
} else {
|
||
document.getElementById("tex").style.visibility="hidden";
|
||
}
|
||
cou=2;
|
||
} else {
|
||
document.getElementById("content").innerHTML=document.getElementById("tex").value;
|
||
document.getElementById("tex").style.visibility="hidden";
|
||
cou=1;
|
||
}
|
||
}
|
||
window.showHtmlCode = showHtmlCode;
|
||
|
||
/** @brief Флаг нового состояния страницы при сохранении */
|
||
window.newPageFunValue = "";
|
||
/**
|
||
* @brief Сохраняет изменения страницы (основной контент, заголовок, плагины)
|
||
*/
|
||
function saveChanges() {
|
||
if (window.newPageFunValue == "newPage") {
|
||
document.getElementById("saveHow").click();
|
||
} else {
|
||
saveContentId();
|
||
saveHeading();
|
||
savePlugins();
|
||
}
|
||
document.getElementById("settingsMain_d").style.visibility="hidden";
|
||
}
|
||
window.saveChanges = saveChanges;
|
||
|
||
/**
|
||
* @brief Сохраняет центральный блок контента
|
||
*/
|
||
function saveContentId() {
|
||
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();
|
||
|
||
jsonrpcRequest("savePageCenterBlock", { saveContentIdData }).then(ans => {
|
||
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 = "<i>{{new_file}}</i>";
|
||
} else if (ans == 'false') {
|
||
createNewFile(currentPath, nameFile, saveContentIdData);
|
||
window.newPageFunValue = "";
|
||
document.getElementById("mainTitle").innerHTML = "<i>{{new_file}}</i>";
|
||
} 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}}'
|
||
? '<div style="margin-right:5px;background-position:-678px -559px;width:26px;height:26px;background-image:url(../../img/pict/mc_iconslyb.svg);display:inline-block;"></div>'
|
||
: '<div style="margin-right:5px;background-position:3px -557px;width:26px;height:26px;background-image:url(../../img/pict/mc_iconslyb.svg);display:inline-block;"></div>'
|
||
let unit = data[i].type === '{{file}}' ? ' files' : ' bytes'
|
||
let sizeText = data[i].size + unit
|
||
let nameCell = isPhone
|
||
? `<div style="display:flex;flex-direction:column;align-items:flex-start;">
|
||
<div style="display:flex;align-items:center;">${icon}${data[i].name}</div>
|
||
<div style="font-size:0.7em;color:#666;margin-left:31px;">${sizeText}</div>
|
||
</div>`
|
||
: `${icon}${data[i].name}`
|
||
rows += `
|
||
<tr style="height:30px;" class="managerTableDivFile" path="${data[i].path}">
|
||
<td ${attr}>${nameCell}</td>
|
||
${isPhone ? '' : `<td>${sizeText}</td>`}
|
||
<td>${data[i].creationTime}</td>
|
||
</tr>`
|
||
}
|
||
}
|
||
|
||
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 = `<div id="saveHowDiv"> {{name}}: <input id="saveHowName" value="${saveHowNameLast}"><span id="saveHowButton">{{save}}</span> </div>`
|
||
managerTopTitleName = '{{save}} как'
|
||
} else if (window.managerDataAction === 'getPage') {
|
||
actionButton = `<div id="saveHowDiv"> {{name}}: <input readonly id="saveHowName" value="{{select_file}}"><span id="saveHowButton">{{open}}</span> </div>`
|
||
managerTopTitleName = '{{open}}'
|
||
} else if (window.managerDataAction === 'propertiesUrl') {
|
||
actionButton = `<div id="saveHowDiv"> {{name}}: <input readonly id="saveHowName" value="{{select_file}}"><span id="saveHowButton">Выбрать</span> </div>`
|
||
managerTopTitleName = '{{open}}'
|
||
} else if (window.managerDataAction === 'selectImgForm') {
|
||
actionButton = `<div id="saveHowDiv"> {{name}}: <input readonly id="saveHowName" value="{{select_file}}"><span id="saveHowButton">{{open}}</span> </div>`
|
||
managerTopTitleName = '{{choose}}'
|
||
} else if (window.managerDataAction === 'selectImgFormToForm') {
|
||
actionButton = `<div id="saveHowDiv"> {{name}}: <input readonly id="saveHowName" value="{{select_file}}"><span id="saveHowButton">{{open}}</span> </div>`
|
||
managerTopTitleName = '{{choose}}'
|
||
}
|
||
|
||
let sizeHeader = isPhone ? '' : `<td style="width:15%;">{{column_size_bytes}}</td>`
|
||
let dateWidth = isPhone ? '55%' : '23%'
|
||
|
||
managerDiv.innerHTML = `
|
||
<div id="managerTop">
|
||
<span id="managerTopTitle">${managerTopTitleName}</span>
|
||
<span id="managerCloseFun" class="editib"></span>
|
||
</div>
|
||
<div id="managerTopDiv" style="margin:7px 20px;">
|
||
<span id="managerHistoryBackFun" class="editib"></span>
|
||
<span id="managerHistoryForwFun" class="editib"></span>
|
||
<span id="managerBackFun" class="editib"></span>
|
||
<span id="managerPath" contentEditable="false">
|
||
<span class="managerPathButton" onclick="managerData('');"> / </span>
|
||
${currentPath.split('/').filter(Boolean).map((seg, idx) => {
|
||
let segPath = '/' + currentPath.split('/').slice(1, idx + 2).join('/')
|
||
return ` <span class="managerPathButton" onclick="managerData('${segPath}');">${seg} /</span>`
|
||
}).join('')}
|
||
</span>
|
||
</div>
|
||
<div id="managerTableDiv">
|
||
<table id="managerTable">
|
||
<tr id="managerTableTitle">
|
||
<td style="width:45%;">{{name}}</td>
|
||
${sizeHeader}
|
||
<td style="width:${dateWidth};">{{column_creation_date}}</td>
|
||
</tr>
|
||
${rows}
|
||
</table>
|
||
</div>
|
||
${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 = "<i>{{new_file}}!</i>";
|
||
});
|
||
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 });
|
||
}
|
||
});
|