Skip to content

Commit

Permalink
feat: insertMarkdown method and signal (#294)
Browse files Browse the repository at this point in the history
The method inserts the given markdown content at the current selection point.
  • Loading branch information
forhappy authored Jan 13, 2024
1 parent 7d100d5 commit c3b8847
Show file tree
Hide file tree
Showing 5 changed files with 256 additions and 7 deletions.
14 changes: 14 additions & 0 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,20 @@ return (
)
```

If you want to insert markdown content after the editor is initialized, you can use `insertMarkdown` ref method to insert markdown content to the current cursor position of the active editor.

```tsx
// create a ref to the editor component
const ref = React.useRef<MDXEditorMethods>(null)
return (
<>
<button onClick={() => ref.current?.insertMarkdown('new markdown to insert')}>Insert new markdown</button>
<button onClick={() => console.log(ref.current?.getMarkdown())}>Get markdown</button>
<MDXEditor ref={ref} markdown="hello world" onChange={console.log} />
</>
)
```

## Next steps

Hopefully, at this point, the editor component is installed and working in your setup, but it's not very useful. Depending on your use case, you will need some additional features. To ensure that the bundle size stays small, MDXEditor uses a plugin system. Below is an example of a few basic plugins being enabled for the editor.
Expand Down
11 changes: 10 additions & 1 deletion src/MDXEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import {
viewMode$,
markdown$,
setMarkdown$,
rootEditor$
rootEditor$,
insertMarkdown$
} from './plugins/core'
import { RealmPlugin, RealmWithPlugins } from './RealmWithPlugins'
import { useCellValues, usePublisher, useRealm } from '@mdxeditor/gurx'
Expand Down Expand Up @@ -121,6 +122,11 @@ export interface MDXEditorMethods {
*/
setMarkdown: (value: string) => void

/**
* Inserts markdown at the current cursor position. Use the focus if necessary.
*/
insertMarkdown: (value: string) => void

/**
* Sets focus on input
*/
Expand Down Expand Up @@ -182,6 +188,9 @@ const Methods: React.FC<{ mdxRef: React.ForwardedRef<MDXEditorMethods> }> = ({ m
setMarkdown: (markdown) => {
realm.pub(setMarkdown$, markdown)
},
insertMarkdown: (markdown) => {
realm.pub(insertMarkdown$, markdown)
},
focus: (callbackFn?: (() => void) | undefined, opts?: { defaultSelection?: 'rootStart' | 'rootEnd'; preventScroll?: boolean }) => {
realm.getValue(rootEditor$)?.focus(callbackFn, opts)
}
Expand Down
188 changes: 188 additions & 0 deletions src/examples/insert-markdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
import React from 'react'
import {
DiffSourceToggleWrapper,
GenericJsxEditor,
JsxComponentDescriptor,
MDXEditor,
MDXEditorMethods,
diffSourcePlugin,
headingsPlugin,
insertMarkdown$,
jsxPlugin,
listsPlugin,
tablePlugin,
toolbarPlugin,
usePublisher
} from '..'

const initialMarkdownContent = `
# Hello World
This is a dummy markdown content.
`

const simpleMarkdownContentToInsert = `
# Hello World
This is a dummy markdown content.
## Hello World 2
This is a dummy markdown content heading 2.
`

const complexMarkdownContentToInsert = `
### List
* hello
* world
* indented
* more
* back
1. more
2. more
* [x] Walk the dog
* [ ] Watch movie
* [ ] Have dinner with family
... an all empty list
* [ ] Walk the dog
* [ ] Watch movie
* [ ] Have dinner with family
### Table
| Syntax | Description | Profit |
| ----------- | ------------- | ------:|
| Header | Title | 50 |
| Paragraph | Text *italic* | 70 |
`

const jsxMarkdownContentToInsert = `
import { BlockNode } from './external';
<BlockNode foo="fooValue">
Content *foo*
more Content
</BlockNode>
`

export function InsertSimpleMarkdown() {
return (
<>
<MDXEditor
markdown={initialMarkdownContent}
plugins={[
headingsPlugin(),
diffSourcePlugin({ viewMode: 'rich-text', diffMarkdown: 'boo' }),
toolbarPlugin({
toolbarContents: () => (
<DiffSourceToggleWrapper>
<InsertSimpleMarkdownButton />
</DiffSourceToggleWrapper>
)
})
]}
onChange={(md) => {
console.log('change', md)
}}
/>
</>
)
}

const InsertSimpleMarkdownButton = () => {
const insertMarkdown = usePublisher(insertMarkdown$)

return (
<>
<button
onClick={() => {
insertMarkdown(simpleMarkdownContentToInsert)
}}
>
Insert markdown
</button>
</>
)
}

export function InsertMarkdownWithTableAndList() {
return (
<>
<MDXEditor
markdown={initialMarkdownContent}
plugins={[
headingsPlugin(),
listsPlugin(),
diffSourcePlugin({ viewMode: 'rich-text', diffMarkdown: 'boo' }),
tablePlugin(),
toolbarPlugin({
toolbarContents: () => (
<DiffSourceToggleWrapper>
<InsertComplexMarkdownButton />
</DiffSourceToggleWrapper>
)
})
]}
onChange={(md) => {
console.log('change', md)
}}
/>
</>
)
}

const InsertComplexMarkdownButton = () => {
const insertMarkdown = usePublisher(insertMarkdown$)

return (
<>
<button
onClick={() => {
insertMarkdown(complexMarkdownContentToInsert)
}}
>
Insert markdown
</button>
</>
)
}

const jsxComponentDescriptors: JsxComponentDescriptor[] = [
{
name: 'BlockNode',
kind: 'flow',
source: './external',
props: [],
hasChildren: true,
Editor: GenericJsxEditor
}
]

export function InsertMarkdownToNestedEditor() {
const ref = React.useRef<MDXEditorMethods>(null)
return (
<>
<button onClick={() => ref.current?.insertMarkdown(complexMarkdownContentToInsert)}>Insert new markdown</button>
<button onClick={() => console.log(ref.current?.getMarkdown())}>Get markdown</button>

<MDXEditor
ref={ref}
markdown={jsxMarkdownContentToInsert}
onChange={console.log}
plugins={[
headingsPlugin(),
listsPlugin(),
tablePlugin(),
jsxPlugin({ jsxComponentDescriptors }),
diffSourcePlugin({ viewMode: 'rich-text', diffMarkdown: 'boo' })
]}
/>
</>
)
}
9 changes: 7 additions & 2 deletions src/importMarkdownToLexical.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,17 @@ function isParent(node: unknown): node is Mdast.Parent {
return (node as { children?: any[] }).children instanceof Array
}

export interface ImportPoint {
append(node: LexicalNode): void
getType(): string
}

/**
* The options of the tree import utility. Not meant to be used directly.
* @internal
*/
export interface MdastTreeImportOptions {
root: LexicalRootNode
root: ImportPoint
visitors: MdastImportVisitor<Mdast.RootContent>[]
mdastRoot: Mdast.Root
}
Expand Down Expand Up @@ -204,5 +209,5 @@ export function importMdastTreeToLexical({ root, mdastRoot, visitors }: MdastTre
})
}

visit(mdastRoot, root, null)
visit(mdastRoot, root as unknown as LexicalNode, null)
}
41 changes: 37 additions & 4 deletions src/plugins/core/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import { mdxMd } from 'micromark-extension-mdx-md'
import React from 'react'
import { LexicalConvertOptions, exportMarkdownFromLexical } from '../../exportMarkdownFromLexical'
import {
ImportPoint,
MarkdownParseError,
MarkdownParseOptions,
MdastImportVisitor,
Expand Down Expand Up @@ -292,7 +293,7 @@ export const setMarkdown$ = Signal<string>((r) => {
([theNewMarkdownValue, , editor, inFocus]) => {
editor?.update(() => {
$getRoot().clear()
tryImportingMarkdown(r, theNewMarkdownValue)
tryImportingMarkdown(r, $getRoot(), theNewMarkdownValue)

if (!inFocus) {
$setSelection(null)
Expand All @@ -304,6 +305,38 @@ export const setMarkdown$ = Signal<string>((r) => {
)
})

/**
* Inserts new markdown value into the current cursor position of the active editor.
* @group Core
*/
export const insertMarkdown$ = Signal<string>((r) => {
r.sub(r.pipe(insertMarkdown$, withLatestFrom(activeEditor$, inFocus$)), ([markdownToInsert, editor, inFocus]) => {
editor?.update(() => {
const selection = $getSelection()
if (selection !== null) {
const importPoint = {
children: [] as LexicalNode[],
append(node: LexicalNode) {
this.children.push(node)
},
getType() {
return selection?.getNodes()[0].getType()
}
}

tryImportingMarkdown(r, importPoint, markdownToInsert)
$insertNodes(importPoint.children)
}

if (!inFocus) {
$setSelection(null)
} else {
editor.focus()
}
})
})
})

function rebind() {
return scan((teardowns, [subs, activeEditorValue]: [EditorSubscription[], LexicalEditor | null]) => {
teardowns.forEach((teardown) => {
Expand Down Expand Up @@ -531,13 +564,13 @@ export const createActiveEditorSubscription$ = Appender(activeEditorSubscription
])
})

function tryImportingMarkdown(r: Realm, markdownValue: string) {
function tryImportingMarkdown(r: Realm, node: ImportPoint, markdownValue: string) {
try {
////////////////////////
// Import initial value
////////////////////////
importMarkdownToLexical({
root: $getRoot(),
root: node,
visitors: r.getValue(importVisitors$),
mdastExtensions: r.getValue(mdastExtensions$),
markdown: markdownValue,
Expand Down Expand Up @@ -566,7 +599,7 @@ export const initialRootEditorState$ = Cell<InitialEditorStateType | null>(null,
r.pub(rootEditor$, theRootEditor)
r.pub(activeEditor$, theRootEditor)

tryImportingMarkdown(r, r.getValue(initialMarkdown$))
tryImportingMarkdown(r, $getRoot(), r.getValue(initialMarkdown$))

const autoFocusValue = r.getValue(autoFocus$)
if (autoFocusValue) {
Expand Down

0 comments on commit c3b8847

Please sign in to comment.