diff --git a/Dockerfile b/Dockerfile index 33edec492..b3b4a405a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ # syntax = docker/dockerfile:1 # Adjust NODE_VERSION as desired -ARG NODE_VERSION=22.6.0 +ARG NODE_VERSION=22.11.0 FROM node:${NODE_VERSION}-alpine as base LABEL fly_launch_runtime="Mana" @@ -48,7 +48,6 @@ RUN yarn install --frozen-lockfile --production=true # Final stage for app image FROM base as runtime -RUN corepack enable WORKDIR /app # Install supervisor in the base image diff --git a/app/_custom/admin-panel-styles.css b/app/_custom/admin-panel-styles.css new file mode 100644 index 000000000..e69de29bb diff --git a/app/components/icons.d.ts b/app/components/icons.d.ts index 6cde5fc22..34d6f2cce 100644 --- a/app/components/icons.d.ts +++ b/app/components/icons.d.ts @@ -1,4 +1,5 @@ export type IconName = + | "eye" | "telescope" | "hash" | "corner-down-right" diff --git a/app/db/collections/images/access.ts b/app/db/collections/images/images.access.ts similarity index 76% rename from app/db/collections/images/access.ts rename to app/db/collections/images/images.access.ts index 6d064c5a3..adbea3127 100644 --- a/app/db/collections/images/access.ts +++ b/app/db/collections/images/images.access.ts @@ -28,3 +28,9 @@ export const canDeleteImages: Access = async ({ req: { user } }) => { // Reject everyone else return false; }; + +export const canCreateImage: Access = async ({ req: { user }, data }) => { + const isSelf = user?.id === data?.createdBy; + const isStaff = user?.roles?.includes("staff"); + return isStaff || isSelf; +}; diff --git a/app/db/collections/images/config.ts b/app/db/collections/images/images.config.ts similarity index 85% rename from app/db/collections/images/config.ts rename to app/db/collections/images/images.config.ts index 69a5832cd..c7fc4b133 100644 --- a/app/db/collections/images/config.ts +++ b/app/db/collections/images/images.config.ts @@ -2,12 +2,8 @@ import type { CollectionConfig } from "payload/types"; import type { User } from "payload/generated-types"; -import { canDeleteImages } from "./access"; -import { - isStaff, - isStaffFieldLevel, - isStaffOrSelf, -} from "../users/users.access"; +import { canDeleteImages, canCreateImage } from "./images.access"; +import { isStaff, isStaffFieldLevel } from "../users/users.access"; export const imagesSlug = "images"; export const Images: CollectionConfig = { @@ -16,7 +12,7 @@ export const Images: CollectionConfig = { read: (): boolean => true, // Everyone can read Images update: isStaff, delete: canDeleteImages, - create: isStaffOrSelf, + create: canCreateImage, }, fields: [ { diff --git a/app/db/collections/index.ts b/app/db/collections/index.ts index c4a605fb9..66cd4a420 100644 --- a/app/db/collections/index.ts +++ b/app/db/collections/index.ts @@ -4,7 +4,7 @@ import { ContentEmbeds } from "./content-embeds/config"; import { CustomPages } from "./custom-pages/custom-pages-config"; import { Entries } from "./entries/entries-config"; import { HomeContents } from "./home-contents/config"; -import { Images } from "./images/config"; +import { Images } from "./images/images.config"; import { PostContents } from "./post-contents/post-contents-config"; import { PostTags } from "./post-tags/post-tags-config"; import { Posts } from "./posts/posts-config"; diff --git a/app/db/collections/users/users.access.ts b/app/db/collections/users/users.access.ts index 99ad0a71e..683174e70 100644 --- a/app/db/collections/users/users.access.ts +++ b/app/db/collections/users/users.access.ts @@ -13,14 +13,6 @@ export const isStaff: Access = ({ req: { user } }) => { return Boolean(user?.roles?.includes("staff")); }; -// This work because the document id here is the user being accessed -export const isSelf: Access = ({ req: { user }, id }) => { - return user?.id === id; -}; - -export const isStaffOrSelf: Access = (props) => - isStaff(props) || isSelf(props); - export const isStaffFieldLevel: FieldAccess = ({ req: { user }, }) => { @@ -38,3 +30,10 @@ export const isSelfFieldLevel: FieldAccess = ({ export const isStaffOrSelfFieldLevel: FieldAccess = ( props, ) => isStaffFieldLevel(props) || isSelfFieldLevel(props); + +export const canUpdateUser: Access = async ({ req: { user }, id }) => { + const isStaff = user?.roles?.includes("staff"); + const isSelf = user?.id === id; + + return isStaff || isSelf; +}; diff --git a/app/db/collections/users/users.config.ts b/app/db/collections/users/users.config.ts index b5f101d17..e41d8820d 100644 --- a/app/db/collections/users/users.config.ts +++ b/app/db/collections/users/users.config.ts @@ -3,12 +3,12 @@ import type { CollectionConfig } from "payload/types"; import { isStaff, isStaffFieldLevel, - isStaffOrSelf, isStaffOrSelfFieldLevel, } from "./users.access"; export const serverEnv = process.env.NODE_ENV; export const usersSlug = "users"; +import { canUpdateUser } from "./users.access"; export const Users: CollectionConfig = { slug: usersSlug, @@ -92,8 +92,8 @@ export const Users: CollectionConfig = { access: { read: () => true, create: () => true, - delete: isStaffOrSelf, - update: isStaffOrSelf, + delete: isStaff, + update: canUpdateUser, // @ts-expect-error - this is a payload type bug admin: isStaff, }, diff --git a/app/db/custom/CustomUsers.ts b/app/db/custom/CustomUsers.ts index 5a04965e5..7b6a604a0 100644 --- a/app/db/custom/CustomUsers.ts +++ b/app/db/custom/CustomUsers.ts @@ -3,7 +3,6 @@ import type { CollectionConfig } from "payload/types"; import { isStaff, isStaffFieldLevel, - isStaffOrSelf, isStaffOrSelfFieldLevel, } from "../collections/users/users.access"; @@ -16,7 +15,7 @@ export const Users: CollectionConfig = { read: isStaff, create: isStaff, delete: isStaff, - update: isStaffOrSelf, + update: isStaff, }, fields: [ { diff --git a/app/db/payload.custom.config.ts b/app/db/payload.custom.config.ts index 64fec904a..407df40d4 100644 --- a/app/db/payload.custom.config.ts +++ b/app/db/payload.custom.config.ts @@ -60,6 +60,7 @@ export default buildConfig({ ogImage: "/og-image.png", titleSuffix: "Mana", }, + css: path.resolve(__dirname, "../_custom/admin-panel-styles.css"), }, plugins: [ selectPlugin(), diff --git a/app/routes/_editor+/blocks+/htmlblock.tsx b/app/routes/_editor+/blocks+/htmlblock.tsx index 7cb7c7a95..e37ad644b 100644 --- a/app/routes/_editor+/blocks+/htmlblock.tsx +++ b/app/routes/_editor+/blocks+/htmlblock.tsx @@ -62,7 +62,7 @@ export function BlockHTMLBlock({ ) : (
updateLabelValue(event.target.value)} placeholder="Start typing..." diff --git a/app/routes/_editor+/blocks+/toggleblock.tsx b/app/routes/_editor+/blocks+/toggleblock.tsx index 0ffb2f7e2..3af9cfecc 100644 --- a/app/routes/_editor+/blocks+/toggleblock.tsx +++ b/app/routes/_editor+/blocks+/toggleblock.tsx @@ -1,14 +1,18 @@ import type { ReactNode } from "react"; +import { useState } from "react"; -import { Disclosure } from "@headlessui/react"; +import { Disclosure, Switch } from "@headlessui/react"; import clsx from "clsx"; -import { useSlate } from "slate-react"; +import type { BaseEditor } from "slate"; +import { Transforms } from "slate"; +import { ReactEditor, useSlate } from "slate-react"; import { Icon } from "~/components/Icon"; // eslint-disable-next-line import/no-cycle import { NestedEditor } from "../core/dnd"; -import type { ToggleBlockElement } from "../core/types"; + +import type { ToggleBlockElement, CustomElement } from "../core/types"; type Props = { element: ToggleBlockElement; @@ -19,9 +23,28 @@ type Props = { export function BlockToggleBlock({ element, children, readOnly }: Props) { //Otherwise render as regular a tag for external links const editor = useSlate(); + const [isOpen, setIsOpen] = useState(element.isOpen); + + interface Editors extends BaseEditor, ReactEditor {} + + function handleUpdateToggleIsOpen( + event: any, + editor: Editors, + element: ToggleBlockElement, + ) { + const path = ReactEditor.findPath(editor, element); + setIsOpen(event); + return Transforms.setNodes( + editor, + { isOpen: event }, + { + at: path, + }, + ); + } return ( - + {({ open }) => ( <> {readOnly ? ( @@ -46,31 +69,53 @@ export function BlockToggleBlock({ element, children, readOnly }: Props) {
) : ( -
- -
- + <> +
+ +
+ + + + handleUpdateToggleIsOpen(e, editor, element) + } + className="dark:border-zinc-600/60 z-30 group-hover:flex bg-white dark:bg-dark450 flex-none absolute top-1 right-3 h-5 w-[36px] items-center rounded-full border" + > +
+
+ Default {isOpen ? "Open" : "Closed"} +
+ +
+ +
+ {children}
- -
- {children}
-
+ )} ; defaultValue: unknown[]; @@ -35,6 +36,7 @@ export function ManaEditor({ entryId?: string | undefined | null; collectionEntity?: string; collectionSlug?: keyof Config["collections"]; + onChange?: (value: Descendant[]) => void; }) { const editor = useEditor(); @@ -45,7 +47,7 @@ export function ManaEditor({ const debouncedValue = useDebouncedValue(value, 1000); useEffect(() => { - if (!isMount) { + if (!isMount && !onChange) { fetcher?.submit( //@ts-ignore { @@ -65,7 +67,7 @@ export function ManaEditor({ return ( diff --git a/app/routes/_site+/c_+/_components/List.tsx b/app/routes/_site+/c_+/_components/List.tsx index 6fe8333ab..d7d186e0a 100644 --- a/app/routes/_site+/c_+/_components/List.tsx +++ b/app/routes/_site+/c_+/_components/List.tsx @@ -49,6 +49,7 @@ export function List({ defaultViewType, gridView, defaultSort, + beforeListComponent, }: { children?: ReactNode; columns: AccessorKeyColumnDefBase[]; @@ -57,6 +58,7 @@ export function List({ gridView?: AccessorKeyColumnDef; columnViewability?: VisibilityState; defaultSort?: SortingState; + beforeListComponent?: ReactNode; }) { //@ts-ignore const { list } = useLoaderData(); @@ -94,7 +96,8 @@ export function List({ className="mt-6 mb-4 mx-auto flex items-center justify-center" selectorId="listDesktopLeaderATF" /> -
+ {beforeListComponent} +
{!collection?.customDatabase && } {children ? ( children diff --git a/app/routes/_site+/c_+/_components/ListTable.tsx b/app/routes/_site+/c_+/_components/ListTable.tsx index 6466bd5e7..8a6bb7f90 100644 --- a/app/routes/_site+/c_+/_components/ListTable.tsx +++ b/app/routes/_site+/c_+/_components/ListTable.tsx @@ -43,6 +43,7 @@ export function ListTable({ pageSize = 60, globalFilterFn, stickyFooter = false, + hideViewMode = false, }: { data: any; columns: AccessorKeyColumnDefBase[] | any; @@ -59,6 +60,7 @@ export function ListTable({ pager?: boolean; globalFilterFn?: FilterFn; stickyFooter?: boolean; + hideViewMode?: boolean; }) { const FilterContext = useContext(TableFilterContext); @@ -117,6 +119,7 @@ export function ListTable({ {FilterContext?.globalColumnFilters && typeof FilterContext?.setGlobalColumnFilters == "function" ? null : ( >; @@ -34,34 +35,37 @@ export function ListTop({ searchPlaceholder?: string; setViewMode: Dispatch>; filters?: TableFilters; + hideViewMode?: boolean; }) { return (
-
- - -
+ + +
+ )} + + + + + \ No newline at end of file