mirror of
https://github.com/rzmk/learnhouse.git
synced 2025-12-19 04:19:25 +00:00
feat: implement bullet lists in the editor
This commit is contained in:
parent
3d489c599e
commit
c605c2a8f4
5 changed files with 151 additions and 21 deletions
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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';
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
9
apps/web/pnpm-lock.yaml
generated
9
apps/web/pnpm-lock.yaml
generated
|
|
@ -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)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue