1017 lines
42 KiB
JavaScript
1017 lines
42 KiB
JavaScript
/**
|
||
* @file site_tree.js
|
||
* @brief Основной файл site_tree, управляет древовидной структурой сайта
|
||
*/
|
||
|
||
/** @brief Счетчик для уникальных id элементов дерева */
|
||
let idCounter = 0;
|
||
siteTreeGeneration();
|
||
|
||
/** @brief Генерация HTML древа сайта */
|
||
function siteTreeGeneration() {
|
||
jsonrpcRequest("getSiteTree", {}).then(data => {
|
||
let treeContainer = document.getElementById('treeTableDiv');
|
||
if (!document.getElementById('treeTableDiv')) return;
|
||
treeContainer.innerHTML = generateTreeHtml(data, "");
|
||
let firstSpan = document.getElementById('treeTableDiv').querySelector('span');
|
||
if (!treeContainer.innerHTML.includes('background-color: #e5f0ff;')) {
|
||
firstSpan.style.backgroundColor = '#e5f0ff';
|
||
}
|
||
});
|
||
idCounter = 0;
|
||
}
|
||
window.siteTreeGeneration = siteTreeGeneration;
|
||
|
||
/** @brief Выбранный элемент дерева */
|
||
let selectedTreeItem = "";
|
||
/** @brief Дополнительный выбранный элемент дерева для вставки данных */
|
||
let selectedTreeItemAdd = "";
|
||
/** @brief Флаг первого <li> для особого выделения */
|
||
let firstLi = true;
|
||
|
||
/** @brief Генерация HTML дерева рекурсивно
|
||
* @param items массив объектов страницы
|
||
* @param checkChildren флаг проверки дочерних элементов
|
||
* @return сгенерированный HTML дерева
|
||
*/
|
||
function generateTreeHtml(items, checkChildren) {
|
||
let html = '<ul style="margin-top: 10px; margin-bottom: 10px; list-style-type: none;">';
|
||
items.forEach(item => {
|
||
if (!item.name && item.tag == "sitename" && checkChildren == "") {
|
||
html += generateTreeHtmlSitename(item, "treeHtmlSitename");
|
||
return;
|
||
}
|
||
if (!item.name && item.tag == "slogan" && checkChildren == "") {
|
||
html += generateTreeHtmlSitename(item, "treeHtmlSlogan");
|
||
return;
|
||
}
|
||
|
||
let hasChildren = item.children && item.children.length;
|
||
let isOpen = (item.isOpen || item.tag === 'index') ? 'block' : 'none';
|
||
let markerText = hasChildren ? (isOpen === 'block' ? '▼' : '►') : '■';
|
||
let style = item.isOpen ? 'background-color: #e5f0ff;' : '';
|
||
id = "";
|
||
if (firstLi == true) {
|
||
id = 'id="treeHtmlIndex"';
|
||
firstLi = false;
|
||
}
|
||
|
||
html += `<li ${id} style="position: relative; margin: 0px 0px 8px -20px;">`;
|
||
html += `<span class="tree-item" style="cursor: pointer; ${style} padding: 3px 6px 3px 3px;" onclick="toggleChildren(this)">`;
|
||
html += `<span class="tree-marker">${markerText}</span> <span class="tree-text">${item.tag} <span class="tree-text-name"> (${item.title})</span></span>`;
|
||
html += `<div class="tree-data" style="display: none;">`;
|
||
html += `url: ${item.url}<br>`;
|
||
html += `title: ${item.title}<br>`;
|
||
html += `name: ${item.name}<br>`;
|
||
html += `template: ${item.template}<br>`;
|
||
html += `PageMenu: ${item.PageMenu}<br>`;
|
||
html += `users: ${item.users}<br>`;
|
||
html += `group: ${item.group}<br>`;
|
||
|
||
let now = new Date();
|
||
let pad = n => n < 10 ? '0' + n : n;
|
||
let nowStr = `${now.getFullYear()}-${pad(now.getMonth()+1)}-${pad(now.getDate())}T${pad(now.getHours())}:${pad(now.getMinutes())}`;
|
||
if (item.news) {
|
||
let newsData = item.news.split(',');
|
||
let range = newsData[0] || '';
|
||
let blocks = newsData.slice(1);
|
||
html += `news: <div><input type="checkbox" class="news-checkbox" checked>show in news</div>`;
|
||
let [start, end] = range.split('/');
|
||
let fmt = s => {
|
||
let p = s.split('.');
|
||
return `${p[0]}-${p[1]}-${p[2]}T${p[3]}:${p[4]}`;
|
||
};
|
||
html += `<div><input type="datetime-local" class="news-start" value="${fmt(start)}">`;
|
||
html += `<input type="datetime-local" class="news-end" value="${fmt(end)}"></div>`;
|
||
html += `<div>${['left','center','right'].map(b => `<label><input type="checkbox" value="${b}"${blocks.includes(b)?' checked':''}>${b}</label>`).join(' ')}</div>`;
|
||
} else {
|
||
html += `news: <div><input type="checkbox" class="news-checkbox">show in news</div>`;
|
||
html += `<div><input type="datetime-local" class="news-start" value="${nowStr}">`;
|
||
html += `<input type="datetime-local" class="news-end" value="${nowStr}"></div>`;
|
||
html += `<div>${['left','center','right'].map(b => `<label><input type="checkbox" value="${b}">${b}</label>`).join(' ')}</div>`;
|
||
}
|
||
|
||
html += `</div>`;
|
||
html += `</span>`;
|
||
if (hasChildren) {
|
||
html += `<div class="details" style="display: ${isOpen};">` + generateTreeHtml(item.children, "children") + `</div>`;
|
||
}
|
||
html += `</li>`;
|
||
|
||
if (style) {
|
||
selectedTreeItemAdd = `url: ${item.url}<br>title: ${item.title}<br>name: ${item.name}<br>template: ${item.template}<br>PageMenu: ${item.PageMenu}<br>users: ${item.users}<br>group: ${item.group}`;
|
||
}
|
||
|
||
});
|
||
html += `</ul>`;
|
||
if (checkChildren == "") {
|
||
firstLi = true;
|
||
}
|
||
moveTreePage();
|
||
return html;
|
||
}
|
||
/** @brief Генерация HTML для элементов типа sitename или slogan
|
||
* @param item объект элемента
|
||
* @param id id для span
|
||
* @return HTML строки элемента
|
||
*/
|
||
function generateTreeHtmlSitename(item, id) {
|
||
let html = `<li style="position: relative; margin: 0px 0px 8px -20px;">`;
|
||
html += `<span id="${id}" class="tree-item" style="cursor: pointer; padding: 3px 6px 3px 3px;" onclick="toggleChildren(this)">`;
|
||
html += `<span class="tree-marker">${item.tag}:</span> <span class="tree-text">${item.content}</span>`;
|
||
html += `</span>`;
|
||
html += `</li>`;
|
||
return html;
|
||
}
|
||
|
||
/* перемещение страниц */
|
||
/** @brief Перемещаемый элемент дерева при drag & drop */
|
||
let draggedItem = null;
|
||
/** @brief Текущий выделенный элемент дерева */
|
||
let selectedItem = null;
|
||
/** @brief Смещение Y при перетаскивании */
|
||
let offsetY = 0;
|
||
/** @brief Начальная координата X при перетаскивании */
|
||
let startX = 0;
|
||
|
||
/** @brief Проверка, является ли child потомком parent
|
||
* @param parent родительский элемент
|
||
* @param child проверяемый элемент
|
||
* @return true если child потомок parent
|
||
*/
|
||
function isDescendant(parent, child) {
|
||
while (child) {
|
||
if (child === parent) return true;
|
||
child = child.parentNode;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
/** @brief Сброс выделения выбранного элемента */
|
||
function clearSelection() {
|
||
if (selectedItem) {
|
||
selectedItem.style.outline = '';
|
||
selectedItem = null;
|
||
}
|
||
}
|
||
|
||
/** @brief Обработка нажатия на элемент дерева для начала перемещения
|
||
* @param event событие pointerdown
|
||
*/
|
||
function moveTreePagePointerDown(event) {
|
||
if (window.treeSettingsMode != '') return;
|
||
let treeTableDiv = document.getElementById('treeTableDiv');
|
||
let li = event.target.closest('li');
|
||
if (li && event.button === 0) {
|
||
if (li.id === 'treeHtmlIndex') return;
|
||
let treeItem = event.target.closest('.tree-item');
|
||
if (!treeItem) return;
|
||
|
||
if (selectedItem && selectedItem !== li) {
|
||
selectedItem.style.outline = '';
|
||
}
|
||
selectedItem = li;
|
||
selectedItem.style.outline = '1px dashed rgb(0,0,0)';
|
||
|
||
draggedItem = li;
|
||
offsetY = event.clientY - treeItem.getBoundingClientRect().top;
|
||
li.style.opacity = '0.5';
|
||
event.preventDefault();
|
||
} else {
|
||
clearSelection();
|
||
}
|
||
}
|
||
|
||
/** @brief Обработка нажатия вне дерева для очистки выделения
|
||
* @param event событие pointerdown
|
||
*/
|
||
function moveTreePageDocumentPointerDown(event) {
|
||
const treeTableDiv = document.getElementById('treeTableDiv');
|
||
if (treeTableDiv && !treeTableDiv.contains(event.target)) {
|
||
clearSelection();
|
||
}
|
||
}
|
||
|
||
/** @brief Обработка движения мыши для перемещения элементов дерева
|
||
* @param event событие pointermove
|
||
*/
|
||
function moveTreePageDocumentPointerMove(event) {
|
||
if (window.treeSettingsMode != '') return;
|
||
let treeTableDiv = document.getElementById('treeTableDiv');
|
||
if (draggedItem && event.target.tagName !== "LI" && event.target.tagName !== "UL") {
|
||
let elemBelow = document.elementFromPoint(event.clientX, event.clientY);
|
||
let liBelow = elemBelow ? elemBelow.closest('li') : null;
|
||
if (liBelow && liBelow !== draggedItem && !isDescendant(draggedItem, liBelow)) {
|
||
let rect = liBelow.getBoundingClientRect();
|
||
let isTargetIndex = liBelow.parentElement?.closest('#treeHtmlIndex') !== null && liBelow.parentElement?.closest('#treeTableDiv')?.querySelector('#treeHtmlIndex')?.contains(liBelow.parentElement);
|
||
if (isTargetIndex) {
|
||
let parentDetails = draggedItem.parentElement.closest('.details');
|
||
if (event.clientY < rect.top + rect.height / 2) {
|
||
liBelow.parentNode.insertBefore(draggedItem, liBelow);
|
||
} else {
|
||
liBelow.parentNode.insertBefore(draggedItem, liBelow.nextSibling);
|
||
}
|
||
if (parentDetails) {
|
||
let parentUl = parentDetails.querySelector('ul');
|
||
if (parentUl && parentUl.children.length === 0) {
|
||
let parentLi = parentDetails.closest('li');
|
||
parentDetails.remove();
|
||
if (parentLi) {
|
||
let marker = parentLi.querySelector('.tree-marker');
|
||
if (marker) marker.textContent = '■';
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/** @brief Обработка отпускания мыши для завершения перемещения
|
||
*/
|
||
function moveTreePageDocumentPointerUp() {
|
||
if (window.treeSettingsMode != '') return;
|
||
if (draggedItem) {
|
||
draggedItem.style.opacity = '';
|
||
draggedItem = null;
|
||
}
|
||
}
|
||
|
||
/** @brief Обработка движения мыши внутри дерева для вложения элементов
|
||
* @param event событие pointermove
|
||
*/
|
||
function moveTreePageTreePointerMove(event) {
|
||
if (window.treeSettingsMode != '') return;
|
||
let li = event.target.closest('li');
|
||
if (li && event.button === 0 && draggedItem) {
|
||
let treeItem = event.target.closest('.tree-item');
|
||
if (!treeItem) return;
|
||
startX = event.clientX - treeItem.getBoundingClientRect().left;
|
||
let upperLi = draggedItem.previousElementSibling;
|
||
if (upperLi) {
|
||
if (startX > 60) {
|
||
let details = upperLi.querySelector('.details');
|
||
if (!details) {
|
||
details = document.createElement('div');
|
||
details.className = 'details';
|
||
let ul = document.createElement('ul');
|
||
ul.setAttribute('style', 'margin-top: 10px; margin-bottom: 10px; list-style-type: none;');
|
||
details.appendChild(ul);
|
||
upperLi.appendChild(details);
|
||
} else {
|
||
if (details.style.display === 'none') {
|
||
details.style.display = 'block';
|
||
}
|
||
}
|
||
let marker = upperLi.querySelector('.tree-marker');
|
||
if (marker) {
|
||
marker.textContent = '▼';
|
||
}
|
||
draggedItem._attachedTo = upperLi;
|
||
let detailsUl = details.querySelector('ul');
|
||
if (detailsUl && draggedItem.parentNode !== detailsUl) {
|
||
detailsUl.appendChild(draggedItem);
|
||
}
|
||
} else if (startX < 20) {
|
||
let details = upperLi.querySelector('.details');
|
||
if (details && details.contains(draggedItem)) {
|
||
let parentUl = upperLi.parentNode;
|
||
parentUl.insertBefore(draggedItem, upperLi.nextSibling);
|
||
delete draggedItem._attachedTo;
|
||
let detailsUl = details.querySelector('ul');
|
||
if (detailsUl && detailsUl.children.length === 0) {
|
||
details.style.display = 'none';
|
||
let marker = upperLi.querySelector('.tree-marker');
|
||
if (marker) {
|
||
marker.textContent = '►';
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/** @brief Обработка нажатий клавиш для управления выбранным элементом дерева
|
||
* @param event событие keydown
|
||
*/
|
||
function moveTreePageDocumentKeyDown(event) {
|
||
if (!selectedItem || window.treeSettingsMode != '') return;
|
||
|
||
let treeIndex = document.getElementById('treeHtmlIndex');
|
||
if (!treeIndex) return;
|
||
|
||
if (!treeIndex.contains(selectedItem)) return;
|
||
|
||
let parentUl = selectedItem.parentNode;
|
||
if (!treeIndex.contains(parentUl)) return;
|
||
|
||
switch (event.key) {
|
||
case 'ArrowUp': {
|
||
let prev = selectedItem.previousElementSibling;
|
||
if (prev && treeIndex.contains(prev)) {
|
||
parentUl.insertBefore(selectedItem, prev);
|
||
event.preventDefault();
|
||
}
|
||
break;
|
||
}
|
||
case 'ArrowDown': {
|
||
let next = selectedItem.nextElementSibling;
|
||
if (next && treeIndex.contains(next)) {
|
||
parentUl.insertBefore(next, selectedItem);
|
||
event.preventDefault();
|
||
}
|
||
break;
|
||
}
|
||
case 'ArrowRight': {
|
||
let prev = selectedItem.previousElementSibling;
|
||
if (!prev || !treeIndex.contains(prev)) break;
|
||
|
||
let details = prev.querySelector('.details');
|
||
if (!details) {
|
||
details = document.createElement('div');
|
||
details.className = 'details';
|
||
let ul = document.createElement('ul');
|
||
ul.style.marginTop = '10px';
|
||
ul.style.marginBottom = '10px';
|
||
ul.style.listStyleType = 'none';
|
||
details.appendChild(ul);
|
||
prev.appendChild(details);
|
||
} else if (details.style.display === 'none') {
|
||
details.style.display = 'block';
|
||
}
|
||
|
||
let marker = prev.querySelector('.tree-marker');
|
||
if (marker) marker.textContent = '▼';
|
||
|
||
let ul = details.querySelector('ul');
|
||
if (ul && selectedItem.parentNode !== ul && treeIndex.contains(ul)) {
|
||
ul.appendChild(selectedItem);
|
||
event.preventDefault();
|
||
}
|
||
break;
|
||
}
|
||
case 'ArrowLeft': {
|
||
let parentDetails = selectedItem.parentNode.closest('.details');
|
||
if (!parentDetails) break;
|
||
|
||
let parentLi = parentDetails.closest('li');
|
||
if (!parentLi) break;
|
||
|
||
let grandParentUl = parentLi.parentNode;
|
||
if (!treeIndex.contains(grandParentUl)) break;
|
||
|
||
grandParentUl.insertBefore(selectedItem, parentLi.nextSibling);
|
||
|
||
let ul = parentDetails.querySelector('ul');
|
||
if (ul.children.length === 0) {
|
||
parentDetails.remove();
|
||
let marker = parentLi.querySelector('.tree-marker');
|
||
if (marker) marker.textContent = '■';
|
||
}
|
||
event.preventDefault();
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
/** @brief Настройка обработчиков событий для перемещения элементов дерева
|
||
*/
|
||
function moveTreePage() {
|
||
const treeTableDiv = document.getElementById('treeTableDiv');
|
||
if (!treeTableDiv) return;
|
||
|
||
treeTableDiv.removeEventListener('pointerdown', moveTreePagePointerDown);
|
||
document.removeEventListener('pointerdown', moveTreePageDocumentPointerDown);
|
||
document.removeEventListener('pointermove', moveTreePageDocumentPointerMove);
|
||
document.removeEventListener('pointerup', moveTreePageDocumentPointerUp);
|
||
treeTableDiv.removeEventListener('pointermove', moveTreePageTreePointerMove);
|
||
document.removeEventListener('keydown', moveTreePageDocumentKeyDown);
|
||
|
||
treeTableDiv.addEventListener('pointerdown', moveTreePagePointerDown);
|
||
document.addEventListener('pointerdown', moveTreePageDocumentPointerDown);
|
||
document.addEventListener('pointermove', moveTreePageDocumentPointerMove);
|
||
document.addEventListener('pointerup', moveTreePageDocumentPointerUp);
|
||
treeTableDiv.addEventListener('pointermove', moveTreePageTreePointerMove);
|
||
document.addEventListener('keydown', moveTreePageDocumentKeyDown);
|
||
}
|
||
|
||
addEventListener("Loadsite_treeJs", function() {
|
||
movementMenu("treeDiv");
|
||
movementMenu("treeProperties");
|
||
|
||
/* сохронения древа */
|
||
let treeSettingsSaveId = document.getElementById("treeSettingsSave");
|
||
treeSettingsSaveId.addEventListener('click', saveTree);
|
||
|
||
function saveTree() {
|
||
let treeTableDiv = document.getElementById('treeTableDiv');
|
||
let ul = treeTableDiv.querySelector('ul');
|
||
let treeData = ul ? parseUl(ul) : [];
|
||
|
||
let siteNameElement = document.getElementById("treeHtmlSitename");
|
||
let siteName = siteNameElement ? siteNameElement.querySelector('.tree-text').textContent.trim() : '';
|
||
let sloganElement = document.getElementById("treeHtmlSlogan");
|
||
let slogan = sloganElement ? sloganElement.querySelector('.tree-text').textContent.trim() : '';
|
||
|
||
let fullData = { sitename: siteName, slogan: slogan, children: treeData };
|
||
|
||
jsonrpcRequest("saveSiteTree", { data: fullData }).then(response => {
|
||
console.log(response);
|
||
});
|
||
}
|
||
|
||
function parseUl(ul) {
|
||
let result = [];
|
||
let items = ul.children;
|
||
|
||
for (let item of items) {
|
||
let span = item.querySelector('.tree-item .tree-text');
|
||
let textName = item.querySelector('.tree-text-name');
|
||
let dataDiv = item.querySelector('.tree-data');
|
||
let childrenUl = item.querySelector('.details > ul');
|
||
|
||
if (span && dataDiv) {
|
||
let data = parseDataDiv(dataDiv);
|
||
let mainText = span.childNodes[0].textContent.trim();
|
||
let extraText = textName ? textName.textContent.trim() : "";
|
||
data['tag'] = mainText;
|
||
data['name'] = mainText + (extraText ? " " + extraText : "");
|
||
if (childrenUl) data['children'] = parseUl(childrenUl);
|
||
result.push(data);
|
||
}
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
function parseDataDiv(div) {
|
||
let data = {};
|
||
let lines = div.innerHTML.split(/<br\s*\/?>/i);
|
||
for (let line of lines) {
|
||
let parts = line.split(': ');
|
||
if (parts.length === 2) data[parts[0].trim()] = parts[1].trim();
|
||
}
|
||
let cb = div.querySelector('.news-checkbox');
|
||
if (cb) {
|
||
if (cb.checked) {
|
||
let start = div.querySelector('.news-start').value;
|
||
let end = div.querySelector('.news-end').value;
|
||
let blocks = Array.from(div.querySelectorAll('input[type="checkbox"][value]:checked'))
|
||
.map(x => x.value);
|
||
let fmt = s => s.replace(/[-:T]/g, '.');
|
||
data.news = `${fmt(start)}/${fmt(end)}${blocks.length ? ',' + blocks.join(',') : ''}`;
|
||
} else {
|
||
data.news = '';
|
||
}
|
||
}
|
||
return data;
|
||
}
|
||
|
||
/* окно настроек древа */
|
||
let targetTreeItem = "";
|
||
let treeSettingsDiv = document.getElementById('treeSettings');
|
||
|
||
function treeSettings() {
|
||
const treeDivDiv = document.getElementById('treeDiv');
|
||
if (!treeDivDiv) return;
|
||
|
||
if (window.treeSettingsMode == 'linkFromPage') {
|
||
document.getElementById('treeTopTitle').innerHTML = "{{select_page_for_link}}";
|
||
document.getElementById('treeSettingsSave').style.display = "none";
|
||
} else {
|
||
document.getElementById('treeTopTitle').innerHTML = "{{tree_site_title}}";
|
||
document.getElementById('treeSettingsSave').style.display = "block";
|
||
}
|
||
|
||
function showTreeSettings(event) {
|
||
event.preventDefault();
|
||
let treeItem = event.target.closest('.tree-item');
|
||
targetTreeItem = treeItem ? treeItem : "";
|
||
treeSettingsDiv.style.left = `${touchX}px`;
|
||
treeSettingsDiv.style.top = `${touchY}px`;
|
||
document.getElementById('treeSettingsAdd').style.display = '';
|
||
document.getElementById('treeSettingsPaste').style.display = '';
|
||
document.getElementById('treeSettingsCopy').style.display = '';
|
||
document.getElementById('treeSettingsRename').style.display = '';
|
||
document.getElementById('treeSettingsProperties').style.display = '';
|
||
document.getElementById('treeSettingsDelete').style.display = '';
|
||
document.getElementById('treeSettingsChoose').style.display = '';
|
||
treeSettingsDiv.style.visibility = targetTreeItem ? "visible" : "hidden";
|
||
let tag = targetTreeItem instanceof Element
|
||
? targetTreeItem.querySelector('.tree-text')?.textContent.trim()
|
||
: null;
|
||
if (tag == "index" && !targetTreeItem.closest('li li')) {
|
||
document.getElementById('treeSettingsRename').style.display = 'none';
|
||
document.getElementById('treeSettingsDelete').style.display = 'none';
|
||
}
|
||
if (targetTreeItem) {
|
||
let firstId = targetTreeItem.closest('li')?.firstElementChild?.id;
|
||
if (firstId == "treeHtmlSitename" || firstId == "treeHtmlSlogan") {
|
||
['Add','Copy','Paste','Delete','Properties','Choose'].forEach(id=>{
|
||
document.getElementById('treeSettings'+id).style.display = 'none';
|
||
});
|
||
}
|
||
}
|
||
if (selectedTreeItem == "") {
|
||
document.getElementById('treeSettingsPaste').style.display = 'none';
|
||
}
|
||
document.addEventListener('pointerdown', function hideMenu(e) {
|
||
if (!treeSettingsDiv.contains(e.target)) {
|
||
treeSettingsDiv.style.visibility = "hidden";
|
||
document.removeEventListener('pointerdown', hideMenu);
|
||
}
|
||
});
|
||
if (window.treeSettingsMode == 'linkFromPage') {
|
||
['Add','Copy','Paste','Delete','Rename','Properties'].forEach(id=>{
|
||
document.getElementById('treeSettings'+id).style.display = 'none';
|
||
});
|
||
} else {
|
||
document.getElementById('treeSettingsChoose').style.display = 'none';
|
||
}
|
||
if ([].slice.call(document.getElementById('treeSettingsDiv').children).every(el=>
|
||
getComputedStyle(el).display === 'none'
|
||
)) {
|
||
treeSettingsDiv.style.visibility = 'hidden';
|
||
}
|
||
}
|
||
|
||
function setupTreeSettingsHandler(){
|
||
const treeDiv=document.getElementById('treeDiv')
|
||
if(!treeDiv) return
|
||
treeDiv.removeEventListener('click',showTreeSettings)
|
||
treeDiv.oncontextmenu=null
|
||
if(isPhone){
|
||
touchLong(treeDiv,showTreeSettings)
|
||
}else{
|
||
treeDiv.oncontextmenu=showTreeSettings
|
||
}
|
||
}
|
||
|
||
setupTreeSettingsHandler();
|
||
window.addEventListener('resize', setupTreeSettingsHandler);
|
||
}
|
||
window.treeSettings = treeSettings;
|
||
|
||
/* кнопки окна настроек древа */
|
||
/* кнопки добовление страниц / начало */
|
||
let treeSettingsAddId = document.getElementById('treeSettingsAdd');
|
||
treeSettingsAddId.addEventListener('click', treeSettingsAddFun);
|
||
async function treeSettingsAddFun() {
|
||
if (window.newPageFunValue == "newPage") {
|
||
document.getElementById("saveHow").click();
|
||
messageFunction("{{save_new_page}}");
|
||
return;
|
||
}
|
||
|
||
let container = getContainer();
|
||
let pageUrl = selectedTreeItemAdd;
|
||
messageQueue.push("{{enter_tag_for_new_page}}");
|
||
let treeText = await messageCreateInput();
|
||
treeText = treeText ? treeText.trim() : "";
|
||
if (!/^[A-Za-z0-9]+$/.test(treeText)) {
|
||
messageFunction("{{tag_only_english_letters}}");
|
||
return;
|
||
}
|
||
if (treeText !== "") {
|
||
let exists = false;
|
||
let lis = container.querySelectorAll('li');
|
||
lis.forEach(function(li) {
|
||
let txt = li.querySelector('.tree-text');
|
||
if (txt && txt.textContent.trim() === treeText) {
|
||
exists = true;
|
||
}
|
||
});
|
||
if (exists) {
|
||
messageFunction("{{tag_already_exists}}");
|
||
return;
|
||
}
|
||
let newItem = createNewTreeItem(pageUrl, treeText);
|
||
let newLi = document.createElement('li');
|
||
newLi.style.position = 'relative';
|
||
newLi.style.margin = '0px 0px 8px -20px';
|
||
newLi.appendChild(newItem);
|
||
container.appendChild(newLi);
|
||
if (targetTreeItem) {
|
||
targetTreeItem.querySelector('.tree-marker').textContent = '▼';
|
||
}
|
||
}
|
||
treeSettingsDiv.style.visibility = "hidden";
|
||
}
|
||
|
||
let treeSettingsPasteId = document.getElementById('treeSettingsPaste');
|
||
treeSettingsPasteId.addEventListener('click', treeSettingsPasteFun);
|
||
async function treeSettingsPasteFun() {
|
||
let container = getContainer();
|
||
let clonedItem = selectedTreeItem.cloneNode(true);
|
||
messageQueue.push("{{enter_tag_for_new_page}}");
|
||
let treeText = await messageCreateInput();
|
||
treeText = treeText ? treeText.trim() : "";
|
||
if (!/^[A-Za-z0-9]+$/.test(treeText)) {
|
||
messageFunction("{{tag_only_english_letters}}");
|
||
return;
|
||
}
|
||
if (treeText !== "") {
|
||
let exists = false;
|
||
let lis = container.querySelectorAll('li');
|
||
lis.forEach(function(li) {
|
||
let txt = li.querySelector('.tree-text');
|
||
if (txt && txt.textContent.trim() === treeText) {
|
||
exists = true;
|
||
}
|
||
});
|
||
if (exists) {
|
||
messageFunction("{{tag_already_exists}}");
|
||
return;
|
||
}
|
||
let textSpan = clonedItem.querySelector('.tree-text');
|
||
let extractedTitle = selectedTreeItem.querySelector('.tree-data').innerHTML.match(/title:\s*([^<]+)/)?.[1].trim();
|
||
textSpan.innerHTML = `${treeText} <span class="tree-text-name"> (${extractedTitle})</span>`;
|
||
let newLi = document.createElement('li');
|
||
newLi.style.position = 'relative';
|
||
newLi.style.margin = '0px 0px 8px -20px';
|
||
newLi.appendChild(clonedItem);
|
||
container.appendChild(newLi);
|
||
if (targetTreeItem) {
|
||
targetTreeItem.querySelector('.tree-marker').textContent = '▼';
|
||
}
|
||
}
|
||
treeSettingsDiv.style.visibility = "hidden";
|
||
}
|
||
|
||
function createNewTreeItem(pageUrl, treeText) {
|
||
let newItem = document.createElement('span');
|
||
newItem.className = 'tree-item';
|
||
newItem.style.cursor = 'pointer';
|
||
newItem.style.padding = '3px 6px 3px 3px';
|
||
|
||
let marker = document.createElement('span');
|
||
marker.className = 'tree-marker';
|
||
marker.textContent = '■';
|
||
|
||
let textSpan = document.createElement('span');
|
||
textSpan.className = 'tree-text';
|
||
let extractedTitle = selectedTreeItemAdd.match(/title:\s*([^<]+)/)?.[1].trim() || "";
|
||
textSpan.innerHTML = `${treeText} <span class="tree-text-name"> (${extractedTitle})</span>`;
|
||
|
||
let dataDiv = document.createElement('div');
|
||
dataDiv.className = 'tree-data';
|
||
dataDiv.style.display = 'none';
|
||
dataDiv.innerHTML = `${pageUrl}`;
|
||
|
||
newItem.appendChild(marker);
|
||
newItem.insertAdjacentText('beforeend', ' ');
|
||
newItem.appendChild(textSpan);
|
||
newItem.appendChild(dataDiv);
|
||
|
||
newItem.setAttribute('onclick', 'toggleChildren(this)');
|
||
|
||
return newItem;
|
||
}
|
||
|
||
function getContainer() {
|
||
let container;
|
||
if (targetTreeItem) {
|
||
let li = targetTreeItem.closest('li');
|
||
let details = li.querySelector('.details');
|
||
if (!details) {
|
||
details = document.createElement('div');
|
||
details.className = 'details';
|
||
details.style.display = 'block';
|
||
li.appendChild(details);
|
||
}
|
||
let detailsUl = details.querySelector('ul');
|
||
if (!detailsUl) {
|
||
detailsUl = document.createElement('ul');
|
||
detailsUl.style.marginTop = '10px';
|
||
detailsUl.style.marginBottom = '10px';
|
||
detailsUl.style.listStyleType = 'none';
|
||
details.appendChild(detailsUl);
|
||
}
|
||
container = detailsUl;
|
||
} else {
|
||
let treeTableDiv = document.getElementById('treeTableDiv');
|
||
let mainUl = treeTableDiv.querySelector('ul');
|
||
if (!mainUl) {
|
||
mainUl = document.createElement('ul');
|
||
mainUl.style.marginTop = '10px';
|
||
mainUl.style.marginBottom = '10px';
|
||
mainUl.style.listStyleType = 'none';
|
||
treeTableDiv.appendChild(mainUl);
|
||
}
|
||
container = mainUl;
|
||
}
|
||
return container;
|
||
}
|
||
/* кнопки добовление страниц / конец */
|
||
|
||
let treeSettingsCopyId = document.getElementById('treeSettingsCopy');
|
||
treeSettingsCopyId.addEventListener('click', treeSettingsCopyFun);
|
||
function treeSettingsCopyFun() {
|
||
selectedTreeItem = targetTreeItem.cloneNode(true);
|
||
selectedTreeItem.querySelector('.tree-marker').textContent = '■';
|
||
if (selectedTreeItem.classList.contains('tree-item')) {
|
||
selectedTreeItem.style.backgroundColor = '';
|
||
} else {
|
||
let treeItem = selectedTreeItem.querySelector('.tree-item');
|
||
if (treeItem) treeItem.style.backgroundColor = '';
|
||
}
|
||
|
||
treeSettingsDiv.style.visibility = "hidden";
|
||
}
|
||
|
||
let treeSettingsRenameId = document.getElementById('treeSettingsRename');
|
||
treeSettingsRenameId.addEventListener('click', treeSettingsRenameFun);
|
||
async function treeSettingsRenameFun() {
|
||
if (targetTreeItem) {
|
||
let li = targetTreeItem.closest('li');
|
||
if (li) {
|
||
let treeText = li.querySelector('.tree-text');
|
||
if (treeText) {
|
||
let firstTextNode = treeText.firstChild;
|
||
let currentName = firstTextNode.nodeValue.trim();
|
||
messageQueue.push("{{enter_new_name}}");
|
||
let newText = await messageCreateInput();
|
||
if (newText !== null) {
|
||
newText = newText.trim();
|
||
if (!/^[A-Za-z0-9]+$/.test(newText)) {
|
||
messageFunction("{{name_only_english_letters}}");
|
||
return;
|
||
}
|
||
firstTextNode.nodeValue = newText + ' ';
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
messageFunction("{{no_item_selected}}");
|
||
}
|
||
|
||
treeSettingsDiv.style.visibility = "hidden";
|
||
}
|
||
|
||
let treeSettingsDeleteId = document.getElementById('treeSettingsDelete');
|
||
treeSettingsDeleteId.addEventListener('click', treeSettingsDeleteFun);
|
||
async function treeSettingsDeleteFun() {
|
||
if (targetTreeItem) {
|
||
let li = targetTreeItem.closest('li');
|
||
if (li) {
|
||
let parentUl = li.parentElement;
|
||
if (li.querySelector('li')) {
|
||
messageQueue.push(`{{delete_all_subpages}} ${targetTreeItem.querySelector('.tree-text').textContent}?`);
|
||
const userConfirmed = await messageCreateQuestion();
|
||
if (userConfirmed) {
|
||
li.remove();
|
||
}
|
||
} else {
|
||
li.remove();
|
||
}
|
||
if (parentUl && parentUl.children.length === 0) {
|
||
let detailsDiv = parentUl.parentElement;
|
||
if (detailsDiv && detailsDiv.classList.contains('details')) {
|
||
let parentLi = detailsDiv.closest('li');
|
||
if (parentLi) {
|
||
let marker = parentLi.querySelector('.tree-marker');
|
||
if (marker) marker.textContent = '■';
|
||
}
|
||
detailsDiv.remove();
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
messageFunction("{{no_item_selected}}");
|
||
}
|
||
|
||
let treeSettingsDiv = document.getElementById('treeSettings');
|
||
treeSettingsDiv.style.visibility = "hidden";
|
||
}
|
||
|
||
/* свойтсва страниц */
|
||
let treeSettingsPropertiesId = document.getElementById('treeSettingsProperties');;
|
||
treeSettingsPropertiesId.onclick = function() {
|
||
document.getElementById('treeProperties').style.visibility = 'hidden';
|
||
let treePropertiesId = document.getElementById('treeProperties');
|
||
let treePropertiesDivId = document.getElementById('treePropertiesDiv');
|
||
let treePropertiesTopNameId = document.getElementById('treePropertiesTopName');
|
||
let treePropertiesWindowPropertiesId = document.getElementById('treePropertiesWindowProperties');
|
||
let treePropertiesWindowRightsId = document.getElementById('treePropertiesWindowRights');
|
||
|
||
let tableProperties = document.createElement('table');
|
||
tableProperties.style.width = "100%";
|
||
tableProperties.innerHTML= treePropertiesDiv();
|
||
|
||
let tableRights = document.createElement('div');
|
||
tableRights.innerHTML = `{{no_rights}}`;
|
||
|
||
treePropertiesTopNameId.textContent = `{{properties}}`;
|
||
treePropertiesDivId.innerHTML = '';
|
||
|
||
treePropertiesWindowPropertiesId.onclick = function() {
|
||
treePropertiesDivId.innerHTML = '';
|
||
treePropertiesDivId.appendChild(tableProperties);
|
||
treePropertiesWindowPropertiesId.style.backgroundColor = "#f3f3f3";
|
||
treePropertiesWindowRightsId.style.backgroundColor = "";
|
||
};
|
||
treePropertiesWindowRightsId.onclick = function() {
|
||
treePropertiesDivId.innerHTML = '';
|
||
treePropertiesDivId.appendChild(tableRights);
|
||
treePropertiesWindowPropertiesId.style.backgroundColor = "";
|
||
treePropertiesWindowRightsId.style.backgroundColor = "#f3f3f3";
|
||
};
|
||
treePropertiesWindowPropertiesId.click();
|
||
|
||
if (treePropertiesId.style.visibility == 'hidden') {
|
||
treePropertiesId.style.visibility = 'visible';
|
||
}
|
||
|
||
let treePropertiesTopCloseId = document.getElementById('treePropertiesTopClose');
|
||
let treePropertiesDivButtonOkId = document.getElementById('treePropertiesDivButtonOk');
|
||
let treePropertiesDivButtonCancelId = document.getElementById('treePropertiesDivButtonCancel');
|
||
treePropertiesTopCloseId.onclick = function() {
|
||
treePropertiesId.style.visibility = 'hidden';
|
||
if (window.managerDataAction == "propertiesUrl") {
|
||
window.managerDataAction = "";
|
||
managerDiv.style.visibility = "hidden";
|
||
removePluginDom("manager");
|
||
}
|
||
};
|
||
treePropertiesDivButtonOkId.onclick = function() {
|
||
saveTreePropertiesDiv();
|
||
treePropertiesId.style.visibility = 'hidden';
|
||
if (window.managerDataAction == "propertiesUrl") {
|
||
window.managerDataAction = "";
|
||
managerDiv.style.visibility = "hidden";
|
||
removePluginDom("manager");
|
||
}
|
||
};
|
||
treePropertiesDivButtonCancelId.onclick = function() {
|
||
treePropertiesId.style.visibility = 'hidden';
|
||
if (window.managerDataAction == "propertiesUrl") {
|
||
window.managerDataAction = "";
|
||
managerDiv.style.visibility = "hidden";
|
||
removePluginDom("manager");
|
||
}
|
||
};
|
||
};
|
||
|
||
function saveTreePropertiesDiv() {
|
||
let treeDataDiv = lastRightClickedItem.querySelector('.tree-data');
|
||
let rows = document.querySelectorAll('#treePropertiesDiv table tr');
|
||
let newData = [];
|
||
|
||
rows.forEach(row => {
|
||
let key = row.querySelector('td:first-child').textContent.trim();
|
||
let valueCell = row.querySelector('td:last-child');
|
||
let value = '';
|
||
|
||
if (key.toLowerCase() === 'pagemenu') {
|
||
let inputs = valueCell.querySelectorAll('input');
|
||
value = Array.from(inputs)
|
||
.map(input => input.value.trim())
|
||
.filter(v => v)
|
||
.join(',');
|
||
}
|
||
else if (key.toLowerCase() === 'template') {
|
||
value = valueCell.querySelector('select').value;
|
||
}
|
||
else if (key.toLowerCase() === 'url') {
|
||
value = document.getElementById('treePropertiesDivUrlValue').textContent;
|
||
}
|
||
else if (key.toLowerCase() === 'news') {
|
||
let cb = valueCell.querySelector('.news-checkbox');
|
||
let parts = [];
|
||
parts.push(`<div><input type="checkbox" class="news-checkbox"${cb.checked?' checked':''}>show in news</div>`);
|
||
let start = valueCell.querySelector('.news-start').value;
|
||
let end = valueCell.querySelector('.news-end').value;
|
||
parts.push(`<div><input type="datetime-local" class="news-start" value="${start}"><input type="datetime-local" class="news-end" value="${end}"></div>`);
|
||
let blocks = Array.from(valueCell.querySelectorAll('input[type="checkbox"][value]'))
|
||
.map(x => `<label><input type="checkbox" value="${x.value}"${x.checked?' checked':''}>${x.value}</label>`);
|
||
parts.push(`<div>${blocks.join(' ')}</div>`);
|
||
value = parts.join('');
|
||
} else {
|
||
let input = valueCell.querySelector('input');
|
||
value = input ? input.value : valueCell.textContent.trim();
|
||
}
|
||
|
||
newData.push(`${key}: ${value}`);
|
||
});
|
||
|
||
if (treeDataDiv) {
|
||
treeDataDiv.innerHTML = newData.join('<br>');
|
||
let name = newData.find(d => d.startsWith('name: '))?.split(': ')[1] || '';
|
||
let title = newData.find(d => d.startsWith('title: '))?.split(': ')[1] || '';
|
||
|
||
let textSpan = lastRightClickedItem.querySelector('.tree-text');
|
||
if (textSpan) {
|
||
textSpan.childNodes[0].textContent = name + ' ';
|
||
let nameSpan = textSpan.querySelector('.tree-text-name');
|
||
if (nameSpan) nameSpan.innerHTML = ` (${title})`;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Выбор страницы для ссылки */
|
||
let treeSettingsChooseId = document.getElementById('treeSettingsChoose');
|
||
treeSettingsChooseId.addEventListener('click', treeSettingsChooseFun);
|
||
function treeSettingsChooseFun() {
|
||
var parts = [];
|
||
var span = targetTreeItem;
|
||
var li = span.closest('li');
|
||
while (li) {
|
||
var itemSpan = li.querySelector(':scope > span.tree-item');
|
||
var text = itemSpan.querySelector('.tree-text').firstChild.textContent.trim();
|
||
parts.unshift(text);
|
||
li = li.parentElement.closest('li');
|
||
}
|
||
if (parts[0] === 'index') {
|
||
parts.shift();
|
||
}
|
||
var url;
|
||
if (parts.length) {
|
||
url = 'http://slava.home/' + parts.join('/') + '.html';
|
||
} else {
|
||
url = 'http://slava.home';
|
||
}
|
||
document.getElementById('link2').value = url;
|
||
document.getElementById("treeDiv").style.visibility = "hidden";
|
||
document.getElementById("treeProperties").style.visibility = "hidden";
|
||
document.getElementById("treeSettings").style.visibility = "hidden";
|
||
removePluginDom("site_tree")
|
||
}
|
||
|
||
function treePropertiesDiv() {
|
||
if (!treePropertiesDivElement) return "";
|
||
let dataHTML = treePropertiesDivElement.querySelector('.tree-data')?.innerHTML || "";
|
||
let lines = dataHTML.split('<br>');
|
||
let rows = [];
|
||
for (let line of lines) {
|
||
if (!line.trim()) continue;
|
||
let parts = line.split(':');
|
||
let key = parts[0].trim();
|
||
let value = parts.slice(1).join(':').trim();
|
||
let inputHtml = value;
|
||
|
||
if (key.toLowerCase() === "url") {
|
||
inputHtml = `<span id="treePropertiesDivUrlValue" style="font-size: inherit;">${value}</span> <button id="treePropertiesDivUrl" type="button">{{select}}</button>`;
|
||
} else if (key.toLowerCase() === "title") {
|
||
inputHtml = `<input type="text" value="${value}" style="font-size: inherit;">`;
|
||
} else if (key.toLowerCase() === "name") {
|
||
inputHtml = `<input type="text" value="${value}" style="font-size: inherit;" pattern="[A-Za-z0-9]+" oninput="this.value = this.value.replace(/[^A-Za-z0-9]/g, '')">`;
|
||
} else if (key.toLowerCase() === "template") {
|
||
let defaultTemplate = value;
|
||
inputHtml = `<select id="templateSelect" style="font-size: inherit;"><option selected>${defaultTemplate}</option></select>`;
|
||
setTimeout(function() {
|
||
jsonrpcRequest("getFolders", { folder: "/template" }).then(data => {
|
||
let select = document.getElementById('templateSelect');
|
||
if (select && data) {
|
||
select.innerHTML = '';
|
||
data.forEach(folder => {
|
||
select.innerHTML += `<option${folder === defaultTemplate ? ' selected' : ''}>${folder}</option>`;
|
||
});
|
||
}
|
||
});
|
||
}, 0);
|
||
} else if (key.toLowerCase() === "pagemenu") {
|
||
let values = value.split(",");
|
||
while (values.length < 3) {
|
||
values.push("");
|
||
}
|
||
inputHtml = values.map((val, index) =>
|
||
`<input type="text" value="${val}" style="width: 30px; font-size: inherit; text-align: center;"
|
||
oninput="this.value = this.value.replace(/[^0-9]/g, '');">`
|
||
).join(" , ");
|
||
} else if (key.toLowerCase() === "users") {
|
||
inputHtml = `<input type="text" value="${value}" style="font-size: inherit;" readonly disabled>`;
|
||
} else if (key.toLowerCase() === "group") {
|
||
inputHtml = `<input type="text" value="${value}" style="font-size: inherit;" readonly disabled>`;
|
||
}
|
||
|
||
|
||
rows.push(`<tr><td class="managerPropertiesDivDivs">${key}</td><td class="managerPropertiesDivDivs">${inputHtml}</td></tr>`);
|
||
}
|
||
setTimeout(treePropertiesDivFunc, 0);
|
||
return `<table style="width: 100%;">${rows.join('')}</table>`;
|
||
}
|
||
function treePropertiesDivFunc() {
|
||
document.getElementById('treePropertiesDivUrl').onclick = async function() {
|
||
await includePlugin("manager");
|
||
window.managerDataAction = "propertiesUrl";
|
||
managerData("/content");
|
||
managerDiv.style.visibility = "visible";
|
||
document.getElementById("settingsMain_d").style.visibility = "hidden";
|
||
};
|
||
}
|
||
|
||
let lastRightClickedItem = null;
|
||
let treePropertiesDivElement = null;
|
||
function treeContext(event){
|
||
let treeItem=event.target.closest('.tree-item')
|
||
if(treeItem){
|
||
lastRightClickedItem=treeItem
|
||
treePropertiesDivElement=treeItem
|
||
}
|
||
}
|
||
document.addEventListener('contextmenu',treeContext)
|
||
touchLong(document,treeContext)
|
||
|
||
}, { once: true }); /* начало */
|