forked from remirror/remirror
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(extension-codemirror6): first version of extension-codemirror6 (r…
- Loading branch information
Showing
21 changed files
with
1,472 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'@remirror/extension-codemirror6': minor | ||
--- | ||
|
||
First version of `@remirror/extension-codemirror6`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
--- | ||
hide_title: true | ||
title: 'CodeMirrorExtension' | ||
--- | ||
|
||
import Basic from '../../website/extension-examples/extension-codemirror6/basic'; | ||
import WithCustomExtension from '../../website/extension-examples/extension-codemirror6/with-custom-extension'; | ||
|
||
# `CodeMirrorExtension` | ||
|
||
## Summary | ||
|
||
The CodeMirror extension adds fenced code blocks to your editor powered by [CodeMirror 6](https://codemirror.net/6/). A code block contains a formatted snippets of source code. | ||
|
||
:::info | ||
|
||
This extension differs from the `CodeExtension`, which provides code marks on inline text. | ||
|
||
::: | ||
|
||
:::info | ||
|
||
Also check a simpler code block implementation at [`CodeBlockExtension`](code-block-extension). | ||
|
||
::: | ||
|
||
## Features | ||
|
||
### Keyboard support | ||
|
||
Users can create a new code block by typing <code>``` </code> (3 ticks and a space). Users can define the language of the code block by typing <code>```html </code>. | ||
|
||
## Usage | ||
|
||
### Installation | ||
|
||
This extension is NOT installed for you when you install the main `remirror` package but need to be installed separately: | ||
|
||
```bash | ||
npm install @remirror/extension-codemirror6 | ||
``` | ||
|
||
You will probably also want to install some basic CodeMirror packages: | ||
|
||
```bash | ||
npm install @codemirror/basic-setup @codemirror/language-data @codemirror/theme-one-dark | ||
``` | ||
|
||
You can use the imports in the following way: | ||
|
||
```ts | ||
import { basicSetup } from '@codemirror/basic-setup'; | ||
import { languages } from '@codemirror/language-data'; | ||
import { oneDark } from '@codemirror/theme-one-dark'; | ||
import { CodeMirrorExtension } from '@remirror/extension-codemirror6'; | ||
|
||
const codeMirrorExtension = new CodeMirrorExtension({ | ||
languages: languages, | ||
extensions: [basicSetup, oneDark], | ||
}); | ||
``` | ||
|
||
### Examples | ||
|
||
<Basic /> | ||
|
||
You can customize your CodeMirror editor by passing your custom extensions. | ||
|
||
<WithCustomExtension /> | ||
|
||
## API | ||
|
||
- [CodeMirrorExtension](../api/extension-codemirror6) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
{ | ||
"name": "@remirror/extension-codemirror6", | ||
"version": "0.0.0", | ||
"description": "Add CodeMirror6 to your editor.", | ||
"keywords": [ | ||
"remirror", | ||
"remirror-extension", | ||
"CodeMirror", | ||
"editor" | ||
], | ||
"homepage": "https://github.com/remirror/remirror/tree/HEAD/packages/remirror__extension-codemirror6", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/remirror/remirror.git", | ||
"directory": "packages/remirror__extension-codemirror6" | ||
}, | ||
"license": "MIT", | ||
"contributors": [ | ||
"Ocavue <[email protected]>" | ||
], | ||
"sideEffects": false, | ||
"exports": { | ||
".": { | ||
"import": "./dist/remirror-extension-codemirror6.esm.js", | ||
"require": "./dist/remirror-extension-codemirror6.cjs.js", | ||
"browser": "./dist/remirror-extension-codemirror6.browser.esm.js", | ||
"types": "./dist/remirror-extension-codemirror6.cjs.d.ts", | ||
"default": "./dist/remirror-extension-codemirror6.esm.js" | ||
}, | ||
"./package.json": "./package.json", | ||
"./types/*": "./dist/declarations/src/*.d.ts" | ||
}, | ||
"main": "dist/remirror-extension-codemirror6.cjs.js", | ||
"module": "dist/remirror-extension-codemirror6.esm.js", | ||
"browser": { | ||
"./dist/remirror-extension-codemirror6.cjs.js": "./dist/remirror-extension-codemirror6.browser.cjs.js", | ||
"./dist/remirror-extension-codemirror6.esm.js": "./dist/remirror-extension-codemirror6.browser.esm.js" | ||
}, | ||
"types": "dist/remirror-extension-codemirror6.cjs.d.ts", | ||
"files": [ | ||
"dist" | ||
], | ||
"dependencies": { | ||
"@babel/runtime": "^7.13.10", | ||
"@codemirror/language": "^0.19.7", | ||
"@codemirror/state": "^0.19.6", | ||
"@codemirror/view": "^0.19.37", | ||
"@remirror/core": "^1.3.1" | ||
}, | ||
"devDependencies": { | ||
"@remirror/pm": "^1.0.7" | ||
}, | ||
"peerDependencies": { | ||
"@remirror/pm": "^1.0.7" | ||
}, | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"@remirror": { | ||
"sizeLimit": "60 KB" | ||
}, | ||
"rn:dev": "src/index.ts" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# @remirror/extension-codemirror6 | ||
|
||
> Add [CodeMirror](https://codemirror.net/) to your editor. | ||
[![Version][version]][npm] [![Weekly Downloads][downloads-badge]][npm] [![Bundled size][size-badge]][size] [![Typed Codebase][typescript]](#) [![MIT License][license]](#) | ||
|
||
[version]: https://flat.badgen.net/npm/v/@remirror/extension-codemirror6 | ||
[npm]: https://npmjs.com/package/@remirror/extension-codemirror6 | ||
[license]: https://flat.badgen.net/badge/license/MIT/purple | ||
[size]: https://bundlephobia.com/result?p=@remirror/extension-codemirror6@next | ||
[size-badge]: https://flat.badgen.net/bundlephobia/minzip/@remirror/extension-codemirror6@next | ||
[typescript]: https://flat.badgen.net/badge/icon/TypeScript?icon=typescript&label | ||
[downloads-badge]: https://badgen.net/npm/dw/@remirror/extension-codemirror6/red?icon=npm | ||
|
||
## Installation | ||
|
||
```bash | ||
yarn add @remirror/extension-codemirror6 # yarn | ||
pnpm add @remirror/extension-codemirror6 # pnpm | ||
npm install @remirror/extension-codemirror6 # npm | ||
``` | ||
|
||
<!-- ## Usage --> |
185 changes: 185 additions & 0 deletions
185
packages/remirror__extension-codemirror6/src/codemirror-extension.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
import type { LanguageDescription, LanguageSupport } from '@codemirror/language'; | ||
import { | ||
ApplySchemaAttributes, | ||
EditorView, | ||
extension, | ||
GetAttributes, | ||
getMatchString, | ||
InputRule, | ||
isElementDomNode, | ||
isTextSelection, | ||
keyBinding, | ||
KeyBindingProps, | ||
NodeExtension, | ||
NodeExtensionSpec, | ||
nodeInputRule, | ||
NodeSpecOverride, | ||
NodeViewMethod, | ||
PrioritizedKeyBindings, | ||
ProsemirrorNode, | ||
} from '@remirror/core'; | ||
import { TextSelection } from '@remirror/pm/state'; | ||
|
||
import { CodeMirror6NodeView } from './codemirror-node-view'; | ||
import { CodeMirrorExtensionOptions } from './codemirror-types'; | ||
import { arrowHandler } from './codemirror-utils'; | ||
|
||
@extension<CodeMirrorExtensionOptions>({ | ||
defaultOptions: { | ||
extensions: null, | ||
languages: null, | ||
}, | ||
}) | ||
export class CodeMirrorExtension extends NodeExtension<CodeMirrorExtensionOptions> { | ||
get name() { | ||
return 'codeMirror' as const; | ||
} | ||
|
||
private languageMap: Record<string, LanguageDescription> | null = null; | ||
|
||
createNodeSpec(extra: ApplySchemaAttributes, override: NodeSpecOverride): NodeExtensionSpec { | ||
return { | ||
group: 'block', | ||
content: 'text*', | ||
marks: '', | ||
defining: true, | ||
...override, | ||
code: true, | ||
attrs: { | ||
...extra.defaults(), | ||
language: { default: '' }, | ||
}, | ||
parseDOM: [ | ||
{ | ||
tag: 'pre', | ||
getAttrs: (node) => (isElementDomNode(node) ? extra.parse(node) : false), | ||
}, | ||
...(override.parseDOM ?? []), | ||
], | ||
toDOM() { | ||
return ['pre', ['code', 0]]; | ||
}, | ||
isolating: true, | ||
}; | ||
} | ||
|
||
createNodeViews(): NodeViewMethod { | ||
return (node: ProsemirrorNode, view: EditorView, getPos: boolean | (() => number)) => { | ||
return new CodeMirror6NodeView({ | ||
node, | ||
view, | ||
getPos: getPos as () => number, | ||
extensions: this.options.extensions, | ||
loadLanguage: this.loadLanguage.bind(this), | ||
}); | ||
}; | ||
} | ||
|
||
createKeymap(): PrioritizedKeyBindings { | ||
return { | ||
ArrowLeft: arrowHandler('left'), | ||
ArrowRight: arrowHandler('right'), | ||
ArrowUp: arrowHandler('up'), | ||
ArrowDown: arrowHandler('down'), | ||
}; | ||
} | ||
|
||
/** | ||
* Create an input rule that listens converts the code fence into a code block | ||
* when typing triple back tick followed by a space. | ||
*/ | ||
createInputRules(): InputRule[] { | ||
const regexp = /^```(\S+) $/; | ||
|
||
const getAttributes: GetAttributes = (match) => { | ||
const language = getMatchString(match, 1) ?? ''; | ||
return { language }; | ||
}; | ||
|
||
return [ | ||
nodeInputRule({ | ||
regexp, | ||
type: this.type, | ||
beforeDispatch: ({ tr, start }) => { | ||
const $pos = tr.doc.resolve(start); | ||
tr.setSelection(new TextSelection($pos)); | ||
}, | ||
getAttributes: getAttributes, | ||
}), | ||
]; | ||
} | ||
|
||
@keyBinding({ shortcut: 'Enter' }) | ||
enterKey({ dispatch, tr }: KeyBindingProps): boolean { | ||
if (!(isTextSelection(tr.selection) && tr.selection.empty)) { | ||
return false; | ||
} | ||
|
||
const { nodeBefore, parent } = tr.selection.$anchor; | ||
|
||
if (!nodeBefore || !nodeBefore.isText || !parent.type.isTextblock) { | ||
return false; | ||
} | ||
|
||
const regex = /^```(\S*)?$/; | ||
const { text, nodeSize } = nodeBefore; | ||
const { textContent } = parent; | ||
|
||
if (!text) { | ||
return false; | ||
} | ||
|
||
const matchesNodeBefore = text.match(regex); | ||
const matchesParent = textContent.match(regex); | ||
|
||
if (!matchesNodeBefore || !matchesParent) { | ||
return false; | ||
} | ||
|
||
const language = getMatchString(matchesNodeBefore, 1) ?? ''; | ||
|
||
const pos = tr.selection.$from.before(); | ||
const end = pos + nodeSize + 1; // +1 to account for the extra pos a node takes up | ||
tr.replaceWith(pos, end, this.type.create({ language })); | ||
|
||
// Set the selection to within the codeBlock | ||
tr.setSelection(TextSelection.create(tr.doc, pos + 1)); | ||
|
||
if (dispatch) { | ||
dispatch(tr); | ||
} | ||
|
||
return true; | ||
} | ||
|
||
private getLanguageMap(): Record<string, LanguageDescription> { | ||
if (!this.languageMap) { | ||
this.languageMap = {}; | ||
|
||
for (const language of this.options.languages ?? []) { | ||
for (const alias of language.alias) { | ||
this.languageMap[alias] = language; | ||
} | ||
} | ||
} | ||
|
||
return this.languageMap; | ||
} | ||
|
||
private loadLanguage( | ||
languageName: string, | ||
): Promise<LanguageSupport> | LanguageSupport | undefined { | ||
if (typeof languageName !== 'string') { | ||
return undefined; | ||
} | ||
|
||
const languageMap = this.getLanguageMap(); | ||
const language = languageMap[languageName.toLowerCase()]; | ||
|
||
if (!language) { | ||
return undefined; | ||
} | ||
|
||
return language.support || language.load(); | ||
} | ||
} |
Oops, something went wrong.