-
Notifications
You must be signed in to change notification settings - Fork 10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(tiptap): blocks editor #1656
Changes from all commits
6f69f07
5dcb97c
2a87cc1
03be205
9a357f3
d96b688
368ecf0
d167857
5bfa1cc
1d68253
5cfcc7a
c6d6c7d
2f5e1e9
e05ab8a
afa938d
17d3102
6a9ba14
d097d4f
0f4e5cc
29c48be
f7fc6c7
f8d87fb
135c23b
23c3d90
bdce5a7
26eb6a2
b476917
fc4e0c9
fad62cb
ef30330
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -73,7 +73,7 @@ const getDraggableAccordionItemStyle = ({ | |
interface SidebarHeaderProps { | ||
title: string | ||
} | ||
const SidebarHeader = ({ title }: SidebarHeaderProps) => { | ||
export const SidebarHeader = ({ title }: SidebarHeaderProps) => { | ||
return ( | ||
<Flex | ||
w="100%" | ||
|
@@ -97,7 +97,7 @@ export const CustomiseSectionsHeader = () => ( | |
</> | ||
) | ||
|
||
interface EmptySectionProps { | ||
interface EmptySectionProps extends FlexProps { | ||
image?: JSX.Element | ||
title: string | ||
subtitle: string | ||
|
@@ -109,13 +109,15 @@ export const EmptySection = ({ | |
title, | ||
subtitle, | ||
isEmpty, | ||
...rest | ||
}: PropsWithChildren<EmptySectionProps>) => { | ||
return isEmpty ? ( | ||
<Flex | ||
alignItems="center" | ||
flexDir="column" | ||
p="3.75rem 1.5rem" | ||
justifyContent="center" | ||
{...rest} | ||
> | ||
{image} | ||
<Text | ||
|
@@ -181,6 +183,7 @@ type DroppableZone = | |
| HomepageDroppableZone | ||
| ContactUsDroppableZone | ||
| NavDroppableZone | ||
| "Blocks" | ||
|
||
type DropInfo = { | ||
droppableId: DroppableZone | ||
|
@@ -293,10 +296,11 @@ interface DraggableAccordionItemProps { | |
// TODO: Should get these props automatically | ||
// rather than having us pass in manually | ||
index: number | ||
draggableId: string | ||
draggableId?: string | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this means not everything is draggable now? |
||
isInvalid?: boolean | ||
isNested?: boolean | ||
} | ||
|
||
// NOTE: Separating editable/draggable | ||
// due to semantics on `Draggables` | ||
/** | ||
|
@@ -465,7 +469,11 @@ const DraggableAccordionItem = ({ | |
) | ||
} | ||
|
||
const EditableAccordion = (props: AccordionProps) => { | ||
const EditableAccordion = ( | ||
props: Omit<AccordionProps, "allowMultiple" | "onChange"> & { | ||
onChange?: (idx: number) => void | ||
} | ||
) => { | ||
return <Accordion allowToggle bg="base.canvas.default" {...props} /> | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -40,8 +40,9 @@ const PagePreview = ({ title, chunk, ...rest }) => { | |
|
||
return ( | ||
<Box | ||
w="50%" | ||
w="100%" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why is this 100% now? |
||
h="100%" | ||
maxH="100vh" | ||
bg="white" | ||
overflowY="auto" | ||
className={editorStyles.pageEditorMain} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { PropsWithChildren, createContext, useContext, useState } from "react" | ||
|
||
type BlocksTab = "content" | "add" | "edit" | ||
|
||
interface UseBlocksReturn { | ||
curTab: BlocksTab | ||
showAddView: () => void | ||
showContentView: () => void | ||
} | ||
|
||
const BlocksContext = createContext<null | UseBlocksReturn>(null) | ||
|
||
export const useBlocks = (): UseBlocksReturn => { | ||
const blocksContext = useContext(BlocksContext) | ||
if (!blocksContext) | ||
throw new Error( | ||
"useBlocksContext must be used within an BlocksContextProvider" | ||
) | ||
|
||
return blocksContext | ||
} | ||
|
||
export const BlocksContextProvider = ({ | ||
children, | ||
}: PropsWithChildren<unknown>): JSX.Element => { | ||
const [curTab, setCurTab] = useState<BlocksTab>("content") | ||
|
||
return ( | ||
<BlocksContext.Provider | ||
value={{ | ||
showAddView: () => setCurTab("add"), | ||
showContentView: () => setCurTab("content"), | ||
curTab, | ||
}} | ||
> | ||
{children} | ||
</BlocksContext.Provider> | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
import { | ||
Card, | ||
Flex, | ||
SimpleGrid, | ||
Text, | ||
VStack, | ||
CardHeader, | ||
CardBody, | ||
CardFooter, | ||
Icon, | ||
Grid, | ||
GridItem, | ||
ButtonGroup, | ||
} from "@chakra-ui/react" | ||
import { | ||
IconButton, | ||
Button, | ||
Infobox, | ||
Textarea, | ||
} from "@opengovsg/design-system-react" | ||
import _ from "lodash" | ||
import { useState } from "react" | ||
import { useForm } from "react-hook-form" | ||
import { BiGridAlt, BiPlus } from "react-icons/bi" | ||
import { useParams } from "react-router-dom" | ||
|
||
import { Editable } from "components/Editable" | ||
import PagePreview from "components/pages/PagePreview" | ||
|
||
import { EditorContextProvider, useEditorContext } from "contexts/EditorContext" | ||
|
||
import { useGetPageHook } from "hooks/pageHooks" | ||
|
||
import { Editor } from "../../components/Editor/Editor" | ||
import { EditPageLayout } from "../EditPageLayout" | ||
|
||
import { useBlocks } from "./BlocksContext" | ||
import { BLOCKS_CONTENT } from "./constants" | ||
import { Preview } from "./Preview" | ||
import { BlockAddView } from "./types" | ||
import { usePreviewEditor } from "./useBlockEditor" | ||
|
||
export const AddBlockView = () => { | ||
const { curTab } = useBlocks() | ||
|
||
if (curTab !== "add") return null | ||
|
||
return ( | ||
<VStack spacing="1.25rem" p="1.25rem" h="100%" align="flex-start"> | ||
<Text textStyle="subhead-3">Content Blocks</Text> | ||
<SimpleGrid columns={2} spacing="1.25rem"> | ||
{_.map(BLOCKS_CONTENT, (block) => ( | ||
<BlockContentCard {...block} /> | ||
))} | ||
</SimpleGrid> | ||
<Infobox> | ||
We are slowly introducing new content types. Looking to add a specific | ||
kind of content to your pages? Let us know here. | ||
</Infobox> | ||
</VStack> | ||
) | ||
} | ||
|
||
export const BlockContentCard = ({ | ||
title, | ||
description, | ||
icon, | ||
variant, | ||
getContent, | ||
}: BlockAddView & { getContent: (val: string) => string }) => { | ||
const { showContentView } = useBlocks() | ||
const { editor } = useEditorContext() | ||
const [isEditable, setIsEditable] = useState(false) | ||
const { register, handleSubmit } = useForm() | ||
const onSave = handleSubmit((data) => { | ||
showContentView() | ||
const { size } = editor.view.state.doc.content | ||
editor?.commands.insertContentAt( | ||
size, | ||
`<div data-type="draggable-item">${getContent(data[variant])}</div>` | ||
) | ||
}) | ||
|
||
return ( | ||
// TODO: Fix the styling for cards - have too much padding | ||
<Card shadow="none" border="1px solid" borderColor="base.divider.medium"> | ||
<CardHeader> | ||
<Flex flexDir="row" align="center"> | ||
<Icon fontSize="1.25rem" mr="0.5rem"> | ||
{icon({})} | ||
</Icon> | ||
<Text>{title}</Text> | ||
</Flex> | ||
</CardHeader> | ||
<CardBody py={0}> | ||
{isEditable ? ( | ||
<Textarea | ||
{...register(variant)} | ||
placeholder="Put your desired content here" | ||
/> | ||
) : ( | ||
<Text>{description}</Text> | ||
)} | ||
</CardBody> | ||
<CardFooter justify="flex-end"> | ||
{isEditable ? ( | ||
<ButtonGroup> | ||
<Button | ||
variant="clear" | ||
colorScheme="critical" | ||
onClick={() => setIsEditable(false)} | ||
> | ||
Cancel | ||
</Button> | ||
<Button variant="clear" onClick={onSave}> | ||
Add to page | ||
</Button> | ||
</ButtonGroup> | ||
) : ( | ||
<Button variant="clear" onClick={() => setIsEditable(true)}> | ||
Add to page | ||
</Button> | ||
)} | ||
</CardFooter> | ||
</Card> | ||
) | ||
} | ||
|
||
export const BlocksEditPage = () => { | ||
const params = useParams<{ siteName: string }>() | ||
const { data: initialPageData, isLoading: isLoadingPage } = useGetPageHook( | ||
params | ||
) | ||
const { curTab, showAddView, showContentView } = useBlocks() | ||
|
||
const editor = usePreviewEditor() | ||
|
||
if (!editor) return null | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why would this happen? is it on the first paint? |
||
|
||
return ( | ||
<EditorContextProvider editor={editor}> | ||
<EditPageLayout | ||
setEditorContent={(content) => { | ||
editor.commands.setContent(content) | ||
}} | ||
getEditorContent={() => editor.getHTML()} | ||
variant="blocks" | ||
> | ||
{/* TODO: Add in icons at bottom for guide + settings etc */} | ||
<VStack | ||
px="0.5rem" | ||
py="1rem" | ||
bg="base.canvas.default" | ||
h="100vh" | ||
borderRight="1px solid" | ||
borderColor="base.divider.medium" | ||
> | ||
<IconButton | ||
variant="clear" | ||
isActive={curTab === "content"} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if there is a list of tab and ids, can we namespace it like CurTabs.content? |
||
aria-label="Show blocks" | ||
onClick={showContentView} | ||
> | ||
<BiGridAlt /> | ||
</IconButton> | ||
<IconButton | ||
variant="clear" | ||
aria-label="Show blocks" | ||
isActive={curTab === "add"} | ||
onClick={showAddView} | ||
> | ||
<BiPlus /> | ||
</IconButton> | ||
</VStack> | ||
<Grid templateColumns="36rem 1fr" w="100%" h="100%"> | ||
<GridItem> | ||
<VStack h="100%"> | ||
<Editable.Sidebar title="Edit Content" w="36rem" h="100%" pb={0}> | ||
{curTab !== "add" && ( | ||
<Editor editor={editor} maxW="100%" maxH="100vh" /> | ||
)} | ||
<AddBlockView /> | ||
</Editable.Sidebar> | ||
</VStack> | ||
</GridItem> | ||
{/* Preview */} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove? |
||
<GridItem> | ||
<Preview | ||
title={initialPageData?.content?.frontMatter?.title || ""} | ||
/> | ||
</GridItem> | ||
</Grid> | ||
</EditPageLayout> | ||
</EditorContextProvider> | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import { Box } from "@chakra-ui/react" | ||
import { PropsWithChildren } from "react" | ||
import { useParams } from "react-router-dom" | ||
|
||
import editorStyles from "styles/isomer-cms/pages/Editor.module.scss" | ||
|
||
import { PageHeader, LeftNav } from "templates/pageComponentsV2" | ||
|
||
import { getDecodedParams } from "utils" | ||
|
||
interface PreviewProps { | ||
title: string | ||
} | ||
|
||
interface CollectionPageRouteParams { | ||
fileName: string | ||
collectionName: string | ||
} | ||
|
||
export const Preview = ({ | ||
title, | ||
children, | ||
}: PropsWithChildren<PreviewProps>) => { | ||
const params = useParams<CollectionPageRouteParams>() | ||
const pageParams = getDecodedParams( | ||
(params as unknown) as Record<string, string> | ||
) | ||
const { collectionName, fileName } = pageParams | ||
|
||
return ( | ||
<Box | ||
w="100%" | ||
h="100vh" | ||
bg="white" | ||
overflowY="auto" | ||
className={editorStyles.pageEditorMain} | ||
> | ||
<Box> | ||
<section | ||
id="display-header" | ||
className="bp-section is-small bp-section-pagetitle" | ||
> | ||
<PageHeader pageParams={{ fileName, collectionName }} title={title} /> | ||
</section> | ||
<section className="bp-section page-content-body"> | ||
<Box className="bp-container padding--top--lg padding--bottom--xl"> | ||
<Box className="row"> | ||
<LeftNav collectionName={collectionName} fileName={fileName} /> | ||
<Box | ||
className={`${"col is-8 is-offset-1-desktop is-12-touch print-content page-content-body"}`} | ||
> | ||
{children} | ||
</Box> | ||
</Box> | ||
</Box> | ||
</section> | ||
</Box> | ||
</Box> | ||
) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe can add ss in pr desc + test plan including common steps to test if all working