mirror of
https://github.com/willmiao/ComfyUI-Lora-Manager.git
synced 2026-03-21 21:22:11 -03:00
Enhance settings modal with video autoplay on hover option and improve layout. Fixes https://github.com/willmiao/ComfyUI-Lora-Manager/issues/92
This commit is contained in:
@@ -196,7 +196,7 @@ body.modal-open {
|
||||
}
|
||||
|
||||
.settings-modal {
|
||||
max-width: 500px;
|
||||
max-width: 650px; /* Further increased from 600px for more space */
|
||||
}
|
||||
|
||||
/* Settings Links */
|
||||
@@ -266,14 +266,22 @@ body.modal-open {
|
||||
}
|
||||
}
|
||||
|
||||
/* API key input specific styles */
|
||||
.api-key-input {
|
||||
width: 100%; /* Take full width of parent */
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.api-key-input input {
|
||||
padding-right: 40px;
|
||||
width: 100%;
|
||||
padding: 6px 40px 6px 10px; /* Add left padding */
|
||||
height: 32px;
|
||||
border-radius: var(--border-radius-xs);
|
||||
border: 1px solid var(--border-color);
|
||||
background-color: var(--lora-surface);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.api-key-input .toggle-visibility {
|
||||
@@ -294,8 +302,10 @@ body.modal-open {
|
||||
.input-help {
|
||||
font-size: 0.85em;
|
||||
color: var(--text-color);
|
||||
opacity: 0.8;
|
||||
margin-top: 4px;
|
||||
opacity: 0.7;
|
||||
margin-top: 8px; /* Space between control and help */
|
||||
line-height: 1.4;
|
||||
width: 100%; /* Full width */
|
||||
}
|
||||
|
||||
/* 统一各个 section 的样式 */
|
||||
@@ -341,8 +351,8 @@ body.modal-open {
|
||||
|
||||
.setting-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: var(--space-2);
|
||||
flex-direction: column; /* Changed to column for help text placement */
|
||||
margin-bottom: var(--space-3); /* Increased to provide more spacing between items */
|
||||
padding: var(--space-1);
|
||||
border-radius: var(--border-radius-xs);
|
||||
}
|
||||
@@ -355,35 +365,52 @@ body.modal-open {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.setting-info {
|
||||
margin-bottom: var(--space-1);
|
||||
/* Control row with label and input together */
|
||||
.setting-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.setting-info {
|
||||
margin-bottom: 0;
|
||||
width: 35%; /* Increased from 30% to prevent wrapping */
|
||||
flex-shrink: 0; /* Prevent shrinking */
|
||||
}
|
||||
|
||||
.setting-info label {
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0;
|
||||
white-space: nowrap; /* Prevent label wrapping */
|
||||
}
|
||||
|
||||
.setting-control {
|
||||
width: 100%;
|
||||
margin-bottom: var(--space-1);
|
||||
width: 60%; /* Decreased slightly from 65% */
|
||||
margin-bottom: 0;
|
||||
display: flex;
|
||||
justify-content: flex-end; /* Right-align all controls */
|
||||
}
|
||||
|
||||
/* Select Control Styles */
|
||||
.select-control {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.select-control select {
|
||||
width: 100%;
|
||||
max-width: 100%; /* Increased from 200px */
|
||||
padding: 6px 10px;
|
||||
border-radius: var(--border-radius-xs);
|
||||
border: 1px solid var(--border-color);
|
||||
background-color: var(--lora-surface);
|
||||
color: var(--text-color);
|
||||
font-size: 0.95em;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
/* Fix dark theme select dropdown text color */
|
||||
@@ -409,6 +436,7 @@ body.modal-open {
|
||||
width: 50px;
|
||||
height: 24px;
|
||||
cursor: pointer;
|
||||
margin-left: auto; /* Push to right side */
|
||||
}
|
||||
|
||||
.toggle-switch input {
|
||||
@@ -458,15 +486,6 @@ input:checked + .toggle-slider:before {
|
||||
width: 22px;
|
||||
}
|
||||
|
||||
/* Update input help styles */
|
||||
.input-help {
|
||||
font-size: 0.85em;
|
||||
color: var(--text-color);
|
||||
opacity: 0.7;
|
||||
margin-top: 4px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* Blur effect for NSFW content */
|
||||
.nsfw-blur {
|
||||
filter: blur(12px);
|
||||
|
||||
@@ -57,10 +57,15 @@ export function createLoraCard(lora) {
|
||||
nsfwText = "R-rated Content";
|
||||
}
|
||||
|
||||
// Check if autoplayOnHover is enabled for video previews
|
||||
const autoplayOnHover = state.global.settings.autoplayOnHover || false;
|
||||
const isVideo = previewUrl.endsWith('.mp4');
|
||||
const videoAttrs = autoplayOnHover ? 'controls muted loop' : 'controls autoplay muted loop';
|
||||
|
||||
card.innerHTML = `
|
||||
<div class="card-preview ${shouldBlur ? 'blurred' : ''}">
|
||||
${previewUrl.endsWith('.mp4') ?
|
||||
`<video controls autoplay muted loop>
|
||||
${isVideo ?
|
||||
`<video ${videoAttrs}>
|
||||
<source src="${versionedPreviewUrl}" type="video/mp4">
|
||||
</video>` :
|
||||
`<img src="${versionedPreviewUrl}" alt="${lora.model_name}">`
|
||||
@@ -246,6 +251,26 @@ export function createLoraCard(lora) {
|
||||
actionGroup.style.display = 'none';
|
||||
});
|
||||
}
|
||||
|
||||
// Add autoplayOnHover handlers for video elements if needed
|
||||
const videoElement = card.querySelector('video');
|
||||
if (videoElement && autoplayOnHover) {
|
||||
const cardPreview = card.querySelector('.card-preview');
|
||||
|
||||
// Remove autoplay attribute and pause initially
|
||||
videoElement.removeAttribute('autoplay');
|
||||
videoElement.pause();
|
||||
|
||||
// Add mouse events to trigger play/pause
|
||||
cardPreview.addEventListener('mouseenter', () => {
|
||||
videoElement.play();
|
||||
});
|
||||
|
||||
cardPreview.addEventListener('mouseleave', () => {
|
||||
videoElement.pause();
|
||||
videoElement.currentTime = 0;
|
||||
});
|
||||
}
|
||||
|
||||
return card;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { LoadingManager } from './managers/LoadingManager.js';
|
||||
import { modalManager } from './managers/ModalManager.js';
|
||||
import { updateService } from './managers/UpdateService.js';
|
||||
import { HeaderManager } from './components/Header.js';
|
||||
import { SettingsManager } from './managers/SettingsManager.js';
|
||||
import { settingsManager } from './managers/SettingsManager.js';
|
||||
import { showToast, initTheme, initBackToTop, lazyLoadImages } from './utils/uiHelpers.js';
|
||||
import { initializeInfiniteScroll } from './utils/infiniteScroll.js';
|
||||
import { migrateStorageItems } from './utils/storageHelpers.js';
|
||||
@@ -26,7 +26,7 @@ export class AppCore {
|
||||
modalManager.initialize();
|
||||
updateService.initialize();
|
||||
window.modalManager = modalManager;
|
||||
window.settingsManager = new SettingsManager();
|
||||
window.settingsManager = settingsManager;
|
||||
|
||||
// Initialize UI components
|
||||
window.headerManager = new HeaderManager();
|
||||
@@ -76,4 +76,4 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
export const appCore = new AppCore();
|
||||
|
||||
// Export common utilities for global use
|
||||
export { showToast, lazyLoadImages, initializeInfiniteScroll };
|
||||
export { showToast, lazyLoadImages, initializeInfiniteScroll };
|
||||
@@ -65,6 +65,12 @@ export class SettingsManager {
|
||||
// Sync with state (backend will set this via template)
|
||||
state.global.settings.show_only_sfw = showOnlySFWCheckbox.checked;
|
||||
}
|
||||
|
||||
// Set video autoplay on hover setting
|
||||
const autoplayOnHoverCheckbox = document.getElementById('autoplayOnHover');
|
||||
if (autoplayOnHoverCheckbox) {
|
||||
autoplayOnHoverCheckbox.checked = state.global.settings.autoplayOnHover || false;
|
||||
}
|
||||
|
||||
// Load default lora root
|
||||
await this.loadLoraRoots();
|
||||
@@ -120,11 +126,170 @@ export class SettingsManager {
|
||||
this.isOpen = !this.isOpen;
|
||||
}
|
||||
|
||||
// Auto-save methods for different control types
|
||||
|
||||
// For toggle switches
|
||||
async saveToggleSetting(elementId, settingKey) {
|
||||
const element = document.getElementById(elementId);
|
||||
if (!element) return;
|
||||
|
||||
const value = element.checked;
|
||||
|
||||
// Update frontend state
|
||||
if (settingKey === 'blur_mature_content') {
|
||||
state.global.settings.blurMatureContent = value;
|
||||
} else if (settingKey === 'show_only_sfw') {
|
||||
state.global.settings.show_only_sfw = value;
|
||||
} else if (settingKey === 'autoplay_on_hover') {
|
||||
state.global.settings.autoplayOnHover = value;
|
||||
} else {
|
||||
// For any other settings that might be added in the future
|
||||
state.global.settings[settingKey] = value;
|
||||
}
|
||||
|
||||
// Save to localStorage
|
||||
setStorageItem('settings', state.global.settings);
|
||||
|
||||
try {
|
||||
// For backend settings, make API call
|
||||
if (['show_only_sfw', 'blur_mature_content', 'autoplay_on_hover'].includes(settingKey)) {
|
||||
const payload = {};
|
||||
payload[settingKey] = value;
|
||||
|
||||
const response = await fetch('/api/settings', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to save setting');
|
||||
}
|
||||
|
||||
showToast(`${settingKey.replace(/_/g, ' ')} updated`, 'success');
|
||||
}
|
||||
|
||||
// Apply frontend settings immediately
|
||||
this.applyFrontendSettings();
|
||||
|
||||
if (settingKey === 'show_only_sfw') {
|
||||
this.reloadContent();
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
showToast('Failed to save setting: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// For select dropdowns
|
||||
async saveSelectSetting(elementId, settingKey) {
|
||||
const element = document.getElementById(elementId);
|
||||
if (!element) return;
|
||||
|
||||
const value = element.value;
|
||||
|
||||
// Update frontend state
|
||||
if (settingKey === 'default_lora_root') {
|
||||
state.global.settings.default_loras_root = value;
|
||||
} else {
|
||||
// For any other settings that might be added in the future
|
||||
state.global.settings[settingKey] = value;
|
||||
}
|
||||
|
||||
// Save to localStorage
|
||||
setStorageItem('settings', state.global.settings);
|
||||
|
||||
try {
|
||||
// For backend settings, make API call
|
||||
const payload = {};
|
||||
payload[settingKey] = value;
|
||||
|
||||
const response = await fetch('/api/settings', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to save setting');
|
||||
}
|
||||
|
||||
showToast(`${settingKey.replace(/_/g, ' ')} updated`, 'success');
|
||||
|
||||
} catch (error) {
|
||||
showToast('Failed to save setting: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// For input fields
|
||||
async saveInputSetting(elementId, settingKey) {
|
||||
const element = document.getElementById(elementId);
|
||||
if (!element) return;
|
||||
|
||||
const value = element.value;
|
||||
|
||||
// For API key or other inputs that need to be saved on backend
|
||||
try {
|
||||
// Check if value has changed from existing value
|
||||
const currentValue = state.global.settings[settingKey] || '';
|
||||
if (value === currentValue) {
|
||||
return; // No change, exit early
|
||||
}
|
||||
|
||||
// Update state
|
||||
state.global.settings[settingKey] = value;
|
||||
|
||||
// Save to localStorage if appropriate
|
||||
if (!settingKey.includes('api_key')) { // Don't store API keys in localStorage for security
|
||||
setStorageItem('settings', state.global.settings);
|
||||
}
|
||||
|
||||
// For backend settings, make API call
|
||||
const payload = {};
|
||||
payload[settingKey] = value;
|
||||
|
||||
const response = await fetch('/api/settings', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to save setting');
|
||||
}
|
||||
|
||||
showToast(`${settingKey.replace(/_/g, ' ')} updated`, 'success');
|
||||
|
||||
} catch (error) {
|
||||
showToast('Failed to save setting: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async reloadContent() {
|
||||
if (this.currentPage === 'loras') {
|
||||
// Reload the loras without updating folders
|
||||
await resetAndReload(false);
|
||||
} else if (this.currentPage === 'recipes') {
|
||||
// Reload the recipes without updating folders
|
||||
await window.recipeManager.loadRecipes();
|
||||
} else if (this.currentPage === 'checkpoints') {
|
||||
// Reload the checkpoints without updating folders
|
||||
await window.checkpointsManager.loadCheckpoints();
|
||||
}
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
// Get frontend settings from UI
|
||||
const blurMatureContent = document.getElementById('blurMatureContent').checked;
|
||||
const showOnlySFW = document.getElementById('showOnlySFW').checked;
|
||||
const defaultLoraRoot = document.getElementById('defaultLoraRoot').value;
|
||||
const autoplayOnHover = document.getElementById('autoplayOnHover').checked;
|
||||
|
||||
// Get backend settings
|
||||
const apiKey = document.getElementById('civitaiApiKey').value;
|
||||
@@ -133,6 +298,7 @@ export class SettingsManager {
|
||||
state.global.settings.blurMatureContent = blurMatureContent;
|
||||
state.global.settings.show_only_sfw = showOnlySFW;
|
||||
state.global.settings.default_loras_root = defaultLoraRoot;
|
||||
state.global.settings.autoplayOnHover = autoplayOnHover;
|
||||
|
||||
// Save settings to localStorage
|
||||
setStorageItem('settings', state.global.settings);
|
||||
@@ -186,11 +352,42 @@ export class SettingsManager {
|
||||
}
|
||||
});
|
||||
|
||||
// Apply autoplay setting to existing videos in card previews
|
||||
const autoplayOnHover = state.global.settings.autoplayOnHover;
|
||||
document.querySelectorAll('.card-preview video').forEach(video => {
|
||||
// Remove previous event listeners by cloning and replacing the element
|
||||
const videoParent = video.parentElement;
|
||||
const videoClone = video.cloneNode(true);
|
||||
|
||||
if (autoplayOnHover) {
|
||||
// Pause video initially and set up mouse events for hover playback
|
||||
videoClone.removeAttribute('autoplay');
|
||||
videoClone.pause();
|
||||
|
||||
// Add mouse events to the parent element
|
||||
videoParent.onmouseenter = () => videoClone.play();
|
||||
videoParent.onmouseleave = () => {
|
||||
videoClone.pause();
|
||||
videoClone.currentTime = 0;
|
||||
};
|
||||
} else {
|
||||
// Use default autoplay behavior
|
||||
videoClone.setAttribute('autoplay', '');
|
||||
videoParent.onmouseenter = null;
|
||||
videoParent.onmouseleave = null;
|
||||
}
|
||||
|
||||
videoParent.replaceChild(videoClone, video);
|
||||
});
|
||||
|
||||
// For show_only_sfw, there's no immediate action needed as it affects content loading
|
||||
// The setting will take effect on next reload
|
||||
}
|
||||
}
|
||||
|
||||
// Create singleton instance
|
||||
export const settingsManager = new SettingsManager();
|
||||
|
||||
// Helper function for toggling API key visibility
|
||||
export function toggleApiKeyVisibility(button) {
|
||||
const input = button.parentElement.querySelector('input');
|
||||
|
||||
@@ -17,16 +17,24 @@
|
||||
<button class="close" onclick="modalManager.closeModal('settingsModal')">×</button>
|
||||
<h2>Settings</h2>
|
||||
<div class="settings-form">
|
||||
<div class="input-group">
|
||||
<label for="civitaiApiKey">Civitai API Key:</label>
|
||||
<div class="api-key-input">
|
||||
<input type="password"
|
||||
id="civitaiApiKey"
|
||||
placeholder="Enter your Civitai API key"
|
||||
value="{{ settings.get('civitai_api_key', '') }}" />
|
||||
<button class="toggle-visibility" onclick="toggleApiKeyVisibility(this)">
|
||||
<i class="fas fa-eye"></i>
|
||||
</button>
|
||||
<div class="setting-item api-key-item">
|
||||
<div class="setting-row">
|
||||
<div class="setting-info">
|
||||
<label for="civitaiApiKey">Civitai API Key:</label>
|
||||
</div>
|
||||
<div class="setting-control">
|
||||
<div class="api-key-input">
|
||||
<input type="password"
|
||||
id="civitaiApiKey"
|
||||
placeholder="Enter your Civitai API key"
|
||||
value="{{ settings.get('civitai_api_key', '') }}"
|
||||
onblur="settingsManager.saveInputSetting('civitaiApiKey', 'civitai_api_key')"
|
||||
onkeydown="if(event.key === 'Enter') { this.blur(); }" />
|
||||
<button class="toggle-visibility" onclick="toggleApiKeyVisibility(this)">
|
||||
<i class="fas fa-eye"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-help">
|
||||
Used for authentication when downloading models from Civitai
|
||||
@@ -37,32 +45,61 @@
|
||||
<h3>Content Filtering</h3>
|
||||
|
||||
<div class="setting-item">
|
||||
<div class="setting-info">
|
||||
<label for="blurMatureContent">Blur NSFW Content</label>
|
||||
<div class="input-help">
|
||||
Blur mature (NSFW) content preview images
|
||||
<div class="setting-row">
|
||||
<div class="setting-info">
|
||||
<label for="blurMatureContent">Blur NSFW Content</label>
|
||||
</div>
|
||||
<div class="setting-control">
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox" id="blurMatureContent" checked
|
||||
onchange="settingsManager.saveToggleSetting('blurMatureContent', 'blur_mature_content')">
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-control">
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox" id="blurMatureContent" checked>
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
<div class="input-help">
|
||||
Blur mature (NSFW) content preview images
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="setting-item">
|
||||
<div class="setting-info">
|
||||
<label for="showOnlySFW">Show Only SFW Results</label>
|
||||
<div class="input-help">
|
||||
Filter out all NSFW content when browsing and searching
|
||||
<div class="setting-row">
|
||||
<div class="setting-info">
|
||||
<label for="showOnlySFW">Show Only SFW Results</label>
|
||||
</div>
|
||||
<div class="setting-control">
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox" id="showOnlySFW" value="{{ settings.get('show_only_sfw', False) }}" {% if settings.get('show_only_sfw', False) %}checked{% endif %}
|
||||
onchange="settingsManager.saveToggleSetting('showOnlySFW', 'show_only_sfw')">
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-control">
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox" id="showOnlySFW" value="{{ settings.get('show_only_sfw', False) }}" {% if settings.get('show_only_sfw', False) %}checked{% endif %}>
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
<div class="input-help">
|
||||
Filter out all NSFW content when browsing and searching
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add Video Settings Section -->
|
||||
<div class="settings-section">
|
||||
<h3>Video Settings</h3>
|
||||
|
||||
<div class="setting-item">
|
||||
<div class="setting-row">
|
||||
<div class="setting-info">
|
||||
<label for="autoplayOnHover">Autoplay Videos on Hover</label>
|
||||
</div>
|
||||
<div class="setting-control">
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox" id="autoplayOnHover"
|
||||
onchange="settingsManager.saveToggleSetting('autoplayOnHover', 'autoplay_on_hover')">
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-help">
|
||||
Only play video previews when hovering over them
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -72,14 +109,16 @@
|
||||
<h3>Folder Settings</h3>
|
||||
|
||||
<div class="setting-item">
|
||||
<div class="setting-info">
|
||||
<label for="defaultLoraRoot">Default LoRA Root</label>
|
||||
</div>
|
||||
<div class="setting-control select-control">
|
||||
<select id="defaultLoraRoot">
|
||||
<option value="">No Default</option>
|
||||
<!-- Options will be loaded dynamically -->
|
||||
</select>
|
||||
<div class="setting-row">
|
||||
<div class="setting-info">
|
||||
<label for="defaultLoraRoot">Default LoRA Root</label>
|
||||
</div>
|
||||
<div class="setting-control select-control">
|
||||
<select id="defaultLoraRoot" onchange="settingsManager.saveSelectSetting('defaultLoraRoot', 'default_lora_root')">
|
||||
<option value="">No Default</option>
|
||||
<!-- Options will be loaded dynamically -->
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="input-help">
|
||||
Set the default LoRA root directory for downloads, imports and moves
|
||||
@@ -87,18 +126,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-actions">
|
||||
<button class="primary-btn" onclick="settingsManager.saveSettings()">Save</button>
|
||||
</div>
|
||||
<!-- Add the new links section -->
|
||||
<div class="settings-links">
|
||||
<a href="https://github.com/willmiao/ComfyUI-Lora-Manager" target="_blank" class="settings-link" title="GitHub Repository">
|
||||
<i class="fab fa-github"></i>
|
||||
</a>
|
||||
<a href="https://github.com/willmiao/ComfyUI-Lora-Manager/issues/new" target="_blank" class="settings-link" title="Report Issue">
|
||||
<i class="fas fa-bug"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user