Misc UX fixes
This commit is contained in:
parent
a2ccad1929
commit
cf165cd9de
10
package-lock.json
generated
10
package-lock.json
generated
@ -17,7 +17,7 @@
|
|||||||
"@sealcode/file-manager": "^1.0.2",
|
"@sealcode/file-manager": "^1.0.2",
|
||||||
"@sealcode/jdd": "^0.7.1",
|
"@sealcode/jdd": "^0.7.1",
|
||||||
"@sealcode/jdd-editor": "^0.1.19",
|
"@sealcode/jdd-editor": "^0.1.19",
|
||||||
"@sealcode/sealgen": "^0.17.24",
|
"@sealcode/sealgen": "^0.17.26",
|
||||||
"@sealcode/simplemde": "^1.12.1",
|
"@sealcode/simplemde": "^1.12.1",
|
||||||
"@sealcode/ts-predicates": "^0.6.2",
|
"@sealcode/ts-predicates": "^0.6.2",
|
||||||
"@types/kill-port": "^2.0.0",
|
"@types/kill-port": "^2.0.0",
|
||||||
@ -1004,9 +1004,9 @@
|
|||||||
"integrity": "sha512-pDsGlk2KokQkwzsJDBUWJFDRpEoxxth6TMQGDJyCTmWnd1Vn+cQb5moXDKaf7cXnWb9Y6QtdNX/fPzM/3RH2Cg=="
|
"integrity": "sha512-pDsGlk2KokQkwzsJDBUWJFDRpEoxxth6TMQGDJyCTmWnd1Vn+cQb5moXDKaf7cXnWb9Y6QtdNX/fPzM/3RH2Cg=="
|
||||||
},
|
},
|
||||||
"node_modules/@sealcode/sealgen": {
|
"node_modules/@sealcode/sealgen": {
|
||||||
"version": "0.17.24",
|
"version": "0.17.26",
|
||||||
"resolved": "https://registry.npmjs.org/@sealcode/sealgen/-/sealgen-0.17.24.tgz",
|
"resolved": "https://registry.npmjs.org/@sealcode/sealgen/-/sealgen-0.17.26.tgz",
|
||||||
"integrity": "sha512-xj3BTX2b3uFaO0Fj71inNY282Y96JiiCDFJF4dCmgWQu+yOLlzaVKF7p4bSi1CoNN9eHsvwXUEYKoRfvz8Q9GQ==",
|
"integrity": "sha512-eehUUh+d3XW8F41d18q9tBuYEg9P21IfvovxFiM/p609OaplcZ4pUD+pma7zu/cMr+Xh85aw/gQUox5C9Obp4A==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@koa/router": "^12.0.1",
|
"@koa/router": "^12.0.1",
|
||||||
"@sealcode/file-manager": "^1.0.2",
|
"@sealcode/file-manager": "^1.0.2",
|
||||||
@ -1017,6 +1017,7 @@
|
|||||||
"esbuild": "^0.20.0",
|
"esbuild": "^0.20.0",
|
||||||
"escape-goat": "^4.0.0",
|
"escape-goat": "^4.0.0",
|
||||||
"google-fonts-helper": "^3.4.1",
|
"google-fonts-helper": "^3.4.1",
|
||||||
|
"husky": "^9.1.7",
|
||||||
"is-what": "^4.1.16",
|
"is-what": "^4.1.16",
|
||||||
"js-convert-case": "^4.2.0",
|
"js-convert-case": "^4.2.0",
|
||||||
"json5": "^2.2.3",
|
"json5": "^2.2.3",
|
||||||
@ -5131,7 +5132,6 @@
|
|||||||
"version": "9.1.7",
|
"version": "9.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz",
|
||||||
"integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==",
|
"integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"husky": "bin.js"
|
"husky": "bin.js"
|
||||||
|
@ -82,7 +82,7 @@
|
|||||||
"@sealcode/file-manager": "^1.0.2",
|
"@sealcode/file-manager": "^1.0.2",
|
||||||
"@sealcode/jdd": "^0.7.1",
|
"@sealcode/jdd": "^0.7.1",
|
||||||
"@sealcode/jdd-editor": "^0.1.19",
|
"@sealcode/jdd-editor": "^0.1.19",
|
||||||
"@sealcode/sealgen": "^0.17.24",
|
"@sealcode/sealgen": "^0.17.26",
|
||||||
"@sealcode/simplemde": "^1.12.1",
|
"@sealcode/simplemde": "^1.12.1",
|
||||||
"@sealcode/ts-predicates": "^0.6.2",
|
"@sealcode/ts-predicates": "^0.6.2",
|
||||||
"@types/kill-port": "^2.0.0",
|
"@types/kill-port": "^2.0.0",
|
||||||
|
@ -3,7 +3,7 @@ import { TempstreamJSX } from "tempstream";
|
|||||||
import { Page } from "@sealcode/sealgen";
|
import { Page } from "@sealcode/sealgen";
|
||||||
import html from "src/back/html.js";
|
import html from "src/back/html.js";
|
||||||
import { button } from "../elements/button.js";
|
import { button } from "../elements/button.js";
|
||||||
import { LoginURL, NavbarLinksCRUDURL, PagesCRUDListURL } from "./urls.js";
|
import { LoginURL, NavbarLinksCRUDListURL, PagesCRUDListURL } from "./urls.js";
|
||||||
|
|
||||||
export const actionName = "Admin";
|
export const actionName = "Admin";
|
||||||
|
|
||||||
@ -24,9 +24,10 @@ export default new (class AdminPage extends Page {
|
|||||||
title: "Admin",
|
title: "Admin",
|
||||||
body: (
|
body: (
|
||||||
<div>
|
<div>
|
||||||
|
<h1>Admin panel </h1>
|
||||||
{[
|
{[
|
||||||
{ text: "Pages", href: PagesCRUDListURL },
|
{ text: "Edit Pages", href: PagesCRUDListURL },
|
||||||
{ text: "Navbar", href: NavbarLinksCRUDURL },
|
{ text: "Edit Navbar", href: NavbarLinksCRUDListURL },
|
||||||
].map((e) => button({ ...e, variant: "accent1" }))}
|
].map((e) => button({ ...e, variant: "accent1" }))}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
|
28
src/back/routes/admin/navbar/[id]/delete.page.tsx
Normal file
28
src/back/routes/admin/navbar/[id]/delete.page.tsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import type { Context } from "koa";
|
||||||
|
import { Mountable } from "@sealcode/sealgen";
|
||||||
|
import type Router from "@koa/router";
|
||||||
|
|
||||||
|
import { NavbarLinks } from "../../../../collections/collections.js";
|
||||||
|
|
||||||
|
import { NavbarLinksCRUDListURL } from "../../../urls.js";
|
||||||
|
|
||||||
|
export const actionName = "NavbarLinksCRUDDelete";
|
||||||
|
|
||||||
|
export default new (class NavbarLinksCRUDDeleteRedirect extends Mountable {
|
||||||
|
canAccess = async (ctx: Context) => {
|
||||||
|
const policy = NavbarLinks.getPolicy("edit");
|
||||||
|
const response = await policy.check(ctx.$context);
|
||||||
|
return { canAccess: response?.allowed || false, message: response?.reason || "" };
|
||||||
|
};
|
||||||
|
|
||||||
|
mount(router: Router, path: string) {
|
||||||
|
router.post(path, async (ctx) => {
|
||||||
|
await ctx.$app.collections["navbar-links"].removeByID(
|
||||||
|
ctx.$context,
|
||||||
|
ctx.params.id!
|
||||||
|
);
|
||||||
|
ctx.status = 302;
|
||||||
|
ctx.redirect(NavbarLinksCRUDListURL);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})();
|
105
src/back/routes/admin/navbar/[id]/edit.form.ts
Normal file
105
src/back/routes/admin/navbar/[id]/edit.form.ts
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import type { Context } from "koa";
|
||||||
|
import type { FormData } from "@sealcode/sealgen";
|
||||||
|
import { Form, Controls, fieldsToShape } from "@sealcode/sealgen";
|
||||||
|
import html from "../../../../html.js";
|
||||||
|
|
||||||
|
import { NavbarLinksFormFields, NavbarLinksFormControls } from "../shared.js";
|
||||||
|
import { NavbarLinks } from "../../../../collections/collections.js";
|
||||||
|
import { NavbarLinksCRUDListURL } from "../../../urls.js";
|
||||||
|
import { tempstream } from "tempstream";
|
||||||
|
|
||||||
|
import { withFallback } from "@sealcode/sealgen";
|
||||||
|
|
||||||
|
export const actionName = "NavbarLinksCRUDEdit";
|
||||||
|
|
||||||
|
const fields = {
|
||||||
|
...NavbarLinksFormFields,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const NavbarLinksCRUDEditShape = fieldsToShape(fields);
|
||||||
|
|
||||||
|
export default new (class NavbarLinksCRUDEditForm extends Form<typeof fields, void> {
|
||||||
|
defaultSuccessMessage = "Formularz wypełniony poprawnie";
|
||||||
|
fields = fields;
|
||||||
|
|
||||||
|
controls = [new Controls.FormHeader("Edit NavbarLinks"), ...NavbarLinksFormControls];
|
||||||
|
|
||||||
|
async getID(ctx: Context): Promise<string> {
|
||||||
|
// trying to automatically guess which param is the one that represents the ID
|
||||||
|
// of the resource to edit.
|
||||||
|
// If sealgen got it wrong, just return the param name that should be used here instead
|
||||||
|
const param_name = "id";
|
||||||
|
const id = ctx.params[param_name];
|
||||||
|
if (!id) {
|
||||||
|
throw new Error("Missing URL parameter: " + param_name);
|
||||||
|
}
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getInitialValues(ctx: Context) {
|
||||||
|
const id = await this.getID(ctx);
|
||||||
|
|
||||||
|
const {
|
||||||
|
items: [item],
|
||||||
|
} = await ctx.$app.collections["navbar-links"]
|
||||||
|
.list(ctx.$context)
|
||||||
|
.ids([id])
|
||||||
|
.attach({})
|
||||||
|
.fetch();
|
||||||
|
|
||||||
|
if (!item) {
|
||||||
|
throw new Error("Item with given id not found: " + id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
label: withFallback(item.get("label"), ""),
|
||||||
|
href: withFallback(item.get("href"), ""),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async onSubmit(ctx: Context) {
|
||||||
|
const data = await this.getParsedValues(ctx);
|
||||||
|
const id = await this.getID(ctx);
|
||||||
|
const {
|
||||||
|
items: [item],
|
||||||
|
} = await ctx.$app.collections["navbar-links"]
|
||||||
|
.list(ctx.$context)
|
||||||
|
.ids([id])
|
||||||
|
.fetch();
|
||||||
|
|
||||||
|
if (!item) {
|
||||||
|
throw new Error("Unknown id: " + id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
throw new Error("Error when parsing the form values");
|
||||||
|
}
|
||||||
|
|
||||||
|
item.setMultiple({
|
||||||
|
label: data["label"] != null ? data["label"] : "",
|
||||||
|
href: data["href"] != null ? data["href"] : "",
|
||||||
|
});
|
||||||
|
await item.save(ctx.$context);
|
||||||
|
}
|
||||||
|
|
||||||
|
canAccess = async (ctx: Context) => {
|
||||||
|
const policy = NavbarLinks.getPolicy("edit");
|
||||||
|
const response = await policy.check(ctx.$context);
|
||||||
|
return { canAccess: response?.allowed || false, message: response?.reason || "" };
|
||||||
|
};
|
||||||
|
|
||||||
|
async render(ctx: Context, data: FormData, show_field_errors: boolean) {
|
||||||
|
return html({
|
||||||
|
ctx,
|
||||||
|
title: "Edit NavbarLinks",
|
||||||
|
body: tempstream/* HTML */ ` <div class="sealgen-crud-form">
|
||||||
|
<a class="" href="${NavbarLinksCRUDListURL}"
|
||||||
|
>← Back to navbar-links list</a
|
||||||
|
>
|
||||||
|
${await super.render(ctx, data, show_field_errors)}
|
||||||
|
</div>`,
|
||||||
|
description: "",
|
||||||
|
css_clumps: ["admin-forms"],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})();
|
63
src/back/routes/admin/navbar/create.form.ts
Normal file
63
src/back/routes/admin/navbar/create.form.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import type { Context } from "koa";
|
||||||
|
import type { FormData } from "@sealcode/sealgen";
|
||||||
|
import { Form, Controls, fieldsToShape } from "@sealcode/sealgen";
|
||||||
|
import html from "../../../html.js";
|
||||||
|
|
||||||
|
import { NavbarLinksFormFields, NavbarLinksFormControls } from "./shared.js";
|
||||||
|
|
||||||
|
import { NavbarLinks } from "../../../collections/collections.js";
|
||||||
|
|
||||||
|
import { NavbarLinksCRUDListURL } from "../../urls.js";
|
||||||
|
|
||||||
|
import { tempstream } from "tempstream";
|
||||||
|
|
||||||
|
export const actionName = "NavbarLinksCRUDCreate";
|
||||||
|
|
||||||
|
const fields = {
|
||||||
|
...NavbarLinksFormFields,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const NavbarLinksCRUDCreateShape = fieldsToShape(fields);
|
||||||
|
|
||||||
|
export default new (class NavbarLinksCRUDCreateForm extends Form<typeof fields, void> {
|
||||||
|
defaultSuccessMessage = "Formularz wypełniony poprawnie";
|
||||||
|
fields = fields;
|
||||||
|
|
||||||
|
controls = [
|
||||||
|
new Controls.FormHeader("Create NavbarLinks"),
|
||||||
|
...NavbarLinksFormControls,
|
||||||
|
];
|
||||||
|
|
||||||
|
async onSubmit(ctx: Context) {
|
||||||
|
const data = await this.getParsedValues(ctx);
|
||||||
|
if (!data) {
|
||||||
|
throw new Error("Error when parsing the form values");
|
||||||
|
}
|
||||||
|
|
||||||
|
await ctx.$app.collections["navbar-links"].create(ctx.$context, {
|
||||||
|
label: data["label"] != null ? data["label"] : "",
|
||||||
|
href: data["href"] != null ? data["href"] : "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
canAccess = async (ctx: Context) => {
|
||||||
|
const policy = NavbarLinks.getPolicy("create");
|
||||||
|
const response = await policy.check(ctx.$context);
|
||||||
|
return { canAccess: response?.allowed || false, message: response?.reason || "" };
|
||||||
|
};
|
||||||
|
|
||||||
|
async render(ctx: Context, data: FormData, show_field_errors: boolean) {
|
||||||
|
return html({
|
||||||
|
ctx,
|
||||||
|
title: "Create navbar-links",
|
||||||
|
body: tempstream/* HTML */ ` <div class="sealgen-crud-form">
|
||||||
|
<a class="" href="${NavbarLinksCRUDListURL}"
|
||||||
|
>← Back to navbar-links list</a
|
||||||
|
>
|
||||||
|
${await super.render(ctx, data, show_field_errors)}
|
||||||
|
</div>`,
|
||||||
|
description: "",
|
||||||
|
css_clumps: ["admin-forms"],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})();
|
159
src/back/routes/admin/navbar/index.list.tsx
Normal file
159
src/back/routes/admin/navbar/index.list.tsx
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
import type { Context } from "koa";
|
||||||
|
import type { CollectionItem } from "sealious";
|
||||||
|
import type { FlatTemplatable, Templatable } from "tempstream";
|
||||||
|
import { TempstreamJSX, tempstream } from "tempstream";
|
||||||
|
import { NavbarLinks } from "src/back/collections/collections.js";
|
||||||
|
import html from "src/back/html.js";
|
||||||
|
import type { ListFilterRender } from "@sealcode/sealgen";
|
||||||
|
import {
|
||||||
|
SealiousItemListPage,
|
||||||
|
BaseListPageFields,
|
||||||
|
DefaultListFilters,
|
||||||
|
} from "@sealcode/sealgen";
|
||||||
|
import qs from "qs";
|
||||||
|
|
||||||
|
import {
|
||||||
|
NavbarLinksCRUDCreateURL,
|
||||||
|
NavbarLinksCRUDEditURL,
|
||||||
|
NavbarLinksCRUDDeleteURL,
|
||||||
|
AdminURL,
|
||||||
|
} from "../../urls.js";
|
||||||
|
|
||||||
|
export const actionName = "NavbarLinksCRUDList";
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
|
const filterFields = [
|
||||||
|
{ field: "label", ...DefaultListFilters["text"] },
|
||||||
|
{ field: "href", ...DefaultListFilters["text"] },
|
||||||
|
] as {
|
||||||
|
field: keyof (typeof NavbarLinks)["fields"];
|
||||||
|
render?: ListFilterRender;
|
||||||
|
prepareValue?: (filter_value: unknown) => unknown; // set this function to change what filter value is passed to Sealious
|
||||||
|
}[];
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
|
const displayFields = [
|
||||||
|
{ field: "label", label: "label" },
|
||||||
|
{ field: "href", label: "href" },
|
||||||
|
] as {
|
||||||
|
field: string;
|
||||||
|
label: string;
|
||||||
|
format?: (
|
||||||
|
value: unknown,
|
||||||
|
item: CollectionItem<typeof NavbarLinks>
|
||||||
|
) => FlatTemplatable;
|
||||||
|
}[];
|
||||||
|
|
||||||
|
export default new (class NavbarLinksCRUDListPage extends SealiousItemListPage<
|
||||||
|
typeof NavbarLinks,
|
||||||
|
typeof BaseListPageFields
|
||||||
|
> {
|
||||||
|
fields = BaseListPageFields;
|
||||||
|
|
||||||
|
async renderFilters(ctx: Context): Promise<FlatTemplatable> {
|
||||||
|
const query_params = qs.parse(ctx.search.slice(1));
|
||||||
|
query_params.page = "1";
|
||||||
|
const filter_values = await super.getFilterValues(ctx);
|
||||||
|
return (
|
||||||
|
<form>
|
||||||
|
{Object.entries(query_params).map(([key, value]) => {
|
||||||
|
if (key == "filter") {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
// this is necessary to not lose any query params when the user changes the filter values
|
||||||
|
return <input type="hidden" name={key} value={value} />;
|
||||||
|
})}
|
||||||
|
{filterFields.map(({ field, render }) => {
|
||||||
|
if (!render) {
|
||||||
|
render = DefaultListFilters.fallback.render;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
render(
|
||||||
|
filter_values[field] || "",
|
||||||
|
this.collection.fields[field]
|
||||||
|
) || ""
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
<input type="submit" />
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getFilterValues(ctx: Context) {
|
||||||
|
// adding opportunity to adjust the values for a given field filter before it's sent to Sealious
|
||||||
|
const values = await super.getFilterValues(ctx);
|
||||||
|
for (const filterField of filterFields) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
|
const key = filterField.field as keyof typeof values;
|
||||||
|
if (key in values) {
|
||||||
|
const prepare_fn = filterField.prepareValue;
|
||||||
|
if (prepare_fn) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment
|
||||||
|
values[key] = prepare_fn(values[key]) as any;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
async renderItem(ctx: Context, item: CollectionItem<typeof NavbarLinks>) {
|
||||||
|
return (
|
||||||
|
<tr>
|
||||||
|
{displayFields.map(({ field, format }) => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-explicit-any
|
||||||
|
const value = item.get(field as any);
|
||||||
|
return <td>{format ? format(value, item) : value}</td>;
|
||||||
|
})}
|
||||||
|
<td>
|
||||||
|
<div class="sealious-list__actions">
|
||||||
|
<a href={NavbarLinksCRUDEditURL(item.id)}>Edit</a>
|
||||||
|
<a
|
||||||
|
data-turbo-method="POST"
|
||||||
|
data-turbo-confirm="Delete item?"
|
||||||
|
href={NavbarLinksCRUDDeleteURL(item.id)}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderListContainer(ctx: Context, content: Templatable): FlatTemplatable {
|
||||||
|
return (
|
||||||
|
<table class="sealious-list navbar-links-crudlist-table">
|
||||||
|
{this.renderTableHead(ctx, displayFields)}
|
||||||
|
<tbody>{content}</tbody>
|
||||||
|
</table>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderTableHead(
|
||||||
|
ctx: Context,
|
||||||
|
fields: { field: string; label?: string }[]
|
||||||
|
): FlatTemplatable {
|
||||||
|
return tempstream/* HTML */ `<thead>
|
||||||
|
<tr>
|
||||||
|
${fields.map(({ label, field }) => this.renderHeading(ctx, field, label))}
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async render(ctx: Context) {
|
||||||
|
return html({
|
||||||
|
ctx,
|
||||||
|
title: "Navbar",
|
||||||
|
description: "",
|
||||||
|
body: (
|
||||||
|
<div class="sealious-list-wrapper navbar-links-crudlist--wrapper">
|
||||||
|
<a href={AdminURL}>← Back to Admin</a>
|
||||||
|
<h2>Edit Navbar items</h2>
|
||||||
|
<a href={NavbarLinksCRUDCreateURL}> Create </a>
|
||||||
|
{super.render(ctx)}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})(NavbarLinks);
|
@ -1,66 +0,0 @@
|
|||||||
import { CRUD } from "@sealcode/crud-ui";
|
|
||||||
import type { CollectionItem } from "sealious";
|
|
||||||
import type { ListFilterRender } from "@sealcode/sealgen";
|
|
||||||
import { Controls, DefaultListFilters, Fields } from "@sealcode/sealgen";
|
|
||||||
import { NavbarLinks } from "src/back/collections/collections.js";
|
|
||||||
import html from "src/back/html.js";
|
|
||||||
|
|
||||||
export const actionName = "NavbarLinksCRUD";
|
|
||||||
|
|
||||||
const edit_fields = <const>{
|
|
||||||
label: new Fields.CollectionField(
|
|
||||||
NavbarLinks.fields.label.required,
|
|
||||||
NavbarLinks.fields.label
|
|
||||||
),
|
|
||||||
href: new Fields.CollectionField(
|
|
||||||
NavbarLinks.fields.href.required,
|
|
||||||
NavbarLinks.fields.href
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
const edit_controls = [
|
|
||||||
new Controls.SimpleInput(edit_fields.label, { label: "label" }),
|
|
||||||
new Controls.SimpleInput(edit_fields.href, { label: "href" }),
|
|
||||||
];
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
||||||
const fields_for_display = [
|
|
||||||
{ field: "label", label: "label" },
|
|
||||||
{ field: "href", label: "href" },
|
|
||||||
] as {
|
|
||||||
field: keyof (typeof NavbarLinks)["fields"];
|
|
||||||
label: string;
|
|
||||||
format?: (
|
|
||||||
value: unknown,
|
|
||||||
item: CollectionItem<typeof NavbarLinks>
|
|
||||||
) => string | Promise<string>;
|
|
||||||
}[];
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
||||||
const fields_for_filters = [
|
|
||||||
{ field: "label", ...DefaultListFilters["text"] },
|
|
||||||
{ field: "href", ...DefaultListFilters["text"] },
|
|
||||||
] as {
|
|
||||||
field: keyof (typeof NavbarLinks)["fields"];
|
|
||||||
render?: ListFilterRender;
|
|
||||||
prepareValue?: (filter_value: unknown) => unknown; // set this function to change what filter value is passed to Sealious
|
|
||||||
}[];
|
|
||||||
|
|
||||||
export default new CRUD({
|
|
||||||
collection: NavbarLinks,
|
|
||||||
nice_collection_name: "NavbarLinks",
|
|
||||||
fields_for_display,
|
|
||||||
fields_for_filters,
|
|
||||||
html,
|
|
||||||
list_title: "Navbar Links",
|
|
||||||
edit_title: "Edit",
|
|
||||||
edit_button_text: "Edit",
|
|
||||||
delete_button_text: "Delete",
|
|
||||||
back_to_list_button_text: "← Back to NavbarLinks list",
|
|
||||||
edit_fields,
|
|
||||||
edit_controls,
|
|
||||||
form_value_to_sealious_value: {},
|
|
||||||
sealious_value_to_form_value: {},
|
|
||||||
post_edit: async () => {},
|
|
||||||
post_create: async () => {},
|
|
||||||
});
|
|
18
src/back/routes/admin/navbar/shared.ts
Normal file
18
src/back/routes/admin/navbar/shared.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { Controls, Fields } from "@sealcode/sealgen";
|
||||||
|
import { NavbarLinks } from "../../../collections/collections.js";
|
||||||
|
|
||||||
|
export const NavbarLinksFormFields = <const>{
|
||||||
|
label: new Fields.CollectionField(
|
||||||
|
NavbarLinks.fields.label.required,
|
||||||
|
NavbarLinks.fields.label
|
||||||
|
),
|
||||||
|
href: new Fields.CollectionField(
|
||||||
|
NavbarLinks.fields.href.required,
|
||||||
|
NavbarLinks.fields.href
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const NavbarLinksFormControls = [
|
||||||
|
new Controls.SimpleInput(NavbarLinksFormFields.label, { label: "label" }),
|
||||||
|
new Controls.SimpleInput(NavbarLinksFormFields.href, { label: "href" }),
|
||||||
|
];
|
@ -81,9 +81,6 @@ export default new (class PagesCRUDEditForm extends Form<typeof fields, void> {
|
|||||||
|
|
||||||
const preparedImageForMetadata =
|
const preparedImageForMetadata =
|
||||||
data.imageForMetadata?.new || data.imageForMetadata?.old;
|
data.imageForMetadata?.new || data.imageForMetadata?.old;
|
||||||
if (!preparedImageForMetadata) {
|
|
||||||
throw new Error("Missing field: imageForMetadata");
|
|
||||||
}
|
|
||||||
|
|
||||||
item.setMultiple({
|
item.setMultiple({
|
||||||
url: data["url"],
|
url: data["url"],
|
||||||
@ -92,7 +89,7 @@ export default new (class PagesCRUDEditForm extends Form<typeof fields, void> {
|
|||||||
title: data["title"] != null ? data["title"] : "",
|
title: data["title"] != null ? data["title"] : "",
|
||||||
heading: data["heading"] != null ? data["heading"] : "",
|
heading: data["heading"] != null ? data["heading"] : "",
|
||||||
description: data["description"] != null ? data["description"] : "",
|
description: data["description"] != null ? data["description"] : "",
|
||||||
imageForMetadata: preparedImageForMetadata,
|
imageForMetadata: preparedImageForMetadata || undefined,
|
||||||
hideNavigation: !!data.hideNavigation != null ? !!data.hideNavigation : false,
|
hideNavigation: !!data.hideNavigation != null ? !!data.hideNavigation : false,
|
||||||
});
|
});
|
||||||
await item.save(ctx.$context);
|
await item.save(ctx.$context);
|
||||||
|
@ -33,9 +33,6 @@ export default new (class PagesCRUDCreateForm extends Form<typeof fields, void>
|
|||||||
|
|
||||||
const preparedImageForMetadata =
|
const preparedImageForMetadata =
|
||||||
data.imageForMetadata?.new || data.imageForMetadata?.old;
|
data.imageForMetadata?.new || data.imageForMetadata?.old;
|
||||||
if (!preparedImageForMetadata) {
|
|
||||||
throw new Error("Missing field: imageForMetadata");
|
|
||||||
}
|
|
||||||
|
|
||||||
await ctx.$app.collections["pages"].create(ctx.$context, {
|
await ctx.$app.collections["pages"].create(ctx.$context, {
|
||||||
url: data["url"],
|
url: data["url"],
|
||||||
@ -44,7 +41,7 @@ export default new (class PagesCRUDCreateForm extends Form<typeof fields, void>
|
|||||||
title: data["title"] != null ? data["title"] : "",
|
title: data["title"] != null ? data["title"] : "",
|
||||||
heading: data["heading"] != null ? data["heading"] : "",
|
heading: data["heading"] != null ? data["heading"] : "",
|
||||||
description: data["description"] != null ? data["description"] : "",
|
description: data["description"] != null ? data["description"] : "",
|
||||||
imageForMetadata: preparedImageForMetadata,
|
imageForMetadata: preparedImageForMetadata || undefined,
|
||||||
hideNavigation: !!data.hideNavigation != null ? !!data.hideNavigation : false,
|
hideNavigation: !!data.hideNavigation != null ? !!data.hideNavigation : false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -16,10 +16,7 @@ export const PagesFormFields = <const>{
|
|||||||
Pages.fields.description.required,
|
Pages.fields.description.required,
|
||||||
Pages.fields.description
|
Pages.fields.description
|
||||||
),
|
),
|
||||||
imageForMetadata: new Fields.File(
|
imageForMetadata: new Fields.File(false, TheFileManager),
|
||||||
Pages.fields.imageForMetadata.required,
|
|
||||||
TheFileManager
|
|
||||||
),
|
|
||||||
hideNavigation: new Fields.Boolean(Pages.fields.hideNavigation.required),
|
hideNavigation: new Fields.Boolean(Pages.fields.hideNavigation.required),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import type { BaseContext } from "koa";
|
import type { BaseContext } from "koa";
|
||||||
import { button } from "src/back/elements/button.js";
|
import { button } from "src/back/elements/button.js";
|
||||||
import { tempstream, type FlatTemplatable } from "tempstream";
|
import { tempstream } from "tempstream";
|
||||||
|
import type { FlatTemplatable } from "tempstream";
|
||||||
|
|
||||||
export async function default_navbar(ctx: BaseContext): Promise<FlatTemplatable> {
|
export async function default_navbar(ctx: BaseContext): Promise<FlatTemplatable> {
|
||||||
const { items: navbar_items } = await ctx.$app.collections["navbar-links"]
|
const { items: navbar_items } = await ctx.$app.collections["navbar-links"]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user