feat: implement bullet lists in the editor

This commit is contained in:
swve 2025-04-18 15:24:06 +02:00
parent 3d489c599e
commit c605c2a8f4
5 changed files with 151 additions and 21 deletions

View file

@ -58,7 +58,18 @@ function Canva(props: Editor) {
const editor: any = useEditor({ const editor: any = useEditor({
editable: isEditable, editable: isEditable,
extensions: [ extensions: [
StarterKit, StarterKit.configure({
bulletList: {
HTMLAttributes: {
class: 'bullet-list',
},
},
orderedList: {
HTMLAttributes: {
class: 'ordered-list',
},
},
}),
NoTextInput, NoTextInput,
// Custom Extensions // Custom Extensions
InfoCallout.configure({ InfoCallout.configure({
@ -213,6 +224,13 @@ const CanvaWrapper = styled.div`
ol { ol {
padding: 0 1rem; padding: 0 1rem;
padding-left: 20px; padding-left: 20px;
}
ul {
list-style-type: disc;
}
ol {
list-style-type: decimal; list-style-type: decimal;
} }

View file

@ -35,7 +35,6 @@ import { getCourseThumbnailMediaDirectory } from '@services/media/media'
import { getLinkExtension } from './EditorConf' import { getLinkExtension } from './EditorConf'
import { Link as LinkExtension } from '@tiptap/extension-link' import { Link as LinkExtension } from '@tiptap/extension-link'
// Lowlight // Lowlight
import { common, createLowlight } from 'lowlight' import { common, createLowlight } from 'lowlight'
const lowlight = createLowlight(common) const lowlight = createLowlight(common)
@ -97,7 +96,18 @@ function Editor(props: Editor) {
const editor: any = useEditor({ const editor: any = useEditor({
editable: true, editable: true,
extensions: [ extensions: [
StarterKit, StarterKit.configure({
bulletList: {
HTMLAttributes: {
class: 'bullet-list',
},
},
orderedList: {
HTMLAttributes: {
class: 'ordered-list',
},
},
}),
InfoCallout.configure({ InfoCallout.configure({
editable: true, editable: true,
}), }),
@ -580,6 +590,13 @@ export const EditorContentWrapper = styled.div`
ol { ol {
padding: 0 1rem; padding: 0 1rem;
padding-left: 20px; padding-left: 20px;
}
ul {
list-style-type: disc;
}
ol {
list-style-type: decimal; list-style-type: decimal;
} }

View file

@ -31,6 +31,8 @@ import {
Tags, Tags,
User, User,
Video, Video,
List,
ListOrdered,
} from 'lucide-react' } from 'lucide-react'
import { SiYoutube } from '@icons-pack/react-simple-icons' import { SiYoutube } from '@icons-pack/react-simple-icons'
import ToolTip from '@components/Objects/StyledElements/Tooltip/Tooltip' import ToolTip from '@components/Objects/StyledElements/Tooltip/Tooltip'
@ -39,6 +41,7 @@ import LinkInputTooltip from './LinkInputTooltip'
export const ToolbarButtons = ({ editor, props }: any) => { export const ToolbarButtons = ({ editor, props }: any) => {
const [showTableMenu, setShowTableMenu] = React.useState(false) const [showTableMenu, setShowTableMenu] = React.useState(false)
const [showListMenu, setShowListMenu] = React.useState(false)
const [showLinkInput, setShowLinkInput] = React.useState(false) const [showLinkInput, setShowLinkInput] = React.useState(false)
const linkButtonRef = React.useRef<HTMLDivElement>(null) const linkButtonRef = React.useRef<HTMLDivElement>(null)
@ -46,18 +49,6 @@ export const ToolbarButtons = ({ editor, props }: any) => {
return null return null
} }
// YouTube extension
const addYoutubeVideo = () => {
const url = prompt('Enter YouTube URL')
if (url) {
editor.commands.setYoutubeVideo({
src: url,
width: 640,
height: 480,
})
}
}
const tableOptions = [ const tableOptions = [
{ {
@ -87,6 +78,33 @@ export const ToolbarButtons = ({ editor, props }: any) => {
} }
] ]
const listOptions = [
{
label: 'Bullet List',
icon: <List size={15} />,
action: () => {
if (editor.isActive('bulletList')) {
editor.chain().focus().toggleBulletList().run()
} else {
editor.chain().focus().toggleOrderedList().run()
editor.chain().focus().toggleBulletList().run()
}
}
},
{
label: 'Ordered List',
icon: <ListOrdered size={15} />,
action: () => {
if (editor.isActive('orderedList')) {
editor.chain().focus().toggleOrderedList().run()
} else {
editor.chain().focus().toggleBulletList().run()
editor.chain().focus().toggleOrderedList().run()
}
}
}
]
const handleLinkClick = () => { const handleLinkClick = () => {
// Store the current selection // Store the current selection
const { from, to } = editor.state.selection const { from, to } = editor.state.selection
@ -154,12 +172,32 @@ export const ToolbarButtons = ({ editor, props }: any) => {
> >
<StrikethroughIcon /> <StrikethroughIcon />
</ToolBtn> </ToolBtn>
<ToolBtn <ListMenuWrapper>
onClick={() => editor.chain().focus().toggleOrderedList().run()} <ToolBtn
className={editor.isActive('orderedList') ? 'is-active' : ''} onClick={() => setShowListMenu(!showListMenu)}
> className={showListMenu || editor.isActive('bulletList') || editor.isActive('orderedList') ? 'is-active' : ''}
<ListBulletIcon /> >
</ToolBtn> <ListBulletIcon />
<ChevronDownIcon />
</ToolBtn>
{showListMenu && (
<ListDropdown>
{listOptions.map((option, index) => (
<ListMenuItem
key={index}
onClick={() => {
option.action()
setShowListMenu(false)
}}
className={editor.isActive(option.label === 'Bullet List' ? 'bulletList' : 'orderedList') ? 'is-active' : ''}
>
<span className="icon">{option.icon}</span>
<span className="label">{option.label}</span>
</ListMenuItem>
))}
</ListDropdown>
)}
</ListMenuWrapper>
<ToolSelect <ToolSelect
value={ value={
editor.isActive('heading', { level: 1 }) ? "1" : editor.isActive('heading', { level: 1 }) ? "1" :
@ -486,6 +524,51 @@ const TableMenuItem = styled.div`
align-items: center; align-items: center;
} }
.label {
font-size: 12px;
font-family: 'DM Sans';
}
`
const ListMenuWrapper = styled.div`
position: relative;
display: inline-block;
`
const ListDropdown = styled.div`
position: absolute;
top: 100%;
left: 0;
background: white;
border: 1px solid rgba(217, 217, 217, 0.5);
border-radius: 6px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
z-index: 1000;
min-width: 180px;
margin-top: 4px;
`
const ListMenuItem = styled.div`
display: flex;
align-items: center;
padding: 8px 12px;
cursor: pointer;
transition: background 0.2s;
&:hover {
background: rgba(217, 217, 217, 0.24);
}
&.is-active {
background: rgba(176, 176, 176, 0.5);
}
.icon {
margin-right: 8px;
display: flex;
align-items: center;
}
.label { .label {
font-size: 12px; font-size: 12px;
font-family: 'DM Sans'; font-family: 'DM Sans';

View file

@ -33,8 +33,11 @@
"@stitches/react": "^1.2.8", "@stitches/react": "^1.2.8",
"@tanstack/react-table": "^8.21.2", "@tanstack/react-table": "^8.21.2",
"@tiptap/core": "^2.11.7", "@tiptap/core": "^2.11.7",
"@tiptap/extension-bullet-list": "^2.11.7",
"@tiptap/extension-code-block-lowlight": "^2.11.7", "@tiptap/extension-code-block-lowlight": "^2.11.7",
"@tiptap/extension-link": "^2.11.7", "@tiptap/extension-link": "^2.11.7",
"@tiptap/extension-list-item": "^2.11.7",
"@tiptap/extension-ordered-list": "^2.11.7",
"@tiptap/extension-table": "^2.11.7", "@tiptap/extension-table": "^2.11.7",
"@tiptap/extension-table-cell": "^2.11.7", "@tiptap/extension-table-cell": "^2.11.7",
"@tiptap/extension-table-header": "^2.11.7", "@tiptap/extension-table-header": "^2.11.7",

View file

@ -78,12 +78,21 @@ importers:
'@tiptap/core': '@tiptap/core':
specifier: ^2.11.7 specifier: ^2.11.7
version: 2.11.7(@tiptap/pm@2.11.7) version: 2.11.7(@tiptap/pm@2.11.7)
'@tiptap/extension-bullet-list':
specifier: ^2.11.7
version: 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))
'@tiptap/extension-code-block-lowlight': '@tiptap/extension-code-block-lowlight':
specifier: ^2.11.7 specifier: ^2.11.7
version: 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/extension-code-block@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7)(highlight.js@11.11.1)(lowlight@3.3.0) version: 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/extension-code-block@2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7)(highlight.js@11.11.1)(lowlight@3.3.0)
'@tiptap/extension-link': '@tiptap/extension-link':
specifier: ^2.11.7 specifier: ^2.11.7
version: 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7) version: 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7)
'@tiptap/extension-list-item':
specifier: ^2.11.7
version: 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))
'@tiptap/extension-ordered-list':
specifier: ^2.11.7
version: 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))
'@tiptap/extension-table': '@tiptap/extension-table':
specifier: ^2.11.7 specifier: ^2.11.7
version: 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7) version: 2.11.7(@tiptap/core@2.11.7(@tiptap/pm@2.11.7))(@tiptap/pm@2.11.7)