Skip to content

Commit

Permalink
chore: use CM for code blocks
Browse files Browse the repository at this point in the history
  • Loading branch information
petyosi committed May 5, 2023
1 parent 03d4fac commit 0536ada
Show file tree
Hide file tree
Showing 16 changed files with 361 additions and 166 deletions.
1 change: 1 addition & 0 deletions src/content/code-highlight.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
top: 0;
border-right: 1px solid #ccc;
padding: 8px;

color: #777;
white-space: pre-wrap;
text-align: right;
Expand Down
10 changes: 6 additions & 4 deletions src/export/visitors.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { $isCodeNode, CodeNode } from '@lexical/code'
import { $isLinkNode, LinkNode } from '@lexical/link'
import { $isListItemNode, $isListNode, ListItemNode, ListNode } from '@lexical/list'
import { $isHorizontalRuleNode, HorizontalRuleNode } from '@lexical/react/LexicalHorizontalRuleNode'
Expand Down Expand Up @@ -28,6 +27,8 @@ import {
ImageNode,
$isFrontmatterNode,
FrontmatterNode,
$isCodeBlockNode,
CodeBlockNode,
} from '../nodes'

import { IS_BOLD, IS_CODE, IS_ITALIC, IS_UNDERLINE } from '../FormatConstants'
Expand Down Expand Up @@ -157,12 +158,13 @@ export const SandpackNodeVisitor: LexicalExportVisitor<SandpackNode, Mdast.Code>
},
}

export const CodeBlockVisitor: LexicalExportVisitor<CodeNode, Mdast.Code> = {
testLexicalNode: $isCodeNode,
export const CodeBlockVisitor: LexicalExportVisitor<CodeBlockNode, Mdast.Code> = {
testLexicalNode: $isCodeBlockNode,
visitLexicalNode: ({ lexicalNode, actions }) => {
actions.addAndStepInto('code', {
value: lexicalNode.getCode(),
lang: lexicalNode.getLanguage(),
value: lexicalNode.getTextContent(),
meta: lexicalNode.getMeta(),
})
},
}
Expand Down
34 changes: 19 additions & 15 deletions src/import/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { $createCodeNode, CodeHighlightNode, CodeNode } from '@lexical/code'
import { $createLinkNode, LinkNode } from '@lexical/link'
import { CodeNode } from '@lexical/code'
import { $createListItemNode, $createListNode, $isListItemNode, ListItemNode, ListNode } from '@lexical/list'
import { $createHorizontalRuleNode, HorizontalRuleNode } from '@lexical/react/LexicalHorizontalRuleNode'
import { $createHeadingNode, $createQuoteNode, $isQuoteNode, HeadingNode, QuoteNode } from '@lexical/rich-text'
Expand All @@ -26,6 +26,7 @@ import {
SandpackNode,
} from '../nodes'
import { $createJsxNode, JsxNode } from '../nodes/Jsx'
import { CodeBlockNode, $createCodeBlockNode } from '../nodes/CodeBlock'

type MdastNode = Mdast.Content

Expand Down Expand Up @@ -152,17 +153,15 @@ export const MdastInlineCodeVisitor: MdastImportVisitor<Mdast.InlineCode> = {
export const MdastCodeVisitor: MdastImportVisitor<Mdast.Code> = {
testNode: 'code',
visitNode({ mdastNode, actions }) {
if (mdastNode.meta?.startsWith('live')) {
actions.addAndStepInto(
$createSandpackNode({
code: mdastNode.value,
language: mdastNode.lang!,
meta: mdastNode.meta,
})
)
} else {
actions.addAndStepInto($createCodeNode(mdastNode.lang).append($createTextNode(mdastNode.value)))
}
const constructor = mdastNode.meta?.startsWith('live') ? $createSandpackNode : $createCodeBlockNode

actions.addAndStepInto(
constructor({
code: mdastNode.value,
language: mdastNode.lang!,
meta: mdastNode.meta!,
})
)
},
}

Expand Down Expand Up @@ -221,7 +220,7 @@ export const MdastMdxJsxElementVisitor: MdastImportVisitor<MdxJsxTextElement> =
$createJsxNode({
name: mdastNode.name!,
kind: mdastNode.type === 'mdxJsxTextElement' ? 'text' : 'flow',
//TODO: expressions are nto supported yet
//TODO: expressions are not supported yet
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
attributes: mdastNode.attributes as any,
updateFn: (lexicalParent) => {
Expand Down Expand Up @@ -323,9 +322,14 @@ export const UsedLexicalNodes = [
HorizontalRuleNode,
ImageNode,
SandpackNode,
CodeNode,
CodeHighlightNode,
CodeBlockNode,
FrontmatterNode,
AdmonitionNode,
JsxNode,
{
replace: CodeNode,
with: (node: CodeNode) => {
return new CodeBlockNode('Replaced?', 'js', '')
},
},
]
135 changes: 135 additions & 0 deletions src/nodes/CodeBlock/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { DecoratorNode, EditorConfig, LexicalNode, NodeKey, SerializedLexicalNode, Spread } from 'lexical'
import React from 'react'
import { useEmitterValues } from '../../system'
import { noop } from '../../utils/fp'
import { CodeBlockEditorProps } from '../../types/NodeDecoratorsProps'

export interface CodeBlockPayload {
code: string
meta: string
language: string
}

export type SerializedCodeBlockNode = Spread<CodeBlockPayload & { type: 'sandpack'; version: 1 }, SerializedLexicalNode>

function voidEmitter() {
let subscription = noop
return {
publish: () => {
subscription()
},
subscribe: (cb: () => void) => {
subscription = cb
},
}
}

function InternalCodeBlockEditor(props: CodeBlockEditorProps) {
const [{ CodeBlockEditor }] = useEmitterValues('nodeDecorators')
return <CodeBlockEditor {...props} />
}

export class CodeBlockNode extends DecoratorNode<JSX.Element> {
__code: string
__meta: string
__language: string
__focusEmitter = voidEmitter()

static getType(): string {
return 'codeblock'
}

static clone(node: CodeBlockNode): CodeBlockNode {
return new CodeBlockNode(node.__code, node.__language, node.__meta, node.__key)
}

static importJSON(serializedNode: SerializedCodeBlockNode): CodeBlockNode {
const { code, meta, language } = serializedNode
return $createCodeBlockNode({
code,
language,
meta,
})
}

constructor(code: string, language: string, meta: string, key?: NodeKey) {
super(key)
this.__code = code
this.__meta = meta
this.__language = language
}

exportJSON(): SerializedCodeBlockNode {
return {
code: this.getCode(),
language: this.getLanguage(),
meta: this.getMeta(),
type: 'sandpack',
version: 1,
}
}

// View
createDOM(_config: EditorConfig): HTMLDivElement {
return document.createElement('div')
}

updateDOM(): false {
return false
}

getCode(): string {
return this.getLatest().__code
}

getMeta(): string {
return this.getLatest().__meta
}

getLanguage(): string {
return this.getLatest().__language
}

setCode(code: string) {
if (code !== this.__code) {
this.getWritable().__code = code
}
}

setMeta(meta: string) {
if (meta !== this.__meta) {
this.getWritable().__meta = meta
}
}

setLanguage(language: string) {
if (language !== this.__language) {
this.getWritable().__language = language
}
}

select() {
this.__focusEmitter.publish()
}

decorate(): JSX.Element {
return (
<InternalCodeBlockEditor
nodeKey={this.getKey()}
code={this.getCode()}
meta={this.getMeta()}
language={this.getLanguage()}
onChange={(code) => this.setCode(code)}
focusEmitter={this.__focusEmitter}
/>
)
}
}

export function $createCodeBlockNode({ code, language, meta }: CodeBlockPayload): CodeBlockNode {
return new CodeBlockNode(code, language, meta)
}

export function $isCodeBlockNode(node: LexicalNode | null | undefined): node is CodeBlockNode {
return node instanceof CodeBlockNode
}
1 change: 1 addition & 0 deletions src/nodes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * from './Sandpack'
export * from './Frontmatter'
export * from './Admonition'
export * from './Jsx'
export * from './CodeBlock'
25 changes: 4 additions & 21 deletions src/system/Editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
REMOVE_LIST_COMMAND,
} from '@lexical/list'
import { $isHeadingNode } from '@lexical/rich-text'
import { $findMatchingParent, $getNearestNodeOfType, $insertNodeToNearestRoot } from '@lexical/utils'
import { $findMatchingParent, $getNearestNodeOfType } from '@lexical/utils'
import {
$getSelection,
$isRangeSelection,
Expand All @@ -34,6 +34,7 @@ import {
formatQuote,
} from '../ui/ToolbarPlugin/BlockTypeSelect/blockFormatters'
import { INSERT_HORIZONTAL_RULE_COMMAND } from '@lexical/react/LexicalHorizontalRuleNode'
import { ActiveEditorType } from '../types/ActiveEditorType'

type Teardowns = Array<() => void>

Expand All @@ -56,11 +57,11 @@ export const [EditorSystem, EditorSystemType] = system((r) => {
const applyFormat = r.node<TextFormatType>()
const applyListType = r.node<ListType | ''>()
const applyBlockType = r.node<BlockType | AdmonitionKind>()
const insertCodeBlock = r.node<true>()
const insertHorizontalRule = r.node<true>()
const createEditorSubscription = r.node<EditorSubscription>()
const editorSubscriptions = r.node<EditorSubscription[]>([])
const inFocus = r.node(false, true)
const activeEditorType = r.node<ActiveEditorType>({ type: 'lexical' })

r.link(
r.pipe(
Expand All @@ -73,24 +74,6 @@ export const [EditorSystem, EditorSystemType] = system((r) => {
editorSubscriptions
)

r.sub(r.pipe(insertCodeBlock, r.o.withLatestFrom(activeEditor)), ([, theEditor]) => {
theEditor?.getEditorState().read(() => {
const selection = $getSelection()

if ($isRangeSelection(selection)) {
const focusNode = selection.focus.getNode()

if (focusNode !== null) {
theEditor.update(() => {
const codeBlockNode = $createCodeNode()
$insertNodeToNearestRoot(codeBlockNode)
codeBlockNode.select()
})
}
}
})
})

r.sub(r.pipe(applyListType, r.o.withLatestFrom(activeEditor)), ([listType, theEditor]) => {
theEditor?.dispatchCommand(ListTypeCommandMap.get(listType)!, undefined)
})
Expand Down Expand Up @@ -248,10 +231,10 @@ export const [EditorSystem, EditorSystemType] = system((r) => {
applyFormat,
applyListType,
applyBlockType,
insertCodeBlock,
insertHorizontalRule,
createEditorSubscription,
editorSubscriptions,
activeEditorType,
inFocus,
}
}, [])
4 changes: 3 additions & 1 deletion src/system/NodeDecorators.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { system } from '../gurx'
import React from 'react'
import { FrontmatterEditorProps, JsxEditorProps, SandpackEditorProps } from '../types/NodeDecoratorsProps'
import { CodeBlockEditorProps, FrontmatterEditorProps, JsxEditorProps, SandpackEditorProps } from '../types/NodeDecoratorsProps'

function ComponentStub(componentName: string) {
return () => {
Expand All @@ -13,13 +13,15 @@ export interface NodeDecorators {
FrontmatterEditor: React.FC<FrontmatterEditorProps>
JsxEditor: React.FC<JsxEditorProps>
SandpackEditor: React.FC<SandpackEditorProps>
CodeBlockEditor: React.FC<CodeBlockEditorProps>
}

export const [NodeDecoratorsSystem, NodeDecoratorsSystemType] = system((r) => {
const nodeDecorators = r.node<NodeDecorators>({
FrontmatterEditor: ComponentStub('FrontmatterEditor'),
JsxEditor: ComponentStub('JsxEditor'),
SandpackEditor: ComponentStub('SandpackEditor'),
CodeBlockEditor: ComponentStub('CodeBlockEditor'),
})

return {
Expand Down
Loading

0 comments on commit 0536ada

Please sign in to comment.