mirror of
https://github.com/andrepimenta/claude-code-chat.git
synced 2025-12-09 19:09:50 +00:00
MCP modal
This commit is contained in:
121
src/extension.ts
121
src/extension.ts
@@ -286,6 +286,15 @@ class ClaudeChatProvider {
|
||||
case 'addPermission':
|
||||
this._addPermission(message.toolName, message.command);
|
||||
return;
|
||||
case 'loadMCPServers':
|
||||
this._loadMCPServers();
|
||||
return;
|
||||
case 'saveMCPServer':
|
||||
this._saveMCPServer(message.name, message.config);
|
||||
return;
|
||||
case 'deleteMCPServer':
|
||||
this._deleteMCPServer(message.name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1451,6 +1460,118 @@ class ClaudeChatProvider {
|
||||
}
|
||||
}
|
||||
|
||||
private async _loadMCPServers(): Promise<void> {
|
||||
try {
|
||||
const mcpConfigPath = this.getMCPConfigPath();
|
||||
if (!mcpConfigPath) {
|
||||
this._sendAndSaveMessage({ type: 'mcpServers', data: {} });
|
||||
return;
|
||||
}
|
||||
|
||||
const mcpConfigUri = vscode.Uri.file(mcpConfigPath);
|
||||
let mcpConfig: any = { mcpServers: {} };
|
||||
|
||||
try {
|
||||
const content = await vscode.workspace.fs.readFile(mcpConfigUri);
|
||||
mcpConfig = JSON.parse(new TextDecoder().decode(content));
|
||||
} catch {
|
||||
// File doesn't exist, return empty servers
|
||||
}
|
||||
|
||||
this._postMessage({ type: 'mcpServers', data: mcpConfig.mcpServers || {} });
|
||||
} catch (error) {
|
||||
console.error('Error loading MCP servers:', error);
|
||||
this._postMessage({ type: 'mcpServerError', data: { error: 'Failed to load MCP servers' } });
|
||||
}
|
||||
}
|
||||
|
||||
private async _saveMCPServer(name: string, config: any): Promise<void> {
|
||||
try {
|
||||
const mcpConfigPath = this.getMCPConfigPath();
|
||||
if (!mcpConfigPath) {
|
||||
this._postMessage({ type: 'mcpServerError', data: { error: 'Storage path not available' } });
|
||||
return;
|
||||
}
|
||||
|
||||
const mcpConfigUri = vscode.Uri.file(mcpConfigPath);
|
||||
let mcpConfig: any = { mcpServers: {} };
|
||||
|
||||
// Load existing config
|
||||
try {
|
||||
const content = await vscode.workspace.fs.readFile(mcpConfigUri);
|
||||
mcpConfig = JSON.parse(new TextDecoder().decode(content));
|
||||
} catch {
|
||||
// File doesn't exist, use default structure
|
||||
}
|
||||
|
||||
// Ensure mcpServers exists
|
||||
if (!mcpConfig.mcpServers) {
|
||||
mcpConfig.mcpServers = {};
|
||||
}
|
||||
|
||||
// Add/update the server
|
||||
mcpConfig.mcpServers[name] = config;
|
||||
|
||||
// Ensure directory exists
|
||||
const mcpDir = vscode.Uri.file(path.dirname(mcpConfigPath));
|
||||
try {
|
||||
await vscode.workspace.fs.stat(mcpDir);
|
||||
} catch {
|
||||
await vscode.workspace.fs.createDirectory(mcpDir);
|
||||
}
|
||||
|
||||
// Save the config
|
||||
const configContent = new TextEncoder().encode(JSON.stringify(mcpConfig, null, 2));
|
||||
await vscode.workspace.fs.writeFile(mcpConfigUri, configContent);
|
||||
|
||||
this._postMessage({ type: 'mcpServerSaved', data: { name } });
|
||||
console.log(`Saved MCP server: ${name}`);
|
||||
} catch (error) {
|
||||
console.error('Error saving MCP server:', error);
|
||||
this._postMessage({ type: 'mcpServerError', data: { error: 'Failed to save MCP server' } });
|
||||
}
|
||||
}
|
||||
|
||||
private async _deleteMCPServer(name: string): Promise<void> {
|
||||
try {
|
||||
const mcpConfigPath = this.getMCPConfigPath();
|
||||
if (!mcpConfigPath) {
|
||||
this._postMessage({ type: 'mcpServerError', data: { error: 'Storage path not available' } });
|
||||
return;
|
||||
}
|
||||
|
||||
const mcpConfigUri = vscode.Uri.file(mcpConfigPath);
|
||||
let mcpConfig: any = { mcpServers: {} };
|
||||
|
||||
// Load existing config
|
||||
try {
|
||||
const content = await vscode.workspace.fs.readFile(mcpConfigUri);
|
||||
mcpConfig = JSON.parse(new TextDecoder().decode(content));
|
||||
} catch {
|
||||
// File doesn't exist, nothing to delete
|
||||
this._postMessage({ type: 'mcpServerError', data: { error: 'MCP config file not found' } });
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete the server
|
||||
if (mcpConfig.mcpServers && mcpConfig.mcpServers[name]) {
|
||||
delete mcpConfig.mcpServers[name];
|
||||
|
||||
// Save the updated config
|
||||
const configContent = new TextEncoder().encode(JSON.stringify(mcpConfig, null, 2));
|
||||
await vscode.workspace.fs.writeFile(mcpConfigUri, configContent);
|
||||
|
||||
this._postMessage({ type: 'mcpServerDeleted', data: { name } });
|
||||
console.log(`Deleted MCP server: ${name}`);
|
||||
} else {
|
||||
this._postMessage({ type: 'mcpServerError', data: { error: `Server '${name}' not found` } });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error deleting MCP server:', error);
|
||||
this._postMessage({ type: 'mcpServerError', data: { error: 'Failed to delete MCP server' } });
|
||||
}
|
||||
}
|
||||
|
||||
public getMCPConfigPath(): string | undefined {
|
||||
const storagePath = this._context.storageUri?.fsPath;
|
||||
if (!storagePath) {return undefined;}
|
||||
|
||||
123
src/ui-styles.ts
123
src/ui-styles.ts
@@ -2307,6 +2307,129 @@ const styles = `
|
||||
color: var(--vscode-foreground);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* MCP Servers styles */
|
||||
.mcp-servers-list {
|
||||
margin-bottom: 20px;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.mcp-server-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 16px;
|
||||
border: 1px solid var(--vscode-panel-border);
|
||||
border-radius: 6px;
|
||||
margin-bottom: 8px;
|
||||
background-color: var(--vscode-editor-background);
|
||||
}
|
||||
|
||||
.server-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.server-name {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
color: var(--vscode-foreground);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.server-type {
|
||||
display: inline-block;
|
||||
background-color: var(--vscode-badge-background);
|
||||
color: var(--vscode-badge-foreground);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-size: 10px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.server-config {
|
||||
font-size: 12px;
|
||||
color: var(--vscode-descriptionForeground);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.server-delete-btn {
|
||||
padding: 4px 8px;
|
||||
font-size: 12px;
|
||||
color: var(--vscode-errorForeground);
|
||||
border-color: var(--vscode-errorForeground);
|
||||
}
|
||||
|
||||
.server-delete-btn:hover {
|
||||
background-color: var(--vscode-inputValidation-errorBackground);
|
||||
border-color: var(--vscode-errorForeground);
|
||||
}
|
||||
|
||||
.mcp-add-server {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.mcp-add-form {
|
||||
background-color: var(--vscode-editor-background);
|
||||
border: 1px solid var(--vscode-panel-border);
|
||||
border-radius: 6px;
|
||||
padding: 16px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
font-weight: 500;
|
||||
font-size: 13px;
|
||||
color: var(--vscode-foreground);
|
||||
}
|
||||
|
||||
.form-group input,
|
||||
.form-group select,
|
||||
.form-group textarea {
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid var(--vscode-input-border);
|
||||
border-radius: 4px;
|
||||
background-color: var(--vscode-input-background);
|
||||
color: var(--vscode-input-foreground);
|
||||
font-size: 13px;
|
||||
font-family: var(--vscode-font-family);
|
||||
}
|
||||
|
||||
.form-group input:focus,
|
||||
.form-group select:focus,
|
||||
.form-group textarea:focus {
|
||||
outline: none;
|
||||
border-color: var(--vscode-focusBorder);
|
||||
box-shadow: 0 0 0 1px var(--vscode-focusBorder);
|
||||
}
|
||||
|
||||
.form-group textarea {
|
||||
resize: vertical;
|
||||
min-height: 60px;
|
||||
}
|
||||
|
||||
.form-buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
justify-content: flex-end;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.no-servers {
|
||||
text-align: center;
|
||||
color: var(--vscode-descriptionForeground);
|
||||
font-style: italic;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
</style>`
|
||||
|
||||
export default styles
|
||||
269
src/ui.ts
269
src/ui.ts
@@ -75,8 +75,8 @@ const html = `<!DOCTYPE html>
|
||||
<path d="M1 2.5l3 3 3-3"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="tools-btn" onclick="showToolsModal()" title="Configure tools">
|
||||
Tools: All
|
||||
<button class="tools-btn" onclick="showMCPModal()" title="Configure MCP servers">
|
||||
MCP
|
||||
<svg width="8" height="8" viewBox="0 0 8 8" fill="currentColor">
|
||||
<path d="M1 2.5l3 3 3-3"></path>
|
||||
</svg>
|
||||
@@ -148,56 +148,55 @@ const html = `<!DOCTYPE html>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tools modal -->
|
||||
<div id="toolsModal" class="tools-modal" style="display: none;">
|
||||
<!-- MCP Servers modal -->
|
||||
<div id="mcpModal" class="tools-modal" style="display: none;">
|
||||
<div class="tools-modal-content">
|
||||
<div class="tools-modal-header">
|
||||
<span>Claude Code Tools</span>
|
||||
<button class="tools-close-btn" onclick="hideToolsModal()">✕</button>
|
||||
<span>MCP Servers</span>
|
||||
<button class="tools-close-btn" onclick="hideMCPModal()">✕</button>
|
||||
</div>
|
||||
<div class="tools-beta-warning">
|
||||
In Beta: All tools are enabled by default. Use at your own risk.
|
||||
<div class="mcp-servers-list" id="mcpServersList">
|
||||
<!-- MCP servers will be loaded here -->
|
||||
</div>
|
||||
<div id="toolsList" class="tools-list">
|
||||
<div class="tool-item">
|
||||
<input type="checkbox" id="tool-bash" checked disabled>
|
||||
<label for="tool-bash">Bash - Execute shell commands</label>
|
||||
<div class="mcp-add-server">
|
||||
<button class="btn outlined" onclick="showAddServerForm()" id="addServerBtn">+ Add MCP Server</button>
|
||||
</div>
|
||||
<div class="mcp-add-form" id="addServerForm" style="display: none;">
|
||||
<div class="form-group">
|
||||
<label for="serverName">Server Name:</label>
|
||||
<input type="text" id="serverName" placeholder="my-server" required>
|
||||
</div>
|
||||
<div class="tool-item">
|
||||
<input type="checkbox" id="tool-read" checked disabled>
|
||||
<label for="tool-read">Read - Read file contents</label>
|
||||
<div class="form-group">
|
||||
<label for="serverType">Server Type:</label>
|
||||
<select id="serverType" onchange="updateServerForm()">
|
||||
<option value="stdio">stdio</option>
|
||||
<option value="http">HTTP</option>
|
||||
<option value="sse">SSE</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="tool-item">
|
||||
<input type="checkbox" id="tool-edit" checked disabled>
|
||||
<label for="tool-edit">Edit - Modify files</label>
|
||||
<div class="form-group" id="commandGroup">
|
||||
<label for="serverCommand">Command:</label>
|
||||
<input type="text" id="serverCommand" placeholder="/path/to/server">
|
||||
</div>
|
||||
<div class="tool-item">
|
||||
<input type="checkbox" id="tool-write" checked disabled>
|
||||
<label for="tool-write">Write - Create new files</label>
|
||||
<div class="form-group" id="urlGroup" style="display: none;">
|
||||
<label for="serverUrl">URL:</label>
|
||||
<input type="text" id="serverUrl" placeholder="https://example.com/mcp">
|
||||
</div>
|
||||
<div class="tool-item">
|
||||
<input type="checkbox" id="tool-glob" checked disabled>
|
||||
<label for="tool-glob">Glob - Find files by pattern</label>
|
||||
<div class="form-group" id="argsGroup">
|
||||
<label for="serverArgs">Arguments (one per line):</label>
|
||||
<textarea id="serverArgs" placeholder="--api-key abc123" rows="3"></textarea>
|
||||
</div>
|
||||
<div class="tool-item">
|
||||
<input type="checkbox" id="tool-grep" checked disabled>
|
||||
<label for="tool-grep">Grep - Search file contents</label>
|
||||
<div class="form-group" id="envGroup">
|
||||
<label for="serverEnv">Environment Variables (KEY=value, one per line):</label>
|
||||
<textarea id="serverEnv" placeholder="API_KEY=123 CACHE_DIR=/tmp" rows="3"></textarea>
|
||||
</div>
|
||||
<div class="tool-item">
|
||||
<input type="checkbox" id="tool-ls" checked disabled>
|
||||
<label for="tool-ls">LS - List directory contents</label>
|
||||
<div class="form-group" id="headersGroup" style="display: none;">
|
||||
<label for="serverHeaders">Headers (KEY=value, one per line):</label>
|
||||
<textarea id="serverHeaders" placeholder="Authorization=Bearer token X-API-Key=key" rows="3"></textarea>
|
||||
</div>
|
||||
<div class="tool-item">
|
||||
<input type="checkbox" id="tool-multiedit" checked disabled>
|
||||
<label for="tool-multiedit">MultiEdit - Edit multiple files</label>
|
||||
</div>
|
||||
<div class="tool-item">
|
||||
<input type="checkbox" id="tool-websearch" checked disabled>
|
||||
<label for="tool-websearch">WebSearch - Search the web</label>
|
||||
</div>
|
||||
<div class="tool-item">
|
||||
<input type="checkbox" id="tool-webfetch" checked disabled>
|
||||
<label for="tool-webfetch">WebFetch - Fetch web content</label>
|
||||
<div class="form-buttons">
|
||||
<button class="btn" onclick="saveMCPServer()">Add Server</button>
|
||||
<button class="btn outlined" onclick="hideAddServerForm()">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1523,8 +1522,10 @@ const html = `<!DOCTYPE html>
|
||||
});
|
||||
|
||||
// Tools modal functions
|
||||
function showToolsModal() {
|
||||
document.getElementById('toolsModal').style.display = 'flex';
|
||||
function showMCPModal() {
|
||||
document.getElementById('mcpModal').style.display = 'flex';
|
||||
// Load existing MCP servers
|
||||
loadMCPServers();
|
||||
}
|
||||
|
||||
function updateYoloWarning() {
|
||||
@@ -1575,17 +1576,173 @@ const html = `<!DOCTYPE html>
|
||||
}
|
||||
}
|
||||
|
||||
function hideToolsModal() {
|
||||
document.getElementById('toolsModal').style.display = 'none';
|
||||
function hideMCPModal() {
|
||||
document.getElementById('mcpModal').style.display = 'none';
|
||||
hideAddServerForm();
|
||||
}
|
||||
|
||||
// Close tools modal when clicking outside
|
||||
document.getElementById('toolsModal').addEventListener('click', (e) => {
|
||||
if (e.target === document.getElementById('toolsModal')) {
|
||||
hideToolsModal();
|
||||
// Close MCP modal when clicking outside
|
||||
document.getElementById('mcpModal').addEventListener('click', (e) => {
|
||||
if (e.target === document.getElementById('mcpModal')) {
|
||||
hideMCPModal();
|
||||
}
|
||||
});
|
||||
|
||||
// MCP Server management functions
|
||||
function loadMCPServers() {
|
||||
vscode.postMessage({ type: 'loadMCPServers' });
|
||||
}
|
||||
|
||||
function showAddServerForm() {
|
||||
document.getElementById('addServerBtn').style.display = 'none';
|
||||
document.getElementById('addServerForm').style.display = 'block';
|
||||
}
|
||||
|
||||
function hideAddServerForm() {
|
||||
document.getElementById('addServerBtn').style.display = 'block';
|
||||
document.getElementById('addServerForm').style.display = 'none';
|
||||
// Clear form
|
||||
document.getElementById('serverName').value = '';
|
||||
document.getElementById('serverCommand').value = '';
|
||||
document.getElementById('serverUrl').value = '';
|
||||
document.getElementById('serverArgs').value = '';
|
||||
document.getElementById('serverEnv').value = '';
|
||||
document.getElementById('serverHeaders').value = '';
|
||||
document.getElementById('serverType').value = 'stdio';
|
||||
updateServerForm();
|
||||
}
|
||||
|
||||
function updateServerForm() {
|
||||
const serverType = document.getElementById('serverType').value;
|
||||
const commandGroup = document.getElementById('commandGroup');
|
||||
const urlGroup = document.getElementById('urlGroup');
|
||||
const argsGroup = document.getElementById('argsGroup');
|
||||
const envGroup = document.getElementById('envGroup');
|
||||
const headersGroup = document.getElementById('headersGroup');
|
||||
|
||||
if (serverType === 'stdio') {
|
||||
commandGroup.style.display = 'block';
|
||||
urlGroup.style.display = 'none';
|
||||
argsGroup.style.display = 'block';
|
||||
envGroup.style.display = 'block';
|
||||
headersGroup.style.display = 'none';
|
||||
} else if (serverType === 'http' || serverType === 'sse') {
|
||||
commandGroup.style.display = 'none';
|
||||
urlGroup.style.display = 'block';
|
||||
argsGroup.style.display = 'none';
|
||||
envGroup.style.display = 'none';
|
||||
headersGroup.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
function saveMCPServer() {
|
||||
const name = document.getElementById('serverName').value.trim();
|
||||
const type = document.getElementById('serverType').value;
|
||||
|
||||
if (!name) {
|
||||
alert('Server name is required');
|
||||
return;
|
||||
}
|
||||
|
||||
const serverConfig = { type };
|
||||
|
||||
if (type === 'stdio') {
|
||||
const command = document.getElementById('serverCommand').value.trim();
|
||||
if (!command) {
|
||||
alert('Command is required for stdio servers');
|
||||
return;
|
||||
}
|
||||
serverConfig.command = command;
|
||||
|
||||
const argsText = document.getElementById('serverArgs').value.trim();
|
||||
if (argsText) {
|
||||
serverConfig.args = argsText.split('\\n').filter(line => line.trim());
|
||||
}
|
||||
|
||||
const envText = document.getElementById('serverEnv').value.trim();
|
||||
if (envText) {
|
||||
serverConfig.env = {};
|
||||
envText.split('\\n').forEach(line => {
|
||||
const [key, ...valueParts] = line.split('=');
|
||||
if (key && valueParts.length > 0) {
|
||||
serverConfig.env[key.trim()] = valueParts.join('=').trim();
|
||||
}
|
||||
});
|
||||
}
|
||||
} else if (type === 'http' || type === 'sse') {
|
||||
const url = document.getElementById('serverUrl').value.trim();
|
||||
if (!url) {
|
||||
alert('URL is required for HTTP/SSE servers');
|
||||
return;
|
||||
}
|
||||
serverConfig.url = url;
|
||||
|
||||
const headersText = document.getElementById('serverHeaders').value.trim();
|
||||
if (headersText) {
|
||||
serverConfig.headers = {};
|
||||
headersText.split('\\n').forEach(line => {
|
||||
const [key, ...valueParts] = line.split('=');
|
||||
if (key && valueParts.length > 0) {
|
||||
serverConfig.headers[key.trim()] = valueParts.join('=').trim();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
vscode.postMessage({
|
||||
type: 'saveMCPServer',
|
||||
name: name,
|
||||
config: serverConfig
|
||||
});
|
||||
|
||||
hideAddServerForm();
|
||||
}
|
||||
|
||||
function deleteMCPServer(serverName) {
|
||||
if (confirm(\`Are you sure you want to delete the server "\${serverName}"?\`)) {
|
||||
vscode.postMessage({
|
||||
type: 'deleteMCPServer',
|
||||
name: serverName
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function displayMCPServers(servers) {
|
||||
const serversList = document.getElementById('mcpServersList');
|
||||
serversList.innerHTML = '';
|
||||
|
||||
if (Object.keys(servers).length === 0) {
|
||||
serversList.innerHTML = '<div class="no-servers">No MCP servers configured</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
for (const [name, config] of Object.entries(servers)) {
|
||||
const serverItem = document.createElement('div');
|
||||
serverItem.className = 'mcp-server-item';
|
||||
|
||||
let configDisplay = '';
|
||||
if (config.type === 'stdio') {
|
||||
configDisplay = \`Command: \${config.command}\`;
|
||||
if (config.args) {
|
||||
configDisplay += \`<br>Args: \${config.args.join(' ')}\`;
|
||||
}
|
||||
} else {
|
||||
configDisplay = \`URL: \${config.url}\`;
|
||||
}
|
||||
|
||||
serverItem.innerHTML = \`
|
||||
<div class="server-info">
|
||||
<div class="server-name">\${name}</div>
|
||||
<div class="server-type">\${config.type.toUpperCase()}</div>
|
||||
<div class="server-config">\${configDisplay}</div>
|
||||
</div>
|
||||
<button class="btn outlined server-delete-btn" onclick="deleteMCPServer('\${name}')">Delete</button>
|
||||
\`;
|
||||
|
||||
serversList.appendChild(serverItem);
|
||||
}
|
||||
}
|
||||
|
||||
// Model selector functions
|
||||
let currentModel = 'opus'; // Default model
|
||||
|
||||
@@ -2141,6 +2298,20 @@ const html = `<!DOCTYPE html>
|
||||
case 'permissionRequest':
|
||||
addPermissionRequestMessage(message.data);
|
||||
break;
|
||||
case 'mcpServers':
|
||||
displayMCPServers(message.data);
|
||||
break;
|
||||
case 'mcpServerSaved':
|
||||
loadMCPServers(); // Reload the servers list
|
||||
addMessage('✅ MCP server "' + message.data.name + '" saved successfully', 'system');
|
||||
break;
|
||||
case 'mcpServerDeleted':
|
||||
loadMCPServers(); // Reload the servers list
|
||||
addMessage('✅ MCP server "' + message.data.name + '" deleted successfully', 'system');
|
||||
break;
|
||||
case 'mcpServerError':
|
||||
addMessage('❌ Error with MCP server: ' + message.data.error, 'error');
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user