Skip to content

Commit

Permalink
chore: improve code highlighting
Browse files Browse the repository at this point in the history
  • Loading branch information
petyosi committed May 4, 2023
1 parent 63ab32e commit 03d4fac
Show file tree
Hide file tree
Showing 7 changed files with 161 additions and 128 deletions.
48 changes: 48 additions & 0 deletions src/content/code-highlight.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
.codeBlock {
@apply block relative p-2 pl-16 m-0 my-2 font-normal overflow-auto;
tab-size: 2;
}

.codeBlock::before {
content: attr(data-gutter);
position: absolute;
background-color: #eee;
left: 0;
top: 0;
border-right: 1px solid #ccc;
padding: 8px;
color: #777;
white-space: pre-wrap;
text-align: right;
min-width: 25px;
}

.codeBlock::after {
content: '';
}

.tokenPunctuation {
@apply text-gray-400;
}

.tokenProperty {
@apply text-fuchsia-800;
}
.tokenSelector {
@apply text-emerald-600;
}

.tokenOperator {
@apply text-orange-800;
}

.tokenAttr {
@apply text-sky-700;
}

.tokenVariable {
@apply text-yellow-600;
}
.tokenFunction {
@apply text-rose-600;
}
2 changes: 1 addition & 1 deletion src/content/theme.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { EditorThemeClasses } from 'lexical'

export const theme: EditorThemeClasses = {
code: 'codeBlock',
code: 'not-prose codeBlock',
codeHighlight: {
atrule: 'tokenAttr',
attr: 'tokenAttr',
Expand Down
2 changes: 2 additions & 0 deletions src/style.css
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
@import url('./content/code-highlight.css') layer(components);

@tailwind components;
@tailwind utilities;
7 changes: 7 additions & 0 deletions src/system/Editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
formatParagraph,
formatQuote,
} from '../ui/ToolbarPlugin/BlockTypeSelect/blockFormatters'
import { INSERT_HORIZONTAL_RULE_COMMAND } from '@lexical/react/LexicalHorizontalRuleNode'

type Teardowns = Array<() => void>

Expand All @@ -56,6 +57,7 @@ export const [EditorSystem, EditorSystemType] = system((r) => {
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)
Expand Down Expand Up @@ -97,6 +99,10 @@ export const [EditorSystem, EditorSystemType] = system((r) => {
theEditor?.dispatchCommand(FORMAT_TEXT_COMMAND, format)
})

r.sub(r.pipe(insertHorizontalRule, r.o.withLatestFrom(activeEditor)), ([, theEditor]) => {
theEditor?.dispatchCommand(INSERT_HORIZONTAL_RULE_COMMAND, undefined)
})

r.sub(r.pipe(applyBlockType, r.o.withLatestFrom(activeEditor)), ([type, theEditor]) => {
if (theEditor) {
switch (type) {
Expand Down Expand Up @@ -243,6 +249,7 @@ export const [EditorSystem, EditorSystemType] = system((r) => {
applyListType,
applyBlockType,
insertCodeBlock,
insertHorizontalRule,
createEditorSubscription,
editorSubscriptions,
inFocus,
Expand Down
1 change: 1 addition & 0 deletions src/system/Sandpack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ export const [SandpackSystem] = system(
// TODO: hack, decoration is not synchronous ;(
setTimeout(() => {
sandpackNode.select()
r.pub(activeSandpackNode, { nodeKey: sandpackNode.getKey() })
}, 100)
})
}
Expand Down
174 changes: 72 additions & 102 deletions src/ui/ToolbarPlugin/index.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
/// <reference types="vite-plugin-svgr/client" />
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { INSERT_HORIZONTAL_RULE_COMMAND } from '@lexical/react/LexicalHorizontalRuleNode'
import { mergeRegister } from '@lexical/utils'
import * as RadixToolbar from '@radix-ui/react-toolbar'
import { COMMAND_PRIORITY_CRITICAL, SELECTION_CHANGE_COMMAND } from 'lexical'
import React from 'react'
import { BlockTypeSelect } from './BlockTypeSelect/'
import { ReactComponent as CodeIcon } from './icons/code.svg'
import { ReactComponent as LiveCodeIcon } from './icons/deployed_code.svg'
import { ReactComponent as DiffIcon } from './icons/difference.svg'
import { ReactComponent as BoldIcon } from './icons/format_bold.svg'
import { ReactComponent as ItalicIcon } from './icons/format_italic.svg'
import { ReactComponent as BulletedListIcon } from './icons/format_list_bulleted.svg'
Expand All @@ -17,52 +12,91 @@ import { ReactComponent as UnderlinedIcon } from './icons/format_underlined.svg'
import { ReactComponent as FrameSourceIcon } from './icons/frame_source.svg'
import { ReactComponent as HorizontalRuleIcon } from './icons/horizontal_rule.svg'
import { ReactComponent as LinkIcon } from './icons/link.svg'
import { ReactComponent as MarkdownIcon } from './icons/markdown.svg'

import classnames from 'classnames'
import { ViewMode } from '../'
import { IS_BOLD, IS_CODE, IS_ITALIC, IS_UNDERLINE } from '../../FormatConstants'
import { useEmitterValues, usePublisher } from '../../system'
import { buttonClasses, toggleItemClasses } from '../commonCssClasses'

export const ToolbarPlugin = () => {
const [currentFormat, currentListType, viewMode, activeSandpackNode] = useEmitterValues(
'currentFormat',
'currentListType',
'viewMode',
'activeSandpackNode'
const [viewMode, activeSandpackNode] = useEmitterValues('viewMode', 'activeSandpackNode')
const setViewMode = usePublisher('viewMode')

return (
<RadixToolbar.Root
className="mb-6 flex flex-row gap-2 rounded-md border-2 border-solid border-surface-50 p-2 items-center"
aria-label="Formatting options"
>
{activeSandpackNode !== null ? null : <RichTextButtonSet />}

<ToolbarSeparator />
<ToggleSingleGroup aria-label="View Mode" onValueChange={setViewMode} value={viewMode} className="ml-auto">
<ToggleItem value="editor" aria-label="Rich text" className="rounded-l-md">
Rich Text
</ToggleItem>

<ToggleItem value="diff" aria-label="View diff" className="">
Diff View
</ToggleItem>

<ToggleItem value="markdown" aria-label="View Markdown" className="rounded-r-md">
Markdown
</ToggleItem>
</ToggleSingleGroup>
</RadixToolbar.Root>
)
}

const ToggleItem = React.forwardRef<HTMLButtonElement, RadixToolbar.ToolbarToggleItemProps>(
({ className: passedClassName, ...props }, forwardedRef) => {
return <RadixToolbar.ToggleItem className={classnames(passedClassName, toggleItemClasses)} {...props} ref={forwardedRef} />
}
)

const ToolbarButton = React.forwardRef<HTMLButtonElement, RadixToolbar.ToolbarButtonProps>((props, forwardedRef) => {
return <RadixToolbar.Button className={buttonClasses} {...props} ref={forwardedRef} />
})

const ToggleSingleGroup = React.forwardRef<HTMLDivElement, Omit<RadixToolbar.ToolbarToggleGroupSingleProps, 'type'>>(
({ children, className, ...props }, forwardedRef) => {
return (
<RadixToolbar.ToggleGroup {...props} type="single" ref={forwardedRef} className={classnames('whitespace-nowrap', className)}>
{children}
</RadixToolbar.ToggleGroup>
)
}
)

const ToggleSingleGroupWithItem = React.forwardRef<
HTMLDivElement,
Omit<RadixToolbar.ToolbarToggleGroupSingleProps, 'type'> & { on: boolean }
>(({ on, children, ...props }, forwardedRef) => {
return (
<ToggleSingleGroup {...props} value={on ? 'on' : 'off'} ref={forwardedRef}>
<ToggleItem value="on">{children}</ToggleItem>
</ToggleSingleGroup>
)
})

const GroupGroup: React.FC<{ children: React.ReactNode }> = ({ children }) => {
return <div className="group flex flex-row gap-[1px] rounded-md">{children}</div>
}

const ToolbarSeparator = React.forwardRef<HTMLDivElement, RadixToolbar.SeparatorProps>(() => {
return <RadixToolbar.Separator className="mx-1" />
})

function RichTextButtonSet() {
const [currentFormat, currentListType] = useEmitterValues('currentFormat', 'currentListType')
const applyFormat = usePublisher('applyFormat')
const applyListType = usePublisher('applyListType')
const setViewMode = usePublisher('viewMode')
const insertCodeBlock = usePublisher('insertCodeBlock')
const insertSandpack = usePublisher('insertSandpack')
const openLinkEditDialog = usePublisher('openLinkEditDialog')
const [editor] = useLexicalComposerContext()
const [activeEditor, setActiveEditor] = React.useState(editor)

React.useEffect(() => {
return mergeRegister(
editor.registerCommand(
SELECTION_CHANGE_COMMAND,
(_payload, newEditor) => {
setActiveEditor(newEditor)
return false
},
COMMAND_PRIORITY_CRITICAL
)
)
}, [editor])

if (activeSandpackNode !== null) {
return <div style={{ height: 64 }}>Sandpack (node: {activeSandpackNode.nodeKey})</div>
}
const insertHorizontalRule = usePublisher('insertHorizontalRule')

return (
<RadixToolbar.Root
className="m-2 mb-6 flex flex-row gap-2 rounded-md border-2 border-solid border-surface-50 p-2 min-w-max max-w-[84rem] items-center"
aria-label="Formatting options"
>
<>
<GroupGroup>
<ToggleSingleGroupWithItem
className="[&_button]:rounded-l-md"
Expand Down Expand Up @@ -127,11 +161,7 @@ export const ToolbarPlugin = () => {
<ToolbarButton onClick={openLinkEditDialog.bind(null, true)}>
<LinkIcon />
</ToolbarButton>
<ToolbarButton
onClick={() => {
activeEditor.dispatchCommand(INSERT_HORIZONTAL_RULE_COMMAND, undefined)
}}
>
<ToolbarButton onClick={insertHorizontalRule.bind(null, true)}>
<HorizontalRuleIcon />
</ToolbarButton>
<ToolbarSeparator />
Expand All @@ -143,66 +173,6 @@ export const ToolbarPlugin = () => {
<ToolbarButton>
<LiveCodeIcon onClick={insertSandpack.bind(null, true)} />
</ToolbarButton>

<ToolbarSeparator />
<ToggleSingleGroup aria-label="View Mode" onValueChange={setViewMode} value={viewMode} className="ml-auto">
<ToggleItem value="editor" aria-label="Rich text" className="rounded-l-md">
Rich Text
</ToggleItem>

<ToggleItem value="diff" aria-label="View diff" className="">
Diff View
</ToggleItem>

<ToggleItem value="markdown" aria-label="View Markdown" className="rounded-r-md">
Markdown
</ToggleItem>
</ToggleSingleGroup>
</RadixToolbar.Root>
</>
)
}

const ViewModeMap = new Map<string, ViewMode>([
['diff', 'diff'],
['markdown', 'markdown'],
['', 'editor'],
])

const ToggleItem = React.forwardRef<HTMLButtonElement, RadixToolbar.ToolbarToggleItemProps>(
({ className: passedClassName, ...props }, forwardedRef) => {
return <RadixToolbar.ToggleItem className={classnames(passedClassName, toggleItemClasses)} {...props} ref={forwardedRef} />
}
)

const ToolbarButton = React.forwardRef<HTMLButtonElement, RadixToolbar.ToolbarButtonProps>((props, forwardedRef) => {
return <RadixToolbar.Button className={buttonClasses} {...props} ref={forwardedRef} />
})

const ToggleSingleGroup = React.forwardRef<HTMLDivElement, Omit<RadixToolbar.ToolbarToggleGroupSingleProps, 'type'>>(
({ children, className, ...props }, forwardedRef) => {
return (
<RadixToolbar.ToggleGroup {...props} type="single" ref={forwardedRef} className={classnames('whitespace-nowrap', className)}>
{children}
</RadixToolbar.ToggleGroup>
)
}
)

const ToggleSingleGroupWithItem = React.forwardRef<
HTMLDivElement,
Omit<RadixToolbar.ToolbarToggleGroupSingleProps, 'type'> & { on: boolean }
>(({ on, children, ...props }, forwardedRef) => {
return (
<ToggleSingleGroup {...props} value={on ? 'on' : 'off'} ref={forwardedRef}>
<ToggleItem value="on">{children}</ToggleItem>
</ToggleSingleGroup>
)
})

const GroupGroup: React.FC<{ children: React.ReactNode }> = ({ children }) => {
return <div className="group flex flex-row gap-[1px] rounded-md">{children}</div>
}

const ToolbarSeparator = React.forwardRef<HTMLDivElement, RadixToolbar.SeparatorProps>(() => {
return <RadixToolbar.Separator className="mx-1" />
})
55 changes: 30 additions & 25 deletions src/ui/Wrapper/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import { NodeDecorators } from '../../system/NodeDecorators'
import { FrontmatterEditor } from '../NodeDecorators/FrontmatterEditor'
import { JsxEditor } from '../NodeDecorators/JsxEditor'
import { SandpackEditor } from '../NodeDecorators/SandpackEditor'
import { TRANSFORMERS } from '@lexical/markdown'
import { MarkdownShortcutPlugin } from '@lexical/react/LexicalMarkdownShortcutPlugin'

export function standardConfig(markdown: string) {
return {
Expand Down Expand Up @@ -55,30 +57,33 @@ const nodeDecorators: NodeDecorators = {

export const Wrapper: React.FC<WrappedEditorProps> = ({ markdown, headMarkdown, jsxComponentDescriptors, sandpackConfig, onChange }) => {
return (
<LexicalComposer initialConfig={standardConfig(markdown)}>
<EditorSystemComponent
headMarkdown={headMarkdown}
markdownSource={markdown}
jsxComponentDescriptors={jsxComponentDescriptors}
sandpackConfig={sandpackConfig}
onChange={onChange}
nodeDecorators={nodeDecorators}
>
<ToolbarPlugin />
<ViewModeToggler>
<RichTextPlugin
contentEditable={<ContentEditable className="" />}
placeholder={<div></div>}
ErrorBoundary={LexicalErrorBoundary}
/>
</ViewModeToggler>
<LexicalLinkPlugin />
<CodeHighlightPlugin />
<HorizontalRulePlugin />
<ListPlugin />
<LinkDialogPlugin />
<HistoryPlugin />
</EditorSystemComponent>
</LexicalComposer>
<div className="p-3 max-w-[90rem] border-slate-100 border-solid border-2">
<LexicalComposer initialConfig={standardConfig(markdown)}>
<EditorSystemComponent
headMarkdown={headMarkdown}
markdownSource={markdown}
jsxComponentDescriptors={jsxComponentDescriptors}
sandpackConfig={sandpackConfig}
onChange={onChange}
nodeDecorators={nodeDecorators}
>
<ToolbarPlugin />
<ViewModeToggler>
<RichTextPlugin
contentEditable={<ContentEditable className="prose font-sans max-w-none w-full focus:outline-none" />}
placeholder={<div></div>}
ErrorBoundary={LexicalErrorBoundary}
/>
</ViewModeToggler>
<LexicalLinkPlugin />
<CodeHighlightPlugin />
<HorizontalRulePlugin />
<ListPlugin />
<LinkDialogPlugin />
<HistoryPlugin />
<MarkdownShortcutPlugin transformers={TRANSFORMERS} />
</EditorSystemComponent>
</LexicalComposer>
</div>
)
}

0 comments on commit 03d4fac

Please sign in to comment.