Skip to content

Commit

Permalink
feat: delete UI for code blocks and images
Browse files Browse the repository at this point in the history
  • Loading branch information
petyosi committed May 20, 2024
1 parent 6baca1e commit 4743184
Show file tree
Hide file tree
Showing 7 changed files with 175 additions and 73 deletions.
5 changes: 5 additions & 0 deletions src/plugins/codeblock/CodeBlockNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,10 @@ export interface CodeBlockEditorContextValue {
* The Lexical node that's being edited.
*/
lexicalNode: CodeBlockNode
/**
* The parent Lexical editor.
*/
parentEditor: LexicalEditor
}

const CodeBlockEditorContext = React.createContext<CodeBlockEditorContextValue | null>(null)
Expand All @@ -171,6 +175,7 @@ const CodeBlockEditorContextProvider: React.FC<{
const contextValue = React.useMemo(() => {
return {
lexicalNode,
parentEditor,
setCode: (code: string) => {
parentEditor.update(() => {
lexicalNode.setCode(code)
Expand Down
49 changes: 43 additions & 6 deletions src/plugins/codemirror/CodeMirrorEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,31 @@
import { useCellValues } from '@mdxeditor/gurx'
import React from 'react'
import styles from '../../styles/ui.module.css'
import { CodeBlockEditorProps } from '../codeblock'
import { useCodeBlockEditorContext } from '../codeblock/CodeBlockNode'
import { readOnly$ } from '../core'
import { useCellValues } from '@mdxeditor/gurx'
import { iconComponentFor$, readOnly$, useTranslation } from '../core'

import { languages } from '@codemirror/language-data'
import { EditorState, Extension } from '@codemirror/state'
import { EditorView, lineNumbers } from '@codemirror/view'
import { basicLight } from 'cm6-theme-basic-light'
import { basicSetup } from 'codemirror'
import { languages } from '@codemirror/language-data'
import { codeBlockLanguages$, codeMirrorAutoLoadLanguageSupport$, codeMirrorExtensions$ } from '.'
import { useCodeMirrorRef } from '../sandpack/useCodeMirrorRef'
import { codeMirrorAutoLoadLanguageSupport$, codeMirrorExtensions$ } from '.'
import { Select } from '../toolbar/primitives/select'

export const COMMON_STATE_CONFIG_EXTENSIONS: Extension[] = []
const EMPTY_VALUE = '__EMPTY_VALUE__'

export const CodeMirrorEditor = ({ language, nodeKey, code, focusEmitter }: CodeBlockEditorProps) => {
const [readOnly, codeMirrorExtensions, autoLoadLanguageSupport] = useCellValues(
const t = useTranslation()
const { parentEditor, lexicalNode } = useCodeBlockEditorContext()
const [readOnly, codeMirrorExtensions, autoLoadLanguageSupport, iconComponentFor, codeBlockLanguages] = useCellValues(
readOnly$,
codeMirrorExtensions$,
codeMirrorAutoLoadLanguageSupport$
codeMirrorAutoLoadLanguageSupport$,
iconComponentFor$,
codeBlockLanguages$
)

const codeMirrorRef = useCodeMirrorRef(nodeKey, 'codeblock', language, focusEmitter)
Expand Down Expand Up @@ -81,6 +87,37 @@ export const CodeMirrorEditor = ({ language, nodeKey, code, focusEmitter }: Code
e.stopPropagation()
}}
>
<div className={styles.codeMirrorToolbar}>
<Select
value={language}
onChange={(language) => {
parentEditor.update(() => {
lexicalNode.setLanguage(language === EMPTY_VALUE ? '' : language)
setTimeout(() => {
parentEditor.update(() => {
lexicalNode.getLatest().select()
})
})
})
}}
triggerTitle={t('codeBlock.selectLanguage', 'Select code block language')}
placeholder={t('codeBlock.inlineLanguage', 'Language')}
items={Object.entries(codeBlockLanguages).map(([value, label]) => ({ value: value ? value : EMPTY_VALUE, label }))}
/>
<button
className={styles.iconButton}
type="button"
title={t('codeblock.delete', 'Delete code block')}
onClick={(e) => {
e.preventDefault()
parentEditor.update(() => {
lexicalNode.remove()
})
}}
>
{iconComponentFor('delete_small')}
</button>
</div>
<div ref={elRef} />
</div>
)
Expand Down
43 changes: 29 additions & 14 deletions src/plugins/image/ImageEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -271,26 +271,41 @@ export function ImageEditor({ src, title, alt, nodeKey, width, height }: ImageEd
{draggable && isFocused && !disableImageResize && (
<ImageResizer editor={editor} imageRef={imageRef} onResizeStart={onResizeStart} onResizeEnd={onResizeEnd} />
)}
{!disableImageSettingsButton && (
<div className={styles.editImageToolbar}>
<button
className={styles.iconButton}
type="button"
className={classNames(styles.iconButton, styles.editImageButton)}
title={t('imageEditor.editImage', 'Edit image')}
disabled={readOnly}
onClick={() => {
openEditImageDialog({
nodeKey: nodeKey,
initialValues: {
src: !initialImagePath ? imageSource : initialImagePath,
title: title ?? '',
altText: alt ?? ''
}
title={t('image.delete', 'Delete image')}
onClick={(e) => {
e.preventDefault()
editor.update(() => {
$getNodeByKey(nodeKey)?.remove()
})
}}
>
{iconComponentFor('settings')}
{iconComponentFor('delete_small')}
</button>
)}
{!disableImageSettingsButton && (
<button
type="button"
className={classNames(styles.iconButton, styles.editImageButton)}
title={t('imageEditor.editImage', 'Edit image')}
disabled={readOnly}
onClick={() => {
openEditImageDialog({
nodeKey: nodeKey,
initialValues: {
src: !initialImagePath ? imageSource : initialImagePath,
title: title ?? '',
altText: alt ?? ''
}
})
}}
>
{iconComponentFor('settings')}
</button>
)}
</div>
</div>
</React.Suspense>
) : null
Expand Down
70 changes: 45 additions & 25 deletions src/plugins/sandpack/SandpackEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { SandpackCodeEditor, SandpackLayout, SandpackPreview, SandpackProvider, useSandpack } from '@codesandbox/sandpack-react'
import { useCellValue, useCellValues } from '@mdxeditor/gurx'
import React from 'react'
import { SandpackPreset } from '.'
import styles from '../../styles/ui.module.css'
import { CodeBlockEditorProps } from '../codeblock'
import { useCodeMirrorRef } from './useCodeMirrorRef'
import { SandpackCodeEditor, SandpackLayout, SandpackPreview, SandpackProvider, useSandpack } from '@codesandbox/sandpack-react'
import { useCodeBlockEditorContext } from '../codeblock/CodeBlockNode'
import { readOnly$ } from '../core'
import { useCellValue } from '@mdxeditor/gurx'
import { iconComponentFor$, readOnly$, useTranslation } from '../core'
import { useCodeMirrorRef } from './useCodeMirrorRef'

interface CodeUpdateEmitterProps {
snippetFileName: string
Expand All @@ -24,29 +25,48 @@ export interface SandpackEditorProps extends CodeBlockEditorProps {

export const SandpackEditor = ({ nodeKey, code, focusEmitter, preset }: SandpackEditorProps) => {
const codeMirrorRef = useCodeMirrorRef(nodeKey, 'sandpack', 'jsx', focusEmitter)
const readOnly = useCellValue(readOnly$)
const [readOnly, iconComponentFor] = useCellValues(readOnly$, iconComponentFor$)
const { setCode } = useCodeBlockEditorContext()
const { parentEditor, lexicalNode } = useCodeBlockEditorContext()
const t = useTranslation()

return (
<SandpackProvider
template={preset.sandpackTemplate}
theme={preset.sandpackTheme}
files={{
[preset.snippetFileName]: code,
...Object.entries(preset.files ?? {}).reduce(
(acc, [filePath, fileContents]) => ({ ...acc, ...{ [filePath]: { code: fileContents, readOnly: true } } }),
{}
)
}}
customSetup={{
dependencies: preset.dependencies
}}
>
<SandpackLayout>
<SandpackCodeEditor readOnly={readOnly} showLineNumbers showInlineErrors ref={codeMirrorRef} />
<SandpackPreview />
</SandpackLayout>
<CodeUpdateEmitter onChange={setCode} snippetFileName={preset.snippetFileName} />
</SandpackProvider>
<div className={styles.sandPackWrapper}>
<div className={styles.codeMirrorToolbar}>
<button
className={styles.iconButton}
type="button"
title={t('codeblock.delete', 'Delete code block')}
onClick={(e) => {
e.preventDefault()
parentEditor.update(() => {
lexicalNode.remove()
})
}}
>
{iconComponentFor('delete_small')}
</button>
</div>
<SandpackProvider
template={preset.sandpackTemplate}
theme={preset.sandpackTheme}
files={{
[preset.snippetFileName]: code,
...Object.entries(preset.files ?? {}).reduce(
(acc, [filePath, fileContents]) => ({ ...acc, ...{ [filePath]: { code: fileContents, readOnly: true } } }),
{}
)
}}
customSetup={{
dependencies: preset.dependencies
}}
>
<SandpackLayout>
<SandpackCodeEditor readOnly={readOnly} showLineNumbers showInlineErrors ref={codeMirrorRef} />
<SandpackPreview />
</SandpackLayout>
<CodeUpdateEmitter onChange={setCode} snippetFileName={preset.snippetFileName} />
</SandpackProvider>
</div>
)
}
35 changes: 18 additions & 17 deletions src/plugins/table/TableEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -208,22 +208,7 @@ export const TableEditor: React.FC<TableEditorProps> = ({ mdastNode, parentEdito
{readOnly || (
<thead>
<tr>
<th className={styles.tableToolsColumn} data-tool-cell={true}>
<button
className={styles.iconButton}
type="button"
title={t('table.deleteTable', 'Delete table')}
onClick={(e) => {
e.preventDefault()
parentEditor.update(() => {
lexicalTable.selectNext()
lexicalTable.remove()
})
}}
>
{iconComponentFor('delete_small')}
</button>
</th>
<th className={styles.tableToolsColumn}></th>
{Array.from({ length: mdastNode.children[0].children.length }, (_, colIndex) => {
return (
<th key={colIndex} data-tool-cell={true}>
Expand All @@ -240,7 +225,23 @@ export const TableEditor: React.FC<TableEditorProps> = ({ mdastNode, parentEdito
</th>
)
})}
<th className={styles.tableToolsColumn}></th>

<th className={styles.tableToolsColumn} data-tool-cell={true}>
<button
className={styles.iconButton}
type="button"
title={t('table.deleteTable', 'Delete table')}
onClick={(e) => {
e.preventDefault()
parentEditor.update(() => {
lexicalTable.selectNext()
lexicalTable.remove()
})
}}
>
{iconComponentFor('delete_small')}
</button>
</th>
</tr>
</thead>
)}
Expand Down
6 changes: 4 additions & 2 deletions src/styles/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
padding: var(--sp-space-4) 0;
}

& .sp-editor .cm-editor {
padding-bottom: 0;
}

& .cm-scroller {
padding: 0 !important;
}
Expand All @@ -24,8 +28,6 @@
}

& .sp-wrapper {
border: 1px solid var(--baseLine);
border-radius: var(--radius-medium);
overflow: hidden;
}

Expand Down
40 changes: 31 additions & 9 deletions src/styles/ui.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,18 @@
filter: drop-shadow(0 2px 2px rgb(0 0 0 / 0.20));
}

@define-mixin nested-editor-toolbar {
position: absolute;
right: 0;
top: 0;
display: flex;
gap: var(--spacing-1);
padding: var(--spacing-1);
z-index: 1;
background-color: var(--baseBase);
border-bottom-left-radius: var(--radius-base);
}

@define-mixin icon-button {
@mixin clear-form-element;
padding: var(--spacing-1);
Expand Down Expand Up @@ -146,8 +158,7 @@
}

.toolbarRoot {
/* border: var(--spacing-px) solid var(--baseBorder); */
z-index: 1;
z-index: 2;
display: flex;
flex-direction: row;
gap: var(--spacing-1);
Expand Down Expand Up @@ -381,6 +392,19 @@
border-radius: var(--radius-medium);
overflow: hidden;
padding: 0.8rem;
position: relative;
}

.sandPackWrapper {
margin-bottom: var(--spacing-5);
border: 1px solid var(--baseLine);
border-radius: var(--radius-medium);
overflow: hidden;
position: relative;
}

.codeMirrorToolbar {
@mixin nested-editor-toolbar;
}

.frontmatterWrapper {
Expand Down Expand Up @@ -793,7 +817,7 @@
}

.tableToolsColumn {
width: 3rem;
width: 2rem;

& button {
margin: auto;
Expand Down Expand Up @@ -906,13 +930,11 @@
cursor: -webkit-grab;
}

.editImageButton {
position: absolute;
right: var(--spacing-2);
top: var(--spacing-2);
background: var(--baseBase);
border-radius: var(--radius-full);
.editImageToolbar {
@mixin nested-editor-toolbar;
}

.editImageButton {
& svg {
display: block;
}
Expand Down

0 comments on commit 4743184

Please sign in to comment.