Add initial frontend

This commit is contained in:
rebel 2023-05-17 02:04:35 +02:00
parent 68c2f8f676
commit bd8cdc8509
8 changed files with 334 additions and 0 deletions

View file

@ -0,0 +1,29 @@
@import colors
.content-wrapper.banned-posts
width: 100%
max-width: 45em
table
border-spacing: 0
width: 100%
tr.default td
background: $default-banned-post-background-color
td, th
padding: .4em
&.color
input[type=text]
width: 8em
&.usages
text-align: center
&.remove, &.set-default
white-space: pre
th
white-space: nowrap
&:first-child
padding-left: 0
&:last-child
padding-right: 0
tfoot
display: none
form
width: auto

View file

@ -0,0 +1,13 @@
<tr data-category='<%- ctx.postBan.checksum %>'><%
<td class='name'>
<%- ctx.postBan.checksum %>
</td>
<td class='time'>
<%- ctx.makeRelativeTime(ctx.postBan.time) %>
</td>
<% if (ctx.canDelete) { %>
<td class='remove'>
<a href>Unban</a>
</td>
<% } %>
</tr>

View file

@ -0,0 +1,25 @@
<div class='content-wrapper banned-posts'>
<form>
<h1>Banned posts</h1>
<div class="table-wrap">
<table>
<thead>
<tr>
<th class='checksum'>Checksum</th>
<th class='time'>Time of ban</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
<div class='messages'></div>
<% if (ctx.canDelete) { %>
<div class='buttons'>
<input type='submit' class='save' value='Save changes'>
</div>
<% } %>
</form>
</div>

View file

@ -0,0 +1,59 @@
"use strict";
const api = require("../api.js");
const BannedPostList = require("../models/banned_post_list.js");
const topNavigation = require("../models/top_navigation.js");
const BannedPostsView = require("../views/banned_posts_view.js");
const EmptyView = require("../views/empty_view.js");
class BannedPostController {
constructor() {
if (!api.hasPrivilege("posts:ban:list")) {
this._view = new EmptyView();
this._view.showError(
"You don't have privileges to view banned posts."
);
return;
}
topNavigation.activate("banned-posts");
topNavigation.setTitle("Listing banned posts");
BannedPostList.get().then(
(response) => {
this._bannedPosts = response.results;
this._view = new BannedPostsView({
bannedPosts: this._bannedPosts,
canDelete: api.hasPrivilege("poolCategories:delete")
});
this._view.addEventListener("submit", (e) =>
this._evtSubmit(e)
);
},
(error) => {
this._view = new EmptyView();
this._view.showError(error.message);
}
);
}
_evtSubmit(e) {
this._view.clearMessages();
this._view.disableForm();
this._bannedPosts.save().then(
() => {
this._view.enableForm();
this._view.showSuccess("Changes saved.");
},
(error) => {
this._view.enableForm();
this._view.showError(error.message);
}
);
}
}
module.exports = (router) => {
router.enter(["banned-posts"], (ctx, next) => {
ctx.controller = new BannedPostController(ctx, next);
});
};

View file

@ -83,6 +83,7 @@ Promise.resolve()
controllers.push(
require("./controllers/user_registration_controller.js")
);
controllers.push(require("./controllers/banned_post_controller.js"));
// 404 controller needs to be registered last
controllers.push(require("./controllers/not_found_controller.js"));

View file

@ -0,0 +1,57 @@
"use strict";
const api = require("../api.js");
const uri = require("../util/uri.js");
const events = require("../events.js");
class BannedPost extends events.EventTarget {
constructor() {
super();
this._checksum = "";
this._time = new Date();
}
get checksum() {
return this._checksum;
}
get time() {
return this._time;
}
set checksum(value) {
this._checksum = value;
}
set time(value) {
this._time = value;
}
static fromResponse(response) {
const ret = new BannedPost();
ret._updateFromResponse(response);
return ret;
}
delete() {
return api
.delete(uri.formatApiLink("post-ban", this._checksum))
.then((response) => {
this.dispatchEvent(
new CustomEvent("delete", {
detail: {
bannedPost: this,
},
})
);
return Promise.resolve();
});
}
_updateFromResponse(response) {
this._checksum = response.checksum;
this.time = response.time;
}
}
module.exports = BannedPost;

View file

@ -0,0 +1,47 @@
const api = require("../api.js");
const uri = require("../util/uri.js");
const AbstractList = require("./abstract_list.js");
const BannedPost = require("./banned_post.js");
class BannedPostList extends AbstractList {
constructor() {
super();
this._deletedBans = [];
this.addEventListener("remove", (e) => this._evtBannedPostDeleted(e));
}
static get() {
return api
.get(uri.formatApiLink("post-ban"))
.then((response) => {
return Promise.resolve(
Object.assign({}, response, {
results: BannedPostList.fromResponse(
response.results
),
})
);
});
}
save() {
let promises = [];
for (let BannedPost of this._deletedBans) {
promises.push(BannedPost.delete());
}
return Promise.all(promises).then((response) => {
this._deletedBans = [];
return Promise.resolve();
});
}
_evtBannedPostDeleted(e) {
this._deletedBans.push(e.detail.BannedPost);
}
}
BannedPostList._itemClass = BannedPost;
BannedPostList._itemName = "bannedPost";
module.exports = BannedPostList;

View file

@ -0,0 +1,103 @@
"use strict";
const events = require("../events.js");
const views = require("../util/views.js");
const BannedPost = require("../models/banned_post.js");
const template = views.getTemplate("banned-post-list");
const rowTemplate = views.getTemplate("banned-post-entry");
class BannedPostsView extends events.EventTarget {
constructor(ctx) {
super();
this._ctx = ctx;
this._hostNode = document.getElementById("content-holder");
views.replaceContent(this._hostNode, template(ctx));
views.syncScrollPosition();
views.decorateValidator(this._formNode);
const bannedPostsToAdd = Array.from(ctx.bannedPosts);
for (let bannedPost of bannedPostsToAdd) {
this._addBannedPostRowNode(bannedPost);
}
ctx.bannedPosts.addEventListener("remove", (e) =>
this._evtBannedPostDeleted(e)
);
this._formNode.addEventListener("submit", (e) =>
this._evtSaveButtonClick(e, ctx)
);
}
enableForm() {
views.enableForm(this._formNode);
}
disableForm() {
views.disableForm(this._formNode);
}
clearMessages() {
views.clearMessages(this._hostNode);
}
showSuccess(message) {
views.showSuccess(this._hostNode, message);
}
showError(message) {
views.showError(this._hostNode, message);
}
get _formNode() {
return this._hostNode.querySelector("form");
}
get _tableBodyNode() {
return this._hostNode.querySelector("tbody");
}
_addBannedPostRowNode(bannedPost) {
const rowNode = rowTemplate(
Object.assign({}, this._ctx, { postBan: bannedPost })
);
const removeLinkNode = rowNode.querySelector(".remove a");
if (removeLinkNode) {
removeLinkNode.addEventListener("click", (e) =>
this._evtDeleteButtonClick(e, rowNode)
);
}
this._tableBodyNode.appendChild(rowNode);
rowNode._bannedPost = bannedPost;
bannedPost._rowNode = rowNode;
}
_removeBannedPostRowNode(bannedPost) {
const rowNode = bannedPost._rowNode;
rowNode.parentNode.removeChild(rowNode);
}
_evtBannedPostDeleted(e) {
this._removeBannedPostRowNode(e.detail.poolCategory);
}
_evtDeleteButtonClick(e, rowNode, link) {
e.preventDefault();
if (e.target.classList.contains("inactive")) {
return;
}
this._ctx.bannedPosts.remove(rowNode._bannedPost);
}
_evtSaveButtonClick(e, ctx) {
e.preventDefault();
this.dispatchEvent(new CustomEvent("submit"));
}
}
module.exports = BannedPostsView;