mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-21 21:22:11 -03:00
feat(sidebar): implement display mode toggle and update sidebar actions for improved navigation. See #389
This commit is contained in:
@@ -400,7 +400,11 @@
|
||||
"sidebar": {
|
||||
"modelRoot": "Modell-Stammverzeichnis",
|
||||
"collapseAll": "Alle Ordner einklappen",
|
||||
"pinToggle": "Seitenleiste anheften/lösen"
|
||||
"pinSidebar": "Sidebar anheften",
|
||||
"unpinSidebar": "Sidebar lösen",
|
||||
"switchToListView": "Zur Listenansicht wechseln",
|
||||
"switchToTreeView": "Zur Baumansicht wechseln",
|
||||
"collapseAllDisabled": "Im Listenmodus nicht verfügbar"
|
||||
},
|
||||
"statistics": {
|
||||
"title": "Statistiken",
|
||||
|
||||
@@ -400,7 +400,11 @@
|
||||
"sidebar": {
|
||||
"modelRoot": "Model Root",
|
||||
"collapseAll": "Collapse All Folders",
|
||||
"pinToggle": "Pin/Unpin Sidebar"
|
||||
"pinSidebar": "Pin Sidebar",
|
||||
"unpinSidebar": "Unpin Sidebar",
|
||||
"switchToListView": "Switch to List View",
|
||||
"switchToTreeView": "Switch to Tree View",
|
||||
"collapseAllDisabled": "Not available in list view"
|
||||
},
|
||||
"statistics": {
|
||||
"title": "Statistics",
|
||||
|
||||
@@ -400,7 +400,11 @@
|
||||
"sidebar": {
|
||||
"modelRoot": "Raíz del modelo",
|
||||
"collapseAll": "Colapsar todas las carpetas",
|
||||
"pinToggle": "Anclar/Desanclar barra lateral"
|
||||
"pinSidebar": "Fijar barra lateral",
|
||||
"unpinSidebar": "Desfijar barra lateral",
|
||||
"switchToListView": "Cambiar a vista de lista",
|
||||
"switchToTreeView": "Cambiar a vista de árbol",
|
||||
"collapseAllDisabled": "No disponible en vista de lista"
|
||||
},
|
||||
"statistics": {
|
||||
"title": "Estadísticas",
|
||||
|
||||
@@ -400,7 +400,11 @@
|
||||
"sidebar": {
|
||||
"modelRoot": "Racine du modèle",
|
||||
"collapseAll": "Réduire tous les dossiers",
|
||||
"pinToggle": "Épingler/Désépingler la barre latérale"
|
||||
"pinSidebar": "Épingler la barre latérale",
|
||||
"unpinSidebar": "Désépingler la barre latérale",
|
||||
"switchToListView": "Passer en vue liste",
|
||||
"switchToTreeView": "Passer en vue arborescence",
|
||||
"collapseAllDisabled": "Non disponible en vue liste"
|
||||
},
|
||||
"statistics": {
|
||||
"title": "Statistiques",
|
||||
|
||||
@@ -400,7 +400,11 @@
|
||||
"sidebar": {
|
||||
"modelRoot": "モデルルート",
|
||||
"collapseAll": "すべてのフォルダを折りたたむ",
|
||||
"pinToggle": "サイドバーの固定/固定解除"
|
||||
"pinSidebar": "サイドバーを固定",
|
||||
"unpinSidebar": "サイドバーの固定を解除",
|
||||
"switchToListView": "リストビューに切り替え",
|
||||
"switchToTreeView": "ツリービューに切り替え",
|
||||
"collapseAllDisabled": "リストビューでは利用できません"
|
||||
},
|
||||
"statistics": {
|
||||
"title": "統計",
|
||||
|
||||
@@ -400,7 +400,11 @@
|
||||
"sidebar": {
|
||||
"modelRoot": "모델 루트",
|
||||
"collapseAll": "모든 폴더 접기",
|
||||
"pinToggle": "사이드바 고정/해제"
|
||||
"pinSidebar": "사이드바 고정",
|
||||
"unpinSidebar": "사이드바 고정 해제",
|
||||
"switchToListView": "목록 보기로 전환",
|
||||
"switchToTreeView": "트리 보기로 전환",
|
||||
"collapseAllDisabled": "목록 보기에서는 사용할 수 없습니다"
|
||||
},
|
||||
"statistics": {
|
||||
"title": "통계",
|
||||
|
||||
@@ -400,7 +400,11 @@
|
||||
"sidebar": {
|
||||
"modelRoot": "Корень моделей",
|
||||
"collapseAll": "Свернуть все папки",
|
||||
"pinToggle": "Закрепить/Открепить боковую панель"
|
||||
"pinSidebar": "Закрепить боковую панель",
|
||||
"unpinSidebar": "Открепить боковую панель",
|
||||
"switchToListView": "Переключить на вид списка",
|
||||
"switchToTreeView": "Переключить на древовидный вид",
|
||||
"collapseAllDisabled": "Недоступно в виде списка"
|
||||
},
|
||||
"statistics": {
|
||||
"title": "Статистика",
|
||||
|
||||
@@ -400,7 +400,11 @@
|
||||
"sidebar": {
|
||||
"modelRoot": "模型根目录",
|
||||
"collapseAll": "折叠所有文件夹",
|
||||
"pinToggle": "固定/取消固定侧边栏"
|
||||
"pinSidebar": "固定侧边栏",
|
||||
"unpinSidebar": "取消固定侧边栏",
|
||||
"switchToListView": "切换到列表视图",
|
||||
"switchToTreeView": "切换到树状视图",
|
||||
"collapseAllDisabled": "列表视图下不可用"
|
||||
},
|
||||
"statistics": {
|
||||
"title": "统计",
|
||||
|
||||
@@ -400,7 +400,11 @@
|
||||
"sidebar": {
|
||||
"modelRoot": "模型根目錄",
|
||||
"collapseAll": "全部摺疊資料夾",
|
||||
"pinToggle": "釘選/取消釘選側邊欄"
|
||||
"pinSidebar": "固定側邊欄",
|
||||
"unpinSidebar": "取消固定側邊欄",
|
||||
"switchToListView": "切換至列表檢視",
|
||||
"switchToTreeView": "切換至樹狀檢視",
|
||||
"collapseAllDisabled": "列表檢視下不可用"
|
||||
},
|
||||
"statistics": {
|
||||
"title": "統計",
|
||||
|
||||
@@ -114,18 +114,18 @@ class BaseModelRoutes(ABC):
|
||||
if not self.template_env or not template_name:
|
||||
return web.Response(text="Template environment or template name not set", status=500)
|
||||
|
||||
# 获取用户语言设置
|
||||
# Get user's language setting
|
||||
user_language = settings.get('language', 'en')
|
||||
|
||||
# 设置服务端i18n语言
|
||||
# Set server-side i18n locale
|
||||
server_i18n.set_locale(user_language)
|
||||
|
||||
# 为模板环境添加i18n过滤器
|
||||
# Add i18n filter to the template environment if not already added
|
||||
if not hasattr(self.template_env, '_i18n_filter_added'):
|
||||
self.template_env.filters['t'] = server_i18n.create_template_filter()
|
||||
self.template_env._i18n_filter_added = True
|
||||
|
||||
# 准备模板上下文
|
||||
# Prepare template context
|
||||
template_context = {
|
||||
'is_initializing': is_initializing,
|
||||
'settings': settings,
|
||||
|
||||
@@ -132,6 +132,11 @@
|
||||
color: var(--lora-accent);
|
||||
}
|
||||
|
||||
.sidebar-action-btn.disabled {
|
||||
opacity: 0.3;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Remove old close button styles */
|
||||
.sidebar-toggle-close {
|
||||
display: none;
|
||||
@@ -365,6 +370,60 @@
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Folder List Mode Styles */
|
||||
.sidebar-folder-item {
|
||||
position: relative;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.sidebar-node-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8px 16px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
font-size: 0.85em;
|
||||
border-left: 3px solid transparent;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.sidebar-node-content:hover {
|
||||
background: var(--lora-surface);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.sidebar-folder-item.selected .sidebar-node-content {
|
||||
background: oklch(var(--lora-accent-l) var(--lora-accent-c) var(--lora-accent-h) / 0.1);
|
||||
color: var(--lora-accent);
|
||||
border-left-color: var(--lora-accent);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.sidebar-folder-icon {
|
||||
margin-right: 8px;
|
||||
color: var(--text-muted);
|
||||
opacity: 0.7;
|
||||
width: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.sidebar-folder-item.selected .sidebar-folder-icon {
|
||||
color: var(--lora-accent);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.sidebar-node-content:hover .sidebar-folder-icon {
|
||||
color: var(--text-color);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.sidebar-folder-name {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
@media (min-width: 2150px) {
|
||||
.folder-sidebar {
|
||||
|
||||
@@ -667,6 +667,8 @@ export class BaseModelApiClient {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
params.append('recursive', pageState.searchOptions.recursive ? 'true' : 'false');
|
||||
|
||||
if (pageState.filters) {
|
||||
if (pageState.filters.tags && pageState.filters.tags.length > 0) {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
*/
|
||||
import { getStorageItem, setStorageItem } from '../utils/storageHelpers.js';
|
||||
import { getModelApiClient } from '../api/modelApiFactory.js';
|
||||
import { translate } from '../utils/i18nHelpers.js';
|
||||
|
||||
export class SidebarManager {
|
||||
constructor() {
|
||||
@@ -18,6 +19,8 @@ export class SidebarManager {
|
||||
this.hoverTimeout = null;
|
||||
this.isHovering = false;
|
||||
this.isInitialized = false;
|
||||
this.displayMode = 'tree'; // 'tree' or 'list'
|
||||
this.foldersList = [];
|
||||
|
||||
// Bind methods
|
||||
this.handleTreeClick = this.handleTreeClick.bind(this);
|
||||
@@ -31,6 +34,8 @@ export class SidebarManager {
|
||||
this.handleHoverAreaEnter = this.handleHoverAreaEnter.bind(this);
|
||||
this.handleHoverAreaLeave = this.handleHoverAreaLeave.bind(this);
|
||||
this.updateContainerMargin = this.updateContainerMargin.bind(this);
|
||||
this.handleDisplayModeToggle = this.handleDisplayModeToggle.bind(this);
|
||||
this.handleFolderListClick = this.handleFolderListClick.bind(this);
|
||||
}
|
||||
|
||||
async initialize(pageControls) {
|
||||
@@ -105,6 +110,7 @@ export class SidebarManager {
|
||||
const sidebarHeader = document.getElementById('sidebarHeader');
|
||||
const sidebar = document.getElementById('folderSidebar');
|
||||
const hoverArea = document.getElementById('sidebarHoverArea');
|
||||
const displayModeToggleBtn = document.getElementById('sidebarDisplayModeToggle');
|
||||
|
||||
if (pinToggleBtn) {
|
||||
pinToggleBtn.removeEventListener('click', this.handlePinToggle);
|
||||
@@ -135,6 +141,10 @@ export class SidebarManager {
|
||||
|
||||
// Remove resize event handler
|
||||
window.removeEventListener('resize', this.updateContainerMargin);
|
||||
|
||||
if (displayModeToggleBtn) {
|
||||
displayModeToggleBtn.removeEventListener('click', this.handleDisplayModeToggle);
|
||||
}
|
||||
}
|
||||
|
||||
async init() {
|
||||
@@ -258,6 +268,12 @@ export class SidebarManager {
|
||||
|
||||
// Add dedicated resize listener for container margin updates
|
||||
window.addEventListener('resize', this.updateContainerMargin);
|
||||
|
||||
// Display mode toggle button
|
||||
const displayModeToggleBtn = document.getElementById('sidebarDisplayModeToggle');
|
||||
if (displayModeToggleBtn) {
|
||||
displayModeToggleBtn.addEventListener('click', this.handleDisplayModeToggle);
|
||||
}
|
||||
}
|
||||
|
||||
handleDocumentClick(event) {
|
||||
@@ -270,7 +286,7 @@ export class SidebarManager {
|
||||
handleSidebarHeaderClick(event) {
|
||||
// Only trigger root selection if clicking on the title area, not the buttons
|
||||
if (!event.target.closest('.sidebar-header-actions')) {
|
||||
this.selectFolder('');
|
||||
this.selectFolder(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -286,7 +302,7 @@ export class SidebarManager {
|
||||
handleCollapseAll(event) {
|
||||
event.stopPropagation();
|
||||
this.expandedNodes.clear();
|
||||
this.renderTree();
|
||||
this.renderFolderDisplay();
|
||||
this.saveExpandedState();
|
||||
}
|
||||
|
||||
@@ -407,21 +423,36 @@ export class SidebarManager {
|
||||
const pinBtn = document.getElementById('sidebarPinToggle');
|
||||
if (pinBtn) {
|
||||
pinBtn.classList.toggle('active', this.isPinned);
|
||||
pinBtn.title = this.isPinned ? 'Unpin Sidebar' : 'Pin Sidebar';
|
||||
pinBtn.title = this.isPinned
|
||||
? translate('sidebar.unpinSidebar')
|
||||
: translate('sidebar.pinSidebar');
|
||||
}
|
||||
}
|
||||
|
||||
async loadFolderTree() {
|
||||
try {
|
||||
const response = await this.apiClient.fetchUnifiedFolderTree();
|
||||
this.treeData = response.tree || {};
|
||||
this.renderTree();
|
||||
if (this.displayMode === 'tree') {
|
||||
const response = await this.apiClient.fetchUnifiedFolderTree();
|
||||
this.treeData = response.tree || {};
|
||||
} else {
|
||||
const response = await this.apiClient.fetchModelFolders();
|
||||
this.foldersList = response.folders || [];
|
||||
}
|
||||
this.renderFolderDisplay();
|
||||
} catch (error) {
|
||||
console.error('Failed to load folder tree:', error);
|
||||
console.error('Failed to load folder data:', error);
|
||||
this.renderEmptyState();
|
||||
}
|
||||
}
|
||||
|
||||
renderFolderDisplay() {
|
||||
if (this.displayMode === 'tree') {
|
||||
this.renderTree();
|
||||
} else {
|
||||
this.renderFolderList();
|
||||
}
|
||||
}
|
||||
|
||||
renderTree() {
|
||||
const folderTree = document.getElementById('sidebarFolderTree');
|
||||
if (!folderTree) return;
|
||||
@@ -476,7 +507,38 @@ export class SidebarManager {
|
||||
`;
|
||||
}
|
||||
|
||||
renderFolderList() {
|
||||
const folderTree = document.getElementById('sidebarFolderTree');
|
||||
if (!folderTree) return;
|
||||
|
||||
if (!this.foldersList || this.foldersList.length === 0) {
|
||||
this.renderEmptyState();
|
||||
return;
|
||||
}
|
||||
|
||||
const foldersHtml = this.foldersList.map(folder => {
|
||||
const displayName = folder === '' ? '/' : folder;
|
||||
const isSelected = this.selectedPath === folder;
|
||||
|
||||
return `
|
||||
<div class="sidebar-folder-item ${isSelected ? 'selected' : ''}" data-path="${folder}">
|
||||
<div class="sidebar-node-content">
|
||||
<i class="fas fa-folder sidebar-folder-icon"></i>
|
||||
<div class="sidebar-folder-name" title="${displayName}">${displayName}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
folderTree.innerHTML = foldersHtml;
|
||||
}
|
||||
|
||||
handleTreeClick(event) {
|
||||
if (this.displayMode === 'list') {
|
||||
this.handleFolderListClick(event);
|
||||
return;
|
||||
}
|
||||
|
||||
const expandIcon = event.target.closest('.sidebar-tree-expand-icon');
|
||||
const nodeContent = event.target.closest('.sidebar-tree-node-content');
|
||||
|
||||
@@ -516,7 +578,7 @@ export class SidebarManager {
|
||||
this.closeDropdown();
|
||||
} else if (breadcrumbItem) {
|
||||
// Handle breadcrumb item click
|
||||
const path = breadcrumbItem.dataset.path || '';
|
||||
const path = breadcrumbItem.dataset.path || null; // null for showing all models
|
||||
const isPlaceholder = breadcrumbItem.classList.contains('placeholder');
|
||||
const isActive = breadcrumbItem.classList.contains('active');
|
||||
const dropdown = breadcrumbItem.closest('.breadcrumb-dropdown');
|
||||
@@ -557,8 +619,8 @@ export class SidebarManager {
|
||||
this.updateSidebarHeader();
|
||||
|
||||
// Update page state
|
||||
this.pageControls.pageState.activeFolder = path || null;
|
||||
setStorageItem(`${this.pageType}_activeFolder`, path || null);
|
||||
this.pageControls.pageState.activeFolder = path;
|
||||
setStorageItem(`${this.pageType}_activeFolder`, path);
|
||||
|
||||
// Reload models with new filter
|
||||
await this.pageControls.resetAndReload();
|
||||
@@ -569,23 +631,87 @@ export class SidebarManager {
|
||||
}
|
||||
}
|
||||
|
||||
handleFolderListClick(event) {
|
||||
const folderItem = event.target.closest('.sidebar-folder-item');
|
||||
|
||||
if (folderItem) {
|
||||
const path = folderItem.dataset.path;
|
||||
this.selectFolder(path);
|
||||
}
|
||||
}
|
||||
|
||||
handleDisplayModeToggle(event) {
|
||||
event.stopPropagation();
|
||||
this.displayMode = this.displayMode === 'tree' ? 'list' : 'tree';
|
||||
this.updateDisplayModeButton();
|
||||
this.updateCollapseAllButton();
|
||||
this.updateSearchRecursiveOption();
|
||||
this.saveDisplayMode();
|
||||
this.loadFolderTree(); // Reload with new display mode
|
||||
}
|
||||
|
||||
updateDisplayModeButton() {
|
||||
const displayModeBtn = document.getElementById('sidebarDisplayModeToggle');
|
||||
if (displayModeBtn) {
|
||||
const icon = displayModeBtn.querySelector('i');
|
||||
if (this.displayMode === 'tree') {
|
||||
icon.className = 'fas fa-sitemap';
|
||||
displayModeBtn.title = translate('sidebar.switchToListView');
|
||||
} else {
|
||||
icon.className = 'fas fa-list';
|
||||
displayModeBtn.title = translate('sidebar.switchToTreeView');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateCollapseAllButton() {
|
||||
const collapseAllBtn = document.getElementById('sidebarCollapseAll');
|
||||
if (collapseAllBtn) {
|
||||
if (this.displayMode === 'list') {
|
||||
collapseAllBtn.disabled = true;
|
||||
collapseAllBtn.classList.add('disabled');
|
||||
collapseAllBtn.title = translate('sidebar.collapseAllDisabled');
|
||||
} else {
|
||||
collapseAllBtn.disabled = false;
|
||||
collapseAllBtn.classList.remove('disabled');
|
||||
collapseAllBtn.title = translate('sidebar.collapseAll');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateSearchRecursiveOption() {
|
||||
this.pageControls.pageState.searchOptions.recursive = this.displayMode === 'tree';
|
||||
}
|
||||
|
||||
updateTreeSelection() {
|
||||
const folderTree = document.getElementById('sidebarFolderTree');
|
||||
if (!folderTree) return;
|
||||
|
||||
// Remove all selections
|
||||
folderTree.querySelectorAll('.sidebar-tree-node-content').forEach(node => {
|
||||
node.classList.remove('selected');
|
||||
});
|
||||
|
||||
// Add selection to current path
|
||||
if (this.selectedPath) {
|
||||
const selectedNode = folderTree.querySelector(`[data-path="${this.selectedPath}"] .sidebar-tree-node-content`);
|
||||
if (selectedNode) {
|
||||
selectedNode.classList.add('selected');
|
||||
|
||||
// Expand parents to show selection
|
||||
this.expandPathParents(this.selectedPath);
|
||||
if (this.displayMode === 'list') {
|
||||
// Remove all selections in list mode
|
||||
folderTree.querySelectorAll('.sidebar-folder-item').forEach(item => {
|
||||
item.classList.remove('selected');
|
||||
});
|
||||
|
||||
// Add selection to current path
|
||||
if (this.selectedPath !== null) {
|
||||
const selectedItem = folderTree.querySelector(`[data-path="${this.selectedPath}"]`);
|
||||
if (selectedItem) {
|
||||
selectedItem.classList.add('selected');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// ...existing tree selection logic...
|
||||
folderTree.querySelectorAll('.sidebar-tree-node-content').forEach(node => {
|
||||
node.classList.remove('selected');
|
||||
});
|
||||
|
||||
if (this.selectedPath) {
|
||||
const selectedNode = folderTree.querySelector(`[data-path="${this.selectedPath}"] .sidebar-tree-node-content`);
|
||||
if (selectedNode) {
|
||||
selectedNode.classList.add('selected');
|
||||
this.expandPathParents(this.selectedPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -799,11 +925,16 @@ export class SidebarManager {
|
||||
restoreSidebarState() {
|
||||
const isPinned = getStorageItem(`${this.pageType}_sidebarPinned`, false);
|
||||
const expandedPaths = getStorageItem(`${this.pageType}_expandedNodes`, []);
|
||||
const displayMode = getStorageItem(`${this.pageType}_displayMode`, 'tree');
|
||||
|
||||
this.isPinned = isPinned;
|
||||
this.expandedNodes = new Set(expandedPaths);
|
||||
this.displayMode = displayMode;
|
||||
|
||||
this.updatePinButton();
|
||||
this.updateDisplayModeButton();
|
||||
this.updateCollapseAllButton();
|
||||
this.updateSearchRecursiveOption();
|
||||
}
|
||||
|
||||
restoreSelectedFolder() {
|
||||
@@ -829,6 +960,10 @@ export class SidebarManager {
|
||||
setStorageItem(`${this.pageType}_expandedNodes`, Array.from(this.expandedNodes));
|
||||
}
|
||||
|
||||
saveDisplayMode() {
|
||||
setStorageItem(`${this.pageType}_displayMode`, this.displayMode);
|
||||
}
|
||||
|
||||
async refresh() {
|
||||
await this.loadFolderTree();
|
||||
this.restoreSelectedFolder();
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
<div class="sidebar-header" id="sidebarHeader">
|
||||
<h3><i class="fas fa-home"></i> <span id="sidebarTitle">{{ t('sidebar.modelRoot') }}</span></h3>
|
||||
<div class="sidebar-header-actions">
|
||||
<button class="sidebar-action-btn" id="sidebarDisplayModeToggle" title="{{ t('sidebar.switchToListView') }}">
|
||||
<i class="fas fa-sitemap"></i>
|
||||
</button>
|
||||
<button class="sidebar-action-btn" id="sidebarCollapseAll" title="{{ t('sidebar.collapseAll') }}">
|
||||
<i class="fas fa-compress-alt"></i>
|
||||
</button>
|
||||
@@ -17,7 +20,7 @@
|
||||
<div class="sidebar-content">
|
||||
<div class="sidebar-tree-container">
|
||||
<div class="sidebar-tree" id="sidebarFolderTree">
|
||||
<!-- Tree will be populated by JavaScript -->
|
||||
<!-- Tree/List will be populated by JavaScript -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user