feat(webui): add Mods card (#252)
This commit is contained in:
parent
ef220ca6d7
commit
f83f80c991
@ -1,16 +1,19 @@
|
|||||||
import { RequestHandler } from "express";
|
import { RequestHandler } from "express";
|
||||||
import { MinItem, warframes, weapons, items } from "@/src/services/itemDataService";
|
import { MinItem, warframes, weapons, items } from "@/src/services/itemDataService";
|
||||||
|
import badItems from "@/static/json/exclude-mods.json";
|
||||||
|
|
||||||
interface ListedItem {
|
interface ListedItem {
|
||||||
uniqueName: string;
|
uniqueName: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
fusionLimit?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
function reduceItems(items: MinItem[]): ListedItem[] {
|
function reduceItems(items: MinItem[]): ListedItem[] {
|
||||||
return items.map((item: MinItem): ListedItem => {
|
return items.map((item: MinItem): ListedItem => {
|
||||||
return {
|
return {
|
||||||
uniqueName: item.uniqueName,
|
uniqueName: item.uniqueName,
|
||||||
name: item.name
|
name: item.name,
|
||||||
|
fusionLimit: (item as any).fusionLimit
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -19,7 +22,11 @@ const getItemListsController: RequestHandler = (_req, res) => {
|
|||||||
res.json({
|
res.json({
|
||||||
warframes: reduceItems(warframes),
|
warframes: reduceItems(warframes),
|
||||||
weapons: reduceItems(weapons.filter(item => item.productCategory != "OperatorAmps")),
|
weapons: reduceItems(weapons.filter(item => item.productCategory != "OperatorAmps")),
|
||||||
miscitems: reduceItems(items.filter(item => item.category == "Misc" || item.category == "Resources"))
|
miscitems: reduceItems(
|
||||||
|
items.filter(item => item.category == "Misc" || item.category == "Resources" || item.category == "Fish")
|
||||||
|
),
|
||||||
|
mods: reduceItems(items.filter(item => item.category == "Mods" || item.category == "Arcanes")),
|
||||||
|
badItems
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -45,16 +45,32 @@
|
|||||||
></button>
|
></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="offcanvas-body">
|
<div class="offcanvas-body">
|
||||||
<ul>
|
<div class="navbar p-0">
|
||||||
<li>
|
<ul class="navbar-nav justify-content-end">
|
||||||
<a href="/webui/inventory" data-bs-dismiss="offcanvas" data-bs-target="#sidebar"
|
<li class="nav-item">
|
||||||
>Inventory</a
|
<a
|
||||||
|
class="nav-link"
|
||||||
|
href="/webui/inventory"
|
||||||
|
data-bs-dismiss="offcanvas"
|
||||||
|
data-bs-target="#sidebar"
|
||||||
>
|
>
|
||||||
|
Inventory
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a
|
||||||
|
class="nav-link"
|
||||||
|
href="/webui/mods"
|
||||||
|
data-bs-dismiss="offcanvas"
|
||||||
|
data-bs-target="#sidebar"
|
||||||
|
>
|
||||||
|
Mods
|
||||||
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li><a href="/webui/mods" data-bs-dismiss="offcanvas" data-bs-target="#sidebar">Mods</a></li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p id="refresh-note" class="mb-4">
|
<p id="refresh-note" class="mb-4">
|
||||||
Note: Changes made here will only be reflected in-game when the game re-downloads your inventory.
|
Note: Changes made here will only be reflected in-game when the game re-downloads your inventory.
|
||||||
@ -117,17 +133,27 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div data-route="/webui/mods" data-title="Mods | OpenWF WebUI">
|
<div data-route="/webui/mods" data-title="Mods | OpenWF WebUI">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xxl-6">
|
||||||
<div class="card mb-4">
|
<div class="card mb-4">
|
||||||
<h5 class="card-header">Add Riven</h5>
|
<h5 class="card-header">Add Riven</h5>
|
||||||
<form class="card-body" onsubmit="doAcquireRiven();return false;">
|
<form class="card-body" onsubmit="doAcquireRiven();return false;">
|
||||||
<select class="form-control mb-3" id="addriven-type">
|
<select class="form-control mb-3" id="addriven-type">
|
||||||
<option value="LotusArchgunRandomModRare">LotusArchgunRandomModRare</option>
|
<option value="LotusArchgunRandomModRare">LotusArchgunRandomModRare</option>
|
||||||
<option value="LotusModularMeleeRandomModRare">LotusModularMeleeRandomModRare</option>
|
<option value="LotusModularMeleeRandomModRare">
|
||||||
<option value="LotusModularPistolRandomModRare">LotusModularPistolRandomModRare</option>
|
LotusModularMeleeRandomModRare
|
||||||
|
</option>
|
||||||
|
<option value="LotusModularPistolRandomModRare">
|
||||||
|
LotusModularPistolRandomModRare
|
||||||
|
</option>
|
||||||
<option value="LotusPistolRandomModRare">LotusPistolRandomModRare</option>
|
<option value="LotusPistolRandomModRare">LotusPistolRandomModRare</option>
|
||||||
<option value="LotusRifleRandomModRare" selected>LotusRifleRandomModRare</option>
|
<option value="LotusRifleRandomModRare" selected>
|
||||||
|
LotusRifleRandomModRare
|
||||||
|
</option>
|
||||||
<option value="LotusShotgunRandomModRare">LotusShotgunRandomModRare</option>
|
<option value="LotusShotgunRandomModRare">LotusShotgunRandomModRare</option>
|
||||||
<option value="PlayerMeleeWeaponRandomModRare">PlayerMeleeWeaponRandomModRare</option>
|
<option value="PlayerMeleeWeaponRandomModRare">
|
||||||
|
PlayerMeleeWeaponRandomModRare
|
||||||
|
</option>
|
||||||
</select>
|
</select>
|
||||||
<textarea
|
<textarea
|
||||||
id="addriven-fingerprint"
|
id="addriven-fingerprint"
|
||||||
@ -135,10 +161,12 @@
|
|||||||
placeholder="Fingerprint"
|
placeholder="Fingerprint"
|
||||||
></textarea>
|
></textarea>
|
||||||
<button class="btn btn-primary" style="margin-right: 5px" type="submit">Add</button>
|
<button class="btn btn-primary" style="margin-right: 5px" type="submit">Add</button>
|
||||||
<a href="https://riven.builds.wf/" target="_blank">Need help with the fingerprint?</a>
|
<a href="https://riven.builds.wf/" target="_blank">
|
||||||
|
Need help with the fingerprint?
|
||||||
|
</a>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="card mb-4 col-xxl-6">
|
<div class="card mb-4">
|
||||||
<h5 class="card-header">Rivens</h5>
|
<h5 class="card-header">Rivens</h5>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<table class="table table-hover w-100">
|
<table class="table table-hover w-100">
|
||||||
@ -147,11 +175,28 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-xxl-6">
|
||||||
|
<div class="card mb-4">
|
||||||
|
<h5 class="card-header">Mods</h5>
|
||||||
|
<div class="card-body">
|
||||||
|
<table class="table table-hover w-100">
|
||||||
|
<tbody id="mods-list"></tbody>
|
||||||
|
</table>
|
||||||
|
<form class="input-group" onsubmit="doAcquireMod();return false;">
|
||||||
|
<input class="form-control" id="mod-to-acquire" list="datalist-mods" />
|
||||||
|
<button class="btn btn-primary" type="submit">Add</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<datalist id="datalist-warframes"></datalist>
|
<datalist id="datalist-warframes"></datalist>
|
||||||
<datalist id="datalist-weapons"></datalist>
|
<datalist id="datalist-weapons"></datalist>
|
||||||
<datalist id="datalist-miscitems"></datalist>
|
<datalist id="datalist-miscitems"></datalist>
|
||||||
|
<datalist id="datalist-mods"></datalist>
|
||||||
<script src="libs/jquery-3.6.0.min.js"></script>
|
<script src="libs/jquery-3.6.0.min.js"></script>
|
||||||
<script src="libs/whirlpool-js.min.js"></script>
|
<script src="libs/whirlpool-js.min.js"></script>
|
||||||
<script src="libs/single.js"></script>
|
<script src="libs/single.js"></script>
|
||||||
|
@ -76,33 +76,47 @@ single.on("route_load", function (event) {
|
|||||||
} else {
|
} else {
|
||||||
$("body").removeClass("logged-in");
|
$("body").removeClass("logged-in");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$(".nav-link").removeClass("active");
|
||||||
|
const navLink = document.querySelector(".nav-link[href='" + event.route.paths[0] + "']");
|
||||||
|
if (navLink) {
|
||||||
|
navLink.classList.add("active");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
window.itemListPromise = new Promise(resolve => {
|
window.itemListPromise = new Promise(resolve => {
|
||||||
const req = $.get("/custom/getItemLists");
|
const req = $.get("/custom/getItemLists");
|
||||||
req.done(data => {
|
req.done(data => {
|
||||||
const itemMap = {};
|
const itemMap = {
|
||||||
|
// Generics for rivens
|
||||||
|
"/Lotus/Weapons/Tenno/Archwing/Primary/ArchGun": { name: "Archgun" },
|
||||||
|
"/Lotus/Weapons/Tenno/Melee/PlayerMeleeWeapon": { name: "Melee" },
|
||||||
|
"/Lotus/Weapons/Tenno/Pistol/LotusPistol": { name: "Pistol" },
|
||||||
|
"/Lotus/Weapons/Tenno/Rifle/LotusRifle": { name: "Rifle" },
|
||||||
|
"/Lotus/Weapons/Tenno/Shotgun/LotusShotgun": { name: "Shotgun" },
|
||||||
|
// Missing in data sources
|
||||||
|
"/Lotus/Upgrades/CosmeticEnhancers/Peculiars/CyoteMod": { name: "Traumatic Peculiar" },
|
||||||
|
"/Lotus/Weapons/Tenno/Grimoire/TnGrimoire": { name: "Grimoire" }
|
||||||
|
};
|
||||||
for (const [type, items] of Object.entries(data)) {
|
for (const [type, items] of Object.entries(data)) {
|
||||||
|
if (type != "badItems") {
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
|
if (item.uniqueName in data.badItems) {
|
||||||
|
item.name += " (Imposter)";
|
||||||
|
} else if (item.uniqueName.substr(0, 18) != "/Lotus/Types/Game/") {
|
||||||
const option = document.createElement("option");
|
const option = document.createElement("option");
|
||||||
option.setAttribute("data-key", item.uniqueName);
|
option.setAttribute("data-key", item.uniqueName);
|
||||||
option.value = item.name;
|
option.value = item.name;
|
||||||
document.getElementById("datalist-" + type).appendChild(option);
|
document.getElementById("datalist-" + type).appendChild(option);
|
||||||
|
}
|
||||||
itemMap[item.uniqueName] = { ...item, type };
|
itemMap[item.uniqueName] = { ...item, type };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
resolve(itemMap);
|
resolve(itemMap);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const rivenGenericCompatNames = {
|
|
||||||
"/Lotus/Weapons/Tenno/Archwing/Primary/ArchGun": "Archgun",
|
|
||||||
"/Lotus/Weapons/Tenno/Melee/PlayerMeleeWeapon": "Melee",
|
|
||||||
"/Lotus/Weapons/Tenno/Pistol/LotusPistol": "Pistol",
|
|
||||||
"/Lotus/Weapons/Tenno/Rifle/LotusRifle": "Rifle",
|
|
||||||
"/Lotus/Weapons/Tenno/Shotgun/LotusShotgun": "Shotgun"
|
|
||||||
};
|
|
||||||
|
|
||||||
function updateInventory() {
|
function updateInventory() {
|
||||||
const req = $.get("/api/inventory.php?" + window.authz);
|
const req = $.get("/api/inventory.php?" + window.authz);
|
||||||
req.done(data => {
|
req.done(data => {
|
||||||
@ -190,6 +204,7 @@ function updateInventory() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById("riven-list").innerHTML = "";
|
document.getElementById("riven-list").innerHTML = "";
|
||||||
|
document.getElementById("mods-list").innerHTML = "";
|
||||||
data.Upgrades.forEach(item => {
|
data.Upgrades.forEach(item => {
|
||||||
if (item.ItemType.substr(0, 32) == "/Lotus/Upgrades/Mods/Randomized/") {
|
if (item.ItemType.substr(0, 32) == "/Lotus/Upgrades/Mods/Randomized/") {
|
||||||
const rivenType = item.ItemType.substr(32);
|
const rivenType = item.ItemType.substr(32);
|
||||||
@ -198,10 +213,7 @@ function updateInventory() {
|
|||||||
const tr = document.createElement("tr");
|
const tr = document.createElement("tr");
|
||||||
{
|
{
|
||||||
const td = document.createElement("td");
|
const td = document.createElement("td");
|
||||||
td.textContent =
|
td.textContent = itemMap[fingerprint.compat]?.name ?? fingerprint.compat;
|
||||||
itemMap[fingerprint.compat]?.name ??
|
|
||||||
rivenGenericCompatNames[fingerprint.compat] ??
|
|
||||||
fingerprint.compat;
|
|
||||||
td.textContent += " " + RivenParser.parseRiven(rivenType, fingerprint, 1).name;
|
td.textContent += " " + RivenParser.parseRiven(rivenType, fingerprint, 1).name;
|
||||||
td.innerHTML += " <span title='Number of buffs'>▲ " + fingerprint.buffs.length + "</span>";
|
td.innerHTML += " <span title='Number of buffs'>▲ " + fingerprint.buffs.length + "</span>";
|
||||||
td.innerHTML += " <span title='Number of curses'>▼ " + fingerprint.curses.length + "</span>";
|
td.innerHTML += " <span title='Number of curses'>▼ " + fingerprint.curses.length + "</span>";
|
||||||
@ -245,6 +257,92 @@ function updateInventory() {
|
|||||||
tr.appendChild(td);
|
tr.appendChild(td);
|
||||||
}
|
}
|
||||||
document.getElementById("riven-list").appendChild(tr);
|
document.getElementById("riven-list").appendChild(tr);
|
||||||
|
} else {
|
||||||
|
const tr = document.createElement("tr");
|
||||||
|
const rank = parseInt(JSON.parse(item.UpgradeFingerprint).lvl);
|
||||||
|
const maxRank = itemMap[item.ItemType]?.fusionLimit ?? 5;
|
||||||
|
{
|
||||||
|
const td = document.createElement("td");
|
||||||
|
td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
|
||||||
|
td.innerHTML += " <span title='Rank'>★ " + rank + "/" + maxRank + "</span>";
|
||||||
|
tr.appendChild(td);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const td = document.createElement("td");
|
||||||
|
td.classList = "text-end";
|
||||||
|
if (rank < maxRank) {
|
||||||
|
const a = document.createElement("a");
|
||||||
|
a.href = "#";
|
||||||
|
a.onclick = function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
setFingerprint(item.ItemType, item.ItemId, { lvl: maxRank });
|
||||||
|
};
|
||||||
|
a.textContent = "Max Rank";
|
||||||
|
td.appendChild(a);
|
||||||
|
|
||||||
|
const span = document.createElement("span");
|
||||||
|
span.innerHTML = " · ";
|
||||||
|
td.appendChild(span);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const a = document.createElement("a");
|
||||||
|
a.href = "#";
|
||||||
|
a.onclick = function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
disposeOfGear("Upgrades", item.ItemId.$oid);
|
||||||
|
};
|
||||||
|
a.textContent = "Remove";
|
||||||
|
td.appendChild(a);
|
||||||
|
}
|
||||||
|
tr.appendChild(td);
|
||||||
|
}
|
||||||
|
document.getElementById("mods-list").appendChild(tr);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
data.RawUpgrades.forEach(item => {
|
||||||
|
if (item.ItemCount > 0) {
|
||||||
|
const maxRank = itemMap[item.ItemType]?.fusionLimit ?? 5;
|
||||||
|
const tr = document.createElement("tr");
|
||||||
|
{
|
||||||
|
const td = document.createElement("td");
|
||||||
|
td.textContent = itemMap[item.ItemType]?.name ?? item.ItemType;
|
||||||
|
td.innerHTML += " <span title='Rank'>★ 0/" + maxRank + "</span>";
|
||||||
|
if (item.ItemCount > 1) {
|
||||||
|
td.innerHTML += " <span title='Count'>🗍 " + parseInt(item.ItemCount) + "</span>";
|
||||||
|
}
|
||||||
|
tr.appendChild(td);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const td = document.createElement("td");
|
||||||
|
td.classList = "text-end";
|
||||||
|
{
|
||||||
|
const a = document.createElement("a");
|
||||||
|
a.href = "#";
|
||||||
|
a.onclick = function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
setFingerprint(item.ItemType, item.LastAdded, { lvl: maxRank });
|
||||||
|
};
|
||||||
|
a.textContent = "Max Rank";
|
||||||
|
td.appendChild(a);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const span = document.createElement("span");
|
||||||
|
span.innerHTML = " · ";
|
||||||
|
td.appendChild(span);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const a = document.createElement("a");
|
||||||
|
a.href = "#";
|
||||||
|
a.onclick = function (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
disposeOfItems("Upgrades", item.ItemType, item.ItemCount);
|
||||||
|
};
|
||||||
|
a.textContent = "Remove";
|
||||||
|
td.appendChild(a);
|
||||||
|
}
|
||||||
|
tr.appendChild(td);
|
||||||
|
}
|
||||||
|
document.getElementById("mods-list").appendChild(tr);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -352,6 +450,29 @@ function disposeOfGear(category, oid) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function disposeOfItems(category, type, count) {
|
||||||
|
const data = {
|
||||||
|
SellCurrency: "SC_RegularCredits",
|
||||||
|
SellPrice: 0,
|
||||||
|
Items: {}
|
||||||
|
};
|
||||||
|
data.Items[category] = [
|
||||||
|
{
|
||||||
|
String: type,
|
||||||
|
Count: count
|
||||||
|
}
|
||||||
|
];
|
||||||
|
revalidateAuthz(() => {
|
||||||
|
$.post({
|
||||||
|
url: "/api/sell.php?" + window.authz,
|
||||||
|
contentType: "text/plain",
|
||||||
|
data: JSON.stringify(data)
|
||||||
|
}).done(function () {
|
||||||
|
updateInventory();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function doAcquireMiscItems() {
|
function doAcquireMiscItems() {
|
||||||
const uniqueName = getKey(document.getElementById("miscitem-type"));
|
const uniqueName = getKey(document.getElementById("miscitem-type"));
|
||||||
if (!uniqueName) {
|
if (!uniqueName) {
|
||||||
@ -445,3 +566,53 @@ function doAcquireRiven() {
|
|||||||
$("#addriven-fingerprint").on("input", () => {
|
$("#addriven-fingerprint").on("input", () => {
|
||||||
$("#addriven-fingerprint").removeClass("is-invalid");
|
$("#addriven-fingerprint").removeClass("is-invalid");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function setFingerprint(ItemType, ItemId, fingerprint) {
|
||||||
|
revalidateAuthz(() => {
|
||||||
|
$.post({
|
||||||
|
url: "/api/artifacts.php?" + window.authz,
|
||||||
|
contentType: "text/plain",
|
||||||
|
data: JSON.stringify({
|
||||||
|
Upgrade: {
|
||||||
|
ItemType,
|
||||||
|
ItemId,
|
||||||
|
UpgradeFingerprint: JSON.stringify(fingerprint)
|
||||||
|
},
|
||||||
|
LevelDiff: 0,
|
||||||
|
Cost: 0,
|
||||||
|
FusionPointCost: 0
|
||||||
|
})
|
||||||
|
}).done(function () {
|
||||||
|
updateInventory();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function doAcquireMod() {
|
||||||
|
const uniqueName = getKey(document.getElementById("mod-to-acquire"));
|
||||||
|
if (!uniqueName) {
|
||||||
|
$("#mod-to-acquire").addClass("is-invalid").focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
revalidateAuthz(() => {
|
||||||
|
$.post({
|
||||||
|
url: "/api/missionInventoryUpdate.php?" + window.authz,
|
||||||
|
contentType: "text/plain",
|
||||||
|
data: JSON.stringify({
|
||||||
|
RawUpgrades: [
|
||||||
|
{
|
||||||
|
ItemType: uniqueName,
|
||||||
|
ItemCount: 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}).done(function () {
|
||||||
|
document.getElementById("mod-to-acquire").value = "";
|
||||||
|
updateInventory();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#mod-to-acquire").on("input", () => {
|
||||||
|
$("#mod-to-acquire").removeClass("is-invalid");
|
||||||
|
});
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
@media (min-width: 992px) {
|
@media (min-width: 992px) {
|
||||||
body.logged-in #main-view {
|
body.logged-in #main-view {
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-columns: 1fr 8fr;
|
|
||||||
gap: 1.5rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body.logged-in #sidebar {
|
body.logged-in #sidebar {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 5rem;
|
top: 5rem;
|
||||||
height: 100px;
|
height: 100px;
|
||||||
|
margin-right: 3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
body:not(.logged-in) #sidebar {
|
body:not(.logged-in) #sidebar {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user