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 ModalOverlay from '@/Components/Modal/ModalOverlay'
|
||||||
import MobileToolbarPlugin from './Plugins/MobileToolbarPlugin/MobileToolbarPlugin'
|
import MobileToolbarPlugin from './Plugins/MobileToolbarPlugin/MobileToolbarPlugin'
|
||||||
import { SuperEditorNodes } from './SuperEditorNodes'
|
import { SuperEditorNodes } from './SuperEditorNodes'
|
||||||
|
import CodeOptionsPlugin from './Plugins/CodeOptionsPlugin/CodeOptions'
|
||||||
|
|
||||||
export const SuperNotePreviewCharLimit = 160
|
export const SuperNotePreviewCharLimit = 160
|
||||||
|
|
||||||
@@ -205,6 +206,7 @@ export const SuperEditor: FunctionComponent<Props> = ({
|
|||||||
<SearchPlugin />
|
<SearchPlugin />
|
||||||
</SuperSearchContextProvider>
|
</SuperSearchContextProvider>
|
||||||
<MobileToolbarPlugin />
|
<MobileToolbarPlugin />
|
||||||
|
<CodeOptionsPlugin />
|
||||||
</BlocksEditor>
|
</BlocksEditor>
|
||||||
</BlocksEditorComposer>
|
</BlocksEditorComposer>
|
||||||
</FilesControllerProvider>
|
</FilesControllerProvider>
|
||||||
|
|||||||
Reference in New Issue
Block a user