feat: filter selection and table render for feed view
All checks were successful
Build / build (push) Successful in 48s
All checks were successful
Build / build (push) Successful in 48s
This commit is contained in:
parent
ff992fefa7
commit
e935d801e6
@ -136,7 +136,7 @@
|
||||
@apply
|
||||
z-80
|
||||
min-w-fit
|
||||
min-h-[150px]
|
||||
min-h-fit
|
||||
max-h-72
|
||||
p-1.5
|
||||
space-y-0.5
|
||||
@ -184,6 +184,38 @@
|
||||
@apply text-xs text-gray-500 dark:text-neutral-500;
|
||||
}
|
||||
|
||||
.cj-tag-select-search {
|
||||
@apply
|
||||
block
|
||||
w-full
|
||||
rounded-lg
|
||||
py-1.5
|
||||
sm:py-2
|
||||
px-3
|
||||
sm:text-sm
|
||||
|
||||
bg-white
|
||||
text-gray-800
|
||||
border-gray-200
|
||||
dark:text-neutral-400
|
||||
dark:bg-neutral-900
|
||||
dark:border-neutral-700;
|
||||
}
|
||||
|
||||
.cj-tag-select-search-wrapper {
|
||||
@apply
|
||||
p-2
|
||||
-mx-1
|
||||
-mt-1
|
||||
sticky
|
||||
top-0
|
||||
bg-none
|
||||
}
|
||||
|
||||
.cj-tag-select-search-no-results {
|
||||
@apply block p-4;
|
||||
}
|
||||
|
||||
/* Normal Select */
|
||||
|
||||
.cj-select-toggle {
|
||||
|
@ -109,16 +109,15 @@ const columnDefs: ConfigColumnDefs[] = [
|
||||
if (data.length === 1) {
|
||||
return wrapper.get(0);
|
||||
}
|
||||
else if (data.length <= 2) {
|
||||
const secondChannelName = "# " + channels.find(c => c.id === data[1].channel_id).name;
|
||||
|
||||
data.shift();
|
||||
|
||||
if (data.length <= 1) {
|
||||
const secondChannelName = "# " + channels.find(c => c.id === data[0].channel_id).name;
|
||||
wrapper.append(tag.clone().text(secondChannelName));
|
||||
data.shift();
|
||||
return wrapper.get(0);
|
||||
}
|
||||
|
||||
// drop the first element to exclude it from the dropdown
|
||||
data.shift();
|
||||
|
||||
const dropdown = $("<div>").addClass("hs-dropdown inline-block");
|
||||
const dropdownBtn = $("<button>").attr("id", `channelDrop-${row.id}`).attr("type", "button").addClass("cursor-pointer inline-flex items-center gap-1 py-1 px-2.5 border text-xs rounded-md bg-white dark:bg-neutral-800 border-gray-200 dark:border-neutral-700 text-gray-800 dark:text-neutral-200");
|
||||
const dropdownMenu = $("<div>").addClass("hs-dropdown-menu hidden opacity-0 hs-dropdown-open:opacity-100 transition-[opacity,margin] overflow-hidden z-10 w-fit max-w-64 border p-2 rounded-md bg-gray-200 dark:bg-neutral-700 border-gray-300 dark:border-neutral-600");
|
||||
@ -141,13 +140,40 @@ const columnDefs: ConfigColumnDefs[] = [
|
||||
orderable: false,
|
||||
searchable: false,
|
||||
className: "size-px whitespace-nowrap",
|
||||
render: (data: prisma.Filter[]) => { return `
|
||||
<div class="px-6 py-4">
|
||||
<span class="cj-table-text">
|
||||
${data}
|
||||
</span>
|
||||
</div>
|
||||
`}
|
||||
render: (data: prisma.Filter[], type: string, row: prisma.Feed) => {
|
||||
if (type !== "display") return data;
|
||||
if (!data.length) return "";
|
||||
|
||||
const wrapper = $("<div>").addClass("flex flex-nowrap gap-1 px-6 py-4");
|
||||
const tag = $("<span>").addClass("inline-flex items-center whitespace-nowrap gap-1 py-1 px-2.5 border text-xs rounded-md bg-white dark:bg-neutral-800 border-gray-200 dark:border-neutral-700 text-gray-800 dark:text-neutral-200");
|
||||
|
||||
wrapper.append(tag.clone().text(data[0].name));
|
||||
|
||||
if (data.length === 1) {
|
||||
return wrapper.get(0);
|
||||
}
|
||||
|
||||
data.shift();
|
||||
|
||||
if (data.length <= 1) {
|
||||
wrapper.append(tag.clone().text(data[0].name));
|
||||
return wrapper.get(0);
|
||||
}
|
||||
|
||||
const dropdown = $("<div>").addClass("hs-dropdown inline-block");
|
||||
const dropdownBtn = $("<button>").attr("id", `channelDrop-${row.id}`).attr("type", "button").addClass("cursor-pointer inline-flex items-center gap-1 py-1 px-2.5 border text-xs rounded-md bg-white dark:bg-neutral-800 border-gray-200 dark:border-neutral-700 text-gray-800 dark:text-neutral-200");
|
||||
const dropdownMenu = $("<div>").addClass("hs-dropdown-menu hidden opacity-0 hs-dropdown-open:opacity-100 transition-[opacity,margin] overflow-hidden z-10 w-fit max-w-64 border p-2 rounded-md bg-gray-200 dark:bg-neutral-700 border-gray-300 dark:border-neutral-600");
|
||||
|
||||
dropdown.append(dropdownBtn.text(`+${data.length}`));
|
||||
|
||||
data.forEach(filter => {
|
||||
dropdownMenu.append(tag.clone().text(filter.name));
|
||||
});
|
||||
|
||||
dropdown.append(dropdownMenu);
|
||||
wrapper.append(dropdown);
|
||||
return wrapper.get(0);
|
||||
}
|
||||
},
|
||||
{
|
||||
target: 5,
|
||||
@ -406,13 +432,14 @@ const channelsSelectOptions: ISelectOptions = {
|
||||
<div data-icon></div>
|
||||
<div>
|
||||
<div data-title></div>
|
||||
<div data-description></div></div>
|
||||
<div class="ms-auto">
|
||||
<span class="hidden hs-selected:block">
|
||||
<svg class="shrink-0 size-4 text-blue-600" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><path d="M12.736 3.97a.733.733 0 0 1 1.047 0c.286.289.29.756.01 1.05L7.88 12.01a.733.733 0 0 1-1.065.02L3.217 8.384a.757.757 0 0 1 0-1.06.733.733 0 0 1 1.047 0l3.052 3.093 5.4-6.425a.247.247 0 0 1 .02-.022Z"/></svg>
|
||||
</span>
|
||||
</div>
|
||||
<div data-description></div>
|
||||
</div>
|
||||
<div class="ms-auto">
|
||||
<span class="hidden hs-selected:block">
|
||||
<svg class="shrink-0 size-4 text-blue-600" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><path d="M12.736 3.97a.733.733 0 0 1 1.047 0c.286.289.29.756.01 1.05L7.88 12.01a.733.733 0 0 1-1.065.02L3.217 8.384a.757.757 0 0 1 0-1.06.733.733 0 0 1 1.047 0l3.052 3.093 5.4-6.425a.247.247 0 0 1 .02-.022Z"/></svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
tagsInputId: "formChannelsInput",
|
||||
wrapperClasses: "cj-tag-select-wrapper",
|
||||
@ -422,14 +449,79 @@ const channelsSelectOptions: ISelectOptions = {
|
||||
dropdownScope: "window",
|
||||
dropdownSpace: 10,
|
||||
dropdownPlacement: "bottom",
|
||||
dropdownVerticalFixedPlacement: null
|
||||
dropdownVerticalFixedPlacement: null,
|
||||
|
||||
hasSearch: false,
|
||||
searchNoResultClasses: "cj-tag-select-search-no-results",
|
||||
};
|
||||
|
||||
const channelSelect: HSSelect = new HSSelect(
|
||||
const channelSelect = new HSSelect(
|
||||
$("#formChannels").get(0) as HTMLElement,
|
||||
channelsSelectOptions
|
||||
);
|
||||
|
||||
const filterSelectOptions: ISelectOptions = {
|
||||
placeholder: "Select option....",
|
||||
mode: "tags",
|
||||
|
||||
tagsItemTemplate: `
|
||||
<div class="flex flex-nowrap items-center relative z-10 bg-white border border-gray-200 rounded-lg p-1 m-1 dark:bg-neutral-900 dark:border-neutral-700 ">
|
||||
<div class="size-6 flex justify-center items-center">
|
||||
<svg class="shrink-0 size-[16px]" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 3c2.755 0 5.455.232 8.083.678.533.09.917.556.917 1.096v1.044a2.25 2.25 0 0 1-.659 1.591l-5.432 5.432a2.25 2.25 0 0 0-.659 1.591v2.927a2.25 2.25 0 0 1-1.244 2.013L9.75 21v-6.568a2.25 2.25 0 0 0-.659-1.591L3.659 7.409A2.25 2.25 0 0 1 3 5.818V4.774c0-.54.384-1.006.917-1.096A48.32 48.32 0 0 1 12 3Z" /></svg>
|
||||
</div>
|
||||
<div class="whitespace-nowrap text-gray-800 dark:text-neutral-200" data-title></div>
|
||||
<div class="inline-flex shrink-0 justify-center items-center size-5 ms-2 rounded-lg text-gray-800 bg-gray-200 hover:bg-gray-300 focus:outline-hidden focus:ring-2 focus:ring-gray-400 text-sm dark:bg-neutral-700/50 dark:hover:bg-neutral-700 dark:text-neutral-400 cursor-pointer" data-remove>
|
||||
<svg class="shrink-0 size-3" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
optionTemplate: `
|
||||
<div class="cj-tag-select-option">
|
||||
<div data-icon>
|
||||
<svg class="shrink-0 size-[18px]" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M12 3c2.755 0 5.455.232 8.083.678.533.09.917.556.917 1.096v1.044a2.25 2.25 0 0 1-.659 1.591l-5.432 5.432a2.25 2.25 0 0 0-.659 1.591v2.927a2.25 2.25 0 0 1-1.244 2.013L9.75 21v-6.568a2.25 2.25 0 0 0-.659-1.591L3.659 7.409A2.25 2.25 0 0 1 3 5.818V4.774c0-.54.384-1.006.917-1.096A48.32 48.32 0 0 1 12 3Z" /></svg>
|
||||
</div>
|
||||
<div>
|
||||
<div data-title></div>
|
||||
<div data-description></div>
|
||||
</div>
|
||||
<div class="ms-auto">
|
||||
<span class="hidden hs-selected:block">
|
||||
<svg class="shrink-0 size-4 text-blue-600" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><path d="M12.736 3.97a.733.733 0 0 1 1.047 0c.286.289.29.756.01 1.05L7.88 12.01a.733.733 0 0 1-1.065.02L3.217 8.384a.757.757 0 0 1 0-1.06.733.733 0 0 1 1.047 0l3.052 3.093 5.4-6.425a.247.247 0 0 1 .02-.022Z"/></svg>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
tagsInputId: "formFiltersInput",
|
||||
wrapperClasses: "cj-tag-select-wrapper",
|
||||
dropdownClasses: "cj-tag-select-dropdown w-full",
|
||||
tagsInputClasses: "cj-tag-select-input",
|
||||
|
||||
dropdownScope: "window",
|
||||
dropdownSpace: 10,
|
||||
dropdownPlacement: "bottom",
|
||||
dropdownVerticalFixedPlacement: null,
|
||||
|
||||
// API
|
||||
apiUrl: `/guild/${guildId}/filters/api/select`,
|
||||
apiQuery: "limit=15",
|
||||
apiFieldsMap: {
|
||||
id: "id",
|
||||
val: "id",
|
||||
title: "name",
|
||||
description: "value",
|
||||
name: "title"
|
||||
},
|
||||
apiSearchQueryKey: "search",
|
||||
hasSearch: false,
|
||||
searchNoResultClasses: "cj-tag-select-search-no-results",
|
||||
|
||||
};
|
||||
|
||||
const filterSelect = new HSSelect(
|
||||
$("#formFilters").get(0),
|
||||
filterSelectOptions
|
||||
);
|
||||
|
||||
$("#editForm").on("submit", async event => {
|
||||
event.preventDefault();
|
||||
|
||||
|
@ -197,9 +197,18 @@
|
||||
</option>
|
||||
<% }); %>
|
||||
</select>
|
||||
<p class="text-input-help">
|
||||
The recipients of content from this feed.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
|
||||
<div class="relative">
|
||||
<label for="formFilters" class="text-input-label">Filters</label>
|
||||
<select name="filters" id="formFilters" class="--prevent-on-load-init" multiple>
|
||||
<option value="">Choose</option>
|
||||
</select>
|
||||
<p class="text-input-help">
|
||||
Filter out unwanted content from this feed.
|
||||
</p>
|
||||
</div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
|
@ -2,6 +2,7 @@ import { Request, Response } from "express";
|
||||
import prisma, { Prisma } from "@server/prisma";
|
||||
import { datatableRequest } from "@server/controllers/guild/api/dt.module";
|
||||
|
||||
// TODO: this doesn't account for guild ID or permissions
|
||||
export const get = async (request: Request, response: Response) => {
|
||||
if (!request.query.id) {
|
||||
response.status(400).json({ error: "missing 'id' query" });
|
||||
@ -118,4 +119,27 @@ export const datatable = async (request: Request, response: Response) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default { get, post, patch, del, datatable };
|
||||
export const select = async (request: Request, response: Response) => {
|
||||
const guildId = request.params.guildId;
|
||||
const { search } = request.query;
|
||||
|
||||
const data = await prisma.filter.findMany({
|
||||
where: {
|
||||
guild_id: guildId,
|
||||
name: { contains: `${search}` }
|
||||
}
|
||||
});
|
||||
|
||||
// Preline Bug: https://github.com/htmlstreamofficial/preline/issues/567
|
||||
// The returned data must have a "title" key, otherwise the advanced
|
||||
// select component with 'tags' mode will have no title, regardless of
|
||||
// mapping.
|
||||
const modifiedResults = data.map(filter => ({
|
||||
...filter,
|
||||
title: filter.name
|
||||
}));
|
||||
|
||||
response.json(modifiedResults);
|
||||
};
|
||||
|
||||
export default { get, post, patch, del, datatable, select };
|
@ -32,6 +32,7 @@ router.patch("/:guildId/feeds/api", feedApiController.patch);
|
||||
router.delete("/:guildId/feeds/api", feedApiController.del);
|
||||
|
||||
router.post("/:guildId/filters/api/datatable", filterApiController.datatable);
|
||||
router.get("/:guildId/filters/api/select", filterApiController.select);
|
||||
router.get("/:guildId/filters/api", filterApiController.get);
|
||||
router.post("/:guildId/filters/api", filterApiController.post);
|
||||
router.patch("/:guildId/filters/api", filterApiController.patch);
|
||||
|
Loading…
x
Reference in New Issue
Block a user