diff --git a/go.mod b/go.mod
index f3ae559eee389aca65d87d1a33193844f18987d7..0bddd18d7893028bc97bf1f9535249db58e8819c 100644
--- a/go.mod
+++ b/go.mod
@@ -10,7 +10,7 @@ require (
 	github.com/cozy/goexif2 v0.0.0-20200819113101-00e1cc8cc9d3
 	github.com/cozy/gomail v0.0.0-20170313100128-1395d9a6a6c0
 	github.com/cozy/httpcache v0.0.0-20210224123405-3f334f841945
-	github.com/cozy/prosemirror-go v0.4.12
+	github.com/cozy/prosemirror-go v0.5.0
 	github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
 	github.com/dhowden/tag v0.0.0-20201120070457-d52dcb253c63
 	github.com/dustin/go-humanize v1.0.0
@@ -44,7 +44,7 @@ require (
 	github.com/spf13/viper v1.9.0
 	github.com/stretchr/testify v1.7.0
 	github.com/ugorji/go/codec v1.2.6
-	github.com/yuin/goldmark v1.4.3
+	github.com/yuin/goldmark v1.4.4
 	golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
 	golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d
 	golang.org/x/net v0.0.0-20211020060615-d418f374d309
diff --git a/go.sum b/go.sum
index 445f0c1ea59a3a1e0c2bc41b7697d37f7ac9beb5..59ef4c815fb1b89fc830f9bbd84976beacf52799 100644
--- a/go.sum
+++ b/go.sum
@@ -99,8 +99,8 @@ github.com/cozy/gomail v0.0.0-20170313100128-1395d9a6a6c0 h1:bQVNaGvnUI7m8J8k3hk
 github.com/cozy/gomail v0.0.0-20170313100128-1395d9a6a6c0/go.mod h1:DlX8Rq7OKA0F9I1e0tz6+PCOXkKZ/l6aD+bWxCC6Qfo=
 github.com/cozy/httpcache v0.0.0-20210224123405-3f334f841945 h1:EfeD2CzaZclMHyFxSuaA1BTfqVTLaFwqlASiNNil4nE=
 github.com/cozy/httpcache v0.0.0-20210224123405-3f334f841945/go.mod h1:rLnjIcybyvs+PoCzi4+GmpOVp0+q+qdcuZKnKUKJoF4=
-github.com/cozy/prosemirror-go v0.4.12 h1:Gp07Ix7euU7mB0N8lq8kLD3NvdBu6xLAWEZQe46cTU8=
-github.com/cozy/prosemirror-go v0.4.12/go.mod h1:v15sN6s5qGHCgb1cgVGk2ZZ53lLzefVmFVwX53E3Lb8=
+github.com/cozy/prosemirror-go v0.5.0 h1:4VhyxSvfTfq5/L8WOjdW85aQRc07yvlcznPDxmjJtKA=
+github.com/cozy/prosemirror-go v0.5.0/go.mod h1:pK1bwqX1BJQ+7QB54pY44j9Dd7rAzorFWZwDRhAu5KM=
 github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
 github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -478,8 +478,8 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
 github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
-github.com/yuin/goldmark v1.4.3 h1:eTEYYWtQLjK7+WK45Tk81OTkp/0UvAyqUj8flU0nTO4=
-github.com/yuin/goldmark v1.4.3/go.mod h1:rmuwmfZ0+bvzB24eSC//bk1R1Zp3hM0OXYv/G2LIilg=
+github.com/yuin/goldmark v1.4.4 h1:zNWRjYUW32G9KirMXYHQHVNFkXvMI7LpgNW2AgYAoIs=
+github.com/yuin/goldmark v1.4.4/go.mod h1:rmuwmfZ0+bvzB24eSC//bk1R1Zp3hM0OXYv/G2LIilg=
 go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
 go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
 go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
diff --git a/model/note/from_markdown.go b/model/note/from_markdown.go
deleted file mode 100644
index ec6a0365749125e7713b8d8184f4dbb5e87c577d..0000000000000000000000000000000000000000
--- a/model/note/from_markdown.go
+++ /dev/null
@@ -1,385 +0,0 @@
-package note
-
-import (
-	"errors"
-	"fmt"
-	"strings"
-
-	"github.com/cozy/prosemirror-go/model"
-	"github.com/yuin/goldmark/ast"
-	extensionast "github.com/yuin/goldmark/extension/ast"
-	"github.com/yuin/goldmark/parser"
-	"github.com/yuin/goldmark/text"
-)
-
-func maybeMerge(a, b *model.Node) *model.Node {
-	if a.IsText() && b.IsText() && model.SameMarkSet(a.Marks, b.Marks) {
-		return a.WithText(*a.Text + *b.Text)
-	}
-	return nil
-}
-
-// MarkdownParseState is an object used to track the context of a running
-// parse.
-type MarkdownParseState struct {
-	Source      []byte
-	Schema      *model.Schema
-	Root        *model.Node
-	Stack       []*StackItem
-	Marks       []*model.Mark
-	ParentMarks []*model.Mark
-}
-
-type StackItem struct {
-	Type    *model.NodeType
-	Attrs   map[string]interface{}
-	Content []*model.Node
-}
-
-type NodeMapper map[ast.NodeKind]NodeMapperFunc
-
-type NodeMapperFunc func(state *MarkdownParseState, node ast.Node, entering bool) error
-
-func (state *MarkdownParseState) Top() *StackItem {
-	if len(state.Stack) == 0 {
-		panic(errors.New("Empty stack"))
-	}
-	last := len(state.Stack) - 1
-	return state.Stack[last]
-}
-
-func (state *MarkdownParseState) Push(node *model.Node) {
-	item := state.Top()
-	item.Content = append(item.Content, node)
-}
-
-func (state *MarkdownParseState) Pop() *StackItem {
-	if len(state.Stack) == 0 {
-		panic(errors.New("Empty stack"))
-	}
-	last := len(state.Stack) - 1
-	item := state.Stack[last]
-	state.Stack = state.Stack[:last]
-	return item
-}
-
-// AddText adds the given text to the current position in the document, using
-// the current marks as styling.
-func (state *MarkdownParseState) AddText(text string) {
-	item := state.Top()
-	node := state.Schema.Text(text, state.Marks)
-	if len(item.Content) > 0 {
-		last := item.Content[len(item.Content)-1]
-		if merged := maybeMerge(last, node); merged != nil {
-			item.Content[len(item.Content)-1] = merged
-			return
-		}
-	}
-	item.Content = append(item.Content, node)
-}
-
-// OpenMark adds the given mark to the set of active marks.
-func (state *MarkdownParseState) OpenMark(mark *model.Mark) {
-	state.Marks = mark.AddToSet(state.Marks)
-}
-
-// CloseMark removes the given mark from the set of active marks.
-func (state *MarkdownParseState) CloseMark(mark *model.Mark) {
-	state.Marks = mark.RemoveFromSet(state.Marks)
-}
-
-func (state *MarkdownParseState) AddParentMark(mark *model.Mark) {
-	state.ParentMarks = mark.AddToSet(state.ParentMarks)
-}
-
-// AddNode adds a node at the current position.
-func (state *MarkdownParseState) AddNode(typ *model.NodeType, attrs map[string]interface{}, content interface{}) (*model.Node, error) {
-	node, err := typ.CreateAndFill(attrs, content, state.Marks)
-	if node == nil {
-		return nil, err
-	}
-	state.Push(node)
-	return node, nil
-}
-
-// OpenNode wraps subsequent content in a node of the given type.
-func (state *MarkdownParseState) OpenNode(typ *model.NodeType, attrs map[string]interface{}) {
-	item := &StackItem{Type: typ, Attrs: attrs}
-	state.Stack = append(state.Stack, item)
-}
-
-// CloseNode closes and returns the node that is currently on top of the stack.
-func (state *MarkdownParseState) CloseNode() (*model.Node, error) {
-	if len(state.ParentMarks) > 0 {
-		state.Marks = state.ParentMarks
-		state.ParentMarks = model.NoMarks
-		defer func() { state.Marks = model.NoMarks }()
-	} else if len(state.Marks) > 0 {
-		state.Marks = model.NoMarks
-	}
-	info := state.Pop()
-	return state.AddNode(info.Type, info.Attrs, info.Content)
-}
-
-// ParseMarkdown parses a string as [CommonMark](http://commonmark.org/)
-// markup, and create a ProseMirror document as prescribed by this parser's
-// rules.
-func ParseMarkdown(parser parser.Parser, funcs NodeMapper, source []byte, schema *model.Schema) (*model.Node, error) {
-	tree := parser.Parse(text.NewReader(source))
-	state := &MarkdownParseState{Source: source, Schema: schema}
-	err := ast.Walk(tree, func(node ast.Node, entering bool) (ast.WalkStatus, error) {
-		if fn, ok := funcs[node.Kind()]; ok {
-			if err := fn(state, node, entering); err != nil {
-				return ast.WalkStop, err
-			}
-		} else {
-			return ast.WalkStop, fmt.Errorf("Node kind %s not supported by markdown parser", node.Kind())
-		}
-		return ast.WalkContinue, nil
-	})
-	if err != nil {
-		return nil, err
-	}
-	if state.Root == nil {
-		return nil, errors.New("Cannot build prosemirror content")
-	}
-	return state.Root, nil
-}
-
-func GenericBlockHandler(nodeType string) NodeMapperFunc {
-	return func(state *MarkdownParseState, node ast.Node, entering bool) error {
-		if entering {
-			typ, err := state.Schema.NodeType(nodeType)
-			if err != nil {
-				return err
-			}
-			state.OpenNode(typ, nil)
-		} else {
-			if _, err := state.CloseNode(); err != nil {
-				return err
-			}
-		}
-		return nil
-	}
-}
-
-func GenericMarkHandler(markType string) NodeMapperFunc {
-	return func(state *MarkdownParseState, node ast.Node, entering bool) error {
-		typ, err := state.Schema.MarkType(markType)
-		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
-	}
-}
-
-func WithoutTrailingNewline(node ast.Node, source []byte) string {
-	var lines []string
-	segments := node.Lines()
-	for i := 0; i < segments.Len(); i++ {
-		segment := segments.At(i)
-		line := segment.Value(source)
-		lines = append(lines, string(line))
-	}
-	str := strings.Join(lines, "")
-	return strings.TrimSuffix(str, "\n")
-}
-
-// DefaultNodeMapper is a parser parsing unextended
-// [CommonMark](http://commonmark.org/), without inline HTML, and producing a
-// document in the basic schema.
-var DefaultNodeMapper = NodeMapper{
-	// Blocks
-	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, state.Marks)
-			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 {
-				return err
-			}
-			state.OpenNode(typ, nil)
-		} else {
-			if _, err := state.CloseNode(); err != nil {
-				return err
-			}
-		}
-		return nil
-	},
-	ast.KindListItem:   GenericBlockHandler("listItem"),
-	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 {
-				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 {
-				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"),
-
-	// Inlines
-	ast.KindText: func(state *MarkdownParseState, node ast.Node, entering bool) error {
-		if entering {
-			segment := node.(*ast.Text).Segment
-			content := segment.Value(state.Source)
-			state.AddText(string(content))
-		}
-		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),
-			"title": string(n.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"),
-}
diff --git a/model/note/import.go b/model/note/import.go
index 95ba7b7127bd5bc61c81e6ce3bce4bbd9830a21b..65efe8dec14a1c83f6e0990d5bb9f27ee0bb8a3a 100644
--- a/model/note/import.go
+++ b/model/note/import.go
@@ -13,6 +13,7 @@ import (
 	"github.com/cozy/cozy-stack/model/vfs"
 	"github.com/cozy/cozy-stack/pkg/consts"
 	"github.com/cozy/cozy-stack/pkg/filetype"
+	"github.com/cozy/prosemirror-go/markdown"
 	"github.com/cozy/prosemirror-go/model"
 	"github.com/gofrs/uuid"
 )
@@ -163,7 +164,7 @@ func parseFile(r io.Reader, schema *model.Schema) (*model.Node, error) {
 	}
 	parser := markdownParser()
 	funcs := markdownNodeMapper()
-	return ParseMarkdown(parser, funcs, buf, schema)
+	return markdown.ParseMarkdown(parser, funcs, buf, schema)
 }
 
 func isTar(buf []byte) bool {
diff --git a/model/note/markdown.go b/model/note/markdown.go
index 99cfd0441a57e6e77a2d98653fc5c8f53d2e83f7..e6a780da6d14dd158c4487153582eb1ffd599c94 100644
--- a/model/note/markdown.go
+++ b/model/note/markdown.go
@@ -164,7 +164,7 @@ func cellMarkup(node *model.Node) string {
 	return attrs
 }
 
-func isTableCell(item *StackItem) bool {
+func isTableCell(item *markdown.StackItem) bool {
 	name := item.Type.Name
 	return name == "tableHeader" || name == "tableCell"
 }
@@ -189,9 +189,9 @@ func cellAttributes(node ast.Node) map[string]interface{} {
 	}
 }
 
-func markdownNodeMapper() NodeMapper {
-	vanilla := DefaultNodeMapper
-	return NodeMapper{
+func markdownNodeMapper() markdown.NodeMapper {
+	vanilla := markdown.DefaultNodeMapper
+	return markdown.NodeMapper{
 		// Blocks
 		ast.KindDocument:        vanilla[ast.KindDocument],
 		ast.KindParagraph:       vanilla[ast.KindParagraph],
@@ -203,7 +203,7 @@ func markdownNodeMapper() NodeMapper {
 		ast.KindCodeBlock:       vanilla[ast.KindCodeBlock],
 		ast.KindFencedCodeBlock: vanilla[ast.KindFencedCodeBlock],
 		ast.KindThematicBreak:   vanilla[ast.KindThematicBreak],
-		custom.KindPanel: func(state *MarkdownParseState, node ast.Node, entering bool) error {
+		custom.KindPanel: func(state *markdown.MarkdownParseState, node ast.Node, entering bool) error {
 			if entering {
 				typ, err := state.Schema.NodeType("panel")
 				if err != nil {
@@ -220,7 +220,7 @@ func markdownNodeMapper() NodeMapper {
 			}
 			return nil
 		},
-		custom.KindTable: func(state *MarkdownParseState, node ast.Node, entering bool) error {
+		custom.KindTable: func(state *markdown.MarkdownParseState, node ast.Node, entering bool) error {
 			tableType, ok := node.AttributeString("class")
 			if entering || !ok {
 				return nil
@@ -294,7 +294,7 @@ func markdownNodeMapper() NodeMapper {
 		ast.KindString:   vanilla[ast.KindString],
 		ast.KindAutoLink: vanilla[ast.KindAutoLink],
 		ast.KindLink:     vanilla[ast.KindLink],
-		ast.KindImage: func(state *MarkdownParseState, node ast.Node, entering bool) error {
+		ast.KindImage: func(state *markdown.MarkdownParseState, node ast.Node, entering bool) error {
 			if entering {
 				return nil
 			}
@@ -334,7 +334,7 @@ func markdownNodeMapper() NodeMapper {
 		ast.KindCodeSpan:               vanilla[ast.KindCodeSpan],
 		ast.KindEmphasis:               vanilla[ast.KindEmphasis],
 		extensionast.KindStrikethrough: vanilla[extensionast.KindStrikethrough],
-		custom.KindSpan: func(state *MarkdownParseState, node ast.Node, entering bool) error {
+		custom.KindSpan: func(state *markdown.MarkdownParseState, node ast.Node, entering bool) error {
 			text := node.(*custom.Span).Value
 
 			var markType, nodeType string