feat: select message style on feed
Some checks failed
Build / build (push) Failing after 52s

This commit is contained in:
Corban-Lee Jones 2025-05-06 16:19:25 +01:00
parent 0dd928b8f4
commit d5af04c317
6 changed files with 119 additions and 28 deletions

View File

@ -243,7 +243,7 @@
@apply
z-80
w-full
min-h-[100px]
min-h-fit
max-h-72
p-1.5
space-y-0.5

View File

@ -177,17 +177,23 @@ const columnDefs: ConfigColumnDefs[] = [
},
{
target: 5,
data: "message_style",
orderable: false, // both should be true, but message_style doesnt exist yet
data: null, // "message_style_id"
orderable: false,
searchable: false,
className: "size-px whitespace-nowrap",
render: (data: string) => { return `
<div class="px-6 py-4">
<span class="cj-table-text">
${data}
</span>
</div>
`}
render: (_data: unknown, type: string, row: any) => {
if (!row.message_style || type !== "display") return null;
const wrapper = $("<div>").addClass("flex px-6 py-4");
const badge = $("<span>").addClass("inline-flex items-center whitespace-nowrap border rounded-md bg-white dark:bg-neutral-800 border-gray-200 dark:border-neutral-700 overflow-hidden");
const colour = $("<span>").addClass("size-6 shrink-0").css("background-color", row.message_style.colour);
const label = $("<span>").addClass("py-1 px-2.5 text-xs text-gray-800 dark:text-neutral-200");
label.text(row.message_style.name);
badge.append(colour).append(label);
wrapper.append(badge);
return wrapper.get(0);
}
},
{
target: 6,
@ -381,6 +387,7 @@ const clearEditModalData = () => {
$("#formActive").prop("checked", true);
channelSelect.setValue([]);
filterSelect.setValue([]);
styleSelect.setValue("");
};
const loadEditModalData = async (id: number) => {
@ -397,6 +404,7 @@ const loadEditModalData = async (id: number) => {
channelSelect.setValue(feed.channels.map(channel => channel.channel_id));
filterSelect.setValue(feed.filters.map(filter => `${filter.id}`));
styleSelect.setValue(`${feed.message_style_id}`);
}
const openEditModal = async (id: number | undefined) => {
@ -525,6 +533,45 @@ const filterSelect = new HSSelect(
filterSelectOptions
);
const styleSelectOptions: ISelectOptions = {
placeholder: "Select option...",
toggleTag: '<button type="button" aria-expanded="false"><span data-title></span></button>',
optionTemplate: `
<div class="flex justify-between items-center w-full">
<span data-title></span>
<span class="hidden hs-selected:block">
<svg class="shrink-0 size-3.5 text-blue-600 dark:text-blue-500" xmlns="http:.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"><polyline points="20 6 9 17 4 12"/></svg>
</span>
</div>`,
toggleClasses: "cj-select-toggle select-input",
optionClasses: "cj-select-option",
dropdownClasses: "cj-select-dropdown",
wrapperClasses: "peer",
dropdownSpace: 10,
dropdownScope: "parent",
dropdownPlacement: "top",
dropdownVerticalFixedPlacement: null,
apiUrl: `/guild/${guildId}/styles/api/select`,
// apiQuery: "limit=15",
apiFieldsMap: {
id: "id",
val: "id",
title: "name",
description: "value",
name: "title"
},
apiSearchQueryKey: "search",
hasSearch: false,
optionAllowEmptyOption: true
};
const styleSelect = new HSSelect(
$("#formMessageStyle").get(0),
styleSelectOptions
);
$("#editForm").on("submit", async event => {
event.preventDefault();

View File

@ -51,7 +51,7 @@
<span class="sr-only">Checkbox</span>
</label>
</th>
<th scope="col" data-dt-column="name" class="cj-table-header">
<th scope="col" class="cj-table-header">
<div class="cj-table-header-content cursor-pointer">
<span>Name</span>
<svg 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">
@ -59,7 +59,7 @@
</svg>
</div>
</th>
<th scope="col" data-dt-column="url" class="cj-table-header">
<th scope="col" class="cj-table-header">
<div class="cj-table-header-content cursor-pointer">
<span>URL</span>
<svg 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">
@ -67,25 +67,22 @@
</svg>
</div>
</th>
<th scope="col" data-dt-column="channels" class="cj-table-header --exclude-from-ordering">
<th scope="col" class="cj-table-header --exclude-from-ordering">
<div class="cj-table-header-content">
<span>Channels</span>
</div>
</th>
<th scope="col" data-dt-column="filters" class="cj-table-header --exclude-from-ordering">
<th scope="col" class="cj-table-header --exclude-from-ordering">
<div class="cj-table-header-content">
<span>Filters</span>
</div>
</th>
<th scope="col" data-dt-column="style" class="cj-table-header">
<th scope="col" class="cj-table-header --exclude-from-ordering">
<div class="cj-table-header-content cursor-pointer">
<span>Style</span>
<svg 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="m7 15 5 5 5-5"></path><path d="m7 9 5-5 5 5"></path>
</svg>
</div>
</th>
<th scope="col" data-dt-column="created_at" class="cj-table-header">
<th scope="col" class="cj-table-header">
<div class="cj-table-header-content cursor-pointer">
<span>Created at</span>
<svg 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">
@ -93,7 +90,7 @@
</svg>
</div>
</th>
<th scope="col" data-dt-column="active" class="cj-table-header">
<th scope="col" class="cj-table-header">
<div class="cj-table-header-content cursor-pointer">
<span>Status</span>
<svg 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">
@ -210,8 +207,18 @@
Filter out unwanted content from this feed.
</p>
</div>
<div></div>
<div></div>
<div class="relative">
<label for="formMessageStyle" class="text-input-label">Message Style</label>
<select name="message_style" id="formMessageStyle" class="--prevent-on-load-init">
<option value="">None</option>
</select>
<p class="text-input-help">
placeholder.
</p>
</div>
<div>
placeholder for publish threshold
</div>
<div>
<label for="formActive" class="flex gap-4">
<input type="checkbox" id="formActive" name="active" class="form-radio relative w-[3.25rem] h-7 p-px bg-gray-100 border-transparent text-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:ring-blue-600 disabled:opacity-50 disabled:pointer-events-none checked:bg-none checked:text-blue-600 checked:border-blue-600 focus:checked:border-blue-600 dark:bg-neutral-800 dark:border-neutral-700 dark:checked:bg-blue-500 dark:checked:border-blue-500 dark:focus:ring-offset-gray-600 before:inline-block before:size-6 before:bg-white checked:before:bg-blue-200 before:translate-x-0 checked:before:translate-x-full before:rounded-full before:shadow-sm before:transform before:ring-0 before:transition before:ease-in-out before:duration-200 dark:before:bg-neutral-400 dark:checked:before:bg-blue-200">

View File

@ -1,6 +1,7 @@
import { Request, Response } from "express";
import prisma, { Prisma } from "@server/prisma";
import { datatableRequest } from "@server/controllers/guild/api/dt.module";
import { logger } from "@server/../log";
export const get = async (request: Request, response: Response) => {
if (!request.query.id) {
@ -23,7 +24,9 @@ export const get = async (request: Request, response: Response) => {
export const post = async (request: Request, response: Response) => {
const guildId = request.params.guildId;
const { name, url, active, channels, filters } = request.body;
const { name, url, active, channels, filters, message_style } = request.body;
logger.debug("Post Feed", request.body);
// channels comes through as either String[] or String
let formattedChannels = undefined;
@ -50,7 +53,8 @@ export const post = async (request: Request, response: Response) => {
guild_id: guildId,
active: active === "on",
channels: { create: formattedChannels },
filters: { connect: formattedFilters }
filters: { connect: formattedFilters },
message_style_id: message_style === "" ? null : Number(message_style)
}
});
}
@ -67,7 +71,9 @@ export const post = async (request: Request, response: Response) => {
export const patch = async (request: Request, response: Response) => {
const guildId = request.params.guildId;
const { id, name, url, active, channels, filters } = request.body;
const { id, name, url, active, channels, filters, message_style } = request.body;
logger.info("Patch Feed", request.body);
// channels comes through as either String[] or String
let formattedChannels = undefined;
@ -101,7 +107,11 @@ export const patch = async (request: Request, response: Response) => {
filters: {
set: [],
connect: formattedFilters
}
},
message_style_id:
message_style === ""
? null
: Number(message_style)
}
});
}
@ -150,7 +160,7 @@ export const datatable = async (request: Request, response: Response) => {
response,
prisma.feed,
[{ updated_at: "desc" }, { id: "asc" }],
{ channels: true, filters: true },
{ channels: true, filters: true, message_style: true },
{ guild_id: request.params.guildId } // TODO: verify authenticated user can access this guild
);
};

View File

@ -1,6 +1,7 @@
import { Request, Response } from "express";
import prisma, { Prisma } from "@server/prisma";
import { datatableRequest } from "@server/controllers/guild/api/dt.module";
import { logger } from "@server/../log";
export const get = async (request: Request, response: Response) => {
if (!request.query.id) {
@ -34,6 +35,8 @@ export const post = async (request: Request, response: Response) => {
description_mutator
} = request.body;
logger.debug("Style Post", request.body);
let style;
try {
@ -147,4 +150,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.messageStyle.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 };

View File

@ -40,6 +40,7 @@ router.patch("/:guildId/filters/api", filterApiController.patch);
router.delete("/:guildId/filters/api", filterApiController.del);
router.post("/:guildId/styles/api/datatable", styleApiController.datatable);
router.get("/:guildId/styles/api/select", styleApiController.select);
router.get("/:guildId/styles/api", styleApiController.get);
router.post("/:guildId/styles/api", styleApiController.post);
router.patch("/:guildId/styles/api", styleApiController.patch);