working on proper client packing
Some checks failed
Build / build (push) Failing after 57s

This commit is contained in:
Corban-Lee Jones 2025-05-08 22:14:55 +01:00
parent d6382347d0
commit 9423a0f1ce
7 changed files with 662 additions and 26 deletions

View File

@ -27,6 +27,7 @@
"devDependencies": {
"@tailwindcss/cli": "^4.1.4",
"@tailwindcss/postcss": "^4.1.4",
"@types/dropzone": "^5.7.9",
"@types/ejs": "^3.1.5",
"@types/express": "^5.0.1",
"@types/jquery": "^3.5.32",

View File

@ -1,5 +1,6 @@
@import "tailwindcss";
@import "./preline";
@import "../../../../node_modules/preline/src/plugins/datepicker/styles.css";
@config "../../../../tailwind.config.js";
@ -370,4 +371,8 @@
dark:focus:outline-hidden
dark:focus:ring-1
dark:focus:ring-neutral-600;
}
}
/* Vanilla Calendar z-index */
.vc { z-index: 80; }

View File

@ -9,6 +9,31 @@ import { formatTimestamp, verifyChannels } from "../../../src/ts/main";
import prisma from "../../../../../generated/prisma";
import HSDatepicker, { ICustomDatepickerOptions } from "@preline/datepicker";
// import "preline";
// import _ from "lodash";
// import noUiSlider from "nouislider";
// import "datatables.net";
// import "dropzone/dist/dropzone-min.js";
// import * as VanillaCalendarPro from "vanilla-calendar-pro";
// // Preline requirements
// window._ = _;
// window.$ = $;
// window.jQuery = $;
// window.DataTable = $.fn.dataTable;
// window.noUiSlider = noUiSlider;
// window.VanillaCalendarPro = VanillaCalendarPro;
// window.HSStaticMethods.autoInit();
// // document.addEventListener("DOMContentLoaded", () => {
// // if (window.HSStaticMethods && typeof window.HSStaticMethods.autoInit === "function") {
// // } else {
// // console.warn("Preline is not available on window.HSStaticMethods.");
// // }
// // });
declare let guildId: string;
declare let channels: Array<any>;
@ -582,11 +607,6 @@ const publishedThresholdOptions: ICustomDatepickerOptions = {
}
};
// const publishedThresholdInput = new HSDatepicker(
// $("#formPublishedThreshold").get(0),
// publishedThresholdOptions
// );
$("#editForm").on("submit", async event => {
event.preventDefault();

View File

@ -0,0 +1,593 @@
import { formatTimestamp, verifyChannels } from "../main";
import HSDropdown from "preline/dist/dropdown";
import HSDataTable, { IDataTableOptions } from "preline/dist/datatable";
import HSSelect, { ISelectOptions } from "preline/dist/select";
import HSOverlay, { IOverlayOptions } from "preline/dist/overlay";
import HSDatepicker, { ICustomDatepickerOptions } from "preline/dist/datepicker";
import { AjaxSettings, ConfigColumnDefs } from "datatables.net-dt";
import prisma from "../../../../../generated/prisma";
declare let guildId: string;
declare let channels: Array<any>;
// #region DataTable
const emptyTableHtml: string = `
<div class="max-w-md w-full min-h-[400px] flex flex-col justify-center mx-auto px-6 py-4">
<div class="flex justify-center items-center size-[46px] bg-gray-100 rounded-lg dark:bg-neutral-800">
<svg class="shrink-0 size-6 text-gray-600 dark:text-neutral-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"><path d="M4 11a9 9 0 0 1 9 9"></path><path d="M4 4a16 16 0 0 1 16 16"></path><circle cx="5" cy="19" r="1"></circle></svg>
</div>
<h2 class="mt-5 font-semibold text-gray-800 dark:text-white">
No results found
</h2>
<p class="mt-2 text-sm text-gray-600 dark:text-neutral-400">
Refine your search or create a new feed.
Alternatively, use a template to deploy a ready-made feed.
</p>
<div class="mt-5 flex flex-col sm:flex-row gap-2">
<button type="button" class="open-edit-modal-js py-2 px-3 inline-flex justify-center items-center gap-x-2 text-sm font-medium rounded-lg border border-transparent bg-blue-600 text-white hover:bg-blue-700 focus:outline-hidden focus:bg-blue-700 disabled:opacity-50 disabled:pointer-events-none">
<svg class="shrink-0 size-4" 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="M5 12h14"/><path d="M12 5v14"/></svg>
Create a feed
</button>
<button type="button" onclick="alert('not implemented');" class="py-2 px-3 inline-flex justify-center items-center gap-x-2 text-sm font-medium rounded-lg border border-gray-200 bg-white text-gray-800 shadow-xs hover:bg-gray-50 focus:outline-hidden focus:bg-gray-50 disabled:opacity-50 disabled:pointer-events-none dark:bg-neutral-800 dark:border-neutral-700 dark:text-white dark:hover:bg-neutral-700 dark:focus:bg-neutral-700">
Use a Template
</button>
</div>
</div>
`;
const columnDefs: ConfigColumnDefs[] = [
// Select checkbox column
{
target: 0,
className: "size-px whitespace-nowrap",
render: (_data: unknown, _type: unknown, row: prisma.Feed) => { return `
<div class="ps-6 py-4">
<label class="rowSelect${row.id}-js" class="flex">
<input type="checkbox" id="rowSelect${row.id}-js" class="cj-table-checkbox" data-hs-datatable-row-selecting-individual="">
<span class="sr-only">Select Row</span>
</label>
</div>
`}
},
{
target: 1,
data: "name",
orderable: true,
searchable: true,
className: "size-px whitespace-nowrap",
render: (data: string, _type: string, row: prisma.Feed) => { return `
<span class="cj-table-link open-edit-modal-js max-w-[250px] truncate" data-id="${row.id}">
${data}
</span>
`}
},
{
target: 2,
data: "url",
orderable: true,
searchable: true,
className: "size-px whitespace-nowrap",
render: (data: string) => { return `
<a href="${data}" class="cj-table-link max-w-[450px] truncate">
${data}
</a>
`}
},
{
target: 3,
data: "channels",
className: "size-px",
render: (data: prisma.Channel[], 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");
if (!verifyChannels(data, channels)) {
wrapper.text("invalid channels").addClass("whitespace-nowrap");
return wrapper.get(0);
}
const firstChannelName = "# " + channels.find(c => c.id === data[0].channel_id).name;
wrapper.append(tag.clone().text(firstChannelName));
// No need to run the dropdown code if there's no more to show
if (data.length === 1) {
return wrapper.get(0);
}
data.shift();
if (data.length <= 1) {
const secondChannelName = "# " + channels.find(c => c.id === data[0].channel_id).name;
wrapper.append(tag.clone().text(secondChannelName));
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(channel => {
const channelName = "# " + channels.find(c => c.id === channel.channel_id).name;
dropdownMenu.append(tag.clone().text(channelName));
});
dropdown.append(dropdownMenu);
wrapper.append(dropdown);
return wrapper.get(0);
}
},
{
target: 4,
data: "filters",
className: "size-px whitespace-nowrap",
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,
data: null, // "message_style_id"
className: "size-px whitespace-nowrap",
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,
data: "created_at",
orderable: true,
className: "size-px whitespace-nowrap",
render: (data: string) => { return `
<div class="px-6 py-4">
<span class="cj-table-text">
${formatTimestamp(data)}
</span>
</div>
`}
},
{
target: 7,
data: "active",
orderable: true,
className: "size-px whitespace-nowrap",
render: (data: boolean) => {
const wrapper = $("<div>").addClass("px-6 py-4");
const badge = $("<span>").addClass("py-1 px-1.5 inline-flex items-center gap-x-1 text-xs font-medium rounded-full");
const label = $("<span>");
if (data) {
badge.addClass("bg-teal-100 text-teal-800 dark:bg-teal-500/10 dark:text-teal-500");
badge.append($('<svg class="size-2.5" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zm-3.97-3.03a.75.75 0 0 0-1.08.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-.01-1.05z"/></svg>'))
.append(label.text("Active"));
} else {
badge.addClass("bg-red-100 text-red-800 dark:bg-red-500/10 dark:text-red-500");
badge.append($('<svg class="size-2.5" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"><path d="M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0zM5.354 4.646a.5.5 0 1 0-.708.708L7.293 8l-2.647 2.646a.5.5 0 0 0 .708.708L8 8.707l2.646 2.647a.5.5 0 0 0 .708-.708L8.707 8l2.647-2.646a.5.5 0 0 0-.708-.708L8 7.293 5.354 4.646z"/></svg>'))
.append(label.text("Inactive"));
}
wrapper.append(badge);
return wrapper.get(0);
}
}
];
const ajaxSettings: AjaxSettings = {
url: `/guild/${guildId}/feeds/api/datatable`,
type: "POST",
contentType: "application/json",
dataSrc: "data",
data: (data: unknown) => {
if (data === undefined) return;
// TODO,
return JSON.stringify(data);
}
};
const tableOptions: IDataTableOptions = {
ajax: ajaxSettings,
serverSide: true,
processing: true,
select: {
style: "multi",
selector: "td:first-child input[type='checkbox']"
},
columnDefs: columnDefs,
pagingOptions: { pageBtnClasses: "hidden" },
rowSelectingOptions: { selectAllSelector: "#selectAllBox" },
language: {
zeroRecords: emptyTableHtml,
emptyTable: emptyTableHtml,
loadingRecords: "Placeholder loading message..."
},
drawCallback: () => HSDropdown.autoInit(),
rowCallback: (row: HTMLTableRowElement) => {
$(row).addClass("bg-white dark:bg-neutral-900");
}
};
let table: HSDataTable;
window.addEventListener("preline:ready", () => {
const tableEl = $("#table").get(0);
if (!HSDataTable.getInstance(tableEl, true)) {
table = new HSDataTable(tableEl, tableOptions);
}
});
// #endregion
// #region Table Paging Select
const pageSelectOptions: ISelectOptions = {
toggleTag: '<button type="button" aria-expanded="false"></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-table-paging-select-toggle",
optionClasses: "cj-table-paging-select-option",
dropdownClasses: "cj-table-paging-select-dropdown",
dropdownSpace: 10,
dropdownScope: "parent",
dropdownPlacement: "top",
dropdownVerticalFixedPlacement: null
};
window.addEventListener("preline:ready", () => {
const selectEl = $("#selectPageSize-js").get(0);
if (!HSSelect.getInstance(selectEl, true)) {
new HSSelect(selectEl, pageSelectOptions);
}
});
// #endregion
// #region Edit Modal
const closeEditModal = () => { editModal.close() };
const openEditModal = async (id: number | undefined) => {
$("#editForm").removeClass("submitted");
editModal.open();
id === undefined
? clearEditModalData()
: loadEditModalData(id);
};
$(document).on("click", ".open-edit-modal-js", async event => {
await openEditModal($(event.target).data("id"));
});
const editModalOptions: IOverlayOptions = {};
let editModal: HSOverlay;
window.addEventListener("preline:ready", () => {
const modalEl = $("#editModal").get(0);
if (!HSOverlay.getInstance(modalEl, true)) {
editModal = new HSOverlay(modalEl, editModalOptions);
}
});
// #endregion
// #region Edit Form
interface ExpandedFeed extends prisma.Feed {
channels: prisma.Channel[],
filters: prisma.Feed[]
}
const clearEditModalData = () => {
$(editModal.el).removeData("id");
$("#formName").val("");
$("#formUrl").val("");
$("#formActive").prop("checked", true);
channelSelect.setValue([]);
filterSelect.setValue([]);
styleSelect.setValue("");
};
const loadEditModalData = async (id: number) => {
const feed: ExpandedFeed = await $.ajax({
url: `/guild/${guildId}/feeds/api?id=${id}`,
method: "get"
});
$(editModal.el).data("id", feed.id);
$("#formName").val(feed.name);
$("#formUrl").val(feed.url);
$("#formActive").prop("checked", feed.active);
channelSelect.setValue(feed.channels.map(channel => channel.channel_id));
filterSelect.setValue(feed.filters.map(filter => `${filter.id}`));
styleSelect.setValue(`${feed.message_style_id}`);
}
$("#editForm").on("submit", async event => {
event.preventDefault();
const form = $(event.target).get(0) as HTMLFormElement;
$(form).addClass("submitted");
const validity = form.checkValidity();
if (!validity) {
console.debug(`Submit form invalid: ${validity}`);
return;
};
let method = "post";
const data = $(event.target).serializeArray();
// If 'id' has a value, we are patching an existing entry
const id: number | undefined = $(editModal.el).data("id");
if (id !== undefined) {
data.push({ name: "id", value: `${id}` });
method = "patch";
}
await $.ajax({
url: `/guild/${guildId}/feeds/api`,
dataType: "json",
method: method,
data: data,
success: () => {
(table as any).dataTable.draw()
closeEditModal();
},
error: error => {
alert(JSON.stringify(error, null, 4));
}
});
});
const channelSelectOptions: 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" data-icon></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></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: "formChannelsInput",
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,
hasSearch: false,
searchNoResultClasses: "cj-tag-select-search-no-results",
};
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 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
};
let channelSelect: HSSelect;
let filterSelect: HSSelect;
let styleSelect: HSSelect;
window.addEventListener("preline:ready", () => {
const exists = (element: HTMLElement) => HSSelect.getInstance(element, true);
const channelEl = $("#formChannels").get(0);
const filterEl = $("#formFilters").get(0);
const styleEl = $("#formMessageStyle").get(0);
if (!exists(channelEl) && !exists(filterEl) && !exists(styleEl)) {
channelSelect = new HSSelect(channelEl, channelSelectOptions);
filterSelect = new HSSelect(filterEl, filterSelectOptions);
styleSelect = new HSSelect(styleEl, styleSelectOptions);
}
});
const publishedThresholdOptions: ICustomDatepickerOptions = {
type: "default",
dateMax: "2050-00-00",
mode: "custom-select",
layouts: {
default: `
<div class="--single-month flex flex-col overflow-hidden dark:bg-neutral-900 dark:border-neutral-700">
<div class="grid grid-cols-5 items-center gap-x-3 mx-1.5 pb-3" data-vc="header">
<div class="col-span-1">
<#CustomArrowPrev />
</div>
<div class="col-span-3 flex justify-center items-center gap-x-1">
<#CustomMonth />
<span class="text-gray-800 dark:text-neutral-200">/</span>
<#CustomYear />
</div>
<div class="col-span-1 flex justify-end">
<#CustomArrowNext />
</div>
</div>
<div data-vc="wrapper">
<div data-vc="content">
<#Week /><#Dates />
</div>
</div>
</div>
<#CustomTime />`
},
inputModeOptions: { itemsSeparator: " / " },
templates: {
arrowPrev: '<button data-vc-arrow="prev"><svg class="shrink-0 size-4" 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="m15 18-6-6 6-6"></path></svg></button>',
arrowNext: '<button data-vc-arrow="next"><svg class="shrink-0 size-4" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"></path></svg></button>'
},
};
let publishedThresholdInput: HSDatepicker;
window.addEventListener("preline:ready", () => {
const publishedEl = $("#formPublishedThreshold").get(0);
if (!HSDatepicker.getInstance(publishedEl, true)) {
publishedThresholdInput = new HSDatepicker(publishedEl, publishedThresholdOptions);
}
});
// #endregion

View File

@ -1,22 +1,31 @@
import { Channel } from "discord.js";
import prisma from "../../../../generated/prisma";
import "preline";
import $ from "jquery";
import _ from "lodash";
import noUiSlider from "nouislider";
import "datatables.net";
import "dropzone/dist/dropzone-min.js";
import * as VanillaCalendarPro from "vanilla-calendar-pro";
import * as FloatingUIDOM from "@floating-ui/dom";
import "../types/client.d.ts";
import { Channel } from "discord.js";
import prisma from "../../../../generated/prisma";
// Preline requirements
// Preline: requirements
window._ = _;
window.$ = $;
window.jQuery = $;
window.DataTable = $.fn.dataTable;
window.noUiSlider = noUiSlider;
window.VanillaCalendarPro = VanillaCalendarPro;
window.FloatingUIDOM = FloatingUIDOM
document.addEventListener("DOMContentLoaded", () => {
window.HSStaticMethods.autoInit("all");
setTimeout(() => {
window.dispatchEvent(new Event("preline:ready"));
}, 100);
});
// Preline: necessary for header events.
window.addEventListener("load", () => {

View File

@ -1,15 +1,23 @@
import type noUiSlider from "nouislider";
import type { IStaticMethods } from "preline/dist";
interface Window {
// Optional third-party libraries
_: any;
$: typeof import("jquery");
jQuery: typeof import("jquery");
DataTable: any;
Dropzone: any;
VanillaCalendarPro: any;
noUiSlider: any;
declare global {
interface Window {
// Optional third-party libraries
_: typeof import("lodash");
$: typeof import("jquery");
jQuery: typeof import("jquery");
DataTable: typeof $.fn.dataTable;
Dropzone: typeof import("dropzone");
noUiSlider: typeof noUiSlider;
VanillaCalendarPro: typeof import("vanilla-calendar-pro");
// Preline UI
HSStaticMethods: IStaticMethods;
// Preline UI
HSStaticMethods: IStaticMethods;
}
// Floating UI
FloatingUIDOM: typeof import("@floating-ui/dom");
}
}
export {};

View File

@ -141,7 +141,7 @@
</div>
</div>
<div id="editModal" class="hs-overlay hidden size-full fixed top-0 start-0 z-80 overflow-x-hidden overflow-y-auto pointer-events-none" role="dialog" tabindex="-1">
<div id="editModal" class="hidden size-full fixed top-0 start-0 z-80 overflow-x-hidden overflow-y-auto pointer-events-none" role="dialog" tabindex="-1">
<div class="hs-overlay-animation-target hs-overlay-open:scale-100 hs-overlay-open:opacity-100 scale-95 opacity-0 ease-in-out transition-all duration-200 lg:max-w-4xl lg:w-full m-3 lg:mx-auto min-h-[calc(100%-3.5rem)] flex items-center">
<div class="w-full p-4 sm:p-7 flex flex-col bg-white border shadow-xs rounded-lg pointer-events-auto dark:bg-neutral-900 dark:border-neutral-800 dark:shadow-neutral-700/70">
<div class="mb-8">
@ -212,7 +212,7 @@
</div>
<div>
<label for="formPublishedThreshold" class="text-input-label">Published Threshold</label>
<input id="formPublishedThreshold" name="published_threshold" required>
<input type="text" id="formPublishedThreshold" name="published_threshold" class="z-[80] py-3 px-4 block w-full border-gray-200 rounded-lg sm:text-sm focus:border-blue-600 focus:ring-blue-600 disabled:opacity-50 disabled:pointer-events-none dark:bg-neutral-900 dark:border-neutral-700 dark:text-neutral-400 dark:placeholder:text-neutral-400 dark:focus:border-blue-500 dark:focus:ring-neutral-500" readonly required>
<p class="text-input-help block peer-invalid:group-[.submitted]:hidden">
placeholder helper text.
</p>
@ -255,4 +255,4 @@
.map(channel => channel.toJSON())
) %>`);
</script>
<% block("scripts").append('<script src="/public/generated/js/guild/feeds.js"></script>'); %>
<% block("scripts").append('<script src="/public/generated/js/guild/test.js"></script>'); %>