Implemented filters in frontend
This commit is contained in:
parent
03c73e24de
commit
9d6fe850ca
@ -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);
|
||||
});
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
44
apps/templates/home/includes/filtermodal.html
Normal file
44
apps/templates/home/includes/filtermodal.html
Normal 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>
|
@ -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>
|
||||
|
@ -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">
|
||||
|
Loading…
x
Reference in New Issue
Block a user