feat: 优化响应式布局并添加签到组件
refactor: 重构CSS和LESS文件以支持响应式设计 fix: 修复图片上传和编辑器解析问题 style: 调整搜索框和日历组件的样式 docs: 更新HTML模板中的广告和操作链接
This commit is contained in:
148
js/edit.js
148
js/edit.js
@@ -65,7 +65,6 @@ const editApp = createApp({
|
||||
|
||||
isLogin.value = true;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
let imageLength = 10;
|
||||
@@ -173,7 +172,6 @@ const editApp = createApp({
|
||||
info.value = infoTarget;
|
||||
token.value = data.token;
|
||||
|
||||
|
||||
initEditor();
|
||||
})
|
||||
.catch((err) => {
|
||||
@@ -211,6 +209,7 @@ const editApp = createApp({
|
||||
onInsertedImage(imageNode) {
|
||||
const { src, alt, url, href } = imageNode;
|
||||
},
|
||||
|
||||
async parseImageSrc(src) {
|
||||
// 如果图片链接中已经包含了 ?aid= ,则说明是本站图片,直接返回,无需处理
|
||||
if (src.includes("?aid=")) return src;
|
||||
@@ -233,8 +232,15 @@ const editApp = createApp({
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Transform network image failed", e);
|
||||
return "";
|
||||
}
|
||||
},
|
||||
checkImage: (src, alt, url) => {
|
||||
if (src.indexOf("http") !== 0) {
|
||||
return "图片网址必须以 http/https 开头";
|
||||
}
|
||||
return true;
|
||||
}, // 也支持 async 函数
|
||||
},
|
||||
|
||||
["uploadImage"]: {
|
||||
@@ -251,6 +257,8 @@ const editApp = createApp({
|
||||
async customUpload(file, insertFn) {
|
||||
try {
|
||||
const img = await uploading(file, file.name, "image");
|
||||
console.log("img", img);
|
||||
|
||||
insertFn(`${img.url}?aid=${img.aid}`);
|
||||
} catch (err) {
|
||||
console.error("上传出错:", err);
|
||||
@@ -261,7 +269,6 @@ const editApp = createApp({
|
||||
["insertVideo"]: {
|
||||
onInsertedVideo(videoNode) {
|
||||
if (videoNode == null) return;
|
||||
// console.log(videoNode);
|
||||
|
||||
const { src } = videoNode;
|
||||
// console.log("inserted video", src);
|
||||
@@ -423,6 +430,7 @@ const editApp = createApp({
|
||||
|
||||
// 定义图片处理函数
|
||||
const processImg = (aid, width, height, href) => {
|
||||
console.log("processImg", aid, width, height, href);
|
||||
const image = imageList.find((img) => String(img.aid) === String(aid));
|
||||
if (!image) return "";
|
||||
|
||||
@@ -433,7 +441,7 @@ const editApp = createApp({
|
||||
let style = "";
|
||||
const formatStyleVal = (val) => {
|
||||
if (!val) return null;
|
||||
if (String(val).endsWith('%')) return val;
|
||||
if (String(val).endsWith("%")) return val;
|
||||
const num = Number(val);
|
||||
return num > 0 ? `${num}px` : null;
|
||||
};
|
||||
@@ -445,7 +453,7 @@ const editApp = createApp({
|
||||
if (wStyle) styleParts.push(`width: ${wStyle}`);
|
||||
if (hStyle) styleParts.push(`height: ${hStyle}`);
|
||||
|
||||
if (styleParts.length > 0) style = `style="${styleParts.join('; ')};"`;
|
||||
if (styleParts.length > 0) style = `style="${styleParts.join("; ")};"`;
|
||||
|
||||
let dataHref = href ? ` data-href="${href}"` : "";
|
||||
return `<img src="${image.url}?aid=${aid}" data-aid="${aid}"${dataHref} ${style}>`;
|
||||
@@ -464,6 +472,41 @@ const editApp = createApp({
|
||||
return imgTag || match;
|
||||
});
|
||||
|
||||
html = html.replace(/\[url=([^\]]+)\]\[img(?:=([0-9.%]+)(?:,([0-9.%]+))?)?\]([^\]]+)\[\/img\]\[\/url\]/gi, (match, href, w, h, inner) => {
|
||||
if (/^\d+$/.test(inner)) return match;
|
||||
const formatStyleVal = (val) => {
|
||||
if (!val) return null;
|
||||
if (String(val).endsWith("%")) return val;
|
||||
const num = Number(val);
|
||||
return num > 0 ? `${num}px` : null;
|
||||
};
|
||||
const wStyle = formatStyleVal(w);
|
||||
const hStyle = formatStyleVal(h);
|
||||
let styleParts = [];
|
||||
if (wStyle) styleParts.push(`width: ${wStyle}`);
|
||||
if (hStyle) styleParts.push(`height: ${hStyle}`);
|
||||
const styleAttr = styleParts.length > 0 ? ` style="${styleParts.join("; ")};"` : "";
|
||||
const dataHref = href ? ` data-href="${href}"` : "";
|
||||
return `<img src="${inner}"${dataHref}${styleAttr}>`;
|
||||
});
|
||||
|
||||
html = html.replace(/\[img(?:=([0-9.%]+)(?:,([0-9.%]+))?)?\]([^\]]+)\[\/img\]/gi, (match, w, h, inner) => {
|
||||
if (/^\d+$/.test(inner)) return match;
|
||||
const formatStyleVal = (val) => {
|
||||
if (!val) return null;
|
||||
if (String(val).endsWith("%")) return val;
|
||||
const num = Number(val);
|
||||
return num > 0 ? `${num}px` : null;
|
||||
};
|
||||
const wStyle = formatStyleVal(w);
|
||||
const hStyle = formatStyleVal(h);
|
||||
let styleParts = [];
|
||||
if (wStyle) styleParts.push(`width: ${wStyle}`);
|
||||
if (hStyle) styleParts.push(`height: ${hStyle}`);
|
||||
const styleAttr = styleParts.length > 0 ? ` style="${styleParts.join("; ")};"` : "";
|
||||
return `<img src="${inner}"${styleAttr}>`;
|
||||
});
|
||||
|
||||
// 6. [attachimg] (兼容旧格式)
|
||||
html = html.replace(/\[attachimg\](\d+)\[\/attachimg\]/gi, (match, aid) => {
|
||||
const imgTag = processImg(aid, 0, 0, null);
|
||||
@@ -507,16 +550,16 @@ const editApp = createApp({
|
||||
__c.innerHTML = html;
|
||||
|
||||
// 确保所有顶层元素都是块级元素 (Slate/WangEditor 要求)
|
||||
const blockTags = ['P', 'DIV', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'BLOCKQUOTE', 'UL', 'OL', 'PRE', 'TABLE', 'FIGURE', 'HR'];
|
||||
const newContainer = document.createElement('div');
|
||||
const blockTags = ["P", "DIV", "H1", "H2", "H3", "H4", "H5", "H6", "BLOCKQUOTE", "UL", "OL", "PRE", "TABLE", "FIGURE", "HR"];
|
||||
const newContainer = document.createElement("div");
|
||||
let currentInlineNodes = [];
|
||||
let lastWasBlock = false;
|
||||
|
||||
const flushInlines = () => {
|
||||
if (currentInlineNodes.length > 0) {
|
||||
if (currentInlineNodes.length > 0) {
|
||||
const p = document.createElement('p');
|
||||
currentInlineNodes.forEach(node => p.appendChild(node));
|
||||
const p = document.createElement("p");
|
||||
currentInlineNodes.forEach((node) => p.appendChild(node));
|
||||
newContainer.appendChild(p);
|
||||
}
|
||||
currentInlineNodes = [];
|
||||
@@ -532,7 +575,7 @@ const editApp = createApp({
|
||||
newContainer.appendChild(node);
|
||||
// 记录最后添加的是块级元素
|
||||
lastWasBlock = true;
|
||||
} else if (node.nodeName === 'BR') {
|
||||
} else if (node.nodeName === "BR") {
|
||||
if (currentInlineNodes.length > 0) {
|
||||
flushInlines();
|
||||
lastWasBlock = false; // 刚刚结束了一个段落,不算紧挨着块级
|
||||
@@ -542,8 +585,8 @@ const editApp = createApp({
|
||||
// 忽略
|
||||
lastWasBlock = false; // 消耗掉块级后的换行状态,避免连续 BR 被吞
|
||||
} else {
|
||||
const p = document.createElement('p');
|
||||
p.innerHTML = '<br>';
|
||||
const p = document.createElement("p");
|
||||
p.innerHTML = "<br>";
|
||||
newContainer.appendChild(p);
|
||||
lastWasBlock = false;
|
||||
}
|
||||
@@ -585,7 +628,7 @@ const editApp = createApp({
|
||||
|
||||
// 提交
|
||||
const submit = (status) => {
|
||||
if (location.hostname == "127.0.0.1") status = 0
|
||||
if (location.hostname == "127.0.0.1") status = 0;
|
||||
|
||||
if (realname.value == 0 && userInfoWin.value?.uin > 0) {
|
||||
openAttest();
|
||||
@@ -600,7 +643,12 @@ const editApp = createApp({
|
||||
const infoTarget = { ...info.value } || {};
|
||||
let content = editor.getHtml();
|
||||
|
||||
const obj = formatContent(content);
|
||||
|
||||
content = obj.content;
|
||||
|
||||
const images = extractImages(editorRef.value);
|
||||
// const images = obj.images;
|
||||
const videos = extractVideos(editorRef.value);
|
||||
|
||||
infoTarget.attachments = infoTarget.attachments || {};
|
||||
@@ -611,16 +659,11 @@ const editApp = createApp({
|
||||
info.value["attachments"]["images"] = images;
|
||||
info.value["attachments"]["videos"] = videos;
|
||||
|
||||
// console.log("原始html", content);
|
||||
content = formatContent(content);
|
||||
// console.log("最终html", content);
|
||||
const data = {
|
||||
...infoTarget,
|
||||
content,
|
||||
};
|
||||
|
||||
// console.log("data", data);
|
||||
|
||||
ajax("/v2/api/forum/postPublishTopic", {
|
||||
info: data,
|
||||
token: token.value,
|
||||
@@ -643,6 +686,8 @@ const editApp = createApp({
|
||||
};
|
||||
|
||||
const formatContent = (html) => {
|
||||
let images = [];
|
||||
|
||||
if (!html) return "";
|
||||
// <p><br></p> 转换为单个换行符
|
||||
html = html.replace(/<p><br><\/p>/gi, "\n");
|
||||
@@ -671,63 +716,65 @@ const editApp = createApp({
|
||||
// 4. 处理图片 [img]
|
||||
html = html.replace(/<img[^>]*>/gi, (imgTag) => {
|
||||
let aid = "";
|
||||
// 尝试从 src 中获取 aid
|
||||
const srcMatch = imgTag.match(/src="([^"]+)"/i);
|
||||
if (srcMatch) {
|
||||
const aidMatch = srcMatch[1].match(/[?&]aid=(\d+)/);
|
||||
if (aidMatch) aid = aidMatch[1];
|
||||
}
|
||||
// 尝试从 data-aid 中获取 aid
|
||||
if (!aid) {
|
||||
const dataAid = imgTag.match(/data-aid="(\d+)"/i);
|
||||
if (dataAid) aid = dataAid[1];
|
||||
}
|
||||
if (!srcMatch) return "";
|
||||
const srcUrl = srcMatch[1];
|
||||
const cleanUrl = srcUrl.split("?")[0];
|
||||
const aidMatch = srcUrl.match(/[?&]aid=(\d+)/);
|
||||
if (aidMatch) aid = aidMatch[1];
|
||||
|
||||
if (!aid) return ""; // 无法获取 aid,跳过
|
||||
|
||||
// 获取宽高 (支持 px 和 %)
|
||||
let w = 0,
|
||||
h = 0;
|
||||
const styleMatch = imgTag.match(/style="([^"]+)"/i);
|
||||
if (styleMatch) {
|
||||
// 匹配数字+单位 (px或%)
|
||||
const wMatch = styleMatch[1].match(/width:\s*([\d.]+(?:px|%)?)/i);
|
||||
const hMatch = styleMatch[1].match(/height:\s*([\d.]+(?:px|%)?)/i);
|
||||
|
||||
if (wMatch) {
|
||||
// 如果是百分比,直接保留字符串;如果是纯数字默认视为 px;如果是 px 去掉单位
|
||||
let val = wMatch[1];
|
||||
if (val.endsWith('%')) w = val; // 保留百分比字符串
|
||||
else w = parseFloat(val); // 转为数字 (px)
|
||||
if (val.endsWith("%")) w = val;
|
||||
else w = parseFloat(val);
|
||||
}
|
||||
if (hMatch) {
|
||||
let val = hMatch[1];
|
||||
if (val.endsWith('%')) h = val;
|
||||
if (val.endsWith("%")) h = val;
|
||||
else h = parseFloat(val);
|
||||
}
|
||||
}
|
||||
// 兼容 width/height 属性 (通常只有数字)
|
||||
if (!w) {
|
||||
const wAttr = imgTag.match(/\swidth="(\d+)"/i);
|
||||
if (wAttr) w = Number(wAttr[1]);
|
||||
}
|
||||
|
||||
if (!h) {
|
||||
const hAttr = imgTag.match(/\sheight="(\d+)"/i);
|
||||
if (hAttr) h = Number(hAttr[1]);
|
||||
}
|
||||
|
||||
const inner = aid ? aid : cleanUrl;
|
||||
let result = "";
|
||||
if (w || h) { // 只要有一个有值
|
||||
if (w || h) {
|
||||
const formatVal = (val) => {
|
||||
if (typeof val === 'string' && val.endsWith('%')) return val;
|
||||
if (typeof val === "string" && val.endsWith("%")) return val;
|
||||
return val ? parseFloat(Number(val).toFixed(2)) : 0;
|
||||
};
|
||||
result = `[img=${formatVal(w)},${formatVal(h)}]${aid}[/img]`;
|
||||
result = `[img=${formatVal(w)},${formatVal(h)}]${inner}[/img]`;
|
||||
|
||||
if (aid) {
|
||||
images.push({ url: cleanUrl, aid: Number(aid) });
|
||||
} else {
|
||||
images.push({ url: cleanUrl });
|
||||
}
|
||||
} else {
|
||||
result = `[img]${aid}[/img]`;
|
||||
result = `[img]${inner}[/img]`;
|
||||
|
||||
if (aid) {
|
||||
images.push({ url: cleanUrl, aid: Number(aid) });
|
||||
} else {
|
||||
images.push({ url: cleanUrl });
|
||||
}
|
||||
}
|
||||
|
||||
// 处理 data-href,包裹在 [url] 中
|
||||
const hrefMatch = imgTag.match(/data-href="([^"]+)"/i);
|
||||
if (hrefMatch && hrefMatch[1]) result = `[url=${hrefMatch[1]}]${result}[/url]`;
|
||||
|
||||
@@ -785,9 +832,13 @@ const editApp = createApp({
|
||||
""": '"',
|
||||
"'": "'",
|
||||
};
|
||||
|
||||
html = html.replace(/&[a-z]+;/gi, (match) => entities[match] || match);
|
||||
|
||||
return html.trim();
|
||||
return {
|
||||
content: html.trim(),
|
||||
images,
|
||||
};
|
||||
};
|
||||
|
||||
const extractImages = (dom) => {
|
||||
@@ -802,11 +853,12 @@ const editApp = createApp({
|
||||
const aid = urlObj.searchParams.get("aid");
|
||||
const queryIndex = url.indexOf("?");
|
||||
const cleanUrl = queryIndex !== -1 ? url.substring(0, queryIndex) : url;
|
||||
|
||||
images.push({
|
||||
url: cleanUrl,
|
||||
aid: Number(aid),
|
||||
});
|
||||
if (Number(aid)) {
|
||||
images.push({
|
||||
url: cleanUrl,
|
||||
aid: Number(aid),
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return images;
|
||||
|
||||
91
js/index.js
91
js/index.js
@@ -1,12 +1,23 @@
|
||||
const { createApp, ref, onMounted, nextTick, onUnmounted, computed, watch, provide } = Vue;
|
||||
import { headTop } from "../component/head-top/head-top.js";
|
||||
import { itemForum } from "../component/item-forum/item-forum.js";
|
||||
import { latestList } from "../component/latest-list/latest-list.js";
|
||||
import { loadBox } from "../component/load-box/load-box.js";
|
||||
|
||||
const { itemForum } = await import(withVer("../component/item-forum/item-forum.js"));
|
||||
const { itemOffer } = await import(withVer("../component/item-offer/item-offer.js"));
|
||||
const { itemSummary } = await import(withVer("../component/item-summary/item-summary.js"));
|
||||
const { itemVote } = await import(withVer("../component/item-vote/item-vote.js"));
|
||||
const { itemMj } = await import(withVer("../component/item-mj/item-mj.js"));
|
||||
const { itemTenement } = await import(withVer("../component/item-tenement/item-tenement.js"));
|
||||
const { headTop } = await import(withVer("../component/head-top/head-top.js"));
|
||||
const { loadBox } = await import(withVer("../component/load-box/load-box.js"));
|
||||
const { itemProject } = await import(withVer("../component/item-project/item-project.js"));
|
||||
const { latestList } = await import(withVer("../component/latest-list/latest-list.js"));
|
||||
|
||||
const appIndex = createApp({
|
||||
setup() {
|
||||
onMounted(() => getUserInfoWin());
|
||||
onMounted(() => {
|
||||
getUserInfoWin();
|
||||
// const preLoader = document.getElementById("pre-loader");
|
||||
// if (preLoader) preLoader.style.display = "none";
|
||||
});
|
||||
|
||||
let isLogin = ref(false);
|
||||
let realname = ref(0); // 是否已经实名
|
||||
@@ -62,6 +73,8 @@ const appIndex = createApp({
|
||||
getTalkingRecommend();
|
||||
getTopicHandpicked();
|
||||
getTopicLatest();
|
||||
|
||||
document.querySelectorAll(".vuehide").forEach((item) => (item.style.display = "none"));
|
||||
});
|
||||
|
||||
let ongoingbj = ref({}); // 话题数据
|
||||
@@ -70,14 +83,21 @@ const appIndex = createApp({
|
||||
ajaxGet("/v2/api/forum/talkingRecommend").then((res) => {
|
||||
if (res.code != 200) return;
|
||||
let data = res["data"] || [];
|
||||
const ongoing = data.ongoing || [];
|
||||
|
||||
ongoing.forEach((item) => {
|
||||
if (Array.isArray(item.commentUser)) item.commentUser = item.commentUser.slice(0, 4);
|
||||
});
|
||||
|
||||
const getTargetItem = (arr) => {
|
||||
const target = arr.find((item) => item.state === 1);
|
||||
return target !== undefined ? target : arr.length > 0 ? arr[0] : null;
|
||||
return target !== undefined ? target : arr.length > 0 ? arr[Math.floor(Math.random() * arr.length)] : null; // 随机返回一个
|
||||
};
|
||||
|
||||
ongoingbj.value = getTargetItem(data.ongoing || []);
|
||||
pastList.value = data.past || [];
|
||||
ongoingbj.value = getTargetItem(ongoing || []) || {};
|
||||
console.log("ongoingbj", ongoingbj.value);
|
||||
const past = data.past || [];
|
||||
pastList.value = past.sort(() => Math.random() - 0.5).slice(0, 5);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -138,11 +158,11 @@ const appIndex = createApp({
|
||||
|
||||
// 处理 offer 列表滚动
|
||||
const offerListScrolling = (data) => {
|
||||
ajax("https://forum.gter.net/api/index/dynamic").then((res) => {
|
||||
ajaxGet("https://api.gter.net/v2/api/forum/getDynamic").then((res) => {
|
||||
if (res.code == 200) {
|
||||
let data = res["data"] || [];
|
||||
|
||||
data.forEach((item) => (item.date = strtimeago(item.date)));
|
||||
data.forEach((item) => (item.date = strtimeago(item.created_at)));
|
||||
|
||||
let targetValue = [];
|
||||
targetValue = [...data, ...data.slice(0, 6)];
|
||||
@@ -168,7 +188,7 @@ const appIndex = createApp({
|
||||
|
||||
if (scrollup) return;
|
||||
scrollup = new ScrollText("offer-box");
|
||||
scrollup.LineHeight = 55;
|
||||
scrollup.LineHeight = 56;
|
||||
scrollup.Amount = 1;
|
||||
scrollup.Delay = 1;
|
||||
scrollup.Start();
|
||||
@@ -185,29 +205,6 @@ const appIndex = createApp({
|
||||
if (!event.relatedTarget || !event.currentTarget.contains(event.relatedTarget)) autoOfferListScroll();
|
||||
};
|
||||
|
||||
const popList = [
|
||||
{
|
||||
title: "26FALL",
|
||||
subtitle: "申请群",
|
||||
img: "https://o.x-php.com/Zvt57TuJSUvkyhw-xG7Y2l-c-5kpcnzqqsgFptxhcq_cQnrlJKN1WgxCBq_D-81qNDQyOQ~~",
|
||||
},
|
||||
{
|
||||
title: "申请求助",
|
||||
subtitle: "寄托院校君",
|
||||
img: "https://u.gter.net/assistantwxqrcode.png",
|
||||
},
|
||||
{
|
||||
title: "香港租房",
|
||||
subtitle: "交流群",
|
||||
img: "https://o.x-php.com/Zvt57TuJSUvkyhw-xG7Y2l-c-5kpcnzqqsgFptxhcq_cQnrlJKN1WgxCBq_D-81qNDQyOQ~~",
|
||||
},
|
||||
{
|
||||
title: "香港租房顾问",
|
||||
subtitle: "寄托方同学",
|
||||
img: "https://o.x-php.com/Zvt57TuJSUvkyhw-xG7Y2l-d_JkpcHnqqsgFptxhcq_cQnrlcaF2WQQQBq_D-81qNDQyOQ~~",
|
||||
},
|
||||
];
|
||||
|
||||
let sectionList = ref([]);
|
||||
const getSectionList = () => {
|
||||
ajaxGet("/v2/api/forum/getSectionList").then((res) => {
|
||||
@@ -238,7 +235,7 @@ const appIndex = createApp({
|
||||
const getList = () => {
|
||||
if (loading.value || page.value == 0) return;
|
||||
loading.value = true;
|
||||
ajaxGet(`/v2/api/forum/topicLists?type=thread&page=${page.value || 1}`)
|
||||
ajaxGet(`/v2/api/forum/topicLists?page=${page.value || 1}`)
|
||||
.then((res) => {
|
||||
if (res.code != 200) return;
|
||||
let data = res.data;
|
||||
@@ -259,12 +256,34 @@ const appIndex = createApp({
|
||||
|
||||
let sidebarHeight = ref(0);
|
||||
|
||||
return { sidebarHeight, matterRef, sidebarFixed, sidebarRef, loading, interviewexperience, vote, offer, topicHandpickedList, list, sectionList, popList, custom_2AdvRef, ongoingbj, pastList, offerMouseover, offerMouseout, offerlist, offerListRef };
|
||||
const handleCheckAttest = (e) => {
|
||||
if (!isLogin.value) {
|
||||
goLogin();
|
||||
e.preventDefault(); // 阻止默认跳转(即使 href 为链接,也强制拦截)
|
||||
return;
|
||||
}
|
||||
if (realname.value === 0 && userInfoWin.value?.uin > 0) {
|
||||
openAttest();
|
||||
e.preventDefault(); // 阻止默认跳转(即使 href 为链接,也强制拦截)
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
SignInComponent.initComponent()
|
||||
})
|
||||
|
||||
return { handleCheckAttest, sidebarHeight, matterRef, sidebarFixed, sidebarRef, loading, interviewexperience, vote, offer, topicHandpickedList, list, sectionList, custom_2AdvRef, ongoingbj, pastList, offerMouseover, offerMouseout, offerlist, offerListRef };
|
||||
},
|
||||
});
|
||||
|
||||
appIndex.component("headTop", headTop);
|
||||
appIndex.component("itemForum", itemForum);
|
||||
appIndex.component("itemOffer", itemOffer);
|
||||
appIndex.component("itemSummary", itemSummary);
|
||||
appIndex.component("itemVote", itemVote);
|
||||
appIndex.component("itemMj", itemMj);
|
||||
appIndex.component("itemTenement", itemTenement);
|
||||
appIndex.component("itemProject", itemProject);
|
||||
appIndex.component("latestList", latestList);
|
||||
appIndex.component("load-box", loadBox);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user