Implemented filters in frontend

This commit is contained in:
Corban-Lee Jones 2024-05-01 12:18:06 +01:00
parent 03c73e24de
commit 9d6fe850ca
6 changed files with 295 additions and 80 deletions

View File

@ -25,7 +25,7 @@ function initFiltersTable() {
title: '<input type="checkbox" class="form-check-input table-select-all" />',
data: null,
orderable: false,
className: "text-center",
className: "text-center col-1",
render: function() {
return '<input type="checkbox" class="form-check-input table-select-row" />'
}
@ -38,24 +38,152 @@ function initFiltersTable() {
return `<a href="#" onclick="showEditFilterModal(${row.id})" class="text-decoration-none">${data}</a>`
}
},
{ title: "Regex", data: "regex" },
{ title: "Used", data: "used_count" },
{
title: "Created",
data: "creation_datetime",
title: "Keywords",
data: "keywords",
render: function(data, type) {
return new Date(data).toISOString().split("T")[0];
if (!data) return "-";
return data;
}
},
{
title: "Active",
data: "active",
orderable: false,
className: "text-center form-switch",
{
title: "Regex",
data: "regex",
render: function(data, type) {
return `<input type="checkbox" class="form-check-input ms-0" ${data ? "checked" : ""} />`
if (!data) return "-";
return data;
}
}
]
});
}
}
$("#filterAdvancedMode").on("change", function() {
const advancedMode = $(this).prop("checked");
setFilterAdvancedMode(advancedMode);
});
function setFilterAdvancedMode(advancedMode) {
if (advancedMode) {
$("#filterForm .simple-filtering").hide();
$("#filterForm .advanced-filtering").show();
$("#filterForm .advanced-filtering").val("");
}
else {
$("#filterForm .advanced-filtering").hide();
$("#filterForm .simple-filtering").show();
$("#filterForm .simple-filtering").val("");
}
}
$("#addFilterBtn").on("click", async function() {
await showEditFilterModal(-1);
})
async function showEditFilterModal(filterId) {
if (filterId === -1) {
$("#filterFormModal input, #filterFormModal textarea").val("");
$("#filterFormModal .form-create").show();
$("#filterFormModal .form-edit").hide();
$("#filterAdvancedMode").prop("checked", false);
$("#filterAdvancedMode").trigger("change");
}
else {
const filter = filtersTable.row(function(idx, data, node) {
return data.id === filterId;
}).data();
$("#filterName").val(filter.name);
$("#filterKeywords").val(filter.keywords);
$("#filterRegex").val(filter.regex);
$("#filterAdvancedMode").prop("checked", filter.regex !== "");
$("#filterAdvancedMode").trigger("change");
$("#filterFormModal .form-create").hide();
$("#filterFormModal .form-edit").show();
}
$("#filterId").val(filterId);
$("#filterFormModal").modal("show");
}
$("#filterForm").on("submit", async function(event) {
event.preventDefault();
var advancedFiltering = $("#filterAdvancedMode").prop("checked");
id = $("#filterId").val();
name = $("#filterName").val();
keywords = advancedFiltering ? "" : $("#filterKeywords").val();
regex = advancedFiltering ? $("#filterRegex").val() : "";
guildId = getCurrentlyActiveServer().guild_id;
if (advancedFiltering) {
keywords = "";
}
else {
regex = ""
}
var filterPrimaryKey = await saveFilter(id, name, keywords, regex, guildId);
if (filterPrimaryKey) {
showToast("success", "Filter Saved", "Filter ID " + filterPrimaryKey);
await loadFilters(guildId);
}
$("#filterFormModal").modal("hide");
});
async function saveFilter(id, name, keywords, regex, guildId) {
var formData = new FormData();
formData.append("name", name);
formData.append("keywords", keywords);
formData.append("regex", regex);
formData.append("guild_id", guildId);
var response;
try {
if (id === "-1") response = await newFilter(formData);
else response = await editFilter(id, formData);
}
catch (err) {
showToast("danger", "Filter Error", err.responseText, 18000);
return false
}
return response.id;
}
function clearExistingFilterRows() {
$("#filtersTable thead .table-select-all").prop("checked", false).prop("indeterminate", false)
filtersTable.clear().draw(false)
}
async function loadFilters(guildId) {
if (!guildId)
return;
$("#deleteSelectedFiltersBtn").prop("disabled", true);
clearExistingFilterRows();
try {
const filters = await getFilters(guildId);
filtersTable.rows.add(filters.results).draw(false);
$("#filtersTable thead .table-select-all").prop("disabled", filters.results.length === 0);
}
catch (err) {
console.error(JSON.stringify(err, null, 4));
showToast("danger", `Error Loading Filters: HTTP ${err.status}`, err.responseJSON.message, 15000);
}
}
$(document).on("selectedServerChange", async function() {
const activeServer = getCurrentlyActiveServer();
await loadFilters(activeServer.guild_id);
});

View File

@ -4,6 +4,9 @@ $(document).ready(async function() {
$("#subscriptionsTab").click();
bindTableCheckboxes("#subTable", subTable, "#deleteSelectedSubscriptionsBtn");
bindTableCheckboxes("#filtersTable", filtersTable, "#deleteSelectedFiltersBtn");
await loadSavedGuilds();
await loadServerOptions();
});
@ -12,4 +15,53 @@ $('#serverTabs [data-bs-toggle="tab"]').on("show.bs.tab", function(event) {
const activeTab = $(event.target);
$(".tab-pane-buttons .tab-pane-buttons-item").hide();
$(`.tab-pane-buttons .tab-pane-buttons-item[data-tab="${activeTab.attr("id")}"]`).show();
});
});
$(document).on("selectedServerChange", function() {
$("#subscriptionsTab").click();
});
function bindTableCheckboxes(tableSelector, tableObject, deleteButtonSelector) {
// Select a row via checkbox
$(tableSelector).on("change", "tbody tr .table-select-row", function() {
var selected = $(this).prop("checked");
rowIndex = $(this).closest("tr").index();
row = tableObject.row(rowIndex);
if (selected) row.select();
else row.deselect();
determineSelectAllState(tableSelector, tableObject, deleteButtonSelector);
});
// Select all rows checkbox
$(tableSelector).on("change", "thead .table-select-all", function() {
var selected = $(this).prop("checked");
$(`${tableSelector} tbody tr`).each(function(rowIndex) {
var row = tableObject.row(rowIndex);
if (selected) row.select();
else row.deselect();
$(this).find('.table-select-row').prop("checked", selected);
});
determineSelectAllState(tableSelector, tableObject, deleteButtonSelector);
});
}
function determineSelectAllState(tableSelector, tableObject, deleteButtonSelector) {
var selectedRowsCount = tableObject.rows(".selected").data().toArray().length;
allRowsCount = tableObject.rows().data().toArray().length;
selectAllCheckbox = $(`${tableSelector} thead .table-select-all`);
checked = selectedRowsCount === allRowsCount;
indeterminate = !checked && selectedRowsCount > 0;
selectAllCheckbox.prop("checked", checked);
selectAllCheckbox.prop("indeterminate", indeterminate);
$(deleteButtonSelector).prop("disabled", !(checked || indeterminate) || !(allRowsCount > 0));
}

View File

@ -76,73 +76,23 @@ function initSubscriptionTable() {
});
}
// Determine and apply the state of the 'select all' checkbox
// indeterminate, checked or neither
function determineSelectAllState() {
var selectedRowsCount = subTable.rows(".selected").data().toArray().length;
allRowsCount = subTable.rows().data().toArray().length;
selectAllCheckbox = $("#subTable thead .table-select-all");
checked = selectedRowsCount === allRowsCount;
indeterminate = !checked && selectedRowsCount > 0;
selectAllCheckbox.prop("checked", checked);
selectAllCheckbox.prop("indeterminate", indeterminate);
$("#tableDeleteSelectedBtn").prop("disabled", !(checked || indeterminate) || !(allRowsCount > 0));
}
// Select a row via checkbox
$("#subTable").on("change", "tbody tr .table-select-row", function() {
var selected = $(this).prop("checked");
rowIndex = $(this).closest("tr").index();
row = subTable.row(rowIndex);
if (selected) row.select();
else row.deselect();
determineSelectAllState();
});
// Select all rows checkbox
$("#subTable").on("change", "thead .table-select-all", function() {
var selected = $(this).prop("checked");
$('#subTable tbody tr').each(function(rowIndex) {
var row = subTable.row(rowIndex)
if (selected) row.select();
else row.deselect();
$(this).find('.table-select-row').prop("checked", selected);
});
determineSelectAllState();
});
// Alert the number of selected rows
$("#tableButton").on("click", function() {
var selectedRows = subTable.rows({ selected: true }).data(false).toArray();
alert(selectedRows.length + " row(s) are selected");
});
// Open new subscription modal
$("#tableAddRowBtn").on("click", async function() {
$("#addSubscriptionBtn").on("click", async function() {
await showEditSubModal(-1);
});
async function showEditSubModal(guildId) {
async function showEditSubModal(subId) {
if (guildId === -1) {
if (subId === -1) {
$("#subFormModal input, #subFormModal textarea").val("");
$("#subFormModal .form-create").show();
$("#subFormModal .form-edit").hide();
$("#subChannels").val("").change();
$("#subFilters").val("").change();
}
else {
const subscription = subTable.row(function(idx, data, node) {
return data.id === guildId;
return data.id === subId;
}).data();
$("#subName").val(subscription.name);
@ -156,7 +106,7 @@ async function showEditSubModal(guildId) {
$("#subChannels").val(channels.results.map(channel => channel.channel_id)).change();
}
$("#subId").val(guildId);
$("#subId").val(subId);
$("#subFormModal").modal("show");
}
@ -224,7 +174,7 @@ async function saveSubChannel(channelId, subscriptionId) {
return response.id
}
function clearExistingTableRows() {
function clearExistingSubRows() {
$("#subTable thead .table-select-all").prop("checked", false).prop("indeterminate", false);
subTable.clear().draw(false);
}
@ -234,8 +184,8 @@ async function loadSubscriptions(guildId) {
if (!guildId)
return;
$("#tableDeleteSelectedBtn").prop("disabled", true);
clearExistingTableRows();
$("#deleteSelectedSubscriptionsBtn").prop("disabled", true);
clearExistingSubRows();
try {
const subscriptions = await getSubscriptions(guildId);
@ -252,9 +202,10 @@ $(document).on("selectedServerChange", async function() {
const activeServer = getCurrentlyActiveServer();
await loadSubscriptions(activeServer.guild_id);
await loadChannelOptions(activeServer.guild_id);
await loadFilterOptions(activeServer.guild_id);
})
$("#tableDeleteSelectedBtn").on("click", async function() {
$("#deleteSelectedSubscriptionsBtn").on("click", async function() {
// showToast("danger", "Not Implemented", "This feature isn't implemented");
var rows = subTable.rows(".selected").data();
@ -317,4 +268,40 @@ async function loadChannelOptions(guildId) {
// Re-enable the input
$("#subChannels").prop("disabled", false);
}
}
async function loadFilterOptions(guildId) {
// Disable input while options are loading
$("#subFilters").prop("disabled", true);
// Delete existing options
$("#subFilters option").each(function() {
if ($(this).val())
$(this).remove();
});
// Clear select2 input
$("#subFilters").val("").change();
try {
const filters = await getFilters(guildId);
console.log(JSON.stringify(filters));
filters.results.forEach(filter => {
$("#subFilters").append($("<option>", {
text: filter.name,
value: filter.id
}));
});
}
catch(error) {
console.error(error);
showToast("danger", "Error loading sub filters", error, 18000);
}
finally {
// Re-enable the input
$("#subFilters").prop("disabled", false);
}
}

View File

@ -0,0 +1,44 @@
<div id="filterFormModal" class="modal fade" data-bs-backdrop="static" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<form id="filterForm" class="mb-0" novalidate>
<div class="modal-header">
<h5 class="modal-title">
<span class="form-create">Add</span>
<span class="form-edit">Edit</span>
Filter
</h5>
</div>
<div class="modal-body">
<input type="hidden" id="filterId" name="filterId">
<div class="mb-3">
<label for="filterName" class="form-label">Name</label>
<input type="text" id="filterName" name="filterName" class="form-control" placeholder="Remove Common Words">
</div>
<div class="mb-3 form-switch">
<input type="checkbox" id="filterAdvancedMode" name="filterAdvancedMode" class="form-check-input" />
<label for="filterAdvancedMode" class="form-check-label">Advanced Filtering</label>
</div>
<div class="simple-filtering">
<label for="filterKeywords" class="form-label">Keywords</label>
<textarea id="filterKeywords" name="filterKeywords" class="form-control" placeholder="one,common,word,or,another"></textarea>
<div class="form-text">Commma separated words to block.</div>
</div>
<div class="advanced-filtering">
<label for="filterRegex" class="form-label">Regex</label>
<input type="text" id="filterRegex" name="filterRegex" class="form-control" placeholder="(?:^|(?<= ))(one|common|word|or|another)(?:(?= )|$)">
<div class="form-text">Block content matching the provided regex.</div>
</div>
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary">
<span class="form-create">Create</span>
<span class="form-edit">Save Changes</span>
</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
</div>
</form>
</div>
</div>
</div>

View File

@ -1,5 +1,5 @@
<div id="serverFormModal" class="modal fade" tabindex="-1">
<div id="serverFormModal" class="modal fade" data-bs-backdrop="static" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<form id="serverForm" class="mb-0" novalidate>
@ -22,7 +22,6 @@
<p class="mb-0 form-text">
You must be an administrator, or own the selected server.
</p>
<input type="text" class="form-control mt-2" placeholder="reference input">
</div>
<div class="modal-footer">
<button type="submit" class="btn btn-primary">Submit</button>

View File

@ -1,5 +1,5 @@
<div id="subFormModal" class="modal fade" tabindex="-1">
<div id="subFormModal" class="modal fade" data-bs-backdrop="static" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<form id="subForm" class="mb-0" novalidate>
@ -14,11 +14,11 @@
<input type="hidden" id="subId" name="subId">
<div class="mb-3">
<label for="subName" class="form-label">Name</label>
<input type="text" id="subName" name="subName" class="form-control" placeholder="BBC News · Top Stories">
<input type="text" id="subName" name="subName" class="form-control" placeholder="My News Feed">
</div>
<div class="mb-3">
<label for="subUrl" class="form-label">URL</label>
<input type="text" id="subUrl" name="subUrl" class="form-control" placeholder="http://feeds.bbci.co.uk/news/rss.xml">
<input type="url" id="subUrl" name="subUrl" class="form-control" placeholder="http://example.com/rss.xml">
<div class="form-text">Must point to a valid <a href="https://en.wikipedia.org/wiki/RSS" class="text-decoration-none">RSS</a> feed.</div>
</div>
<div class="mb-3">
@ -26,9 +26,14 @@
<select name="subChannels" id="subChannels" class="select-2" multiple data-dropdownparent="#subFormModal"></select>
<div class="form-text">Subscription content will be sent to these channels.</div>
</div>
<div class="mb-3">
<label for="subFilters" class="form-label">Filters</label>
<select name="subFilters" id="subFilters" class="select-2" multiple data-dropdownparent="#subFormModal"></select>
<div class="form-text">Filters to apply to this subscription's content.</div>
</div>
<div>
<label for="subExtraNotes" class="form-label">Extra Notes</label>
<textarea id="subExtraNotes" name="subExtraNotes" class="form-control" placeholder="Articles from the front page of the BBC."></textarea>
<textarea id="subExtraNotes" name="subExtraNotes" class="form-control" placeholder=""></textarea>
</div>
</div>
<div class="modal-footer">