Initial commit

This commit is contained in:
2025-07-27 18:47:50 +03:00
parent ae1ed5b41b
commit d127006d9d
196 changed files with 72333 additions and 0 deletions

View File

@@ -0,0 +1,145 @@
<?php
/* перебор файлов и папок */
function scanDirTree() {
global $path, $_SESSION;
$file = $path . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'filepath.' . $_SESSION['lng'] . '.php';
$content = file_get_contents($file);
$xml = simplexml_load_string($content);
function generateArray($xml, $urlParts = [], $isOpen = false) {
$result = [];
foreach ($xml->children() as $child) {
$item = [
'name' => (string) $child->attributes()->name,
'tag' => $child->getName(),
'children' => generateArray($child, $urlParts, $isOpen),
'isOpen' => false,
'url' => (string) $child->attributes()->url,
'title' => (string) $child->attributes()->title,
'template' => (string) $child->attributes()->template,
'PageMenu' => (string) $child->attributes()->PageMenu,
'users' => (string) $child->attributes()->users,
'group' => (string) $child->attributes()->group,
'content' => (string)$child
];
if (!empty($urlParts) && $urlParts[0] == $item['tag']) {
array_shift($urlParts);
if (empty($urlParts)) {
if ($item['tag'] !== 'index') {
$item['isOpen'] = true;
}
}
}
$result[] = $item;
}
return $result;
}
$url = $_SERVER['REQUEST_URI'];
$urlParts = array_filter(explode('/', trim($url, '/')));
$urlParts = array_map(function($part) {
return pathinfo($part, PATHINFO_FILENAME);
}, $urlParts);
$result = generateArray($xml, $urlParts);
return ['items' => $result];
}
/* сохронения древа */
function saveTree($params) {
global $path, $_SESSION;
$file = $path . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'filepath.' . ($_SESSION['lng'] ?? 'en') . '.php';
if (!isset($params['data']) || !is_array($params['data'])) {
return ['status' => 'error', 'message' => 'Invalid data'];
}
$treeData = $params['data'];
$siteName = isset($treeData['sitename']) ? htmlspecialchars($treeData['sitename'], ENT_XML1 | ENT_QUOTES, 'UTF-8') : '';
$slogan = isset($treeData['slogan']) ? htmlspecialchars($treeData['slogan'], ENT_XML1 | ENT_QUOTES, 'UTF-8') : '';
$xmlContent = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n".
"<site>\n".
" <sitename>$siteName</sitename>\n".
" <slogan>$slogan</slogan>\n".
buildXml($treeData['children'] ?? []).
"</site>\n";
$result = @file_put_contents($file, $xmlContent);
if ($result === false) {
return ['status' => 'error', 'message' => 'Failed to write file'];
}
if (!file_exists($file)) {
return ['status' => 'error', 'message' => 'File does not exist after write'];
}
return ['status' => 'success', 'message' => 'File saved successfully'];
}
function buildXml($data, $level = 1) {
$xml = "";
foreach ($data as $item) {
if (!isset($item['name']) || empty($item['name'])) continue;
$tag = htmlspecialchars(trim(explode(' ', $item['name'])[0]), ENT_XML1 | ENT_QUOTES, 'UTF-8');
$nameAttr = htmlspecialchars(trim($tag), ENT_XML1 | ENT_QUOTES, 'UTF-8');
$attributes = [
'url' => isset($item['url']) ? htmlspecialchars($item['url'], ENT_XML1 | ENT_QUOTES, 'UTF-8') : '',
'title' => isset($item['title']) ? htmlspecialchars($item['title'], ENT_XML1 | ENT_QUOTES, 'UTF-8') : '',
'name' => $nameAttr,
'template' => isset($item['template']) ? htmlspecialchars($item['template'], ENT_XML1 | ENT_QUOTES, 'UTF-8') : '',
'PageMenu' => isset($item['PageMenu']) ? htmlspecialchars($item['PageMenu'], ENT_XML1 | ENT_QUOTES, 'UTF-8') : '',
'users' => isset($item['users']) ? htmlspecialchars($item['users'], ENT_XML1 | ENT_QUOTES, 'UTF-8') : '',
'group' => isset($item['group']) ? htmlspecialchars($item['group'], ENT_XML1 | ENT_QUOTES, 'UTF-8') : ''
];
$attrString = "";
foreach ($attributes as $key => $value) {
$attrString .= " $key='$value'";
}
$xml .= str_repeat(" ", $level) . "<$tag$attrString>\n";
if (!empty($item['children'])) {
$xml .= buildXml($item['children'], $level + 1);
}
$xml .= str_repeat(" ", $level) . "</$tag>\n";
}
return $xml;
}
/* текущий файл страницы */
function currentPageFile() {
global $_SESSION, $path;
return ['folders' => $folders];
}
/* текущий файл страницы */
function getFolderNames() {
global $path;
$folder = $path . DIRECTORY_SEPARATOR . $_POST['folder'];
$folders = array();
if ($folder && is_dir($folder)) {
$files = scandir($folder);
foreach ($files as $file) {
if ($file != '.' && $file != '..' && is_dir($folder . DIRECTORY_SEPARATOR . $file)) {
$folders[] = $file;
}
}
}
return ['folders' => $folders];
}
?>

View File

@@ -0,0 +1,8 @@
<?php
$lang = include $path . 'lang.php';
$lng = $_GET['lng'] ?? ($_SESSION['lng'] ?? 'en');
$placeholders = [];
foreach ($lang[$lng] as $key => $value) {
$placeholders['{{' . $key . '}}'] = $value;
}
echo strtr(file_get_contents($path . 'site_tree.js'), $placeholders);

View File

@@ -0,0 +1,83 @@
<?php
$lang = [
'ru' => [
'tree_site_title' => 'Дерево сайта',
'save' => 'Сохранить',
'add' => 'Добавить',
'paste' => 'Вставить',
'copy' => 'Копировать',
'rename' => 'Переименовать',
'properties' => 'Свойства',
'delete' => 'Удалить',
'choose' => 'Выбрать',
'enter_new_name' => 'Введите новое имя',
'name_only_english_letters' => 'Имя должно содержать только английские буквы без пробелов и символов.',
'no_item_selected' => 'Нет выбранного элемента.',
'delete_all_subpages' => 'Удалить все подстраницы страницы',
'save_new_page' => 'Сохраните новую страницу!',
'enter_tag_for_new_page' => 'Введите тег для новой страницы:',
'tag_only_english_letters' => 'Тег должен содержать только английские буквы без пробелов и символов.',
'tag_already_exists' => 'Элемент с таким тегом уже существует.',
'select_page_for_link' => 'Выбор страницы для ссылки',
'rights' => 'Права',
'ok' => 'ОК',
'cancel' => 'Отмена',
'select' => 'Выбрать',
'no_rights' => 'Прав пока что нету',
'properties' => 'Свойства',
],
'en' => [
'tree_site_title' => 'Site Tree',
'save' => 'Save',
'add' => 'Add',
'paste' => 'Paste',
'copy' => 'Copy',
'rename' => 'Rename',
'properties' => 'Properties',
'delete' => 'Delete',
'choose' => 'Choose',
'enter_new_name' => 'Enter new name',
'name_only_english_letters' => 'Name must contain only English letters without spaces and symbols.',
'no_item_selected' => 'No item selected.',
'delete_all_subpages' => 'Delete all subpages of the page',
'save_new_page' => 'Save the new page!',
'enter_tag_for_new_page' => 'Enter tag for the new page:',
'tag_only_english_letters' => 'Tag must contain only English letters without spaces and symbols.',
'tag_already_exists' => 'An element with this tag already exists.',
'select_page_for_link' => 'Select page for the link',
'rights' => 'Rights',
'ok' => 'OK',
'cancel' => 'Cancel',
'select' => 'Select',
'no_rights' => 'No rights yet',
'properties' => 'Properties',
],
'lv' => [
'tree_site_title' => 'Saites koks',
'save' => 'Saglabāt',
'add' => 'Pievienot',
'paste' => 'Ielīmēt',
'copy' => 'Kopēt',
'rename' => 'Pārdēvēt',
'properties' => 'Īpašības',
'delete' => 'Dzēst',
'choose' => 'Izvēlēties',
'enter_new_name' => 'Ievadiet jaunu nosaukumu',
'name_only_english_letters' => 'Nosaukumam jābūt tikai angļu burtiem bez atstarpēm un simboliem.',
'no_item_selected' => 'Nav izvēlēts neviens elements.',
'delete_all_subpages' => 'Dzēst visas apakšlapas',
'save_new_page' => 'Saglabājiet jauno lapu!',
'enter_tag_for_new_page' => 'Ievadiet taga nosaukumu jaunai lapai:',
'tag_only_english_letters' => 'Tagam jābūt tikai angļu burtiem bez atstarpēm un simboliem.',
'tag_already_exists' => 'Tādā pašā tagā jau pastāv elements.',
'select_page_for_link' => 'Izvēlieties lapu saitei',
'rights' => 'Tiesības',
'ok' => 'Labi',
'cancel' => 'Atcelt',
'select' => 'Izvēlēties',
'no_rights' => 'Nav tiesību vēl',
'properties' => 'Īpašības',
],
];
return $lang;

View File

@@ -0,0 +1,18 @@
<?php
global $path, $_SESSION, $configAdmins;
$lang = include $path . 'main_plugin/site_tree/lang.php';
$lng = $_SESSION['lng'] ?? 'en';
if (in_array($_SESSION['username'], $configAdmins, true)) {
include_once $path . 'main_plugin/site_tree/func.site_tree.php';
$Html = file_get_contents($path . 'main_plugin/site_tree/site_tree.php');
foreach ($lang[$lng] as $key => $value) {
$Html = str_replace('{{' . $key . '}}', $value, $Html);
}
echo $Html;
echo '<link rel="stylesheet" href="/main_plugin/site_tree/site_tree.css">';
echo '<script type="text/javascript" src="/main_plugin/site_tree/lang.js.php?lng=' . $lng . '"></script>';
}
?>

View File

@@ -0,0 +1,189 @@
/* основной div */
#treeDiv {
display: inline-block;
position: fixed;
z-index: 100;
user-select: none;
background-color: rgba(255, 255, 255, 0.92);
border: 1px solid #000000;
width: 800px;
border-radius: 5px;
height: 600px;
font-size: 1em;
box-shadow: 0px 0px 5px #777;
max-width: calc(100% - 20px);
}
/* верхний див */
#treeTop {
text-align: center;
border-bottom: 1px #40464d solid;
padding: 5px;
background-color: rgba(255, 255, 255, 0.6);
}
#treeTopTitle {
text-align: center;
}
#treeCloseFun {
float: right;
width: 20px;
height: 20px;
background-position: -159px -121px;
cursor: pointer;
}
/* основное древо сайта */
#treeTableDiv {
top: 36px;
height: 480px;
position: relative;
margin: 0px 20px 0px 20px;
border: 1px #40464d solid;
overflow-y: overlay;
border-radius: 5px;
width: calc(100% - 40px);
}
li.has-children::marker {
content: "► ";
}
li.has-children.open::marker {
content: "▼ ";
}
li.no-children::marker {
content: "□ ";
}
/* окно страницы */
.tree-details {
display: none;
background: white;
border: 1px solid black;
padding: 3px;
margin: 3px 0px 10px 0px;
z-index: 10;
position: relative;
width: max-content;
}
.tree-details div {
margin: 3px;
}
/* окно настроек */
#treeSettings {
display: inline-block;
position: fixed;
z-index: 100;
user-select: none;
background-color: rgba(255, 255, 255, 0.92);
border: 1px solid #000000;
border-radius: 5px;
font-size: 1em;
box-shadow: 0px 0px 5px #777;
padding: 5px;
animation: fadeIn 0.5s ease-in forwards;
}
.treeSettingsButtons {
background-color: rgba(255, 255, 255, 1);
border-radius: 5px;
padding: 2px;
margin: 3px;
cursor: pointer;
display: block;
}
.treeSettingsButtons:hover {
color: #787878;
}
#treeSettingsSave {
border: 1px #40464d solid;
border-radius: 5px;
padding: 5px;
margin: 44px 20px 0px 10px;
cursor: pointer;
display: block;
width: 91px;
float: right;
}
#treeSettingsSave:hover {
color: #787878;
}
/* окно свойств */
#treeProperties {
display: inline-block;
position: fixed;
z-index: 100;
user-select: none;
background-color: rgba(255, 255, 255, 0.97);
border: 1px solid #000000;
border-radius: 5px;
font-size: 1em;
box-shadow: 0px 0px 5px #777;
width: 600px;
max-width: calc(100% - 20px);
}
#treePropertiesMiddle {
margin: 0px 20px 0px 20px;
}
#treePropertiesTop {
border-bottom: 1px solid #000000;
padding: 5px;
text-align: center;
}
#treePropertiesTopName {
text-align: center;
}
#treePropertiesTopClose {
float: right;
width: 20px;
height: 20px;
background-position: -159px -121px;
cursor: pointer;
}
#treePropertiesWindow {
text-align: center;
display: flex;
}
.treePropertiesWindowDiv {
cursor: pointer;
text-align: center;
width: 50%;
display: inline-block;
padding: 9px;
}
.treePropertiesWindowDiv:hover {
color: #787878;
}
#treePropertiesDiv {
border: 1px solid #000000;
border-radius: 5px;
}
.treePropertiesDivDivs {
padding: 6px 8px 6px 8px;
border-radius: 5px;
}
#treePropertiesDivButtons {
padding: 9px;
display: flex;
justify-content: right;
align-items: center;
}
#treePropertiesDivButtons {
text-align: center;
}
.treePropertiesDivButton {
margin: 3px 3px 3px 15px;
cursor: pointer;
display: inline-block;
}
.treePropertiesDivButton:hover {
color: #787878;
}

View File

@@ -0,0 +1,795 @@
/* показ окна древа */
let idCounter = 0;
siteTreeGeneration();
/* генерация html древа */
function siteTreeGeneration() {
handleJsonRpcRequest('scanDirTree', {}, 1).then(data => {
if (data && Array.isArray(data.items)) {
let treeContainer = document.getElementById('treeTableDiv');
treeContainer.innerHTML = generateTreeHtml(data.items, "");
let firstSpan = treeContainer.querySelector('span');
if (!treeContainer.innerHTML.includes('background-color: #e5f0ff;')) {
firstSpan.style.backgroundColor = '#e5f0ff';
}
}
}).catch(err => {
console.error(err);
});
idCounter = 0;
}
window.siteTreeGeneration = siteTreeGeneration;
let selectedTreeItem = "";
let selectedTreeItemAdd = "";
let firstLi = true;
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">&nbsp;(${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}`;
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;
}
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;
}
/* перемещение страниц */
function moveTreePage() {
let treeTableDiv = document.getElementById('treeTableDiv');
let draggedItem = null;
let offsetY = 0;
let startX = 0;
function isDescendant(parent, child) {
while (child) {
if (child === parent) return true;
child = child.parentNode;
}
return false;
}
treeTableDiv.addEventListener('pointerdown', function(event) {
if (window.treeSettingsMode != '') return;
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;
draggedItem = li;
offsetY = event.clientY - treeItem.getBoundingClientRect().top;
li.style.opacity = '0.5';
event.preventDefault();
}
});
document.addEventListener('pointermove', function(event) {
if (window.treeSettingsMode != '') return;
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 = '■';
}
}
}
}
}
}
});
document.addEventListener('pointerup', function() {
if (window.treeSettingsMode != '') return;
if (draggedItem) {
draggedItem.style.opacity = '';
draggedItem = null;
}
});
treeTableDiv.addEventListener('pointermove', function(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 = '►';
}
}
}
}
}
}
});
}
addEventListener("load", 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 };
handleJsonRpcRequest('saveTree', fullData, 1).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();
}
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);
function treeSettingsAddFun() {
if (window.newPageFunValue == "newPage") {
document.getElementById("saveHow").click();
messageFunction("{{save_new_page}}");
return;
}
let container = getContainer();
let pageUrl = selectedTreeItemAdd;
let treeText = prompt("{{enter_tag_for_new_page}}");
treeText = treeText ? treeText.trim() : "";
if (!/^[A-Za-z]+$/.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) {
alert("{{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);
function treeSettingsPasteFun() {
let container = getContainer();
let clonedItem = selectedTreeItem.cloneNode(true);
let treeText = prompt("{{enter_tag_for_new_page}}");
treeText = treeText ? treeText.trim() : "";
if (!/^[A-Za-z]+$/.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">&nbsp;(${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">&nbsp;(${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);
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();
let newText = prompt("{{enter_new_name}}", currentName);
if (newText !== null) {
newText = newText.trim();
if (!/^[A-Za-z]+$/.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() {
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";
}
};
treePropertiesDivButtonOkId.onclick = function() {
saveTreePropertiesDiv();
treePropertiesId.style.visibility = 'hidden';
if (window.managerDataAction == "propertiesUrl") {
window.managerDataAction = "";
managerDiv.style.visibility = "hidden";
}
};
treePropertiesDivButtonCancelId.onclick = function() {
treePropertiesId.style.visibility = 'hidden';
if (window.managerDataAction == "propertiesUrl") {
window.managerDataAction = "";
managerDiv.style.visibility = "hidden";
}
};
};
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 {
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 = `&nbsp;(${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";
}
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-z]+" oninput="this.value = this.value.replace(/[^A-Za-z]/g, '')">`;
} else if (key.toLowerCase() === "template") {
let defaultTemplate = value;
inputHtml = `<select id="templateSelect" style="font-size: inherit;"><option selected>${defaultTemplate}</option></select>`;
setTimeout(function() {
handleJsonRpcRequest('getFolderNames', { folder: '/template' }, 1).then(data => {
let select = document.getElementById('templateSelect');
if (select && data && data.folders) {
select.innerHTML = '';
data.folders.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 = function() {
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)
}); /* начало */

View File

@@ -0,0 +1,39 @@
<div id="treeDiv" style="visibility: hidden; top: 20%; left: 50%; transform: translate(-50%, -20%);">
<div id="treeTop">
<span id="treeTopTitle">{{tree_site_title}}</span>
<span id="treeCloseFun" class="editib"></span>
</div>
<div id="treeTableDiv"></div>
<span id="treeSettingsSave">{{save}}</span>
</div>
<div id="treeSettings" style="visibility: hidden; top: 0px; left: 0px;">
<div id="treeSettingsDiv">
<span id="treeSettingsAdd" class="treeSettingsButtons">{{add}}</span>
<span id="treeSettingsPaste" class="treeSettingsButtons">{{paste}}</span>
<span id="treeSettingsCopy" class="treeSettingsButtons">{{copy}}</span>
<span id="treeSettingsRename" class="treeSettingsButtons">{{rename}}</span>
<span id="treeSettingsProperties" class="treeSettingsButtons">{{properties}}</span>
<span id="treeSettingsDelete" class="treeSettingsButtons">{{delete}}</span>
<span id="treeSettingsChoose" class="treeSettingsButtons">{{choose}}</span>
</div>
</div>
<div id="treeProperties" style="visibility: hidden; top: 20%; left: 50%; transform: translate(-50%, -20%);">
<div id="treePropertiesTop">
<span id="treePropertiesTopName" class="treePropertiesTop"></span>
<span id="treePropertiesTopClose" class="editib"></span>
</div>
<div id="treePropertiesMiddle">
<div id="treePropertiesWindow">
<span id="treePropertiesWindowProperties" class="treePropertiesWindowDiv">{{properties}}</span>
<span id="treePropertiesWindowRights" class="treePropertiesWindowDiv">{{rights}}</span>
</div>
<div id="treePropertiesDiv">
</div>
<div id="treePropertiesDivButtons">
<div id="treePropertiesDivButtonOk" class="treePropertiesDivButton">{{ok}}</div>
<div id="treePropertiesDivButtonCancel" class="treePropertiesDivButton">{{cancel}}</div>
</div>
</div>
</div>