Add pagination
Note: Requests API endpoints /getRequests and /getRequestsAll have breaking changes (the response schema changed entirely). Fixes #26
This commit is contained in:
		
							parent
							
								
									c634e763f3
								
							
						
					
					
						commit
						84a22ccffd
					
				
					 6 changed files with 139 additions and 53 deletions
				
			
		| 
						 | 
					@ -1,29 +1,49 @@
 | 
				
			||||||
var requestsDiv = document.getElementById("requests");
 | 
					var requestsDiv = document.getElementById("requests");
 | 
				
			||||||
var cronJobs = ['processBans']
 | 
					var cronJobs = ['processBans'];
 | 
				
			||||||
 | 
					var currentPage = 1;
 | 
				
			||||||
 | 
					var totalPages = 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function getRequests(count,allRequests) {
 | 
					function getRequests(count,offset,allRequests) {
 | 
				
			||||||
	var reqUrl;
 | 
						if (allRequests) var reqUrl = "/api/getAllRequests";
 | 
				
			||||||
	if (allRequests) {
 | 
						else var reqUrl = "/api/getRequests";
 | 
				
			||||||
		reqUrl = "/api/getAllRequests";
 | 
						reqUrl += `?count=${count}&offset=${offset}`;
 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		reqUrl = "/api/getRequests";
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	reqUrl += `?count=${count}`;
 | 
					 | 
				
			||||||
	fetch(reqUrl)
 | 
						fetch(reqUrl)
 | 
				
			||||||
		.then(response => response.json())
 | 
							.then(response => response.json())
 | 
				
			||||||
		.then(requests => {
 | 
							.then(requests => {
 | 
				
			||||||
			window.requests = requests;
 | 
								buildTable(requests);
 | 
				
			||||||
			buildTable();
 | 
					 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function buildTable() {
 | 
					function buildTable(requests) {
 | 
				
			||||||
 | 
						totalPages = Math.ceil(requests.total/document.getElementById("count").value);
 | 
				
			||||||
 | 
						document.getElementById("totalPages").innerText = totalPages;
 | 
				
			||||||
 | 
						if (currentPage <= 1) {
 | 
				
			||||||
 | 
							currentPage = 1;
 | 
				
			||||||
 | 
							document.getElementById("pageBtnFirst").disabled = true;
 | 
				
			||||||
 | 
							document.getElementById("pageBtnPrev").disabled = true;
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							document.getElementById("pageBtnFirst").disabled = false;
 | 
				
			||||||
 | 
							document.getElementById("pageBtnPrev").disabled = false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if (currentPage >= totalPages) {
 | 
				
			||||||
 | 
							currentPage = totalPages;
 | 
				
			||||||
 | 
							document.getElementById("pageBtnLast").disabled = true;
 | 
				
			||||||
 | 
							document.getElementById("pageBtnNext").disabled = true;
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							document.getElementById("pageBtnLast").disabled = false;
 | 
				
			||||||
 | 
							document.getElementById("pageBtnNext").disabled = false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						document.getElementById("page").innerHTML = "";
 | 
				
			||||||
 | 
						for (i = 1; i <= totalPages; i++) {
 | 
				
			||||||
 | 
							document.getElementById("page").innerHTML += `<option value=${i}>${i}</option>`;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						document.getElementById("page").value = currentPage;
 | 
				
			||||||
	var requestsDivHTML = '<table><tr><th class="request-link">Song</th><th class="request-requester">Requester</th><th class="request-score">Score</th>';
 | 
						var requestsDivHTML = '<table><tr><th class="request-link">Song</th><th class="request-requester">Requester</th><th class="request-score">Score</th>';
 | 
				
			||||||
	requestsDivHTML += '<th class="request-state">State</td>';
 | 
						requestsDivHTML += '<th class="request-state">State</td>';
 | 
				
			||||||
	if (window.loggedIn) requestsDivHTML += '<th class="request-vote">Vote</td>';
 | 
						if (window.loggedIn) requestsDivHTML += '<th class="request-vote">Vote</td>';
 | 
				
			||||||
	if (window.isStreamer) requestsDivHTML += '<th class="request-update">Update</th>'
 | 
						if (window.isStreamer) requestsDivHTML += '<th class="request-update">Update</th>'
 | 
				
			||||||
	requestsDivHTML += "</tr>";
 | 
						requestsDivHTML += "</tr>";
 | 
				
			||||||
	for (request of requests) {
 | 
						for (request of requests.requests) {
 | 
				
			||||||
		requestsDivHTML += `<tr><td class="request-link"><a href="${request.url}" target="_blank">${request.title}</a></td>\
 | 
							requestsDivHTML += `<tr><td class="request-link"><a href="${request.url}" target="_blank">${request.title}</a></td>\
 | 
				
			||||||
			<td class="request-requester">${request.imageurl ? `<img src="${request.imageurl}" class="table-userpic"/>` : ''}${request.requester}</td>\
 | 
								<td class="request-requester">${request.imageurl ? `<img src="${request.imageurl}" class="table-userpic"/>` : ''}${request.requester}</td>\
 | 
				
			||||||
			<td class="request-score">${request.score}</td>`;
 | 
								<td class="request-score">${request.score}</td>`;
 | 
				
			||||||
| 
						 | 
					@ -45,8 +65,10 @@ function buildTable() {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function updateTable() {
 | 
					function updateTable() {
 | 
				
			||||||
	allRequests = document.getElementById("allRequests").checked;
 | 
						var count = document.getElementById("count").value;
 | 
				
			||||||
	getRequests(document.getElementById("count").value,allRequests);
 | 
						var offset = (currentPage - 1) * count;
 | 
				
			||||||
 | 
						var allRequests = document.getElementById("allRequests").checked;
 | 
				
			||||||
 | 
						getRequests(count,offset,allRequests);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function applyUrlTransforms(url) {
 | 
					function applyUrlTransforms(url) {
 | 
				
			||||||
| 
						 | 
					@ -66,6 +88,11 @@ function applyUrlTransforms(url) {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function goToPage(page) {
 | 
				
			||||||
 | 
						currentPage = parseInt(page,10);
 | 
				
			||||||
 | 
						updateTable();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function getColorObject() {
 | 
					function getColorObject() {
 | 
				
			||||||
	return {
 | 
						return {
 | 
				
			||||||
		bg: {
 | 
							bg: {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -111,6 +111,11 @@ div#nav-userpic {
 | 
				
			||||||
	text-align: right;
 | 
						text-align: right;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#tableSettings {
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  justify-content: space-between;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#modalBackground {
 | 
					#modalBackground {
 | 
				
			||||||
	display: none;
 | 
						display: none;
 | 
				
			||||||
	position: fixed;
 | 
						position: fixed;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										43
									
								
								src/app.ts
									
										
									
									
									
								
							
							
						
						
									
										43
									
								
								src/app.ts
									
										
									
									
									
								
							| 
						 | 
					@ -42,31 +42,47 @@ app.use(session({
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// API
 | 
					// API
 | 
				
			||||||
app.get("/api/getRequests", async (request, response) => {
 | 
					app.get("/api/getRequests", async (request, response) => {
 | 
				
			||||||
	if (!request.session) {
 | 
						if (!request.session) throw new Error ("Missing request.session");
 | 
				
			||||||
		throw new Error ("Missing request.session")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	var requestCount = ( request.query.count ? parseInt(request.query.count as string, 10) : 5 );
 | 
					 | 
				
			||||||
	await validateApiToken(request.session);
 | 
						await validateApiToken(request.session);
 | 
				
			||||||
 | 
						var requestCount = ( request.query.count ? parseInt(request.query.count as string, 10) : 5 );
 | 
				
			||||||
 | 
						var requestOffset = ( request.query.offset ? parseInt(request.query.offset as string, 10) : 0 );
 | 
				
			||||||
 | 
						var requestsTotal = await requests.getRequestsTotal();
 | 
				
			||||||
	if (request.session.user) {
 | 
						if (request.session.user) {
 | 
				
			||||||
		requests.getRequestsVoted(requestCount,request.session.user.id).then((val: Array<any>) => response.send(val))
 | 
							requests.getRequestsVoted(requestCount,requestOffset,request.session.user.id)
 | 
				
			||||||
 | 
								.then((val: Array<any>) => response.send({
 | 
				
			||||||
 | 
										total: requestsTotal,
 | 
				
			||||||
 | 
										requests: val
 | 
				
			||||||
 | 
									}))
 | 
				
			||||||
			.catch((e: any) => errorHandler(request,response,e));
 | 
								.catch((e: any) => errorHandler(request,response,e));
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		requests.getRequests(requestCount).then((val: Array<any>) => response.send(val))
 | 
							requests.getRequests(requestCount,requestOffset)
 | 
				
			||||||
 | 
								.then((val: Array<any>) => response.send({
 | 
				
			||||||
 | 
										total: requestsTotal,
 | 
				
			||||||
 | 
										requests: val
 | 
				
			||||||
 | 
									}))
 | 
				
			||||||
			.catch((e: any) => errorHandler(request,response,e));
 | 
								.catch((e: any) => errorHandler(request,response,e));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
app.get("/api/getAllRequests", async (request, response) => {
 | 
					app.get("/api/getAllRequests", async (request, response) => {
 | 
				
			||||||
	if (!request.session) {
 | 
						if (!request.session) throw new Error ("Missing request.session");
 | 
				
			||||||
		throw new Error ("Missing request.session")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	var requestCount = ( request.query.count ? parseInt(request.query.count as string, 10) : 5 );
 | 
					 | 
				
			||||||
	await validateApiToken(request.session);
 | 
						await validateApiToken(request.session);
 | 
				
			||||||
 | 
						var requestCount = ( request.query.count ? parseInt(request.query.count as string, 10) : 5 );
 | 
				
			||||||
 | 
						var requestOffset = ( request.query.offset ? parseInt(request.query.offset as string, 10) : 0 );
 | 
				
			||||||
 | 
						var requestsTotal = await requests.getAllRequestsTotal();
 | 
				
			||||||
	if (request.session.user) {
 | 
						if (request.session.user) {
 | 
				
			||||||
		requests.getAllRequestsVoted(requestCount,request.session.user.id).then((val: Array<any>) => response.send(val))
 | 
							requests.getAllRequestsVoted(requestCount,requestOffset,request.session.user.id)
 | 
				
			||||||
 | 
								.then((val: Array<any>) => response.send({
 | 
				
			||||||
 | 
										total: requestsTotal,
 | 
				
			||||||
 | 
										requests: val
 | 
				
			||||||
 | 
									}))
 | 
				
			||||||
			.catch((e: any) => errorHandler(request,response,e));
 | 
								.catch((e: any) => errorHandler(request,response,e));
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		requests.getAllRequests(requestCount).then((val: Array<any>) => response.send(val))
 | 
							requests.getAllRequests(requestCount,requestOffset)
 | 
				
			||||||
 | 
								.then((val: Array<any>) => response.send({
 | 
				
			||||||
 | 
										total: requestsTotal,
 | 
				
			||||||
 | 
										requests: val
 | 
				
			||||||
 | 
									}))
 | 
				
			||||||
			.catch((e: any) => errorHandler(request,response,e));
 | 
								.catch((e: any) => errorHandler(request,response,e));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -79,7 +95,8 @@ app.post("/api/addRequest", async (request, response) => {
 | 
				
			||||||
		response.send("Session expired; please log in again");
 | 
							response.send("Session expired; please log in again");
 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	var banned = await db.query(Object.assign(queries.checkBan, { values: [request.session.user.id] })).then((result: pg.QueryResult) => result.rowCount > 0);
 | 
						var banned = await db.query(Object.assign(queries.checkBan, { values: [request.session.user.id] }))
 | 
				
			||||||
 | 
							.then((result: pg.QueryResult) => result.rowCount > 0);
 | 
				
			||||||
	if (banned) {
 | 
						if (banned) {
 | 
				
			||||||
		response.status(401);
 | 
							response.status(401);
 | 
				
			||||||
		response.send("You are banned; you may not add new requests.");
 | 
							response.send("You are banned; you may not add new requests.");
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -56,27 +56,38 @@ export const updateVotePoints = {
 | 
				
			||||||
export const getRequests = {
 | 
					export const getRequests = {
 | 
				
			||||||
	name: "getRequests",
 | 
						name: "getRequests",
 | 
				
			||||||
	text: "SELECT * FROM requests_vw \
 | 
						text: "SELECT * FROM requests_vw \
 | 
				
			||||||
		JOIN states ON requests_vw.state = states.state \
 | 
							JOIN states ON requests_vw.state = states.state WHERE active \
 | 
				
			||||||
		WHERE active ORDER BY score DESC, reqTimestamp ASC LIMIT $1"
 | 
							ORDER BY score DESC, reqTimestamp ASC LIMIT $1 OFFSET $2"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const getRequestsVoted = {
 | 
					export const getRequestsVoted = {
 | 
				
			||||||
	name: "getRequestsVoted",
 | 
						name: "getRequestsVoted",
 | 
				
			||||||
	text: "SELECT * FROM get_requests_voted($2) \
 | 
						text: "SELECT * FROM get_requests_voted($3) \
 | 
				
			||||||
		JOIN states ON get_requests_voted.state = states.state \
 | 
							JOIN states ON get_requests_voted.state = states.state WHERE active \
 | 
				
			||||||
		WHERE active ORDER BY score DESC, reqTimestamp ASC LIMIT $1"
 | 
							ORDER BY score DESC, reqTimestamp ASC LIMIT $1 OFFSET $2"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getRequestsTotal = {
 | 
				
			||||||
 | 
						name: "getRequestsTotal",
 | 
				
			||||||
 | 
						text: "SELECT COUNT(*) FROM requests_vw \
 | 
				
			||||||
 | 
							JOIN states ON requests_vw.state = states.state WHERE active"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const getAllRequests = {
 | 
					export const getAllRequests = {
 | 
				
			||||||
	name: "getAllRequests",
 | 
						name: "getAllRequests",
 | 
				
			||||||
	text: "SELECT * FROM requests_vw \
 | 
						text: "SELECT * FROM requests_vw \
 | 
				
			||||||
		ORDER BY score DESC, reqTimestamp ASC LIMIT $1"
 | 
							ORDER BY score DESC, reqTimestamp ASC LIMIT $1 OFFSET $2"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const getAllRequestsVoted = {
 | 
					export const getAllRequestsVoted = {
 | 
				
			||||||
	name: "getAllRequestsVoted",
 | 
						name: "getAllRequestsVoted",
 | 
				
			||||||
	text: "SELECT * FROM get_requests_voted($2) \
 | 
						text: "SELECT * FROM get_requests_voted($3) \
 | 
				
			||||||
		ORDER BY score DESC, reqTimestamp ASC LIMIT $1"
 | 
							ORDER BY score DESC, reqTimestamp ASC LIMIT $1 OFFSET $2"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getAllRequestsTotal = {
 | 
				
			||||||
 | 
						name: "getAllRequestsTotal",
 | 
				
			||||||
 | 
						text: "SELECT COUNT(*) FROM requests_vw"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const checkRequestExists = {
 | 
					export const checkRequestExists = {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,30 +4,40 @@ import { log, LogLevel } from "./logging"
 | 
				
			||||||
import pg from "pg";
 | 
					import pg from "pg";
 | 
				
			||||||
import db from "./db";
 | 
					import db from "./db";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export async function getRequests(count: number) {
 | 
					export async function getRequests(count: number, offset: number) {
 | 
				
			||||||
	var query = Object.assign(queries.getRequests, { values: [count] });
 | 
						var query = Object.assign(queries.getRequests, { values: [count,offset] });
 | 
				
			||||||
	return db.query(query)
 | 
						return db.query(query)
 | 
				
			||||||
	.then((result: pg.QueryResult) => result.rows);
 | 
						.then((result: pg.QueryResult) => result.rows);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export async function getAllRequests(count: number) {
 | 
					export async function getRequestsVoted(count: number, offset: number, user: number) {
 | 
				
			||||||
	var query = Object.assign(queries.getAllRequests, { values: [count] });
 | 
						var query = Object.assign(queries.getRequestsVoted, { values: [count,offset,user] });
 | 
				
			||||||
	return db.query(query)
 | 
						return db.query(query)
 | 
				
			||||||
	.then((result: pg.QueryResult) => result.rows);
 | 
						.then((result: pg.QueryResult) => result.rows);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export async function getRequestsVoted(count: number, user: number) {
 | 
					export async function getRequestsTotal() {
 | 
				
			||||||
	var query = Object.assign(queries.getRequestsVoted, { values: [count,user] });
 | 
						return db.query(queries.getRequestsTotal)
 | 
				
			||||||
 | 
						.then((result: pg.QueryResult) => result.rows[0]["count"]);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function getAllRequests(count: number, offset: number) {
 | 
				
			||||||
 | 
						var query = Object.assign(queries.getAllRequests, { values: [count,offset] });
 | 
				
			||||||
	return db.query(query)
 | 
						return db.query(query)
 | 
				
			||||||
	.then((result: pg.QueryResult) => result.rows);
 | 
						.then((result: pg.QueryResult) => result.rows);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export async function getAllRequestsVoted(count: number,user: number) {
 | 
					export async function getAllRequestsVoted(count: number, offset: number, user: number) {
 | 
				
			||||||
	var query = Object.assign(queries.getAllRequestsVoted, { values: [count,user] });
 | 
						var query = Object.assign(queries.getAllRequestsVoted, { values: [count,offset,user] });
 | 
				
			||||||
	return db.query(query)
 | 
						return db.query(query)
 | 
				
			||||||
	.then((result: pg.QueryResult) => result.rows);
 | 
						.then((result: pg.QueryResult) => result.rows);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function getAllRequestsTotal() {
 | 
				
			||||||
 | 
						return db.query(queries.getAllRequestsTotal)
 | 
				
			||||||
 | 
						.then((result: pg.QueryResult) => result.rows[0]["count"]);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const validUrlRegexes = [
 | 
					const validUrlRegexes = [
 | 
				
			||||||
	/^https:\/\/www\.youtube\.com\/watch\?v=[a-zA-Z0-9_-]{11}$/
 | 
						/^https:\/\/www\.youtube\.com\/watch\?v=[a-zA-Z0-9_-]{11}$/
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -30,6 +30,8 @@
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
		<div id="main">
 | 
							<div id="main">
 | 
				
			||||||
			<div id="requests"></div><br>
 | 
								<div id="requests"></div><br>
 | 
				
			||||||
 | 
								<div id="tableSettings">
 | 
				
			||||||
 | 
									<span style="width:420px">
 | 
				
			||||||
					Count:
 | 
										Count:
 | 
				
			||||||
					<select id="count" value="10" onchange="updateTable()">
 | 
										<select id="count" value="10" onchange="updateTable()">
 | 
				
			||||||
						<option>5</option>
 | 
											<option>5</option>
 | 
				
			||||||
| 
						 | 
					@ -38,7 +40,21 @@
 | 
				
			||||||
						<option>50</option>
 | 
											<option>50</option>
 | 
				
			||||||
						<option>100</option>
 | 
											<option>100</option>
 | 
				
			||||||
					</select>
 | 
										</select>
 | 
				
			||||||
			<input type="checkbox" id="allRequests" onchange="updateTable()">View requests in any state</input>
 | 
									</span>
 | 
				
			||||||
 | 
									<span>
 | 
				
			||||||
 | 
										<button id="pageBtnFirst" onclick="goToPage(1)"><<</button>
 | 
				
			||||||
 | 
										<button id="pageBtnPrev" onclick="goToPage(currentPage-1)"><</button>
 | 
				
			||||||
 | 
										<select id="page" onchange="goToPage(this.value)">
 | 
				
			||||||
 | 
											<option value=1>1</option>
 | 
				
			||||||
 | 
										</select>
 | 
				
			||||||
 | 
										of <span id="totalPages">?</span>
 | 
				
			||||||
 | 
										<button id="pageBtnNext" onclick="goToPage(currentPage+1)">></button>
 | 
				
			||||||
 | 
										<button id="pageBtnLast" onclick="goToPage(totalPages)">>></button>
 | 
				
			||||||
 | 
									</span>
 | 
				
			||||||
 | 
									<span style="width:420px">
 | 
				
			||||||
 | 
										<input type="checkbox" id="allRequests" onchange="updateTable()">View learned and rejected requests</input>
 | 
				
			||||||
 | 
									</span>
 | 
				
			||||||
 | 
								</div>
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
	<div id="modalBackground">
 | 
						<div id="modalBackground">
 | 
				
			||||||
			<div class="modal" id="messageModal">
 | 
								<div class="modal" id="messageModal">
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue