mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-12 02:37:45 +08:00
feat: Implement CLAUDE.md Manager View with file tree, viewer, and metadata actions
- Added main JavaScript functionality for CLAUDE.md management including file loading, rendering, and editing capabilities. - Created a test HTML file to validate the functionality of the CLAUDE.md manager. - Introduced CLI generation examples and documentation for rules creation via CLI. - Enhanced error handling and notifications for file operations.
This commit is contained in:
@@ -335,9 +335,482 @@ function editRule(ruleName, location) {
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Create Rule Modal ==========
|
||||
var ruleCreateState = {
|
||||
location: 'project',
|
||||
fileName: '',
|
||||
subdirectory: '',
|
||||
isConditional: false,
|
||||
paths: [''],
|
||||
content: '',
|
||||
mode: 'input',
|
||||
generationType: 'description',
|
||||
description: '',
|
||||
extractScope: '',
|
||||
extractFocus: ''
|
||||
};
|
||||
|
||||
function openRuleCreateModal() {
|
||||
// Open create modal (to be implemented with modal)
|
||||
if (window.showToast) {
|
||||
showToast(t('rules.createNotImplemented'), 'info');
|
||||
// Reset state
|
||||
ruleCreateState = {
|
||||
location: 'project',
|
||||
fileName: '',
|
||||
subdirectory: '',
|
||||
isConditional: false,
|
||||
paths: [''],
|
||||
content: '',
|
||||
mode: 'input',
|
||||
generationType: 'description',
|
||||
description: '',
|
||||
extractScope: '',
|
||||
extractFocus: ''
|
||||
};
|
||||
|
||||
// Create modal HTML
|
||||
const modalHtml = `
|
||||
<div class="modal-overlay fixed inset-0 bg-black/50 z-50 flex items-center justify-center" onclick="closeRuleCreateModal(event)">
|
||||
<div class="modal-dialog bg-card rounded-lg shadow-lg w-full max-w-2xl max-h-[90vh] mx-4 flex flex-col" onclick="event.stopPropagation()">
|
||||
<!-- Header -->
|
||||
<div class="flex items-center justify-between px-6 py-4 border-b border-border">
|
||||
<h3 class="text-lg font-semibold text-foreground">${t('rules.createRule')}</h3>
|
||||
<button class="w-8 h-8 flex items-center justify-center text-xl text-muted-foreground hover:text-foreground hover:bg-hover rounded"
|
||||
onclick="closeRuleCreateModal()">×</button>
|
||||
</div>
|
||||
|
||||
<!-- Body -->
|
||||
<div class="flex-1 overflow-y-auto p-6 space-y-5">
|
||||
<!-- Location Selection -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-foreground mb-2">${t('rules.location')}</label>
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<button class="location-btn px-4 py-3 text-left border-2 rounded-lg transition-all ${ruleCreateState.location === 'project' ? 'border-primary bg-primary/10' : 'border-border hover:border-primary/50'}"
|
||||
onclick="selectRuleLocation('project')">
|
||||
<div class="flex items-center gap-2">
|
||||
<i data-lucide="folder" class="w-5 h-5"></i>
|
||||
<div>
|
||||
<div class="font-medium">${t('rules.projectRules')}</div>
|
||||
<div class="text-xs text-muted-foreground">.claude/rules/</div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
<button class="location-btn px-4 py-3 text-left border-2 rounded-lg transition-all ${ruleCreateState.location === 'user' ? 'border-primary bg-primary/10' : 'border-border hover:border-primary/50'}"
|
||||
onclick="selectRuleLocation('user')">
|
||||
<div class="flex items-center gap-2">
|
||||
<i data-lucide="user" class="w-5 h-5"></i>
|
||||
<div>
|
||||
<div class="font-medium">${t('rules.userRules')}</div>
|
||||
<div class="text-xs text-muted-foreground">~/.claude/rules/</div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mode Selection -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-foreground mb-2">${t('rules.createMode')}</label>
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<button class="mode-btn px-4 py-3 text-left border-2 rounded-lg transition-all ${ruleCreateState.mode === 'input' ? 'border-primary bg-primary/10' : 'border-border hover:border-primary/50'}"
|
||||
onclick="switchRuleCreateMode('input')">
|
||||
<div class="flex items-center gap-2">
|
||||
<i data-lucide="edit" class="w-5 h-5"></i>
|
||||
<div>
|
||||
<div class="font-medium">${t('rules.manualInput')}</div>
|
||||
<div class="text-xs text-muted-foreground">${t('rules.manualInputHint')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
<button class="mode-btn px-4 py-3 text-left border-2 rounded-lg transition-all ${ruleCreateState.mode === 'cli-generate' ? 'border-primary bg-primary/10' : 'border-border hover:border-primary/50'}"
|
||||
onclick="switchRuleCreateMode('cli-generate')">
|
||||
<div class="flex items-center gap-2">
|
||||
<i data-lucide="sparkles" class="w-5 h-5"></i>
|
||||
<div>
|
||||
<div class="font-medium">${t('rules.cliGenerate')}</div>
|
||||
<div class="text-xs text-muted-foreground">${t('rules.cliGenerateHint')}</div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- File Name -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-foreground mb-2">${t('rules.fileName')}</label>
|
||||
<input type="text" id="ruleFileName"
|
||||
class="w-full px-3 py-2 bg-background border border-border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-primary"
|
||||
placeholder="my-rule.md"
|
||||
value="${ruleCreateState.fileName}">
|
||||
<p class="text-xs text-muted-foreground mt-1">${t('rules.fileNameHint')}</p>
|
||||
</div>
|
||||
|
||||
<!-- Subdirectory -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-foreground mb-2">${t('rules.subdirectory')} <span class="text-muted-foreground">${t('common.optional')}</span></label>
|
||||
<input type="text" id="ruleSubdirectory"
|
||||
class="w-full px-3 py-2 bg-background border border-border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-primary"
|
||||
placeholder="category/subcategory"
|
||||
value="${ruleCreateState.subdirectory}">
|
||||
<p class="text-xs text-muted-foreground mt-1">${t('rules.subdirectoryHint')}</p>
|
||||
</div>
|
||||
|
||||
<!-- CLI Generation Type (CLI mode only) -->
|
||||
<div id="ruleGenerationTypeSection" style="display: ${ruleCreateState.mode === 'cli-generate' ? 'block' : 'none'}">
|
||||
<label class="block text-sm font-medium text-foreground mb-2">${t('rules.generationType')}</label>
|
||||
<div class="flex gap-3">
|
||||
<label class="flex items-center gap-2 cursor-pointer">
|
||||
<input type="radio" name="ruleGenType" value="description"
|
||||
class="w-4 h-4 text-primary bg-background border-border focus:ring-2 focus:ring-primary"
|
||||
${ruleCreateState.generationType === 'description' ? 'checked' : ''}
|
||||
onchange="switchRuleGenerationType('description')">
|
||||
<span class="text-sm">${t('rules.fromDescription')}</span>
|
||||
</label>
|
||||
<label class="flex items-center gap-2 cursor-pointer">
|
||||
<input type="radio" name="ruleGenType" value="extract"
|
||||
class="w-4 h-4 text-primary bg-background border-border focus:ring-2 focus:ring-primary"
|
||||
${ruleCreateState.generationType === 'extract' ? 'checked' : ''}
|
||||
onchange="switchRuleGenerationType('extract')">
|
||||
<span class="text-sm">${t('rules.fromCodeExtract')}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Description Input (CLI mode, description type) -->
|
||||
<div id="ruleDescriptionSection" style="display: ${ruleCreateState.mode === 'cli-generate' && ruleCreateState.generationType === 'description' ? 'block' : 'none'}">
|
||||
<label class="block text-sm font-medium text-foreground mb-2">${t('rules.description')}</label>
|
||||
<textarea id="ruleDescription"
|
||||
class="w-full px-3 py-2 bg-background border border-border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-primary"
|
||||
rows="4"
|
||||
placeholder="${t('rules.descriptionPlaceholder')}">${ruleCreateState.description}</textarea>
|
||||
<p class="text-xs text-muted-foreground mt-1">${t('rules.descriptionHint')}</p>
|
||||
</div>
|
||||
|
||||
<!-- Code Extract Options (CLI mode, extract type) -->
|
||||
<div id="ruleExtractSection" style="display: ${ruleCreateState.mode === 'cli-generate' && ruleCreateState.generationType === 'extract' ? 'block' : 'none'}">
|
||||
<div class="space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-foreground mb-2">${t('rules.extractScope')}</label>
|
||||
<input type="text" id="ruleExtractScope"
|
||||
class="w-full px-3 py-2 bg-background border border-border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-primary font-mono"
|
||||
placeholder="src/**/*.ts"
|
||||
value="${ruleCreateState.extractScope}">
|
||||
<p class="text-xs text-muted-foreground mt-1">${t('rules.extractScopeHint')}</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-foreground mb-2">${t('rules.extractFocus')}</label>
|
||||
<input type="text" id="ruleExtractFocus"
|
||||
class="w-full px-3 py-2 bg-background border border-border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-primary"
|
||||
placeholder="naming, error-handling, state-management"
|
||||
value="${ruleCreateState.extractFocus}">
|
||||
<p class="text-xs text-muted-foreground mt-1">${t('rules.extractFocusHint')}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Conditional Rule Toggle (Manual mode only) -->
|
||||
<div id="ruleConditionalSection" style="display: ${ruleCreateState.mode === 'input' ? 'block' : 'none'}">
|
||||
<label class="flex items-center gap-2 cursor-pointer">
|
||||
<input type="checkbox" id="ruleConditional"
|
||||
class="w-4 h-4 text-primary bg-background border-border rounded focus:ring-2 focus:ring-primary"
|
||||
${ruleCreateState.isConditional ? 'checked' : ''}
|
||||
onchange="toggleRuleConditional()">
|
||||
<span class="text-sm font-medium text-foreground">${t('rules.conditionalRule')}</span>
|
||||
</label>
|
||||
<p class="text-xs text-muted-foreground mt-1 ml-6">${t('rules.conditionalHint')}</p>
|
||||
</div>
|
||||
|
||||
<!-- Path Conditions -->
|
||||
<div id="rulePathsContainer" style="display: ${ruleCreateState.isConditional ? 'block' : 'none'}">
|
||||
<label class="block text-sm font-medium text-foreground mb-2">${t('rules.pathConditions')}</label>
|
||||
<div id="rulePathsList" class="space-y-2">
|
||||
${ruleCreateState.paths.map((path, index) => `
|
||||
<div class="flex gap-2">
|
||||
<input type="text" class="rule-path-input flex-1 px-3 py-2 bg-background border border-border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-primary"
|
||||
placeholder="src/**/*.ts"
|
||||
value="${path}"
|
||||
data-index="${index}">
|
||||
${index > 0 ? `
|
||||
<button class="px-3 py-2 text-destructive hover:bg-destructive/10 rounded-lg transition-colors"
|
||||
onclick="removeRulePath(${index})">
|
||||
<i data-lucide="x" class="w-4 h-4"></i>
|
||||
</button>
|
||||
` : ''}
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
<button class="mt-2 px-3 py-1.5 text-sm text-primary hover:bg-primary/10 rounded-lg transition-colors flex items-center gap-1"
|
||||
onclick="addRulePath()">
|
||||
<i data-lucide="plus" class="w-4 h-4"></i>
|
||||
${t('rules.addPath')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Content (Manual mode only) -->
|
||||
<div id="ruleContentSection" style="display: ${ruleCreateState.mode === 'input' ? 'block' : 'none'}">
|
||||
<label class="block text-sm font-medium text-foreground mb-2">${t('rules.content')}</label>
|
||||
<textarea id="ruleContent"
|
||||
class="w-full px-3 py-2 bg-background border border-border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-primary font-mono"
|
||||
rows="10"
|
||||
placeholder="${t('rules.contentPlaceholder')}">${ruleCreateState.content}</textarea>
|
||||
<p class="text-xs text-muted-foreground mt-1">${t('rules.contentHint')}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="flex items-center justify-end gap-3 px-6 py-4 border-t border-border">
|
||||
<button class="px-4 py-2 text-sm text-muted-foreground hover:text-foreground transition-colors"
|
||||
onclick="closeRuleCreateModal()">
|
||||
${t('common.cancel')}
|
||||
</button>
|
||||
<button class="px-4 py-2 text-sm bg-primary text-primary-foreground rounded-lg hover:opacity-90 transition-opacity"
|
||||
onclick="createRule()">
|
||||
${t('rules.create')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Add to DOM
|
||||
const modalContainer = document.createElement('div');
|
||||
modalContainer.id = 'ruleCreateModal';
|
||||
modalContainer.innerHTML = modalHtml;
|
||||
document.body.appendChild(modalContainer);
|
||||
|
||||
// Initialize Lucide icons
|
||||
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||||
}
|
||||
|
||||
function closeRuleCreateModal(event) {
|
||||
if (event && event.target !== event.currentTarget) return;
|
||||
const modal = document.getElementById('ruleCreateModal');
|
||||
if (modal) modal.remove();
|
||||
}
|
||||
|
||||
function selectRuleLocation(location) {
|
||||
ruleCreateState.location = location;
|
||||
// Re-render modal
|
||||
closeRuleCreateModal();
|
||||
openRuleCreateModal();
|
||||
}
|
||||
|
||||
function toggleRuleConditional() {
|
||||
ruleCreateState.isConditional = !ruleCreateState.isConditional;
|
||||
const pathsContainer = document.getElementById('rulePathsContainer');
|
||||
if (pathsContainer) {
|
||||
pathsContainer.style.display = ruleCreateState.isConditional ? 'block' : 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function addRulePath() {
|
||||
ruleCreateState.paths.push('');
|
||||
// Re-render paths list
|
||||
const pathsList = document.getElementById('rulePathsList');
|
||||
if (pathsList) {
|
||||
const index = ruleCreateState.paths.length - 1;
|
||||
const pathHtml = `
|
||||
<div class="flex gap-2">
|
||||
<input type="text" class="rule-path-input flex-1 px-3 py-2 bg-background border border-border rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-primary"
|
||||
placeholder="src/**/*.ts"
|
||||
value=""
|
||||
data-index="${index}">
|
||||
<button class="px-3 py-2 text-destructive hover:bg-destructive/10 rounded-lg transition-colors"
|
||||
onclick="removeRulePath(${index})">
|
||||
<i data-lucide="x" class="w-4 h-4"></i>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
pathsList.insertAdjacentHTML('beforeend', pathHtml);
|
||||
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||||
}
|
||||
}
|
||||
|
||||
function removeRulePath(index) {
|
||||
ruleCreateState.paths.splice(index, 1);
|
||||
// Re-render paths list
|
||||
closeRuleCreateModal();
|
||||
openRuleCreateModal();
|
||||
}
|
||||
|
||||
function switchRuleCreateMode(mode) {
|
||||
ruleCreateState.mode = mode;
|
||||
|
||||
// Toggle visibility of sections
|
||||
const generationTypeSection = document.getElementById('ruleGenerationTypeSection');
|
||||
const descriptionSection = document.getElementById('ruleDescriptionSection');
|
||||
const extractSection = document.getElementById('ruleExtractSection');
|
||||
const conditionalSection = document.getElementById('ruleConditionalSection');
|
||||
const contentSection = document.getElementById('ruleContentSection');
|
||||
|
||||
if (mode === 'cli-generate') {
|
||||
if (generationTypeSection) generationTypeSection.style.display = 'block';
|
||||
if (conditionalSection) conditionalSection.style.display = 'none';
|
||||
if (contentSection) contentSection.style.display = 'none';
|
||||
|
||||
// Show appropriate generation section
|
||||
if (ruleCreateState.generationType === 'description') {
|
||||
if (descriptionSection) descriptionSection.style.display = 'block';
|
||||
if (extractSection) extractSection.style.display = 'none';
|
||||
} else {
|
||||
if (descriptionSection) descriptionSection.style.display = 'none';
|
||||
if (extractSection) extractSection.style.display = 'block';
|
||||
}
|
||||
} else {
|
||||
if (generationTypeSection) generationTypeSection.style.display = 'none';
|
||||
if (descriptionSection) descriptionSection.style.display = 'none';
|
||||
if (extractSection) extractSection.style.display = 'none';
|
||||
if (conditionalSection) conditionalSection.style.display = 'block';
|
||||
if (contentSection) contentSection.style.display = 'block';
|
||||
}
|
||||
|
||||
// Re-render modal to update button states
|
||||
closeRuleCreateModal();
|
||||
openRuleCreateModal();
|
||||
}
|
||||
|
||||
function switchRuleGenerationType(type) {
|
||||
ruleCreateState.generationType = type;
|
||||
|
||||
// Toggle visibility of generation sections
|
||||
const descriptionSection = document.getElementById('ruleDescriptionSection');
|
||||
const extractSection = document.getElementById('ruleExtractSection');
|
||||
|
||||
if (type === 'description') {
|
||||
if (descriptionSection) descriptionSection.style.display = 'block';
|
||||
if (extractSection) extractSection.style.display = 'none';
|
||||
} else if (type === 'extract') {
|
||||
if (descriptionSection) descriptionSection.style.display = 'none';
|
||||
if (extractSection) extractSection.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
async function createRule() {
|
||||
const fileNameInput = document.getElementById('ruleFileName');
|
||||
const subdirectoryInput = document.getElementById('ruleSubdirectory');
|
||||
const contentInput = document.getElementById('ruleContent');
|
||||
const pathInputs = document.querySelectorAll('.rule-path-input');
|
||||
const descriptionInput = document.getElementById('ruleDescription');
|
||||
const extractScopeInput = document.getElementById('ruleExtractScope');
|
||||
const extractFocusInput = document.getElementById('ruleExtractFocus');
|
||||
|
||||
const fileName = fileNameInput ? fileNameInput.value.trim() : ruleCreateState.fileName;
|
||||
const subdirectory = subdirectoryInput ? subdirectoryInput.value.trim() : ruleCreateState.subdirectory;
|
||||
|
||||
// Validate file name
|
||||
if (!fileName) {
|
||||
if (window.showToast) {
|
||||
showToast(t('rules.fileNameRequired'), 'error');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!fileName.endsWith('.md')) {
|
||||
if (window.showToast) {
|
||||
showToast(t('rules.fileNameMustEndMd'), 'error');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare request based on mode
|
||||
let requestBody;
|
||||
|
||||
if (ruleCreateState.mode === 'cli-generate') {
|
||||
// CLI generation mode
|
||||
const description = descriptionInput ? descriptionInput.value.trim() : ruleCreateState.description;
|
||||
const extractScope = extractScopeInput ? extractScopeInput.value.trim() : ruleCreateState.extractScope;
|
||||
const extractFocus = extractFocusInput ? extractFocusInput.value.trim() : ruleCreateState.extractFocus;
|
||||
|
||||
// Validate based on generation type
|
||||
if (ruleCreateState.generationType === 'description' && !description) {
|
||||
if (window.showToast) {
|
||||
showToast(t('rules.descriptionRequired'), 'error');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (ruleCreateState.generationType === 'extract' && !extractScope) {
|
||||
if (window.showToast) {
|
||||
showToast(t('rules.extractScopeRequired'), 'error');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
requestBody = {
|
||||
mode: 'cli-generate',
|
||||
fileName,
|
||||
location: ruleCreateState.location,
|
||||
subdirectory: subdirectory || undefined,
|
||||
projectPath,
|
||||
generationType: ruleCreateState.generationType,
|
||||
description: ruleCreateState.generationType === 'description' ? description : undefined,
|
||||
extractScope: ruleCreateState.generationType === 'extract' ? extractScope : undefined,
|
||||
extractFocus: ruleCreateState.generationType === 'extract' ? extractFocus : undefined
|
||||
};
|
||||
|
||||
// Show progress message
|
||||
if (window.showToast) {
|
||||
showToast(t('rules.cliGenerating'), 'info');
|
||||
}
|
||||
} else {
|
||||
// Manual input mode
|
||||
const content = contentInput ? contentInput.value.trim() : ruleCreateState.content;
|
||||
|
||||
// Collect paths from inputs
|
||||
const paths = [];
|
||||
if (ruleCreateState.isConditional && pathInputs) {
|
||||
pathInputs.forEach(input => {
|
||||
const path = input.value.trim();
|
||||
if (path) paths.push(path);
|
||||
});
|
||||
}
|
||||
|
||||
// Validate content
|
||||
if (!content) {
|
||||
if (window.showToast) {
|
||||
showToast(t('rules.contentRequired'), 'error');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
requestBody = {
|
||||
mode: 'input',
|
||||
fileName,
|
||||
content,
|
||||
paths: paths.length > 0 ? paths : undefined,
|
||||
location: ruleCreateState.location,
|
||||
subdirectory: subdirectory || undefined,
|
||||
projectPath
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/rules/create', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(requestBody)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.error || 'Failed to create rule');
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
// Close modal
|
||||
closeRuleCreateModal();
|
||||
|
||||
// Reload rules data
|
||||
await loadRulesData();
|
||||
renderRulesView();
|
||||
|
||||
// Show success message
|
||||
if (window.showToast) {
|
||||
showToast(t('rules.created', { name: result.fileName }), 'success');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed to create rule:', err);
|
||||
if (window.showToast) {
|
||||
showToast(err.message || t('rules.createError'), 'error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user