Skip to content

Commit 33b054a

Browse files
committed
Add promisemirror markdown capabilities
Signed-off-by: Émile Ré <emile@getprobo.com>
1 parent 990f84f commit 33b054a

File tree

12 files changed

+1178
-0
lines changed

12 files changed

+1178
-0
lines changed

cmd/proboctl/main.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
2+
//
3+
// Permission to use, copy, modify, and/or distribute this software for any
4+
// purpose with or without fee is hereby granted, provided that the above
5+
// copyright notice and this permission notice appear in all copies.
6+
//
7+
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
8+
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
9+
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
10+
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
11+
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
12+
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
13+
// PERFORMANCE OF THIS SOFTWARE.
14+
15+
package main
16+
17+
import (
18+
"fmt"
19+
"os"
20+
21+
"go.probo.inc/probo/pkg/cmd/cmdutil"
22+
"go.probo.inc/probo/pkg/cmd/iostreams"
23+
"go.probo.inc/probo/pkg/proboctl/root"
24+
)
25+
26+
func main() {
27+
ios := iostreams.System()
28+
ios.ApplyColorProfile()
29+
30+
f := &cmdutil.Factory{
31+
IOStreams: ios,
32+
}
33+
34+
cmd := root.NewCmdRoot(f)
35+
36+
if err := cmd.Execute(); err != nil {
37+
fmt.Fprintf(os.Stderr, "Error: %s\n", err)
38+
os.Exit(1)
39+
}
40+
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ require (
3131
github.com/scim2/filter-parser/v2 v2.2.0
3232
github.com/stretchr/testify v1.11.1
3333
github.com/vektah/gqlparser/v2 v2.5.32
34+
github.com/yuin/goldmark v1.8.2
3435
go.gearno.de/crypto/uuid v0.1.1-0.20251208105319-3f587312a712
3536
go.gearno.de/kit v0.1.1
3637
go.gearno.de/x/ref v0.0.0-20260216110753-a700c951377c

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,8 @@ github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavM
320320
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
321321
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
322322
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
323+
github.com/yuin/goldmark v1.8.2 h1:kEGpgqJXdgbkhcOgBxkC0X0PmoPG1ZyoZ117rDVp4zE=
324+
github.com/yuin/goldmark v1.8.2/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
323325
go.gearno.de/crypto/uuid v0.1.1-0.20251208105319-3f587312a712 h1:J5ccbcxFuwxe6Oa9fVi9FqQOo+n17ni4wbl9t4NuEzc=
324326
go.gearno.de/crypto/uuid v0.1.1-0.20251208105319-3f587312a712/go.mod h1:fnIIvKO9QnsyLO3ZJLJT3r8KZv/p0FOeT5eZKilYWXg=
325327
go.gearno.de/kit v0.1.1 h1:QuBZCZ/h2Eyh6DjjR6CGjkdsab/ztHz6xUiIk0FeREE=
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
2+
//
3+
// Permission to use, copy, modify, and/or distribute this software for any
4+
// purpose with or without fee is hereby granted, provided that the above
5+
// copyright notice and this permission notice appear in all copies.
6+
//
7+
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
8+
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
9+
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
10+
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
11+
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
12+
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
13+
// PERFORMANCE OF THIS SOFTWARE.
14+
15+
package documentversion
16+
17+
import (
18+
"github.com/spf13/cobra"
19+
"go.probo.inc/probo/pkg/cmd/cmdutil"
20+
"go.probo.inc/probo/pkg/cmd/documentversion/updatecontent"
21+
)
22+
23+
func NewCmdDocumentVersion(f *cmdutil.Factory) *cobra.Command {
24+
cmd := &cobra.Command{
25+
Use: "document-version <command>",
26+
Short: "Manage document versions",
27+
}
28+
29+
cmd.AddCommand(updatecontent.NewCmdUpdateContent(f))
30+
31+
return cmd
32+
}
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
2+
//
3+
// Permission to use, copy, modify, and/or distribute this software for any
4+
// purpose with or without fee is hereby granted, provided that the above
5+
// copyright notice and this permission notice appear in all copies.
6+
//
7+
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
8+
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
9+
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
10+
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
11+
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
12+
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
13+
// PERFORMANCE OF THIS SOFTWARE.
14+
15+
package updatecontent
16+
17+
import (
18+
"encoding/json"
19+
"fmt"
20+
"io"
21+
22+
"github.com/spf13/cobra"
23+
"go.probo.inc/probo/pkg/cli/api"
24+
"go.probo.inc/probo/pkg/cmd/cmdutil"
25+
"go.probo.inc/probo/pkg/prosemirror"
26+
)
27+
28+
const updateContentMutation = `
29+
mutation($input: UpdateDocumentVersionContentInput!) {
30+
updateDocumentVersionContent(input: $input) {
31+
content
32+
}
33+
}
34+
`
35+
36+
type updateContentResponse struct {
37+
UpdateDocumentVersionContent struct {
38+
Content string `json:"content"`
39+
} `json:"updateDocumentVersionContent"`
40+
}
41+
42+
func NewCmdUpdateContent(f *cmdutil.Factory) *cobra.Command {
43+
var (
44+
flagID string
45+
flagContent string
46+
flagFromMarkdown string
47+
)
48+
49+
cmd := &cobra.Command{
50+
Use: "update-content",
51+
Short: "Update document version content",
52+
Example: ` # Update with ProseMirror JSON
53+
prb document-version update-content --id <version-id> --content '{"type":"doc",...}'
54+
55+
# Update from markdown
56+
prb document-version update-content --id <version-id> --from-markdown "# Hello"
57+
58+
# Update from stdin
59+
cat content.json | prb document-version update-content --id <version-id>`,
60+
RunE: func(cmd *cobra.Command, args []string) error {
61+
cfg, err := f.Config()
62+
if err != nil {
63+
return err
64+
}
65+
66+
host, hc, err := cfg.DefaultHost()
67+
if err != nil {
68+
return err
69+
}
70+
71+
client := api.NewClient(
72+
host,
73+
hc.Token,
74+
"/api/console/v1/graphql",
75+
cfg.HTTPTimeoutDuration(),
76+
)
77+
78+
var content string
79+
switch {
80+
case flagFromMarkdown != "":
81+
doc, err := prosemirror.ParseMarkdown(flagFromMarkdown)
82+
if err != nil {
83+
return err
84+
}
85+
out, err := json.Marshal(doc)
86+
if err != nil {
87+
return fmt.Errorf("cannot marshal prosemirror document: %w", err)
88+
}
89+
content = string(out)
90+
case flagContent != "":
91+
content = flagContent
92+
default:
93+
data, err := io.ReadAll(f.IOStreams.In)
94+
if err != nil {
95+
return fmt.Errorf("cannot read from stdin: %w", err)
96+
}
97+
content = string(data)
98+
}
99+
100+
input := map[string]any{
101+
"id": flagID,
102+
"content": content,
103+
}
104+
105+
data, err := client.Do(
106+
updateContentMutation,
107+
map[string]any{"input": input},
108+
)
109+
if err != nil {
110+
return err
111+
}
112+
113+
var resp updateContentResponse
114+
if err := json.Unmarshal(data, &resp); err != nil {
115+
return fmt.Errorf("cannot parse response: %w", err)
116+
}
117+
118+
_, _ = fmt.Fprintf(
119+
f.IOStreams.Out,
120+
"Updated document version content %s\n",
121+
flagID,
122+
)
123+
124+
return nil
125+
},
126+
}
127+
128+
cmd.Flags().StringVar(&flagID, "id", "", "Document version ID (required)")
129+
cmd.Flags().StringVar(&flagContent, "content", "", "ProseMirror JSON content")
130+
cmd.Flags().StringVar(
131+
&flagFromMarkdown,
132+
"from-markdown",
133+
"",
134+
"Markdown content to convert and upload",
135+
)
136+
137+
_ = cmd.MarkFlagRequired("id")
138+
139+
return cmd
140+
}

pkg/cmd/root/root.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
cmdconfig "go.probo.inc/probo/pkg/cmd/config"
2626
cmdcontext "go.probo.inc/probo/pkg/cmd/context"
2727
"go.probo.inc/probo/pkg/cmd/control"
28+
"go.probo.inc/probo/pkg/cmd/documentversion"
2829
"go.probo.inc/probo/pkg/cmd/finding"
2930
"go.probo.inc/probo/pkg/cmd/framework"
3031
"go.probo.inc/probo/pkg/cmd/org"
@@ -73,6 +74,7 @@ func NewCmdRoot(f *cmdutil.Factory) *cobra.Command {
7374
cmd.AddCommand(cmdconfig.NewCmdConfig(f))
7475
cmd.AddCommand(cmdcontext.NewCmdContext(f))
7576
cmd.AddCommand(control.NewCmdControl(f))
77+
cmd.AddCommand(documentversion.NewCmdDocumentVersion(f))
7678
cmd.AddCommand(finding.NewCmdFinding(f))
7779
cmd.AddCommand(framework.NewCmdFramework(f))
7880
cmd.AddCommand(org.NewCmdOrg(f))
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
2+
//
3+
// Permission to use, copy, modify, and/or distribute this software for any
4+
// purpose with or without fee is hereby granted, provided that the above
5+
// copyright notice and this permission notice appear in all copies.
6+
//
7+
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
8+
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
9+
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
10+
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
11+
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
12+
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
13+
// PERFORMANCE OF THIS SOFTWARE.
14+
15+
package mdtoprosemirror
16+
17+
import (
18+
"encoding/json"
19+
"fmt"
20+
"io"
21+
22+
"github.com/spf13/cobra"
23+
"go.probo.inc/probo/pkg/cmd/cmdutil"
24+
"go.probo.inc/probo/pkg/prosemirror"
25+
)
26+
27+
func NewCmdMdToProsemirror(f *cmdutil.Factory) *cobra.Command {
28+
var flagContent string
29+
30+
cmd := &cobra.Command{
31+
Use: "md-to-prosemirror",
32+
Short: "Convert markdown to ProseMirror JSON",
33+
Example: ` # Convert from flag
34+
proboctl md-to-prosemirror --content "# Hello"
35+
36+
# Convert from stdin
37+
echo "# Hello" | proboctl md-to-prosemirror
38+
39+
# Convert from file
40+
proboctl md-to-prosemirror < document.md`,
41+
RunE: func(cmd *cobra.Command, args []string) error {
42+
var input string
43+
if flagContent != "" {
44+
input = flagContent
45+
} else {
46+
data, err := io.ReadAll(f.IOStreams.In)
47+
if err != nil {
48+
return fmt.Errorf("cannot read from stdin: %w", err)
49+
}
50+
input = string(data)
51+
}
52+
53+
doc, err := prosemirror.ParseMarkdown(input)
54+
if err != nil {
55+
return err
56+
}
57+
58+
out, err := json.Marshal(doc)
59+
if err != nil {
60+
return fmt.Errorf("cannot marshal prosemirror document: %w", err)
61+
}
62+
63+
fmt.Fprintln(f.IOStreams.Out, string(out))
64+
65+
return nil
66+
},
67+
}
68+
69+
cmd.Flags().StringVar(
70+
&flagContent,
71+
"content",
72+
"",
73+
"Markdown content to convert (reads from stdin if not provided)",
74+
)
75+
76+
return cmd
77+
}

pkg/proboctl/root/root.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
2+
//
3+
// Permission to use, copy, modify, and/or distribute this software for any
4+
// purpose with or without fee is hereby granted, provided that the above
5+
// copyright notice and this permission notice appear in all copies.
6+
//
7+
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
8+
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
9+
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
10+
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
11+
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
12+
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
13+
// PERFORMANCE OF THIS SOFTWARE.
14+
15+
package root
16+
17+
import (
18+
"github.com/spf13/cobra"
19+
"go.probo.inc/probo/pkg/cmd/cmdutil"
20+
"go.probo.inc/probo/pkg/proboctl/mdtoprosemirror"
21+
)
22+
23+
func NewCmdRoot(f *cmdutil.Factory) *cobra.Command {
24+
cmd := &cobra.Command{
25+
Use: "proboctl <command> [flags]",
26+
Short: "Probo admin CLI",
27+
Long: "proboctl is a command-line tool for Probo administrative operations.",
28+
SilenceUsage: true,
29+
SilenceErrors: true,
30+
}
31+
32+
cmd.AddCommand(mdtoprosemirror.NewCmdMdToProsemirror(f))
33+
34+
return cmd
35+
}

0 commit comments

Comments
 (0)