Documentation ¶
Index ¶
- Variables
- func ParseMarkdown(parser parser.Parser, funcs NodeMapper, source []byte, schema *model.Schema) (*model.Node, error)
- func WithoutTrailingNewline(node ast.Node, source []byte) string
- type MarkSerializerSpec
- type MarkdownParseState
- func (state *MarkdownParseState) AddNode(typ *model.NodeType, attrs map[string]interface{}, content interface{}) (*model.Node, error)
- func (state *MarkdownParseState) AddText(text string)
- func (state *MarkdownParseState) CloseMark(mark *model.Mark)
- func (state *MarkdownParseState) CloseNode() (*model.Node, error)
- func (state *MarkdownParseState) OpenMark(mark *model.Mark)
- func (state *MarkdownParseState) OpenNode(typ *model.NodeType, attrs map[string]interface{})
- func (state *MarkdownParseState) Pop() *StackItem
- func (state *MarkdownParseState) Push(node *model.Node)
- func (state *MarkdownParseState) Top() *StackItem
- type NodeMapper
- type NodeMapperFunc
- type NodeSerializerFunc
- type Serializer
- type SerializerState
- func (s *SerializerState) CloseBlock(node *model.Node)
- func (s *SerializerState) EnsureNewLine()
- func (s *SerializerState) Esc(str string, startOfLine ...bool) string
- func (s *SerializerState) MarkString(mark *model.Mark, open bool, parent *model.Node, index int) string
- func (s *SerializerState) Quote(str string) string
- func (s *SerializerState) Render(node, parent *model.Node, index int)
- func (s *SerializerState) RenderContent(parent *model.Node)
- func (s *SerializerState) RenderInline(parent *model.Node)
- func (s *SerializerState) RenderList(node *model.Node, delim string, firstDelim func(i int) string)
- func (s *SerializerState) Text(text string, escape ...bool)
- func (s *SerializerState) WrapBlock(delim string, firstDelim *string, node *model.Node, f func())
- func (s *SerializerState) Write(content ...string)
- type StackItem
Constants ¶
This section is empty.
Variables ¶
var DefaultNodeMapper = NodeMapper{ ast.KindDocument: func(state *MarkdownParseState, node ast.Node, entering bool) error { if entering { typ, err := state.Schema.NodeType(state.Schema.Spec.TopNode) if err != nil { return err } state.OpenNode(typ, nil) } else { info := state.Pop() node, err := info.Type.CreateAndFill(info.Attrs, info.Content, model.NoMarks) if err != nil { return err } state.Root = node } return nil }, ast.KindParagraph: GenericBlockHandler("paragraph"), ast.KindHeading: func(state *MarkdownParseState, node ast.Node, entering bool) error { if entering { typ, err := state.Schema.NodeType("heading") if err != nil { return err } level := node.(*ast.Heading).Level attrs := map[string]interface{}{"level": level} state.OpenNode(typ, attrs) } else { if _, err := state.CloseNode(); err != nil { return err } } return nil }, ast.KindList: func(state *MarkdownParseState, node ast.Node, entering bool) error { nodeType := "bulletList" if node.(*ast.List).IsOrdered() { nodeType = "orderedList" } if entering { typ, err := state.Schema.NodeType(nodeType) if err != nil { nodeType = strings.Replace(nodeType, "L", "_l", 1) typ, err = state.Schema.NodeType(nodeType) if err != nil { return err } } var attrs map[string]interface{} if node.(*ast.List).IsOrdered() { order := float64(node.(*ast.List).Start) attrs = map[string]interface{}{"order": order} } state.OpenNode(typ, attrs) } else { if _, err := state.CloseNode(); err != nil { return err } } return nil }, ast.KindListItem: func(state *MarkdownParseState, node ast.Node, entering bool) error { if entering { typ, err := state.Schema.NodeType("listItem") if err != nil { typ, err = state.Schema.NodeType("list_item") if err != nil { return err } } state.OpenNode(typ, nil) } else { if _, err := state.CloseNode(); err != nil { return err } } return nil }, ast.KindTextBlock: GenericBlockHandler("paragraph"), ast.KindBlockquote: GenericBlockHandler("blockquote"), ast.KindCodeBlock: func(state *MarkdownParseState, node ast.Node, entering bool) error { if entering { node := node.(*ast.CodeBlock) typ, err := state.Schema.NodeType("codeBlock") if err != nil { typ, err = state.Schema.NodeType("code_block") if err != nil { return err } } state.OpenNode(typ, nil) state.AddText(WithoutTrailingNewline(node, state.Source)) } else { if _, err := state.CloseNode(); err != nil { return err } } return nil }, ast.KindFencedCodeBlock: func(state *MarkdownParseState, node ast.Node, entering bool) error { if entering { node := node.(*ast.FencedCodeBlock) typ, err := state.Schema.NodeType("codeBlock") if err != nil { typ, err = state.Schema.NodeType("code_block") if err != nil { return err } } lang := node.Language(state.Source) attrs := map[string]interface{}{ "language": string(lang), } state.OpenNode(typ, attrs) state.AddText(WithoutTrailingNewline(node, state.Source)) } else { if _, err := state.CloseNode(); err != nil { return err } } return nil }, ast.KindThematicBreak: GenericBlockHandler("rule"), ast.KindText: func(state *MarkdownParseState, node ast.Node, entering bool) error { if entering { n := node.(*ast.Text) content := n.Segment.Value(state.Source) if len(content) > 0 { state.AddText(string(content)) } if n.HardLineBreak() { typ, err := state.Schema.NodeType("hardBreak") if err != nil { typ, err = state.Schema.NodeType("hard_break") if err != nil { return err } } if _, err := state.AddNode(typ, nil, nil); err != nil { return err } } } return nil }, ast.KindString: func(state *MarkdownParseState, node ast.Node, entering bool) error { if entering { content := node.(*ast.String).Value state.AddText(string(content)) } return nil }, ast.KindAutoLink: func(state *MarkdownParseState, node ast.Node, entering bool) error { typ, err := state.Schema.MarkType("link") if err != nil { return err } n := node.(*ast.AutoLink) url := string(n.URL(state.Source)) attrs := map[string]interface{}{"href": url} mark := typ.Create(attrs) if entering { state.OpenMark(mark) state.AddText(url) } else { state.CloseMark(mark) } return nil }, ast.KindLink: func(state *MarkdownParseState, node ast.Node, entering bool) error { typ, err := state.Schema.MarkType("link") if err != nil { return err } n := node.(*ast.Link) attrs := map[string]interface{}{ "href": string(n.Destination), } if title := string(n.Title); len(title) > 0 { attrs["title"] = strings.ReplaceAll(title, `\"`, `"`) } mark := typ.Create(attrs) if entering { state.OpenMark(mark) } else { state.CloseMark(mark) } return nil }, ast.KindImage: func(state *MarkdownParseState, node ast.Node, entering bool) error { if entering { typ, err := state.Schema.NodeType("image") if err != nil { return err } n := node.(*ast.Image) attrs := map[string]interface{}{ "src": string(n.Destination), "title": string(n.Title), } state.OpenNode(typ, attrs) } else { if _, err := state.CloseNode(); err != nil { return err } } return nil }, ast.KindCodeSpan: GenericMarkHandler("code"), ast.KindEmphasis: func(state *MarkdownParseState, node ast.Node, entering bool) error { var typ *model.MarkType var err error if node.(*ast.Emphasis).Level == 2 { typ, err = state.Schema.MarkType("strong") } else { typ, err = state.Schema.MarkType("em") } if err != nil { return err } var attrs map[string]interface{} mark := typ.Create(attrs) if entering { state.OpenMark(mark) } else { state.CloseMark(mark) } return nil }, extensionast.KindStrikethrough: GenericMarkHandler("strike"), }
DefaultNodeMapper is a parser parsing unextended [CommonMark](http://commonmark.org/), without inline HTML, and producing a document in the basic schema.
var DefaultSerializer = NewSerializer(map[string]NodeSerializerFunc{ "blockquote": func(state *SerializerState, node, _parent *model.Node, _index int) { state.WrapBlock("> ", nil, node, func() { state.RenderContent(node) }) }, "code_block": func(state *SerializerState, node, _parent *model.Node, _index int) { fence := "```" content := node.TextContent() matches := backticksRegexp.FindAllString(content, -1) for _, backticks := range matches { if len(backticks) >= len(fence) { fence = backticks + "`" } } params, _ := node.Attrs["params"].(string) state.Write(fence + params + "\n") state.Text(content, false) state.Write("\n") state.Write(fence) state.CloseBlock(node) }, "heading": func(state *SerializerState, node, _parent *model.Node, _index int) { level := getAttrInt(node.Attrs, "level", 1) state.Write(strings.Repeat("#", level) + " ") state.RenderInline(node) state.CloseBlock(node) }, "horizontal_rule": func(state *SerializerState, node, _parent *model.Node, _index int) { markup := "---" if m, ok := node.Attrs["markup"].(string); ok { markup = m } state.Write(markup) state.CloseBlock(node) }, "bullet_list": func(state *SerializerState, node, _parent *model.Node, _index int) { bullet := "*" if b, ok := node.Attrs["bullet"].(string); ok { bullet = b } state.RenderList(node, " ", func(_ int) string { return bullet + " " }) }, "ordered_list": func(state *SerializerState, node, _parent *model.Node, _index int) { start := getAttrInt(node.Attrs, "order", 1) maxW := len(fmt.Sprintf("%d", start+node.ChildCount()-1)) space := strings.Repeat(" ", maxW+2) state.RenderList(node, space, func(i int) string { nStr := fmt.Sprintf("%d", start+i) return strings.Repeat(" ", maxW-len(nStr)) + nStr + ". " }) }, "list_item": func(state *SerializerState, node, _parent *model.Node, _index int) { state.RenderContent(node) }, "paragraph": func(state *SerializerState, node, _parent *model.Node, _index int) { state.RenderInline(node) state.CloseBlock(node) }, "image": func(state *SerializerState, node, _parent *model.Node, _index int) { alt, _ := node.Attrs["alt"].(string) src, _ := node.Attrs["src"].(string) src = strings.ReplaceAll(src, "(", "\\(") src = strings.ReplaceAll(src, ")", "\\)") title := "" if t, ok := node.Attrs["title"].(string); ok { title = ` "` + strings.ReplaceAll(t, `"`, `\"`) + `"` } state.Write(fmt.Sprintf("![%s](%s)%s", state.Esc(alt), src, title)) }, "hard_break": func(state *SerializerState, node, parent *model.Node, index int) { for i := index; i < parent.ChildCount(); i++ { if child, err := parent.Child(i); err == nil { if child.Type != node.Type { state.Write("\\\n") return } } } }, "text": func(state *SerializerState, node, _parent *model.Node, _index int) { state.Text(*node.Text, !state.InAutoLink) }, }, map[string]MarkSerializerSpec{ "em": {Open: "*", Close: "*", Mixable: true, ExpelEnclosingWhitespace: true}, "strong": {Open: "**", Close: "**", Mixable: true, ExpelEnclosingWhitespace: true}, "link": { Open: func(state *SerializerState, mark *model.Mark, parent *model.Node, index int) string { state.InAutoLink = isPlainURL(mark, parent, index) if state.InAutoLink { return "<" } return "[" }, Close: func(state *SerializerState, mark *model.Mark, parent *model.Node, index int) string { if state.InAutoLink { state.InAutoLink = false return ">" } href, _ := mark.Attrs["href"].(string) href = strings.ReplaceAll(href, "(", "\\(") href = strings.ReplaceAll(href, ")", "\\)") href = strings.ReplaceAll(href, `"`, `\"`) title, _ := mark.Attrs["title"].(string) if title != "" { title = ` "` + strings.ReplaceAll(title, `"`, `\"`) + `"` } return fmt.Sprintf("](%s%s)", href, title) }, Mixable: true, }, "code": { Open: func(_state *SerializerState, _mark *model.Mark, parent *model.Node, index int) string { child, err := parent.Child(index) if err != nil { return "`" } return backticksFor(child, -1) }, Close: func(_state *SerializerState, _mark *model.Mark, parent *model.Node, index int) string { child, err := parent.Child(index - 1) if err != nil { return "`" } return backticksFor(child, 1) }, NoEscape: true, }, })
DefaultSerializer is a serializer for the [basic schema](#schema).
Functions ¶
func ParseMarkdown ¶ added in v0.5.0
func ParseMarkdown(parser parser.Parser, funcs NodeMapper, source []byte, schema *model.Schema) (*model.Node, error)
ParseMarkdown parses a string as [CommonMark](http://commonmark.org/) markup, and create a ProseMirror document as prescribed by this parser's rules.
Types ¶
type MarkSerializerSpec ¶
type MarkSerializerSpec struct { Open interface{} // Can be a string or a func Close interface{} // Can be a string or a func Mixable bool ExpelEnclosingWhitespace bool NoEscape bool }
MarkSerializerSpec is the serializer info for a mark.
type MarkdownParseState ¶ added in v0.5.0
type MarkdownParseState struct { Source []byte Schema *model.Schema Root *model.Node Stack []*StackItem }
MarkdownParseState is an object used to track the context of a running parse.
func (*MarkdownParseState) AddNode ¶ added in v0.5.0
func (state *MarkdownParseState) AddNode(typ *model.NodeType, attrs map[string]interface{}, content interface{}) (*model.Node, error)
AddNode adds a node at the current position.
func (*MarkdownParseState) AddText ¶ added in v0.5.0
func (state *MarkdownParseState) AddText(text string)
AddText adds the given text to the current position in the document, using the current marks as styling.
func (*MarkdownParseState) CloseMark ¶ added in v0.5.0
func (state *MarkdownParseState) CloseMark(mark *model.Mark)
CloseMark removes the given mark from the set of active marks.
func (*MarkdownParseState) CloseNode ¶ added in v0.5.0
func (state *MarkdownParseState) CloseNode() (*model.Node, error)
CloseNode closes and returns the node that is currently on top of the stack.
func (*MarkdownParseState) OpenMark ¶ added in v0.5.0
func (state *MarkdownParseState) OpenMark(mark *model.Mark)
OpenMark adds the given mark to the set of active marks.
func (*MarkdownParseState) OpenNode ¶ added in v0.5.0
func (state *MarkdownParseState) OpenNode(typ *model.NodeType, attrs map[string]interface{})
OpenNode wraps subsequent content in a node of the given type.
func (*MarkdownParseState) Pop ¶ added in v0.5.0
func (state *MarkdownParseState) Pop() *StackItem
func (*MarkdownParseState) Push ¶ added in v0.5.0
func (state *MarkdownParseState) Push(node *model.Node)
func (*MarkdownParseState) Top ¶ added in v0.5.0
func (state *MarkdownParseState) Top() *StackItem
type NodeMapper ¶ added in v0.5.0
type NodeMapper map[ast.NodeKind]NodeMapperFunc
type NodeMapperFunc ¶ added in v0.5.0
type NodeMapperFunc func(state *MarkdownParseState, node ast.Node, entering bool) error
func GenericBlockHandler ¶ added in v0.5.0
func GenericBlockHandler(nodeType string) NodeMapperFunc
func GenericMarkHandler ¶ added in v0.5.0
func GenericMarkHandler(markType string) NodeMapperFunc
type NodeSerializerFunc ¶
type NodeSerializerFunc func(state *SerializerState, node, parent *model.Node, index int)
NodeSerializerFunc is the function to serialize a node.
type Serializer ¶
type Serializer struct { Nodes map[string]NodeSerializerFunc Marks map[string]MarkSerializerSpec }
Serializer is a specification for serializing a ProseMirror document as Markdown/CommonMark text.
func NewSerializer ¶
func NewSerializer(nodes map[string]NodeSerializerFunc, marks map[string]MarkSerializerSpec) *Serializer
NewSerializer constructs a serializer with the given configuration. The `nodes` object should map node names in a given schema to function that take a serializer state and such a node, and serialize the node.
The `marks` object should hold objects with `open` and `close` properties, which hold the strings that should appear before and after a piece of text marked that way, either directly or as a function that takes a serializer state and a mark, and returns a string. `open` and `close` can also be functions, which will be called as
(state: MarkdownSerializerState, mark: Mark, parent: Fragment, index: number) → string
Where `parent` and `index` allow you to inspect the mark's context to see which nodes it applies to.
Mark information objects can also have a `mixable` property which, when `true`, indicates that the order in which the mark's opening and closing syntax appears relative to other mixable marks can be varied. (For example, you can say `**a *b***` and `*a **b***`, but not “ `a *b*` “.)
To disable character escaping in a mark, you can give it an `escape` property of `false`. Such a mark has to have the highest precedence (must always be the innermost mark).
The `expelEnclosingWhitespace` mark property causes the serializer to move enclosing whitespace from inside the marks to outside the marks. This is necessary for emphasis marks as CommonMark does not permit enclosing whitespace inside emphasis marks, see: http://spec.commonmark.org/0.26/#example-330
func (*Serializer) Serialize ¶
func (s *Serializer) Serialize(content *model.Node, options ...map[string]interface{}) string
Serialize the content of the given node to [CommonMark](http://commonmark.org/).
type SerializerState ¶
type SerializerState struct { Nodes map[string]NodeSerializerFunc Marks map[string]MarkSerializerSpec Delim string Out string Closed *model.Node InAutoLink bool AtBlockStart bool InTightList bool // contains filtered or unexported fields }
SerializerState is an object used to track state and expose methods related to markdown serialization. Instances are passed to node and mark serialization methods (see `toMarkdown`).
func NewSerializerState ¶
func NewSerializerState( nodes map[string]NodeSerializerFunc, marks map[string]MarkSerializerSpec, options map[string]interface{}, ) *SerializerState
NewSerializerState is the constructor for NewSerializerState.
Options are the options passed to the serializer.
tightLists:: ?bool Whether to render lists in a tight style. This can be overridden on a node level by specifying a tight attribute on the node. Defaults to false.
func (*SerializerState) CloseBlock ¶
func (s *SerializerState) CloseBlock(node *model.Node)
CloseBlock closes the block for the given node.
func (*SerializerState) EnsureNewLine ¶
func (s *SerializerState) EnsureNewLine()
EnsureNewLine ensures the current content ends with a newline.
func (*SerializerState) Esc ¶
func (s *SerializerState) Esc(str string, startOfLine ...bool) string
Esc escapes the given string so that it can safely appear in Markdown content. If `startOfLine` is true, also escape characters that have special meaning only at the start of the line.
func (*SerializerState) MarkString ¶
func (s *SerializerState) MarkString(mark *model.Mark, open bool, parent *model.Node, index int) string
MarkString gets the markdown string for a given opening or closing mark.
func (*SerializerState) Quote ¶
func (s *SerializerState) Quote(str string) string
Quote wraps the string as a quote.
func (*SerializerState) Render ¶
func (s *SerializerState) Render(node, parent *model.Node, index int)
Render the given node as a block.
func (*SerializerState) RenderContent ¶
func (s *SerializerState) RenderContent(parent *model.Node)
RenderContent renders the contents of `parent` as block nodes.
func (*SerializerState) RenderInline ¶
func (s *SerializerState) RenderInline(parent *model.Node)
RenderInline renders the contents of `parent` as inline content.
func (*SerializerState) RenderList ¶
RenderList renders 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.
func (*SerializerState) Text ¶
func (s *SerializerState) Text(text string, escape ...bool)
Text adds the given text to the document. When escape is not `false`, it will be escaped.
func (*SerializerState) WrapBlock ¶
func (s *SerializerState) WrapBlock(delim string, firstDelim *string, node *model.Node, f func())
WrapBlock renders 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.
func (*SerializerState) Write ¶
func (s *SerializerState) Write(content ...string)
Write prepares the state for writing output (closing closed paragraphs, adding delimiters, and so on), and then optionally add content (unescaped) to the output.