Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions frontends/ol-components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"@remixicon/react": "^4.2.0",
"@testing-library/dom": "^10.4.0",
"@tiptap/core": "^3.11.0",
"@tiptap/extension-blockquote": "^3.11.0",
"@tiptap/extension-document": "^3.11.1",
"@tiptap/extension-heading": "^3.11.1",
"@tiptap/extension-highlight": "^3.11.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import { Alert, Button, ButtonLink } from "@mitodl/smoot-design"
import Typography from "@mui/material/Typography"
import { useUserHasPermission, Permission } from "api/hooks/user"
import { BannerNode } from "./extensions/node/Banner/BannerNode"
import { Quote } from "./extensions/node/Quote/Quote"
import {
HEADER_HEIGHT,
HEADER_HEIGHT_MD,
Expand Down Expand Up @@ -274,6 +275,7 @@ const ArticleEditor = ({ onSave, readOnly, article }: ArticleEditorProps) => {
Subscript,
Selection,
Image,
Quote,
MediaEmbedNode,
DividerNode,
ArticleByLineInfoBarNode,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { UndoRedoButton } from "./vendor/components/tiptap-ui/undo-redo-button"
import { LearningResourceButton } from "./extensions/ui/LearningResource/LearningResourceButton"
import { Button } from "./vendor/components/tiptap-ui-primitive/button"
import { DividerButton } from "./extensions/ui/Divider/DividerButton"
import { QuoteButton } from "./extensions/ui/Quote/QuoteButton"
import {
DropdownMenu,
DropdownMenuTrigger,
Expand Down Expand Up @@ -135,6 +136,23 @@ const StyledEditorContent = styled(EditorContent, {
marginBottom: 0,
},
},
quote: {
backgroundColor: theme.custom.colors.black,
padding: "40px",
color: theme.custom.colors.white,
borderRadius: "8px",
marginBottom: "40px",
display: "block",
":before": {
display: "none",
},
p: {
position: "relative",
},
"p:last-child": {
marginBottom: 0,
},
},
},
}))

Expand Down Expand Up @@ -180,6 +198,9 @@ export function InsertDropdownMenu({ editor }: TiptapEditorToolbarProps) {
<DropdownMenuItem asChild>
<DividerButton editor={editor} text="Divider" />
</DropdownMenuItem>
<DropdownMenuItem asChild>
<QuoteButton text="Quote" />
</DropdownMenuItem>
</StyledDropdownMenuWrapper>
</DropdownMenu>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Blockquote from "@tiptap/extension-blockquote"

export const Quote = Blockquote.extend({
name: "quote",

parseHTML() {
return [
{ tag: "quote" }, // Custom tag
{ tag: "blockquote" }, // Fallback for pasted content
]
},

// eslint-disable-next-line @typescript-eslint/no-explicit-any
renderHTML({ HTMLAttributes }: { HTMLAttributes: { [key: string]: any } }) {
return ["quote", HTMLAttributes, 0]
},
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import React, { forwardRef, useCallback } from "react"

// --- Tiptap UI ---
import type { UseQuoteConfig } from "./"
import { QUOTE_SHORTCUT_KEY, useQuote } from "./"

// --- Hooks ---
import { useTiptapEditor } from "../../../vendor/hooks/use-tiptap-editor"

// --- Lib ---
import { parseShortcutKeys } from "../../../vendor/lib/tiptap-utils"

// --- UI Primitives ---
import type { ButtonProps } from "../../../vendor/components/tiptap-ui-primitive/button"
import { Button } from "../../../vendor/components/tiptap-ui-primitive/button"
import { Badge } from "../../../vendor/components/tiptap-ui-primitive/badge"

export interface QuoteButtonProps
extends Omit<ButtonProps, "type">,
UseQuoteConfig {
/**
* Optional text to display alongside the icon.
*/
text?: string
/**
* Optional show shortcut keys in the button.
* @default false
*/
showShortcut?: boolean
}

export function QuoteShortcutBadge({
shortcutKeys = QUOTE_SHORTCUT_KEY,
}: {
shortcutKeys?: string
}) {
return <Badge>{parseShortcutKeys({ shortcutKeys })}</Badge>
}

/**
* Button component for toggling blockquote in a Tiptap editor.
*
* For custom button implementations, use the `useQuote` hook instead.
*/
export const QuoteButton = forwardRef<HTMLButtonElement, QuoteButtonProps>(
(
{
editor: providedEditor,
text,
hideWhenUnavailable = false,
onToggled,
showShortcut = false,
onClick,
children,
...buttonProps
},
ref,
) => {
const { editor } = useTiptapEditor(providedEditor)
const {
isVisible,
canToggle,
isActive,
handleToggle,
label,
shortcutKeys,
Icon,
} = useQuote({
editor,
hideWhenUnavailable,
onToggled,
})

const handleClick = useCallback(
(event: React.MouseEvent<HTMLButtonElement>) => {
onClick?.(event)
if (event.defaultPrevented) return
handleToggle()
},
[handleToggle, onClick],
)

if (!isVisible) {
return null
}

return (
<Button
type="button"
data-style="ghost"
data-active-state={isActive ? "on" : "off"}
tabIndex={-1}
disabled={!canToggle}
data-disabled={!canToggle}
aria-label={label}
aria-pressed={isActive}
tooltip="Quote"
onClick={handleClick}
{...buttonProps}
ref={ref}
>
{children ?? (
<>
<Icon className="tiptap-button-icon" />
{text && <span className="tiptap-button-text">{text}</span>}
{showShortcut && <QuoteShortcutBadge shortcutKeys={shortcutKeys} />}
</>
)}
</Button>
)
},
)

QuoteButton.displayName = "QuoteButton"
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./QuoteButton"
export * from "./useQuote"
Loading
Loading