Release v0.2.10: Update package-lock.json and CI config
This commit is contained in:
@@ -922,13 +922,17 @@
|
||||
</div>
|
||||
|
||||
<div class="property-group">
|
||||
<label for="locationName">Name</label>
|
||||
<input type="text" id="locationName" placeholder="Location Name">
|
||||
<label>Name</label>
|
||||
<div class="property-row">
|
||||
<input type="text" id="locationName_en" placeholder="Name (EN)">
|
||||
<input type="text" id="locationName_es" placeholder="Nombre (ES)">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="property-group">
|
||||
<label for="locationDescription">Description</label>
|
||||
<textarea id="locationDescription" placeholder="Enter location description..."></textarea>
|
||||
<label>Description</label>
|
||||
<textarea id="locationDescription_en" placeholder="Description (EN)" style="margin-bottom: 5px; min-height: 60px;"></textarea>
|
||||
<textarea id="locationDescription_es" placeholder="Descripción (ES)" style="min-height: 60px;"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="property-group">
|
||||
|
||||
@@ -12,6 +12,14 @@ let liveStats = { players: {}, enemies: {} };
|
||||
let canvas, ctx;
|
||||
let currentTab = 'locations';
|
||||
|
||||
// Helper for i18n display
|
||||
function getI18nDisplay(val) {
|
||||
if (typeof val === 'object' && val !== null) {
|
||||
return val.en || val.es || '';
|
||||
}
|
||||
return val || '';
|
||||
}
|
||||
|
||||
// View state
|
||||
let scale = 50;
|
||||
let minScale = 10;
|
||||
@@ -192,7 +200,8 @@ function renderLocationList() {
|
||||
currentLocations.forEach(location => {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'location-item';
|
||||
item.dataset.name = location.name;
|
||||
const displayName = getI18nDisplay(location.name);
|
||||
item.dataset.name = displayName;
|
||||
item.dataset.id = location.id;
|
||||
if (location.id === selectedLocationId) {
|
||||
item.classList.add('active');
|
||||
@@ -202,7 +211,7 @@ function renderLocationList() {
|
||||
const enemyCount = liveStats.enemies[location.id] || 0;
|
||||
|
||||
item.innerHTML = `
|
||||
<div class="location-item-name">${location.name}</div>
|
||||
<div class="location-item-name">${displayName}</div>
|
||||
<div class="location-item-coords">📍 (${location.x}, ${location.y}) | Danger: ${location.danger_level}</div>
|
||||
${playerCount > 0 || enemyCount > 0 ? `<div class="location-item-stats">👥 ${playerCount} | 👹 ${enemyCount}</div>` : ''}
|
||||
`;
|
||||
@@ -233,8 +242,26 @@ function populateForm(location) {
|
||||
document.getElementById('propertiesForm').classList.remove('hidden');
|
||||
|
||||
document.getElementById('locationId').value = location.id;
|
||||
document.getElementById('locationName').value = location.name;
|
||||
document.getElementById('locationDescription').value = location.description;
|
||||
|
||||
// Handle i18n name
|
||||
const name = location.name || '';
|
||||
if (typeof name === 'object') {
|
||||
document.getElementById('locationName_en').value = name.en || '';
|
||||
document.getElementById('locationName_es').value = name.es || '';
|
||||
} else {
|
||||
document.getElementById('locationName_en').value = name;
|
||||
document.getElementById('locationName_es').value = '';
|
||||
}
|
||||
|
||||
// Handle i18n description
|
||||
const desc = location.description || '';
|
||||
if (typeof desc === 'object') {
|
||||
document.getElementById('locationDescription_en').value = desc.en || '';
|
||||
document.getElementById('locationDescription_es').value = desc.es || '';
|
||||
} else {
|
||||
document.getElementById('locationDescription_en').value = desc;
|
||||
document.getElementById('locationDescription_es').value = '';
|
||||
}
|
||||
document.getElementById('locationX').value = location.x;
|
||||
document.getElementById('locationY').value = location.y;
|
||||
document.getElementById('dangerLevel').value = location.danger_level;
|
||||
@@ -521,7 +548,7 @@ function drawLocations(centerX, centerY) {
|
||||
ctx.fillStyle = '#e0e0e0';
|
||||
ctx.font = '12px sans-serif';
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillText(location.name, x, y + 30);
|
||||
ctx.fillText(getI18nDisplay(location.name), x, y + 30);
|
||||
|
||||
// Draw live stats badges
|
||||
const playerCount = liveStats.players[location.id] || 0;
|
||||
@@ -713,8 +740,14 @@ function createNewLocation() {
|
||||
async function saveLocation() {
|
||||
const locationData = {
|
||||
id: document.getElementById('locationId').value,
|
||||
name: document.getElementById('locationName').value,
|
||||
description: document.getElementById('locationDescription').value,
|
||||
name: {
|
||||
en: document.getElementById('locationName_en').value,
|
||||
es: document.getElementById('locationName_es').value
|
||||
},
|
||||
description: {
|
||||
en: document.getElementById('locationDescription_en').value,
|
||||
es: document.getElementById('locationDescription_es').value
|
||||
},
|
||||
x: parseFloat(document.getElementById('locationX').value),
|
||||
y: parseFloat(document.getElementById('locationY').value),
|
||||
danger_level: parseInt(document.getElementById('dangerLevel').value),
|
||||
@@ -1364,8 +1397,8 @@ function editInteractableInstance(instanceId, templateId) {
|
||||
const editor = document.getElementById('interactableInstanceEditor');
|
||||
editor.innerHTML = `
|
||||
<div style="background: rgba(255,255,255,0.05); padding: 15px; border-radius: 8px; margin-bottom: 20px;">
|
||||
<h4 style="margin: 0 0 5px 0;">${template.name}</h4>
|
||||
<p style="margin: 0; opacity: 0.7; font-size: 0.9em;">${template.description}</p>
|
||||
<h4 style="margin: 0 0 5px 0;">${getI18nDisplay(template.name)}</h4>
|
||||
<p style="margin: 0; opacity: 0.7; font-size: 0.9em;">${getI18nDisplay(template.description)}</p>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; flex-direction: column; gap: 20px;">
|
||||
@@ -1448,7 +1481,7 @@ function editInteractableInstance(instanceId, templateId) {
|
||||
<div id="item_rewards_${actionId}" style="margin-bottom: 10px;">
|
||||
${(outcome.rewards?.items || []).map((reward, idx) => {
|
||||
const selectedItem = availableItems.find(i => i.id === reward.item_id);
|
||||
const displayValue = selectedItem ? `${selectedItem.emoji || '📦'} ${selectedItem.name}` : '';
|
||||
const displayValue = selectedItem ? `${selectedItem.emoji || '📦'} ${getI18nDisplay(selectedItem.name)}` : '';
|
||||
return `
|
||||
<div class="reward-item" style="display: flex; gap: 10px; margin-bottom: 5px; align-items: center; position: relative;">
|
||||
<div style="flex: 2; position: relative;">
|
||||
@@ -1484,7 +1517,7 @@ function editInteractableInstance(instanceId, templateId) {
|
||||
<div id="crit_item_rewards_${actionId}" style="margin-bottom: 10px;">
|
||||
${(outcome.rewards?.crit_items || []).map((reward, idx) => {
|
||||
const selectedItem = availableItems.find(i => i.id === reward.item_id);
|
||||
const displayValue = selectedItem ? `${selectedItem.emoji || '📦'} ${selectedItem.name}` : '';
|
||||
const displayValue = selectedItem ? `${selectedItem.emoji || '📦'} ${getI18nDisplay(selectedItem.name)}` : '';
|
||||
return `
|
||||
<div class="reward-item" style="display: flex; gap: 10px; margin-bottom: 5px; align-items: center; position: relative;">
|
||||
<div style="flex: 2; position: relative;">
|
||||
@@ -1590,7 +1623,7 @@ function filterItemDropdown(input) {
|
||||
|
||||
// Filter items
|
||||
const filteredItems = availableItems.filter(item => {
|
||||
const itemText = `${item.emoji || ''} ${item.name || ''} ${item.id}`.toLowerCase();
|
||||
const itemText = `${item.emoji || ''} ${getI18nDisplay(item.name)} ${item.id}`.toLowerCase();
|
||||
return itemText.includes(searchText);
|
||||
});
|
||||
|
||||
@@ -1612,7 +1645,7 @@ function filterItemDropdown(input) {
|
||||
optionDiv.style.borderBottom = '1px solid #2a2a4a';
|
||||
|
||||
optionDiv.innerHTML = `
|
||||
${item.emoji || '📦'} ${item.name || item.id}
|
||||
${item.emoji || '📦'} ${getI18nDisplay(item.name) || item.id}
|
||||
<span style="opacity: 0.5; font-size: 0.85em; margin-left: 5px;">${item.id}</span>
|
||||
`;
|
||||
|
||||
@@ -1628,7 +1661,7 @@ function filterItemDropdown(input) {
|
||||
optionDiv.addEventListener('click', function (e) {
|
||||
e.stopPropagation();
|
||||
// Set the visible input to show emoji + name
|
||||
input.value = `${item.emoji || '📦'} ${item.name || item.id}`;
|
||||
input.value = `${item.emoji || '📦'} ${getI18nDisplay(item.name) || item.id}`;
|
||||
// Set the hidden input to store the item_id
|
||||
hiddenInput.value = item.id;
|
||||
dropdown.style.display = 'none';
|
||||
@@ -1674,7 +1707,7 @@ function filterMaterialDropdown(input, prefix) {
|
||||
|
||||
// Filter items
|
||||
const filteredItems = availableItems.filter(item => {
|
||||
const itemText = `${item.emoji || ''} ${item.name || ''} ${item.id}`.toLowerCase();
|
||||
const itemText = `${item.emoji || ''} ${getI18nDisplay(item.name)} ${item.id}`.toLowerCase();
|
||||
return itemText.includes(searchText);
|
||||
});
|
||||
|
||||
@@ -1696,7 +1729,7 @@ function filterMaterialDropdown(input, prefix) {
|
||||
optionDiv.style.borderBottom = '1px solid #2a2a4a';
|
||||
|
||||
optionDiv.innerHTML = `
|
||||
${item.emoji || '📦'} ${item.name || item.id}
|
||||
${item.emoji || '📦'} ${getI18nDisplay(item.name) || item.id}
|
||||
<span style="opacity: 0.5; font-size: 0.85em; margin-left: 5px;">${item.id}</span>
|
||||
`;
|
||||
|
||||
@@ -1711,7 +1744,7 @@ function filterMaterialDropdown(input, prefix) {
|
||||
// Add click handler
|
||||
optionDiv.addEventListener('click', function (e) {
|
||||
e.stopPropagation();
|
||||
input.value = `${item.emoji || '📦'} ${item.name || item.id}`;
|
||||
input.value = `${item.emoji || '📦'} ${getI18nDisplay(item.name) || item.id}`;
|
||||
hiddenInput.value = item.id;
|
||||
dropdown.style.display = 'none';
|
||||
});
|
||||
@@ -1734,7 +1767,7 @@ function filterToolDropdown(input, prefix) {
|
||||
|
||||
// Filter items
|
||||
const filteredItems = availableItems.filter(item => {
|
||||
const itemText = `${item.emoji || ''} ${item.name || ''} ${item.id}`.toLowerCase();
|
||||
const itemText = `${item.emoji || ''} ${getI18nDisplay(item.name)} ${item.id}`.toLowerCase();
|
||||
return itemText.includes(searchText);
|
||||
});
|
||||
|
||||
@@ -1756,7 +1789,7 @@ function filterToolDropdown(input, prefix) {
|
||||
optionDiv.style.borderBottom = '1px solid #2a2a4a';
|
||||
|
||||
optionDiv.innerHTML = `
|
||||
${item.emoji || '🔧'} ${item.name || item.id}
|
||||
${item.emoji || '🔧'} ${getI18nDisplay(item.name) || item.id}
|
||||
<span style="opacity: 0.5; font-size: 0.85em; margin-left: 5px;">${item.id}</span>
|
||||
`;
|
||||
|
||||
@@ -1771,7 +1804,7 @@ function filterToolDropdown(input, prefix) {
|
||||
// Add click handler
|
||||
optionDiv.addEventListener('click', function (e) {
|
||||
e.stopPropagation();
|
||||
input.value = `${item.emoji || '🔧'} ${item.name || item.id}`;
|
||||
input.value = `${item.emoji || '🔧'} ${getI18nDisplay(item.name) || item.id}`;
|
||||
hiddenInput.value = item.id;
|
||||
dropdown.style.display = 'none';
|
||||
});
|
||||
@@ -1932,14 +1965,14 @@ function renderInteractablesList(interactables) {
|
||||
// Try to find template in available interactables
|
||||
const template = availableInteractables.find(i => i.id === templateId);
|
||||
if (template) {
|
||||
displayName = template.name;
|
||||
displayName = getI18nDisplay(template.name);
|
||||
actionCount = Object.keys(template.actions || {}).length;
|
||||
console.log(`Found template: ${displayName} with ${actionCount} actions`);
|
||||
} else {
|
||||
console.log(`Template ${templateId} not found in availableInteractables`);
|
||||
// For new format instances that have full data
|
||||
if (instance.name) {
|
||||
displayName = instance.name;
|
||||
displayName = getI18nDisplay(instance.name);
|
||||
}
|
||||
if (instance.actions) {
|
||||
actionCount = Object.keys(instance.actions).length;
|
||||
@@ -2327,14 +2360,15 @@ function renderNPCManagementList() {
|
||||
availableNPCs.forEach(npc => {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'management-item';
|
||||
item.dataset.name = npc.name;
|
||||
const displayName = getI18nDisplay(npc.name);
|
||||
item.dataset.name = displayName;
|
||||
item.dataset.id = npc.id;
|
||||
if (npc.id === selectedNPCId) {
|
||||
item.classList.add('active');
|
||||
}
|
||||
|
||||
item.innerHTML = `
|
||||
<div><strong>${npc.emoji} ${npc.name}</strong></div>
|
||||
<div><strong>${npc.emoji} ${displayName}</strong></div>
|
||||
<div style="font-size: 0.85em; opacity: 0.7; margin-top: 5px;">
|
||||
HP: ${npc.hp_min}-${npc.hp_max} | DMG: ${npc.damage_min}-${npc.damage_max}
|
||||
</div>
|
||||
@@ -2374,7 +2408,10 @@ function selectNPC(npcId) {
|
||||
</div>
|
||||
<div class="property-group">
|
||||
<label>Name</label>
|
||||
<input type="text" id="npcName" value="${npc.name}">
|
||||
<div class="property-row">
|
||||
<input type="text" id="npcName_en" value="${typeof npc.name === 'object' ? npc.name.en || '' : npc.name || ''}" placeholder="Name (EN)">
|
||||
<input type="text" id="npcName_es" value="${typeof npc.name === 'object' ? npc.name.es || '' : ''}" placeholder="Nombre (ES)">
|
||||
</div>
|
||||
</div>
|
||||
<div class="property-group">
|
||||
<label>Emoji</label>
|
||||
@@ -2417,7 +2454,8 @@ function selectNPC(npcId) {
|
||||
|
||||
<div class="property-group">
|
||||
<label>Description</label>
|
||||
<textarea id="npcDescription" rows="3" style="width: 100%;">${npc.description || ''}</textarea>
|
||||
<textarea id="npcDescription_en" rows="3" placeholder="Description (EN)" style="width: 100%; margin-bottom: 5px;">${typeof npc.description === 'object' ? npc.description.en || '' : npc.description || ''}</textarea>
|
||||
<textarea id="npcDescription_es" rows="3" placeholder="Descripción (ES)" style="width: 100%;">${typeof npc.description === 'object' ? npc.description.es || '' : ''}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="property-group">
|
||||
@@ -2587,7 +2625,10 @@ async function saveCurrentNPC() {
|
||||
|
||||
const npcData = {
|
||||
id: document.getElementById('npcId').value,
|
||||
name: document.getElementById('npcName').value,
|
||||
name: {
|
||||
en: document.getElementById('npcName_en').value,
|
||||
es: document.getElementById('npcName_es').value
|
||||
},
|
||||
emoji: document.getElementById('npcEmoji').value,
|
||||
hp_min: parseInt(document.getElementById('npcHpMin').value),
|
||||
hp_max: parseInt(document.getElementById('npcHpMax').value),
|
||||
@@ -2595,7 +2636,10 @@ async function saveCurrentNPC() {
|
||||
damage_max: parseInt(document.getElementById('npcDamageMax').value),
|
||||
xp_reward: parseInt(document.getElementById('npcXp').value),
|
||||
defense: parseInt(document.getElementById('npcDefense').value),
|
||||
description: document.getElementById('npcDescription').value,
|
||||
description: {
|
||||
en: document.getElementById('npcDescription_en').value,
|
||||
es: document.getElementById('npcDescription_es').value
|
||||
},
|
||||
image_path: document.getElementById('npcImagePath').value,
|
||||
loot_table: getNPCLootTable(),
|
||||
corpse_loot: getNPCCorpseLoot()
|
||||
@@ -2685,7 +2729,7 @@ function filterNPCLootItemDropdown(index) {
|
||||
|
||||
const filtered = availableItems.filter(item =>
|
||||
item.id.toLowerCase().includes(searchTerm) ||
|
||||
(item.name && item.name.toLowerCase().includes(searchTerm)) ||
|
||||
(getI18nDisplay(item.name).toLowerCase().includes(searchTerm)) ||
|
||||
(item.emoji && item.emoji.includes(searchTerm))
|
||||
);
|
||||
|
||||
@@ -2700,7 +2744,7 @@ function filterNPCLootItemDropdown(index) {
|
||||
option.style.padding = '8px';
|
||||
option.style.cursor = 'pointer';
|
||||
option.style.borderBottom = '1px solid #2a2a4a';
|
||||
option.innerHTML = `${item.emoji || '📦'} ${item.name || item.id} <span style="opacity: 0.6; font-size: 0.9em;">(${item.id})</span>`;
|
||||
option.innerHTML = `${item.emoji || '📦'} ${getI18nDisplay(item.name) || item.id} <span style="opacity: 0.6; font-size: 0.9em;">(${item.id})</span>`;
|
||||
|
||||
// Add hover effects
|
||||
option.addEventListener('mouseover', function () {
|
||||
@@ -2732,7 +2776,7 @@ function filterNPCCorpseItemDropdown(index) {
|
||||
|
||||
const filtered = availableItems.filter(item =>
|
||||
item.id.toLowerCase().includes(searchTerm) ||
|
||||
(item.name && item.name.toLowerCase().includes(searchTerm)) ||
|
||||
(getI18nDisplay(item.name).toLowerCase().includes(searchTerm)) ||
|
||||
(item.emoji && item.emoji.includes(searchTerm))
|
||||
);
|
||||
|
||||
@@ -2747,7 +2791,7 @@ function filterNPCCorpseItemDropdown(index) {
|
||||
option.style.padding = '8px';
|
||||
option.style.cursor = 'pointer';
|
||||
option.style.borderBottom = '1px solid #2a2a4a';
|
||||
option.innerHTML = `${item.emoji || '📦'} ${item.name || item.id} <span style="opacity: 0.6; font-size: 0.9em;">(${item.id})</span>`;
|
||||
option.innerHTML = `${item.emoji || '📦'} ${getI18nDisplay(item.name) || item.id} <span style="opacity: 0.6; font-size: 0.9em;">(${item.id})</span>`;
|
||||
|
||||
// Add hover effects
|
||||
option.addEventListener('mouseover', function () {
|
||||
@@ -2858,7 +2902,8 @@ function renderItemManagementList() {
|
||||
availableItems.forEach(item => {
|
||||
const elem = document.createElement('div');
|
||||
elem.className = 'management-item';
|
||||
elem.dataset.name = item.name;
|
||||
const displayName = getI18nDisplay(item.name);
|
||||
elem.dataset.name = displayName;
|
||||
elem.dataset.id = item.id;
|
||||
elem.dataset.type = item.type || '';
|
||||
if (item.id === selectedItemId) {
|
||||
@@ -2866,7 +2911,7 @@ function renderItemManagementList() {
|
||||
}
|
||||
|
||||
elem.innerHTML = `
|
||||
<div><strong>${item.emoji ? item.emoji + ' ' : ''}${item.name}</strong></div>
|
||||
<div><strong>${item.emoji ? item.emoji + ' ' : ''}${displayName}</strong></div>
|
||||
<div style="font-size: 0.85em; opacity: 0.7; margin-top: 5px;">
|
||||
Type: ${item.type} | Weight: ${item.weight}kg
|
||||
</div>
|
||||
@@ -2913,7 +2958,10 @@ function selectItem(itemId) {
|
||||
</div>
|
||||
<div class="property-group">
|
||||
<label>Name</label>
|
||||
<input type="text" id="itemName" value="${item.name}">
|
||||
<div class="property-row">
|
||||
<input type="text" id="itemName_en" value="${typeof item.name === 'object' ? item.name.en || '' : item.name || ''}" placeholder="Name (EN)">
|
||||
<input type="text" id="itemName_es" value="${typeof item.name === 'object' ? item.name.es || '' : ''}" placeholder="Nombre (ES)">
|
||||
</div>
|
||||
</div>
|
||||
<div class="property-group">
|
||||
<label>Emoji</label>
|
||||
@@ -2947,7 +2995,8 @@ function selectItem(itemId) {
|
||||
|
||||
<div class="property-group">
|
||||
<label>Description</label>
|
||||
<textarea id="itemDescription" rows="3" style="width: 100%;">${item.description || ''}</textarea>
|
||||
<textarea id="itemDescription_en" rows="3" placeholder="Description (EN)" style="width: 100%; margin-bottom: 5px;">${typeof item.description === 'object' ? item.description.en || '' : item.description || ''}</textarea>
|
||||
<textarea id="itemDescription_es" rows="3" placeholder="Descripción (ES)" style="width: 100%;">${typeof item.description === 'object' ? item.description.es || '' : ''}</textarea>
|
||||
</div>
|
||||
|
||||
<div class="property-group">
|
||||
@@ -3204,7 +3253,8 @@ function renderMaterialsList(materials, prefix) {
|
||||
|
||||
return materials.map((mat, index) => {
|
||||
const selectedItem = availableItems.find(i => i.id === mat.item_id);
|
||||
const displayValue = selectedItem ? `${selectedItem.emoji || '📦'} ${selectedItem.name}` : '';
|
||||
const displayValue = selectedItem ? `${selectedItem.emoji || '📦'} ${getI18nDisplay(selectedItem.name)}` : '';
|
||||
|
||||
|
||||
return `
|
||||
<div class="material-item" style="display: flex; gap: 10px; margin-bottom: 10px; align-items: center; background: #1a1a3e; padding: 10px; border-radius: 4px;">
|
||||
@@ -3233,7 +3283,7 @@ function renderToolsList(tools, prefix) {
|
||||
|
||||
return tools.map((tool, index) => {
|
||||
const selectedItem = availableItems.find(i => i.id === tool.item_id);
|
||||
const displayValue = selectedItem ? `${selectedItem.emoji || '🔧'} ${selectedItem.name}` : '';
|
||||
const displayValue = selectedItem ? `${selectedItem.emoji || '🔧'} ${getI18nDisplay(selectedItem.name)}` : '';
|
||||
|
||||
return `
|
||||
<div class="tool-item" style="display: flex; gap: 10px; margin-bottom: 10px; align-items: center; background: #1a1a3e; padding: 10px; border-radius: 4px;">
|
||||
@@ -3327,12 +3377,18 @@ async function saveCurrentItem_() {
|
||||
// Build complete item data object
|
||||
const itemData = {
|
||||
id: document.getElementById('itemId').value,
|
||||
name: document.getElementById('itemName').value,
|
||||
name: {
|
||||
en: document.getElementById('itemName_en').value,
|
||||
es: document.getElementById('itemName_es').value
|
||||
},
|
||||
emoji: document.getElementById('itemEmoji').value,
|
||||
type: document.getElementById('itemType').value,
|
||||
weight: parseFloat(document.getElementById('itemWeight').value),
|
||||
volume: parseFloat(document.getElementById('itemVolume').value),
|
||||
description: document.getElementById('itemDescription').value,
|
||||
description: {
|
||||
en: document.getElementById('itemDescription_en').value,
|
||||
es: document.getElementById('itemDescription_es').value
|
||||
},
|
||||
image_path: document.getElementById('itemImagePath')?.value || '',
|
||||
stackable: document.getElementById('itemStackable').checked,
|
||||
|
||||
|
||||
Reference in New Issue
Block a user