feat: Added language selection dropdown in Super when cursor is in a code block
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
|
||||
import { $findMatchingParent, mergeRegister } from '@lexical/utils'
|
||||
import { $getNodeByKey, $getSelection, $isRangeSelection, $isRootOrShadowRoot, NodeKey } from 'lexical'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { $isCodeNode, CODE_LANGUAGE_MAP, CODE_LANGUAGE_FRIENDLY_NAME_MAP, normalizeCodeLang } from '@lexical/code'
|
||||
import Dropdown from '@/Components/Dropdown/Dropdown'
|
||||
|
||||
function getCodeLanguageOptions(): [string, string][] {
|
||||
const options: [string, string][] = []
|
||||
|
||||
for (const [lang, friendlyName] of Object.entries(CODE_LANGUAGE_FRIENDLY_NAME_MAP)) {
|
||||
options.push([lang, friendlyName])
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
const CODE_LANGUAGE_OPTIONS = getCodeLanguageOptions()
|
||||
|
||||
const CodeOptionsPlugin = () => {
|
||||
const [editor] = useLexicalComposerContext()
|
||||
|
||||
const [isCode, setIsCode] = useState(false)
|
||||
const [codeLanguage, setCodeLanguage] = useState<keyof typeof CODE_LANGUAGE_MAP>('')
|
||||
const [selectedElementKey, setSelectedElementKey] = useState<NodeKey | null>(null)
|
||||
|
||||
const updateToolbar = useCallback(() => {
|
||||
const selection = $getSelection()
|
||||
if (!$isRangeSelection(selection)) {
|
||||
return
|
||||
}
|
||||
|
||||
const anchorNode = selection.anchor.getNode()
|
||||
let element =
|
||||
anchorNode.getKey() === 'root'
|
||||
? anchorNode
|
||||
: $findMatchingParent(anchorNode, (e) => {
|
||||
const parent = e.getParent()
|
||||
return parent !== null && $isRootOrShadowRoot(parent)
|
||||
})
|
||||
|
||||
if (element === null) {
|
||||
element = anchorNode.getTopLevelElementOrThrow()
|
||||
}
|
||||
|
||||
const elementKey = element.getKey()
|
||||
const elementDOM = editor.getElementByKey(elementKey)
|
||||
|
||||
if (elementDOM !== null) {
|
||||
setSelectedElementKey(elementKey)
|
||||
if ($isCodeNode(element)) {
|
||||
setIsCode(true)
|
||||
const language = element.getLanguage() as keyof typeof CODE_LANGUAGE_MAP
|
||||
setCodeLanguage(language ? CODE_LANGUAGE_MAP[language] || language : '')
|
||||
} else {
|
||||
setIsCode(false)
|
||||
}
|
||||
}
|
||||
}, [editor])
|
||||
|
||||
useEffect(() => {
|
||||
return mergeRegister(
|
||||
editor.registerUpdateListener(({ editorState }) => {
|
||||
editorState.read(() => {
|
||||
updateToolbar()
|
||||
})
|
||||
}),
|
||||
)
|
||||
}, [editor, updateToolbar])
|
||||
|
||||
const onCodeLanguageSelect = useCallback(
|
||||
(value: string) => {
|
||||
editor.update(() => {
|
||||
if (selectedElementKey !== null) {
|
||||
const node = $getNodeByKey(selectedElementKey)
|
||||
if ($isCodeNode(node)) {
|
||||
node.setLanguage(value)
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
[editor, selectedElementKey],
|
||||
)
|
||||
|
||||
if (!isCode) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="absolute top-2 right-6 rounded border border-border bg-default p-2">
|
||||
<Dropdown
|
||||
id="code-language-dropdown"
|
||||
label="Change code block language"
|
||||
items={CODE_LANGUAGE_OPTIONS.map(([value, label]) => ({
|
||||
label,
|
||||
value,
|
||||
}))}
|
||||
value={normalizeCodeLang(codeLanguage)}
|
||||
onChange={(value: string) => {
|
||||
onCodeLanguageSelect(value)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default CodeOptionsPlugin
|
||||
@@ -43,6 +43,7 @@ import { SearchPlugin } from './Plugins/SearchPlugin/SearchPlugin'
|
||||
import ModalOverlay from '@/Components/Modal/ModalOverlay'
|
||||
import MobileToolbarPlugin from './Plugins/MobileToolbarPlugin/MobileToolbarPlugin'
|
||||
import { SuperEditorNodes } from './SuperEditorNodes'
|
||||
import CodeOptionsPlugin from './Plugins/CodeOptionsPlugin/CodeOptions'
|
||||
|
||||
export const SuperNotePreviewCharLimit = 160
|
||||
|
||||
@@ -205,6 +206,7 @@ export const SuperEditor: FunctionComponent<Props> = ({
|
||||
<SearchPlugin />
|
||||
</SuperSearchContextProvider>
|
||||
<MobileToolbarPlugin />
|
||||
<CodeOptionsPlugin />
|
||||
</BlocksEditor>
|
||||
</BlocksEditorComposer>
|
||||
</FilesControllerProvider>
|
||||
|
||||
Reference in New Issue
Block a user