mirror of
https://github.com/gosticks/DefinitelyTyped.git
synced 2025-10-16 12:05:41 +00:00
feat: update prosemirror-markdown (#37698)
This commit is contained in:
parent
859c748181
commit
ef3fae5058
364
types/prosemirror-markdown/index.d.ts
vendored
364
types/prosemirror-markdown/index.d.ts
vendored
@ -4,11 +4,57 @@
|
||||
// David Hahn <https://github.com/davidka>
|
||||
// Tim Baumann <https://github.com/timjb>
|
||||
// Patrick Simmelbauer <https://github.com/patsimm>
|
||||
// Ifiokj Jr. <https://github.com/ifiokjr>
|
||||
// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
|
||||
// TypeScript Version: 2.3
|
||||
|
||||
import MarkdownIt = require('markdown-it');
|
||||
import { Node as ProsemirrorNode, Schema } from 'prosemirror-model';
|
||||
import Token = require('markdown-it/lib/token');
|
||||
import { Fragment, Mark, Node as ProsemirrorNode, Schema } from 'prosemirror-model';
|
||||
|
||||
export interface TokenConfig {
|
||||
/**
|
||||
* This token maps to a single node, whose type can be looked up
|
||||
* in the schema under the given name. Exactly one of `node`,
|
||||
* `block`, or `mark` must be set.
|
||||
*/
|
||||
node?: string;
|
||||
|
||||
/**
|
||||
* This token also comes in `_open` and `_close` variants, but
|
||||
* should add a mark (named by the value) to its content, rather
|
||||
* than wrapping it in a node.
|
||||
*/
|
||||
mark?: string;
|
||||
|
||||
/**
|
||||
* This token comes in `_open` and `_close` variants (which are
|
||||
* appended to the base token name provides a the object
|
||||
* property), and wraps a block of content. The block should be
|
||||
* wrapped in a node of the type named to by the property's
|
||||
* value.
|
||||
*/
|
||||
block?: string;
|
||||
|
||||
/**
|
||||
* Attributes for the node or mark. When `getAttrs` is provided,
|
||||
* it takes precedence.
|
||||
*/
|
||||
attrs?: Record<string, any>;
|
||||
|
||||
/**
|
||||
* A function used to compute the attributes for the node or mark
|
||||
* that takes a [markdown-it
|
||||
* token](https://markdown-it.github.io/markdown-it/#Token) and
|
||||
* returns an attribute object.
|
||||
*/
|
||||
getAttrs?(token: Token): Record<string, any>;
|
||||
|
||||
/**
|
||||
* When true, ignore content for the matched token.
|
||||
*/
|
||||
ignore?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* A configuration of a Markdown parser. Such a parser uses
|
||||
@ -17,93 +63,110 @@ import { Node as ProsemirrorNode, Schema } from 'prosemirror-model';
|
||||
* the tokens to create a ProseMirror document tree.
|
||||
*/
|
||||
export class MarkdownParser<S extends Schema = any> {
|
||||
/**
|
||||
* Create a parser with the given configuration. You can configure
|
||||
* the markdown-it parser to parse the dialect you want, and provide
|
||||
* a description of the ProseMirror entities those tokens map to in
|
||||
* the `tokens` object, which maps token names to descriptions of
|
||||
* what to do with them. Such a description is an object, and may
|
||||
* have the following properties:
|
||||
*
|
||||
* **`node`**`: ?string`
|
||||
* : This token maps to a single node, whose type can be looked up
|
||||
* in the schema under the given name. Exactly one of `node`,
|
||||
* `block`, or `mark` must be set.
|
||||
*
|
||||
* **`block`**`: ?string`
|
||||
* : This token comes in `_open` and `_close` variants (which are
|
||||
* appended to the base token name provides a the object
|
||||
* property), and wraps a block of content. The block should be
|
||||
* wrapped in a node of the type named to by the property's
|
||||
* value.
|
||||
*
|
||||
* **`mark`**`: ?string`
|
||||
* : This token also comes in `_open` and `_close` variants, but
|
||||
* should add a mark (named by the value) to its content, rather
|
||||
* than wrapping it in a node.
|
||||
*
|
||||
* **`attrs`**`: ?Object`
|
||||
* : Attributes for the node or mark. When `getAttrs` is provided,
|
||||
* it takes precedence.
|
||||
*
|
||||
* **`getAttrs`**`: ?(MarkdownToken) → Object`
|
||||
* : A function used to compute the attributes for the node or mark
|
||||
* that takes a [markdown-it
|
||||
* token](https://markdown-it.github.io/markdown-it/#Token) and
|
||||
* returns an attribute object.
|
||||
*
|
||||
* **`ignore`**`: ?bool`
|
||||
* : When true, ignore content for the matched token.
|
||||
*/
|
||||
constructor(schema: S, tokenizer: MarkdownIt, tokens: { [key: string]: any });
|
||||
/**
|
||||
* The value of the `tokens` object used to construct
|
||||
* this parser. Can be useful to copy and modify to base other
|
||||
* parsers on.
|
||||
*/
|
||||
tokens: { [key: string]: any };
|
||||
/**
|
||||
* Parse a string as [CommonMark](http://commonmark.org/) markup,
|
||||
* and create a ProseMirror document as prescribed by this parser's
|
||||
* rules.
|
||||
*/
|
||||
parse(text: string): ProsemirrorNode<S>;
|
||||
/**
|
||||
* Create a parser with the given configuration. You can configure
|
||||
* the markdown-it parser to parse the dialect you want, and provide
|
||||
* a description of the ProseMirror entities those tokens map to in
|
||||
* the `tokens` object, which maps token names to descriptions of
|
||||
* what to do with them. Such a description is an object, and may
|
||||
* have the following properties:
|
||||
*
|
||||
* **`node`**`: ?string`
|
||||
* : This token maps to a single node, whose type can be looked up
|
||||
* in the schema under the given name. Exactly one of `node`,
|
||||
* `block`, or `mark` must be set.
|
||||
*
|
||||
* **`block`**`: ?string`
|
||||
* : This token comes in `_open` and `_close` variants (which are
|
||||
* appended to the base token name provides a the object
|
||||
* property), and wraps a block of content. The block should be
|
||||
* wrapped in a node of the type named to by the property's
|
||||
* value.
|
||||
*
|
||||
* **`mark`**`: ?string`
|
||||
* : This token also comes in `_open` and `_close` variants, but
|
||||
* should add a mark (named by the value) to its content, rather
|
||||
* than wrapping it in a node.
|
||||
*
|
||||
* **`attrs`**`: ?Object`
|
||||
* : Attributes for the node or mark. When `getAttrs` is provided,
|
||||
* it takes precedence.
|
||||
*
|
||||
* **`getAttrs`**`: ?(MarkdownToken) → Object`
|
||||
* : A function used to compute the attributes for the node or mark
|
||||
* that takes a [markdown-it
|
||||
* token](https://markdown-it.github.io/markdown-it/#Token) and
|
||||
* returns an attribute object.
|
||||
*
|
||||
* **`ignore`**`: ?bool`
|
||||
* : When true, ignore content for the matched token.
|
||||
*/
|
||||
constructor(schema: S, tokenizer: MarkdownIt, tokens: { [key: string]: TokenConfig });
|
||||
/**
|
||||
* The value of the `tokens` object used to construct
|
||||
* this parser. Can be useful to copy and modify to base other
|
||||
* parsers on.
|
||||
*/
|
||||
tokens: { [key: string]: Token };
|
||||
/**
|
||||
* Parse a string as [CommonMark](http://commonmark.org/) markup,
|
||||
* and create a ProseMirror document as prescribed by this parser's
|
||||
* rules.
|
||||
*/
|
||||
parse(text: string): ProsemirrorNode<S>;
|
||||
}
|
||||
/**
|
||||
* A parser parsing unextended [CommonMark](http://commonmark.org/),
|
||||
* without inline HTML, and producing a document in the basic schema.
|
||||
*/
|
||||
export let defaultMarkdownParser: MarkdownParser;
|
||||
|
||||
export type MarkSerializerMethod<S extends Schema = any> = (
|
||||
state: MarkdownSerializerState<S>,
|
||||
mark: Mark<S>,
|
||||
parent: Fragment<S>,
|
||||
index: number,
|
||||
) => void;
|
||||
|
||||
export interface MarkSerializerConfig<S extends Schema = any> {
|
||||
open: string | MarkSerializerMethod<S>;
|
||||
close: string | MarkSerializerMethod<S>;
|
||||
mixable?: boolean;
|
||||
expelEnclosingWhitespace?: boolean;
|
||||
escape?: boolean;
|
||||
}
|
||||
/**
|
||||
* A specification for serializing a ProseMirror document as
|
||||
* Markdown/CommonMark text.
|
||||
*/
|
||||
export class MarkdownSerializer<S extends Schema = any> {
|
||||
constructor(
|
||||
nodes: {
|
||||
[name: string]: (
|
||||
state: MarkdownSerializerState<S>,
|
||||
node: ProsemirrorNode<S>,
|
||||
parent: ProsemirrorNode<S>,
|
||||
index: number
|
||||
) => void;
|
||||
},
|
||||
marks: { [key: string]: any }
|
||||
);
|
||||
/**
|
||||
* The node serializer
|
||||
* functions for this serializer.
|
||||
*/
|
||||
nodes: { [name: string]: (p1: MarkdownSerializerState<S>, p2: ProsemirrorNode<S>) => void };
|
||||
/**
|
||||
* The mark serializer info.
|
||||
*/
|
||||
marks: { [key: string]: any };
|
||||
/**
|
||||
* Serialize the content of the given node to
|
||||
* [CommonMark](http://commonmark.org/).
|
||||
*/
|
||||
serialize(content: ProsemirrorNode<S>, options?: { [key: string]: any }): string;
|
||||
constructor(
|
||||
nodes: {
|
||||
[name: string]: (
|
||||
state: MarkdownSerializerState<S>,
|
||||
node: ProsemirrorNode<S>,
|
||||
parent: ProsemirrorNode<S>,
|
||||
index: number,
|
||||
) => void;
|
||||
},
|
||||
marks: {
|
||||
[key: string]: MarkSerializerConfig;
|
||||
},
|
||||
);
|
||||
/**
|
||||
* The node serializer
|
||||
* functions for this serializer.
|
||||
*/
|
||||
nodes: { [name: string]: (p1: MarkdownSerializerState<S>, p2: ProsemirrorNode<S>) => void };
|
||||
/**
|
||||
* The mark serializer info.
|
||||
*/
|
||||
marks: { [key: string]: any };
|
||||
/**
|
||||
* Serialize the content of the given node to
|
||||
* [CommonMark](http://commonmark.org/).
|
||||
*/
|
||||
serialize(content: ProsemirrorNode<S>, options?: { [key: string]: any }): string;
|
||||
}
|
||||
/**
|
||||
* A serializer for the [basic schema](#schema).
|
||||
@ -115,74 +178,81 @@ export let defaultMarkdownSerializer: MarkdownSerializer;
|
||||
* node and mark serialization methods (see `toMarkdown`).
|
||||
*/
|
||||
export class MarkdownSerializerState<S extends Schema = any> {
|
||||
/**
|
||||
* The options passed to the serializer.
|
||||
*/
|
||||
options: { tightLists?: boolean | null };
|
||||
/**
|
||||
* Render a block, prefixing each line with `delim`, and the first
|
||||
* line in `firstDelim`. `node` should be the node that is closed at
|
||||
* the end of the block, and `f` is a function that renders the
|
||||
* content of the block.
|
||||
*/
|
||||
wrapBlock(
|
||||
delim: string,
|
||||
firstDelim: string | undefined,
|
||||
node: ProsemirrorNode<S>,
|
||||
f: () => void
|
||||
): void;
|
||||
/**
|
||||
* Ensure the current content ends with a newline.
|
||||
*/
|
||||
ensureNewLine(): void;
|
||||
/**
|
||||
* Prepare the state for writing output (closing closed paragraphs,
|
||||
* adding delimiters, and so on), and then optionally add content
|
||||
* (unescaped) to the output.
|
||||
*/
|
||||
write(content?: string): void;
|
||||
/**
|
||||
* Close the block for the given node.
|
||||
*/
|
||||
closeBlock(node: ProsemirrorNode<S>): void;
|
||||
/**
|
||||
* Add the given text to the document. When escape is not `false`,
|
||||
* it will be escaped.
|
||||
*/
|
||||
text(text: string, escape?: boolean): void;
|
||||
/**
|
||||
* Render the given node as a block.
|
||||
*/
|
||||
render(node: ProsemirrorNode<S>): void;
|
||||
/**
|
||||
* Render the contents of `parent` as block nodes.
|
||||
*/
|
||||
renderContent(parent: ProsemirrorNode<S>): void;
|
||||
/**
|
||||
* Render the contents of `parent` as inline content.
|
||||
*/
|
||||
renderInline(parent: ProsemirrorNode<S>): void;
|
||||
/**
|
||||
* Render a node's content as a list. `delim` should be the extra
|
||||
* indentation added to all lines except the first in an item,
|
||||
* `firstDelim` is a function going from an item index to a
|
||||
* delimiter for the first line of the item.
|
||||
*/
|
||||
renderList(node: ProsemirrorNode<S>, delim: string, firstDelim: (p: number) => string): void;
|
||||
/**
|
||||
* Escape the given string so that it can safely appear in Markdown
|
||||
* content. If `startOfLine` is true, also escape characters that
|
||||
* has special meaning only at the start of the line.
|
||||
*/
|
||||
esc(str: string, startOfLine?: boolean): string;
|
||||
/**
|
||||
* Repeat the given string `n` times.
|
||||
*/
|
||||
repeat(str: string, n: number): string;
|
||||
/**
|
||||
* Get leading and trailing whitespace from a string. Values of
|
||||
* leading or trailing property of the return object will be undefined
|
||||
* if there is no match.
|
||||
*/
|
||||
getEnclosingWhitespace(text: string): { leading?: string | null; trailing?: string | null };
|
||||
/**
|
||||
* The options passed to the serializer.
|
||||
*/
|
||||
options: { tightLists?: boolean | null };
|
||||
/**
|
||||
* Render a block, prefixing each line with `delim`, and the first
|
||||
* line in `firstDelim`. `node` should be the node that is closed at
|
||||
* the end of the block, and `f` is a function that renders the
|
||||
* content of the block.
|
||||
*/
|
||||
wrapBlock(delim: string, firstDelim: string | undefined, node: ProsemirrorNode<S>, f: () => void): void;
|
||||
/**
|
||||
* Ensure the current content ends with a newline.
|
||||
*/
|
||||
ensureNewLine(): void;
|
||||
/**
|
||||
* Prepare the state for writing output (closing closed paragraphs,
|
||||
* adding delimiters, and so on), and then optionally add content
|
||||
* (unescaped) to the output.
|
||||
*/
|
||||
write(content?: string): void;
|
||||
/**
|
||||
* Close the block for the given node.
|
||||
*/
|
||||
closeBlock(node: ProsemirrorNode<S>): void;
|
||||
/**
|
||||
* Add the given text to the document. When escape is not `false`,
|
||||
* it will be escaped.
|
||||
*/
|
||||
text(text: string, escape?: boolean): void;
|
||||
|
||||
/**
|
||||
* Render the given node as a block.
|
||||
*/
|
||||
render(node: ProsemirrorNode<S>): void;
|
||||
|
||||
/**
|
||||
* Render the contents of `parent` as block nodes.
|
||||
*/
|
||||
renderContent(parent: ProsemirrorNode<S>): void;
|
||||
|
||||
/**
|
||||
* Render the contents of `parent` as inline content.
|
||||
*/
|
||||
renderInline(parent: ProsemirrorNode<S>): void;
|
||||
|
||||
/**
|
||||
* Render a node's content as a list. `delim` should be the extra
|
||||
* indentation added to all lines except the first in an item,
|
||||
* `firstDelim` is a function going from an item index to a
|
||||
* delimiter for the first line of the item.
|
||||
*/
|
||||
renderList(node: ProsemirrorNode<S>, delim: string, firstDelim: (p: number) => string): void;
|
||||
|
||||
/**
|
||||
* Escape the given string so that it can safely appear in Markdown
|
||||
* content. If `startOfLine` is true, also escape characters that
|
||||
* has special meaning only at the start of the line.
|
||||
*/
|
||||
esc(str: string, startOfLine?: boolean): string;
|
||||
|
||||
/**
|
||||
* Repeat the given string `n` times.
|
||||
*/
|
||||
repeat(str: string, n: number): string;
|
||||
|
||||
/**
|
||||
* Get leading and trailing whitespace from a string. Values of
|
||||
* leading or trailing property of the return object will be undefined
|
||||
* if there is no match.
|
||||
*/
|
||||
getEnclosingWhitespace(text: string): { leading?: string | null; trailing?: string | null };
|
||||
|
||||
/**
|
||||
* Wraps the passed string in a string of its own
|
||||
*/
|
||||
quote(str: string): string;
|
||||
}
|
||||
|
||||
@ -1 +1,178 @@
|
||||
import * as markdown from 'prosemirror-markdown';
|
||||
import { MarkdownParser, MarkdownSerializer } from 'prosemirror-markdown';
|
||||
import { Schema, Node as ProsemirrorNode, Mark, Fragment } from 'prosemirror-model';
|
||||
import md = require('markdown-it');
|
||||
|
||||
/**
|
||||
* Parses markdown content into a ProsemirrorNode compatible with the provided schema.
|
||||
*/
|
||||
export const fromMarkdown = (markdown: string, schema: Schema) =>
|
||||
new MarkdownParser(schema, md('commonmark'), {
|
||||
blockquote: { block: 'blockquote' },
|
||||
paragraph: { block: 'paragraph' },
|
||||
list_item: { block: 'listItem' },
|
||||
bullet_list: { block: 'bulletList' },
|
||||
ordered_list: {
|
||||
block: 'orderedList',
|
||||
getAttrs: tok => ({ order: parseInt(tok.attrGet('order') || '1', 10) }),
|
||||
},
|
||||
heading: { block: 'heading', getAttrs: tok => ({ level: +tok.tag.slice(1) }) },
|
||||
code_block: { block: 'codeBlock' },
|
||||
fence: { block: 'codeBlock', getAttrs: tok => ({ params: tok.info || '' }) },
|
||||
hr: { node: 'horizontalRule' },
|
||||
image: {
|
||||
node: 'image',
|
||||
getAttrs: tok => ({
|
||||
src: tok.attrGet('src'),
|
||||
title: tok.attrGet('title') || null,
|
||||
alt: (tok.children[0] && tok.children[0].content) || null,
|
||||
}),
|
||||
},
|
||||
hardbreak: { node: 'hardBreak' },
|
||||
em: { mark: 'italic' },
|
||||
strong: { mark: 'bold' },
|
||||
link: {
|
||||
mark: 'link',
|
||||
getAttrs: tok => ({
|
||||
href: tok.attrGet('href'),
|
||||
title: tok.attrGet('title') || null,
|
||||
}),
|
||||
},
|
||||
code_inline: { mark: 'code' },
|
||||
}).parse(markdown);
|
||||
|
||||
export const toMarkdown = (content: ProsemirrorNode) =>
|
||||
new MarkdownSerializer(
|
||||
{
|
||||
blockquote(state, node) {
|
||||
state.wrapBlock('> ', undefined, node, () => state.renderContent(node));
|
||||
},
|
||||
codeBlock(state, node) {
|
||||
// tslint:disable-next-line: prefer-template
|
||||
state.write('```' + (node.attrs.language || '') + '\n');
|
||||
state.text(node.textContent, false);
|
||||
state.ensureNewLine();
|
||||
state.write('```');
|
||||
state.closeBlock(node);
|
||||
},
|
||||
heading(state, node) {
|
||||
state.write(state.repeat('#', node.attrs.level) + ' ');
|
||||
state.renderInline(node);
|
||||
state.closeBlock(node);
|
||||
},
|
||||
horizontalRule(state, node) {
|
||||
state.write(node.attrs.markup || '---');
|
||||
state.closeBlock(node);
|
||||
},
|
||||
bulletList(state, node) {
|
||||
state.renderList(node, ' ', () => (node.attrs.bullet || '*') + ' ');
|
||||
},
|
||||
orderedList(state, node) {
|
||||
const start = node.attrs.order || 1;
|
||||
const maxW = String(start + node.childCount - 1).length;
|
||||
const space = state.repeat(' ', maxW + 2);
|
||||
state.renderList(node, space, i => {
|
||||
const nStr = String(start + i);
|
||||
// tslint:disable-next-line: prefer-template
|
||||
return state.repeat(' ', maxW - nStr.length) + nStr + '. ';
|
||||
});
|
||||
},
|
||||
listItem(state, node) {
|
||||
state.renderContent(node);
|
||||
},
|
||||
paragraph(state, node) {
|
||||
console.log(state, node);
|
||||
state.renderInline(node);
|
||||
state.closeBlock(node);
|
||||
},
|
||||
image(state, node) {
|
||||
state.write(
|
||||
// tslint:disable-next-line: prefer-template
|
||||
' +
|
||||
(node.attrs.title ? ' ' + state.quote(node.attrs.title) : '') +
|
||||
')',
|
||||
);
|
||||
},
|
||||
hardBreak(state, node, parent, index) {
|
||||
for (let i = index + 1; i < parent.childCount; i++) {
|
||||
if (parent.child(i).type !== node.type) {
|
||||
state.write('\\\n');
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
text(state, node) {
|
||||
if (!node.text) {
|
||||
return;
|
||||
}
|
||||
state.text(node.text);
|
||||
},
|
||||
},
|
||||
{
|
||||
italic: { open: '*', close: '*', mixable: true, expelEnclosingWhitespace: true },
|
||||
bold: { open: '**', close: '**', mixable: true, expelEnclosingWhitespace: true },
|
||||
link: {
|
||||
open(_state, mark, parent, index) {
|
||||
return isPlainURL(mark, parent, index, 1) ? '<' : '[';
|
||||
},
|
||||
close(state, mark, parent, index) {
|
||||
return isPlainURL(mark, parent, index, -1)
|
||||
? '>'
|
||||
: // tslint:disable-next-line: prefer-template
|
||||
'](' +
|
||||
state.esc(mark.attrs.href) +
|
||||
(mark.attrs.title ? ' ' + state.quote(mark.attrs.title) : '') +
|
||||
')';
|
||||
},
|
||||
},
|
||||
code: {
|
||||
open(_state, _mark, parent, index) {
|
||||
return backticksFor(parent.child(index), -1);
|
||||
},
|
||||
close(_state, _mark, parent, index) {
|
||||
return backticksFor(parent.child(index - 1), 1);
|
||||
},
|
||||
escape: false,
|
||||
},
|
||||
},
|
||||
).serialize(content);
|
||||
|
||||
type Side = -1 | 1;
|
||||
|
||||
function isPlainURL(link: Mark, parent: Fragment, index: number, side: Side) {
|
||||
if (link.attrs.title) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const content = parent.child(index + (side < 0 ? -1 : 0));
|
||||
if (!content.isText || content.text !== link.attrs.href || content.marks[content.marks.length - 1] !== link) {
|
||||
return false;
|
||||
}
|
||||
if (index === (side < 0 ? 1 : parent.childCount - 1)) {
|
||||
return true;
|
||||
}
|
||||
const next = parent.child(index + (side < 0 ? -2 : 1));
|
||||
return !link.isInSet(next.marks);
|
||||
}
|
||||
|
||||
function backticksFor(node: ProsemirrorNode, side: Side) {
|
||||
const ticks = /`+/g;
|
||||
let m;
|
||||
let len = 0;
|
||||
if (node.isText) {
|
||||
// tslint:disable-next-line:no-conditional-assignment
|
||||
while ((m = ticks.exec(node.text!))) {
|
||||
len = Math.max(len, m[0].length);
|
||||
}
|
||||
}
|
||||
let result = len > 0 && side > 0 ? ' `' : '`';
|
||||
for (let i = 0; i < len; i++) {
|
||||
result += '`';
|
||||
}
|
||||
if (len > 0 && side < 0) {
|
||||
result += ' ';
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user