Skip to content

Commit

Permalink
enhance(apps/frontend-manage): introduce typing for slate editor
Browse files Browse the repository at this point in the history
  • Loading branch information
sjschlapbach committed Oct 20, 2024
1 parent 864ad79 commit 8c2dcd1
Showing 1 changed file with 94 additions and 53 deletions.
147 changes: 94 additions & 53 deletions apps/frontend-manage/src/components/common/ContentInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,35 +9,69 @@ import {
faRotateLeft,
faRotateRight,
faSuperscript,
IconDefinition,
} from '@fortawesome/free-solid-svg-icons'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import {
convertToMd,
convertToSlate,
} from '@klicker-uzh/shared-components/src/utils/slateMdConversion'
import { Tooltip } from '@uzh-bf/design-system'
import isHotkey from 'is-hotkey'
import { useTranslations } from 'next-intl'
import React, {
PropsWithChildren,
Ref,
useCallback,
useMemo,
useState,
} from 'react'
import React, { PropsWithChildren, useCallback, useMemo, useState } from 'react'
import {
BaseEditor,
createEditor,
Descendant,
Editor,
Element as SlateElement,
Transforms,
createEditor,
} from 'slate'
import { HistoryEditor, withHistory } from 'slate-history'
import { Editable, ReactEditor, Slate, useSlate, withReact } from 'slate-react'
import { twMerge } from 'tailwind-merge'

import {
convertToMd,
convertToSlate,
} from '@klicker-uzh/shared-components/src/utils/slateMdConversion'
import MediaLibrary from './MediaLibrary'

// ! START SLATE TYPE DEFINITIONS
type CustomEditor = BaseEditor & ReactEditor & HistoryEditor

type ParagraphElement = {
type: 'paragraph'
children: CustomText[]
}

type ListItemElement = {
type: 'list-item'
children: CustomText[]
}

type BlockType = 'block-quote' | 'bulleted-list' | 'numbered-list'
type BlockElement = {
type: BlockType
children: CustomElement[]
}

type FormatType = 'bold' | 'italic' | 'code'
type CustomText = {
text: string
bold?: boolean
italic?: boolean
code?: boolean
}

type CustomElement = ParagraphElement | ListItemElement | BlockElement
type CustomElementTypes = CustomElement['type']

declare module 'slate' {
interface CustomTypes {
Editor: CustomEditor
Element: CustomElement
Text: CustomText
}
}
// ! END SLATE TYPE DEFINITIONS

export interface ContentInputClassName {
root?: string
toolbar?: string
Expand All @@ -61,12 +95,11 @@ interface Props {
}
}

const HOTKEYS: Record<string, string> = {
const HOTKEYS: Record<string, FormatType> = {
'mod+b': 'bold',
'mod+i': 'italic',
}
const LIST_TYPES = ['numbered-list', 'bulleted-list']
type OrNull<T> = T | null

function ContentInput({
content,
Expand All @@ -89,7 +122,7 @@ function ContentInput({
const editor = useMemo(() => withHistory(withReact(createEditor())), [])

const editorValue = useMemo(() => {
return convertToSlate(content)
return convertToSlate(content) as Descendant[]
}, [content])

return (
Expand Down Expand Up @@ -333,20 +366,19 @@ function ContentInput({

const toggleBlock = (
editor: BaseEditor & ReactEditor & HistoryEditor,
format: string
format: BlockType
) => {
const isActive = isBlockActive(editor, format)
const isList = LIST_TYPES.includes(format)

Transforms.unwrapNodes(editor, {
match: (n) =>
!Editor.isEditor(n) &&
SlateElement.isElement(n) &&
LIST_TYPES.includes(n.type),
match: (node) =>
!Editor.isEditor(node) &&
SlateElement.isElement(node) &&
LIST_TYPES.includes(node.type),
split: true,
})
const newProperties: Partial<SlateElement> = {
// eslint-disable-next-line no-nested-ternary
const newProperties: { type: CustomElementTypes } = {
type: isActive ? 'paragraph' : isList ? 'list-item' : format,
}
Transforms.setNodes<SlateElement>(editor, newProperties)
Expand All @@ -359,7 +391,7 @@ const toggleBlock = (

const toggleMark = (
editor: BaseEditor & ReactEditor & HistoryEditor,
format: string
format: FormatType
) => {
const isActive = isMarkActive(editor, format)

Expand Down Expand Up @@ -390,7 +422,7 @@ const isBlockActive = (

const isMarkActive = (
editor: BaseEditor & ReactEditor & HistoryEditor,
format: string
format: FormatType
) => {
const marks = Editor.marks(editor)
return marks ? marks[format] === true : false
Expand Down Expand Up @@ -438,7 +470,15 @@ const Leaf = ({ attributes, children, leaf }: any) => {
return <span {...attributes}>{formattedChildren}</span>
}

const BlockButton = ({ format, icon, className }: any) => {
const BlockButton = ({
format,
icon,
className,
}: {
format: BlockType
icon: IconDefinition
className?: string
}) => {
const editor = useSlate()
return (
<SlateButton
Expand All @@ -460,7 +500,15 @@ const BlockButton = ({ format, icon, className }: any) => {
)
}

const MarkButton = ({ format, icon, className }: any) => {
const MarkButton = ({
format,
icon,
className,
}: {
format: FormatType
icon: IconDefinition
className?: string
}) => {
const editor = useSlate()
return (
<SlateButton
Expand All @@ -480,33 +528,26 @@ const MarkButton = ({ format, icon, className }: any) => {
)
}

export const SlateButton = React.forwardRef(
(
{
export const SlateButton = React.forwardRef<
HTMLSpanElement,
PropsWithChildren<{
active: boolean
reversed: boolean
className: string
[key: string]: any
}>
>(({ className, active, reversed, ...props }, ref) => (
<span
{...props}
className={twMerge(
className,
active,
reversed,
...props
}: PropsWithChildren<{
active: boolean
reversed: boolean
className: string
[key: string]: unknown
}>,
ref: Ref<OrNull<HTMLSpanElement>>
) => (
<span
{...props}
className={twMerge(
className,
'my-auto flex h-7 w-7 cursor-pointer items-center justify-center rounded',
active && !reversed && 'bg-uzh-grey-40',
!active && reversed && 'bg-uzh-grey-40'
)}
ref={ref}
/>
)
)
'my-auto flex h-7 w-7 cursor-pointer items-center justify-center rounded',
active && !reversed && 'bg-uzh-grey-40',
!active && reversed && 'bg-uzh-grey-40'
)}
ref={ref}
/>
))
SlateButton.displayName = 'Button'

export default ContentInput

0 comments on commit 8c2dcd1

Please sign in to comment.