add blocks
This commit is contained in:
12
static/blocks/pages/userSlava/content.md
Normal file
12
static/blocks/pages/userSlava/content.md
Normal file
@@ -0,0 +1,12 @@
|
||||
<div id="hed">
|
||||
|
||||
# Пользователи
|
||||
|
||||
</div>
|
||||
|
||||
<div id="regPanel">
|
||||
<a href="/userSlava/?sPage=login" data-link="true" data-target="regDiv">Вход</a>
|
||||
<a href="/userSlava/?sPage=new" data-link="true" data-target="regDiv">Регистрация</a>
|
||||
<a href="/userSlava/?sPage=edit" data-link="true" data-target="regDiv">Пользователи</a>
|
||||
</div>
|
||||
<div id="regDiv"></div>
|
||||
18
static/blocks/pages/userSlava/edit/content.md
Normal file
18
static/blocks/pages/userSlava/edit/content.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# Пользовательская панель
|
||||
|
||||
user:<br>
|
||||
<select id="users_list"></select>
|
||||
<button id="btn_load_users">Load Users</button><br>
|
||||
|
||||
ID:<br>
|
||||
<input id="ud_id" placeholder="ID"><br>
|
||||
user:<br>
|
||||
<input id="ud_username" placeholder="Username"><br>
|
||||
password:<br>
|
||||
<input id="ud_password" placeholder="New password" type="password">
|
||||
<button class="toggle-pass" type="button">👁</button><br><br>
|
||||
|
||||
<button id="btn_update_pass">Update password</button>
|
||||
<button id="btn_delete_user">Delete user</button>
|
||||
|
||||
<div class="blockTest">ТестТестТест</div>
|
||||
56
static/blocks/pages/userSlava/edit/script.js
Normal file
56
static/blocks/pages/userSlava/edit/script.js
Normal file
@@ -0,0 +1,56 @@
|
||||
users_list.onchange = (e) => getUserData(e.target.value);
|
||||
async function getUserData(username) {
|
||||
const data = await apiProtected(
|
||||
`/api/users/getUserData?username=${encodeURIComponent(username)}`
|
||||
);
|
||||
|
||||
ud_id.value = data.ID;
|
||||
ud_username.value = data.Username;
|
||||
}
|
||||
|
||||
btn_load_users.onclick = getUsersNames;
|
||||
document.addEventListener("DOMContentLoaded", getUsersNames);
|
||||
async function getUsersNames() {
|
||||
const users = await apiProtected("/api/users/getUsersNames");
|
||||
|
||||
users_list.innerHTML = "";
|
||||
users.forEach((username) => {
|
||||
const opt = document.createElement("option");
|
||||
opt.value = username;
|
||||
opt.textContent = username;
|
||||
users_list.appendChild(opt);
|
||||
});
|
||||
|
||||
if (users.length > 0) getUserData(users[0]);
|
||||
}
|
||||
|
||||
btn_update_pass.onclick = updatePassword;
|
||||
async function updatePassword() {
|
||||
const username = ud_username.value;
|
||||
const password = ud_password.value;
|
||||
|
||||
if (!password) return alert("Введите пароль");
|
||||
|
||||
await apiProtected("/api/users/update_password", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ username, password })
|
||||
});
|
||||
|
||||
alert("Пароль обновлён");
|
||||
}
|
||||
|
||||
btn_delete_user.onclick = deleteUser;
|
||||
async function deleteUser() {
|
||||
const username = users_list.value;
|
||||
if (!username) return;
|
||||
|
||||
if (!confirm(`Удалить пользователя "${username}"?`)) return;
|
||||
|
||||
await apiProtected("/api/users/delete_user", {
|
||||
method: "Delete",
|
||||
body: JSON.stringify({ username })
|
||||
});
|
||||
|
||||
alert("Удалён");
|
||||
getUsersNames();
|
||||
}
|
||||
1
static/blocks/pages/userSlava/edit/style.css
Normal file
1
static/blocks/pages/userSlava/edit/style.css
Normal file
@@ -0,0 +1 @@
|
||||
.blockTest { color: blue; }
|
||||
17
static/blocks/pages/userSlava/login/content.md
Normal file
17
static/blocks/pages/userSlava/login/content.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# Вход
|
||||
|
||||
user:<br>
|
||||
<input id="log_user" placeholder="Username">
|
||||
<br>
|
||||
password:<br>
|
||||
<input id="log_pass" placeholder="password" type="password"><button class="toggle-pass" type="button">👁</button>
|
||||
<br><br>
|
||||
<button id="btn_log">Login</button>
|
||||
<br>
|
||||
|
||||
<button id="btn_prot">Protected</button>
|
||||
<pre id="out"></pre>
|
||||
|
||||
<button id="btn_logout">Logout</button>
|
||||
|
||||
<div class="blockTest">ТестТестТест</div>
|
||||
37
static/blocks/pages/userSlava/login/script.js
Normal file
37
static/blocks/pages/userSlava/login/script.js
Normal file
@@ -0,0 +1,37 @@
|
||||
btn_logout.onclick = UserLogout;
|
||||
btn_log.onclick = UserLogin;
|
||||
|
||||
btn_prot.onclick = async () => {
|
||||
try {
|
||||
const data = await apiProtected("/api/protected");
|
||||
out.textContent = JSON.stringify(data, null, 2);
|
||||
} catch {
|
||||
out.textContent = "err";
|
||||
}
|
||||
};
|
||||
|
||||
async function UserLogout() {
|
||||
accessToken = "";
|
||||
await fetch("/api/users/logout", { method: "POST", credentials: "include" });
|
||||
};
|
||||
|
||||
async function UserLogin() {
|
||||
const u = log_user.value,
|
||||
p = log_pass.value;
|
||||
|
||||
const r = await fetch("/api/users/login", {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ username: u, password: p })
|
||||
});
|
||||
|
||||
if (!r.ok) return alert("Ошибка");
|
||||
|
||||
const data = await r.json();
|
||||
accessToken = data.access_token;
|
||||
alert("logged in");
|
||||
log_user.value = "",
|
||||
log_pass.value = "";
|
||||
|
||||
};
|
||||
1
static/blocks/pages/userSlava/login/style.css
Normal file
1
static/blocks/pages/userSlava/login/style.css
Normal file
@@ -0,0 +1 @@
|
||||
.blockTest { color: green; }
|
||||
9
static/blocks/pages/userSlava/new/content.md
Normal file
9
static/blocks/pages/userSlava/new/content.md
Normal file
@@ -0,0 +1,9 @@
|
||||
# Регистрация
|
||||
|
||||
user:<br>
|
||||
<input id="reg_user" placeholder="Username"><br>
|
||||
password:<br>
|
||||
<input id="reg_pass" placeholder="password" type="password"><button class="toggle-pass" type="button">👁</button><br><br>
|
||||
<button id="btn_reg">Register</button>
|
||||
|
||||
<div class="blockTest">ТестТестТест</div>
|
||||
16
static/blocks/pages/userSlava/new/script.js
Normal file
16
static/blocks/pages/userSlava/new/script.js
Normal file
@@ -0,0 +1,16 @@
|
||||
async function UserReg() {
|
||||
const u = reg_user.value,
|
||||
p = reg_pass.value;
|
||||
|
||||
const r = await fetch("/api/users/register", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ username: u, password: p })
|
||||
});
|
||||
|
||||
if (!r.ok) return alert("Ошибка");
|
||||
alert("ok");
|
||||
reg_user.value = "",
|
||||
reg_pass.value = "";
|
||||
};
|
||||
btn_reg.onclick = UserReg;
|
||||
1
static/blocks/pages/userSlava/new/style.css
Normal file
1
static/blocks/pages/userSlava/new/style.css
Normal file
@@ -0,0 +1 @@
|
||||
.blockTest { color: red; }
|
||||
15
static/blocks/pages/userSlava/popup/content.md
Normal file
15
static/blocks/pages/userSlava/popup/content.md
Normal file
@@ -0,0 +1,15 @@
|
||||
<br><br>
|
||||
<input type="text" id="inputPopup" style="width: 600px;" value=''><br>
|
||||
<div>{"act":"message","text":"Тест"}</div><br>
|
||||
<div>{"act":"menu","text":"Удалить, Редактировать, Копировать, Удалить"}</div><br>
|
||||
<div>
|
||||
{
|
||||
"act":"file",
|
||||
"text":{
|
||||
"Общие":{"Имя":"photo.png","Тип":"PNG изображение","Размер":"826 KB","Путь":"C:/Users/Admin/Desktop/","Дата изменения":"06.12.2025 13:40"},
|
||||
"Безопасность":{"Чтение":"✔","Запись":"✔","Исполнение":"✖","Владелец":"Admin"},
|
||||
"Подробно":{"Формат":"Portable Network Graphics","Кодировка":"N/A","Атрибуты":"archive"}
|
||||
}
|
||||
}
|
||||
</div><br>
|
||||
<button id="createPopup">Сообщения</button>
|
||||
234
static/blocks/pages/userSlava/popup/script.js
Normal file
234
static/blocks/pages/userSlava/popup/script.js
Normal file
@@ -0,0 +1,234 @@
|
||||
const inputPopup = document.getElementById('inputPopup');
|
||||
const createPopup = document.getElementById('createPopup');
|
||||
|
||||
const actionTable = {
|
||||
'Редактировать': () => console.log('Редактировать'),
|
||||
'Удалить': () => console.log('Удалить'),
|
||||
'Копировать': () => console.log('Скопировано')
|
||||
};
|
||||
|
||||
let menuDiv = document.querySelector('.window-menu');
|
||||
if (!menuDiv) {
|
||||
menuDiv = document.createElement('div');
|
||||
menuDiv.className = 'window-menu';
|
||||
menuDiv.style.position = 'absolute';
|
||||
menuDiv.style.display = 'none';
|
||||
document.body.appendChild(menuDiv);
|
||||
}
|
||||
|
||||
function parseJSONSafe(str) {
|
||||
if (!str) return null;
|
||||
try { return JSON.parse(str); } catch (e) { return null; }
|
||||
}
|
||||
|
||||
function splitMenuText(text) {
|
||||
if (!text) return [];
|
||||
return String(text).split(',').map(s => s.trim()).filter(Boolean);
|
||||
}
|
||||
|
||||
function buildMenu(items) {
|
||||
menuDiv.innerHTML = '';
|
||||
items.forEach(label => {
|
||||
const it = document.createElement('div');
|
||||
it.className = 'window-item';
|
||||
it.textContent = label;
|
||||
it.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
if (actionTable[label]) actionTable[label]();
|
||||
hideMenu();
|
||||
});
|
||||
menuDiv.appendChild(it);
|
||||
});
|
||||
}
|
||||
|
||||
function showMenuAt(x, y, items) {
|
||||
if (!items || !items.length) return;
|
||||
buildMenu(items);
|
||||
menuDiv.style.display = 'block';
|
||||
menuDiv.style.left = x + 'px';
|
||||
menuDiv.style.top = y + 'px';
|
||||
const rect = menuDiv.getBoundingClientRect();
|
||||
if (rect.right > window.innerWidth) menuDiv.style.left = (x - rect.width) + 'px';
|
||||
if (rect.bottom > window.innerHeight) menuDiv.style.top = (y - rect.height) + 'px';
|
||||
setTimeout(() => document.addEventListener('click', hideMenu, { once: true }), 0);
|
||||
}
|
||||
|
||||
function hideMenu() {
|
||||
menuDiv.style.display = 'none';
|
||||
}
|
||||
|
||||
function showOverlayMessage(text) {
|
||||
if (!text) return;
|
||||
const overlay = document.createElement('div');
|
||||
overlay.className = 'overlay';
|
||||
const popup = document.createElement('div');
|
||||
popup.className = 'window-popup';
|
||||
const content = document.createElement('div');
|
||||
content.textContent = String(text);
|
||||
const closeBtn = document.createElement('button');
|
||||
closeBtn.textContent = 'Закрыть';
|
||||
closeBtn.addEventListener('click', () => overlay.remove());
|
||||
overlay.addEventListener('click', (e) => { if (e.target === overlay) overlay.remove(); });
|
||||
popup.appendChild(content);
|
||||
popup.appendChild(closeBtn);
|
||||
overlay.appendChild(popup);
|
||||
document.body.appendChild(overlay);
|
||||
}
|
||||
|
||||
function popupFileFromSections(title, sections, afterClose) {
|
||||
const popup = document.createElement('div');
|
||||
popup.className = 'window-panel';
|
||||
const header = document.createElement('div');
|
||||
header.className = 'window-header';
|
||||
header.textContent = 'Свойства: ' + (title || '');
|
||||
const tabs = document.createElement('div');
|
||||
tabs.className = 'window-tabs';
|
||||
const tabButtons = [];
|
||||
const tabContents = [];
|
||||
const keys = Object.keys(sections || {});
|
||||
|
||||
keys.forEach((name, i) => {
|
||||
const t = document.createElement('div');
|
||||
t.className = 'window-tab' + (i === 0 ? ' active' : '');
|
||||
t.textContent = name;
|
||||
t.addEventListener('click', () => switchTab(i));
|
||||
tabButtons.push(t);
|
||||
tabs.appendChild(t);
|
||||
|
||||
const content = document.createElement('div');
|
||||
content.className = 'window-content' + (i === 0 ? ' active' : '');
|
||||
const val = sections[name];
|
||||
|
||||
if (Array.isArray(val)) {
|
||||
val.forEach(row => {
|
||||
const rowEl = document.createElement('div');
|
||||
rowEl.className = 'window-row';
|
||||
if (Array.isArray(row) && row.length >= 2) {
|
||||
const k = document.createElement('span');
|
||||
k.textContent = row[0];
|
||||
const v = document.createElement('span');
|
||||
v.textContent = row[1];
|
||||
rowEl.append(k, v);
|
||||
} else {
|
||||
rowEl.textContent = String(row);
|
||||
}
|
||||
content.appendChild(rowEl);
|
||||
});
|
||||
} else if (val && typeof val === 'object') {
|
||||
Object.keys(val).forEach(kname => {
|
||||
const rowEl = document.createElement('div');
|
||||
rowEl.className = 'window-row';
|
||||
const k = document.createElement('span');
|
||||
k.textContent = kname + ':';
|
||||
const v = document.createElement('span');
|
||||
v.textContent = String(val[kname]);
|
||||
rowEl.append(k, v);
|
||||
content.appendChild(rowEl);
|
||||
});
|
||||
} else {
|
||||
content.innerHTML = String(val || '');
|
||||
}
|
||||
|
||||
tabContents.push(content);
|
||||
});
|
||||
|
||||
function switchTab(i) {
|
||||
tabButtons.forEach((t, idx) => t.classList.toggle('active', idx === i));
|
||||
tabContents.forEach((c, idx) => c.classList.toggle('active', idx === i));
|
||||
}
|
||||
|
||||
const buttons = document.createElement('div');
|
||||
buttons.className = 'window-buttons';
|
||||
const okBtn = document.createElement('button');
|
||||
okBtn.textContent = 'ОК';
|
||||
const cancelBtn = document.createElement('button');
|
||||
cancelBtn.textContent = 'Отмена';
|
||||
okBtn.onclick = close;
|
||||
cancelBtn.onclick = close;
|
||||
buttons.append(okBtn, cancelBtn);
|
||||
|
||||
let offsetX, offsetY, dragging = false;
|
||||
|
||||
header.addEventListener('mousedown', (e) => {
|
||||
dragging = true;
|
||||
offsetX = e.clientX - popup.offsetLeft;
|
||||
offsetY = e.clientY - popup.offsetTop;
|
||||
header.style.userSelect = 'none';
|
||||
});
|
||||
|
||||
document.addEventListener('mousemove', (e) => {
|
||||
if (dragging) {
|
||||
popup.style.left = (e.clientX - offsetX) + 'px';
|
||||
popup.style.top = (e.clientY - offsetY) + 'px';
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('mouseup', () => {
|
||||
dragging = false;
|
||||
});
|
||||
|
||||
function close() {
|
||||
popup.remove();
|
||||
if (typeof afterClose === 'function') afterClose();
|
||||
}
|
||||
|
||||
popup.append(header, tabs, ...tabContents, buttons);
|
||||
document.body.append(popup);
|
||||
}
|
||||
|
||||
function handleCommand(raw, opts) {
|
||||
if (!raw) return;
|
||||
const parsed = typeof raw === 'string' ? parseJSONSafe(raw.trim()) : raw;
|
||||
if (!parsed || !parsed.act) return;
|
||||
|
||||
if (parsed.act === 'message' && opts && opts.source === 'button') {
|
||||
const text = parsed.text || '';
|
||||
if (!text) return;
|
||||
showOverlayMessage(text);
|
||||
return;
|
||||
}
|
||||
|
||||
if (parsed.act === 'menu' && opts && opts.source === 'context') {
|
||||
const items = Array.isArray(parsed.text)
|
||||
? parsed.text.map(s => String(s).trim()).filter(Boolean)
|
||||
: splitMenuText(String(parsed.text || ''));
|
||||
|
||||
if (!items.length) return;
|
||||
|
||||
const x = opts.coords && typeof opts.coords.x === 'number' ? opts.coords.x : (parsed.x || 0);
|
||||
const y = opts.coords && typeof opts.coords.y === 'number' ? opts.coords.y : (parsed.y || 0);
|
||||
|
||||
showMenuAt(x, y, items);
|
||||
return;
|
||||
}
|
||||
|
||||
if (parsed.act === 'file' && opts && opts.source === 'button') {
|
||||
const sections = parsed.text && typeof parsed.text === 'object' ? parsed.text : null;
|
||||
const title = sections && sections['Общие'] && sections['Общие']['Имя']
|
||||
? sections['Общие']['Имя']
|
||||
: parsed.title || '';
|
||||
|
||||
if (!sections) return;
|
||||
|
||||
popupFileFromSections(title, sections);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
createPopup.addEventListener('click', () => {
|
||||
const raw = (inputPopup && inputPopup.value) ? inputPopup.value : '';
|
||||
handleCommand(raw, { source: 'button' });
|
||||
});
|
||||
|
||||
document.addEventListener('contextmenu', (e) => {
|
||||
const raw = (inputPopup && inputPopup.value) ? inputPopup.value : '';
|
||||
const parsed = parseJSONSafe(raw);
|
||||
if (!parsed || parsed.act !== 'menu') return;
|
||||
|
||||
e.preventDefault();
|
||||
handleCommand(raw, { source: 'context', coords: { x: e.pageX, y: e.pageY } });
|
||||
});
|
||||
|
||||
window.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') hideMenu();
|
||||
});
|
||||
0
static/blocks/pages/userSlava/popup/style.css
Normal file
0
static/blocks/pages/userSlava/popup/style.css
Normal file
73
static/blocks/pages/userSlava/script.js
Normal file
73
static/blocks/pages/userSlava/script.js
Normal file
@@ -0,0 +1,73 @@
|
||||
|
||||
const panel = document.getElementById("regPanel");
|
||||
|
||||
panel.addEventListener("click", (e) => {
|
||||
// ищем ближайшую <a> с data-link="true"
|
||||
const link = e.target.closest("a[data-link='true']");
|
||||
if (!link || !panel.contains(link)) return; // если клик не по нужной ссылке — выходим
|
||||
|
||||
e.preventDefault(); // отменяем переход по ссылке
|
||||
e.stopPropagation(); // предотвращаем всплытие события
|
||||
|
||||
const urlStruct = urlStrBr(link.href);
|
||||
const targetId = link.dataset.target || "regDiv";
|
||||
|
||||
if (urlStruct.action === "sPage") {
|
||||
// Меняем URL в браузере без перезагрузки
|
||||
const newUrl = `/${urlStruct.url}/?${urlStruct.action}=${urlStruct.data}`;
|
||||
history.pushState(null, "", newUrl);
|
||||
|
||||
// Загружаем блок
|
||||
loadBlock(`pages/${urlStruct.url}/${urlStruct.data}`, targetId);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function handleCurrentUrl() {
|
||||
const urlStruct = urlStrBr(window.location.href);
|
||||
// console.log("Текущий URL:", urlStruct);
|
||||
if (urlStruct.action === "sPage") {
|
||||
// пример вызова loadBlock для sPage
|
||||
loadBlock("pages/" + urlStruct.url + "/" + urlStruct.data, "regDiv");
|
||||
}
|
||||
}
|
||||
|
||||
function urlStrBr(url){
|
||||
// Создаём объект URL
|
||||
const parsedUrl = new URL(url);
|
||||
// 1. firstPart — берём путь до "?" и удаляем "/"
|
||||
const firstPart = parsedUrl.pathname.replace(/^\/|\/$/g, "");
|
||||
// 2. secondPart — берём ключ последнего параметра
|
||||
const searchParams = parsedUrl.searchParams;
|
||||
const secondPart = Array.from(searchParams.keys()).pop(); // берём последний ключ
|
||||
// 3. thirdPart — берём значение последнего параметра
|
||||
const thirdPart = searchParams.get(secondPart);
|
||||
// Формируем структуру
|
||||
return {
|
||||
url: firstPart,
|
||||
action: secondPart,
|
||||
data: thirdPart
|
||||
};
|
||||
}
|
||||
|
||||
// При загрузке страницы
|
||||
handleCurrentUrl();
|
||||
|
||||
// При переходах по истории (назад/вперед)
|
||||
window.addEventListener("popstate", handleCurrentUrl);
|
||||
|
||||
|
||||
document.addEventListener("click", (e) => {
|
||||
if (!e.target.classList.contains("toggle-pass")) return;
|
||||
|
||||
const input = e.target.previousElementSibling;
|
||||
if (!input) return;
|
||||
|
||||
if (input.type === "password") {
|
||||
input.type = "text";
|
||||
e.target.textContent = "🙈";
|
||||
} else {
|
||||
input.type = "password";
|
||||
e.target.textContent = "👁";
|
||||
}
|
||||
});
|
||||
36
static/blocks/pages/userSlava/style.css
Normal file
36
static/blocks/pages/userSlava/style.css
Normal file
@@ -0,0 +1,36 @@
|
||||
#content {
|
||||
display: grid;
|
||||
width: 70%;
|
||||
min-height: calc(100vh - 100px); /* чтобы footer/header не ломали сетку */
|
||||
grid-template-rows: 200px 1fr;
|
||||
grid-template-columns: 200px 1fr;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
/* Верхний блок */
|
||||
#hed {
|
||||
grid-row: 1;
|
||||
grid-column: 1 / 3; /* растягиваем на две колонки */
|
||||
background: #eee;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* Левая нижняя колонка */
|
||||
#regPanel {
|
||||
grid-row: 2;
|
||||
grid-column: 1;
|
||||
background: #f0f0f0;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
/* Правая нижняя колонка */
|
||||
#regDiv {
|
||||
grid-row: 2;
|
||||
grid-column: 2;
|
||||
background: #fff;
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
Reference in New Issue
Block a user