mirror of
				https://github.com/kovetskiy/mark.git
				synced 2025-11-04 06:17:36 +08:00 
			
		
		
		
	Add support for mermaid via library
Implementation is largely based on: https://github.com/kovetskiy/mark/pull/167 Co-Authored-By: Manuel Rueger <manuel@rueg.eu>
This commit is contained in:
		
							parent
							
								
									88c070f524
								
							
						
					
					
						commit
						d9d560eda0
					
				
							
								
								
									
										2
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							@ -11,7 +11,7 @@ on:
 | 
				
			|||||||
    - master
 | 
					    - master
 | 
				
			||||||
 | 
					
 | 
				
			||||||
env:
 | 
					env:
 | 
				
			||||||
  GO_VERSION: "~1.20.2"
 | 
					  GO_VERSION: "~1.20.3"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
jobs:
 | 
					jobs:
 | 
				
			||||||
  # Runs Golangci-lint on the source code
 | 
					  # Runs Golangci-lint on the source code
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										10
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								Dockerfile
									
									
									
									
									
								
							@ -1,11 +1,15 @@
 | 
				
			|||||||
FROM golang:1.20.2 as builder
 | 
					FROM golang:1.20.3 as builder
 | 
				
			||||||
ENV GOPATH="/go"
 | 
					ENV GOPATH="/go"
 | 
				
			||||||
WORKDIR /go/src/github.com/kovetskiy/mark
 | 
					WORKDIR /go/src/github.com/kovetskiy/mark
 | 
				
			||||||
COPY / .
 | 
					COPY / .
 | 
				
			||||||
RUN make get \
 | 
					RUN make get \
 | 
				
			||||||
&& make build
 | 
					&& make build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
FROM alpine:3.17
 | 
					FROM chromedp/headless-shell:latest
 | 
				
			||||||
RUN apk --no-cache add ca-certificates bash sed git
 | 
					RUN apt-get update \
 | 
				
			||||||
 | 
					&& apt-get install --no-install-recommends -qq ca-certificates bash sed git \
 | 
				
			||||||
 | 
					&& apt-get clean \
 | 
				
			||||||
 | 
					&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
COPY --from=builder /go/src/github.com/kovetskiy/mark/mark /bin/
 | 
					COPY --from=builder /go/src/github.com/kovetskiy/mark/mark /bin/
 | 
				
			||||||
WORKDIR /docs
 | 
					WORKDIR /docs
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										15
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								README.md
									
									
									
									
									
								
							@ -553,6 +553,21 @@ And attach any image with the following
 | 
				
			|||||||
The width will be the commented html after the image (in this case 300px).
 | 
					The width will be the commented html after the image (in this case 300px).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Render Mermaid Diagram
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Confluence doesn't provide [mermaid.js](https://github.com/mermaid-js/mermaid) support natively. Mark provides a convenient way to enable the feature like [Github does](https://github.blog/2022-02-14-include-diagrams-markdown-files-mermaid/).
 | 
				
			||||||
 | 
					As long as you have a code block and are marked as "mermaid", the mark will automatically render it as a PNG image and insert into before the code block.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ```mermaid title diagrams_example
 | 
				
			||||||
 | 
					    graph TD;
 | 
				
			||||||
 | 
					    A-->B;
 | 
				
			||||||
 | 
					    ```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					In order to properly render mermaid, you can choose between the following mermaid providers:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					* "mermaid-go" via [mermaid.go](https://github.com/dreampuf/mermaid.go)
 | 
				
			||||||
 | 
					* "cloudscript" via [cloudscript-io-mermaid-addon](https://marketplace.atlassian.com/apps/1219878/cloudscript-io-mermaid-addon)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Installation
 | 
					## Installation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Homebrew
 | 
					### Homebrew
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										16
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								go.mod
									
									
									
									
									
								
							@ -3,27 +3,37 @@ module github.com/kovetskiy/mark
 | 
				
			|||||||
go 1.19
 | 
					go 1.19
 | 
				
			||||||
 | 
					
 | 
				
			||||||
require (
 | 
					require (
 | 
				
			||||||
 | 
						github.com/dreampuf/mermaid.go v0.0.4-0.20220314184516-44d4f4fc9d39
 | 
				
			||||||
	github.com/kovetskiy/gopencils v0.0.0-20230119081704-a73db75b2f69
 | 
						github.com/kovetskiy/gopencils v0.0.0-20230119081704-a73db75b2f69
 | 
				
			||||||
	github.com/kovetskiy/lorg v1.2.0
 | 
						github.com/kovetskiy/lorg v1.2.0
 | 
				
			||||||
	github.com/reconquest/karma-go v0.0.0-20220904173930-21741aa386a6
 | 
						github.com/reconquest/karma-go v0.0.0-20230425053540-765a8ab89f64
 | 
				
			||||||
	github.com/reconquest/pkg v1.3.0
 | 
						github.com/reconquest/pkg v1.3.0
 | 
				
			||||||
	github.com/reconquest/regexputil-go v0.0.0-20160905154124-38573e70c1f4
 | 
						github.com/reconquest/regexputil-go v0.0.0-20160905154124-38573e70c1f4
 | 
				
			||||||
	github.com/stretchr/testify v1.8.2
 | 
						github.com/stretchr/testify v1.8.2
 | 
				
			||||||
	github.com/urfave/cli/v2 v2.25.1
 | 
						github.com/urfave/cli/v2 v2.25.1
 | 
				
			||||||
	github.com/yuin/goldmark v1.5.4
 | 
						github.com/yuin/goldmark v1.5.4
 | 
				
			||||||
	golang.org/x/tools v0.7.0
 | 
						golang.org/x/tools v0.8.0
 | 
				
			||||||
	gopkg.in/yaml.v3 v3.0.1
 | 
						gopkg.in/yaml.v3 v3.0.1
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
require (
 | 
					require (
 | 
				
			||||||
	github.com/BurntSushi/toml v1.2.1 // indirect
 | 
						github.com/BurntSushi/toml v1.2.1 // indirect
 | 
				
			||||||
 | 
						github.com/chromedp/cdproto v0.0.0-20230329100754-6125fc8d7142 // indirect
 | 
				
			||||||
 | 
						github.com/chromedp/chromedp v0.9.1 // indirect
 | 
				
			||||||
 | 
						github.com/chromedp/sysutil v1.0.0 // indirect
 | 
				
			||||||
	github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
 | 
						github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
 | 
				
			||||||
	github.com/davecgh/go-spew v1.1.1 // indirect
 | 
						github.com/davecgh/go-spew v1.1.1 // indirect
 | 
				
			||||||
	github.com/kr/pretty v0.1.0 // indirect
 | 
						github.com/gobwas/httphead v0.1.0 // indirect
 | 
				
			||||||
 | 
						github.com/gobwas/pool v0.2.1 // indirect
 | 
				
			||||||
 | 
						github.com/gobwas/ws v1.1.0 // indirect
 | 
				
			||||||
 | 
						github.com/josharian/intern v1.0.0 // indirect
 | 
				
			||||||
 | 
						github.com/kr/pretty v0.3.1 // indirect
 | 
				
			||||||
 | 
						github.com/mailru/easyjson v0.7.7 // indirect
 | 
				
			||||||
	github.com/pmezard/go-difflib v1.0.0 // indirect
 | 
						github.com/pmezard/go-difflib v1.0.0 // indirect
 | 
				
			||||||
	github.com/reconquest/cog v0.0.0-20210820140837-c5c4e8f49c65 // indirect
 | 
						github.com/reconquest/cog v0.0.0-20210820140837-c5c4e8f49c65 // indirect
 | 
				
			||||||
	github.com/russross/blackfriday/v2 v2.1.0 // indirect
 | 
						github.com/russross/blackfriday/v2 v2.1.0 // indirect
 | 
				
			||||||
	github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
 | 
						github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
 | 
				
			||||||
	github.com/zazab/zhash v0.0.0-20210630080733-6e809466f8d3 // indirect
 | 
						github.com/zazab/zhash v0.0.0-20210630080733-6e809466f8d3 // indirect
 | 
				
			||||||
 | 
						golang.org/x/sys v0.7.0 // indirect
 | 
				
			||||||
	gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
 | 
						gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										48
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										48
									
								
								go.sum
									
									
									
									
									
								
							@ -1,32 +1,58 @@
 | 
				
			|||||||
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
 | 
					github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
 | 
				
			||||||
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
 | 
					github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
 | 
				
			||||||
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
 | 
					github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
 | 
				
			||||||
 | 
					github.com/chromedp/cdproto v0.0.0-20230220211738-2b1ec77315c9/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
 | 
				
			||||||
 | 
					github.com/chromedp/cdproto v0.0.0-20230329100754-6125fc8d7142 h1:7H1PudqT2SgX/U2ZwPOBOvj75Jnoks+eYVvEEFpi7h0=
 | 
				
			||||||
 | 
					github.com/chromedp/cdproto v0.0.0-20230329100754-6125fc8d7142/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
 | 
				
			||||||
 | 
					github.com/chromedp/chromedp v0.9.1 h1:CC7cC5p1BeLiiS2gfNNPwp3OaUxtRMBjfiw3E3k6dFA=
 | 
				
			||||||
 | 
					github.com/chromedp/chromedp v0.9.1/go.mod h1:DUgZWRvYoEfgi66CgZ/9Yv+psgi+Sksy5DTScENWjaQ=
 | 
				
			||||||
 | 
					github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic=
 | 
				
			||||||
 | 
					github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
 | 
				
			||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
 | 
					github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
 | 
				
			||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
 | 
					github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
 | 
				
			||||||
 | 
					github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 | 
				
			||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
					github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
				
			||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 | 
					github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 | 
				
			||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
					github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
				
			||||||
 | 
					github.com/dreampuf/mermaid.go v0.0.4-0.20220314184516-44d4f4fc9d39 h1:oeOSCOh+XHblSoj/FsR0X/BOT6f3oj0OE58Y2tPoISM=
 | 
				
			||||||
 | 
					github.com/dreampuf/mermaid.go v0.0.4-0.20220314184516-44d4f4fc9d39/go.mod h1:e9BYes2y5OhMb70VsPioP2vksPofJQ40ywhFwtmQ45w=
 | 
				
			||||||
 | 
					github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
 | 
				
			||||||
 | 
					github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
 | 
				
			||||||
 | 
					github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
 | 
				
			||||||
 | 
					github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
 | 
				
			||||||
 | 
					github.com/gobwas/ws v1.1.0 h1:7RFti/xnNkMJnrK7D1yQ/iCIB5OrrY/54/H930kIbHA=
 | 
				
			||||||
 | 
					github.com/gobwas/ws v1.1.0/go.mod h1:nzvNcVha5eUziGrbxFCo6qFIojQHjJV5cLYIbezhfL0=
 | 
				
			||||||
 | 
					github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
 | 
				
			||||||
 | 
					github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
 | 
				
			||||||
github.com/kovetskiy/gopencils v0.0.0-20230119081704-a73db75b2f69 h1:vn82v0gKhTTm67znr7nxYBNW4mJ8zfY7dywZivUy3tY=
 | 
					github.com/kovetskiy/gopencils v0.0.0-20230119081704-a73db75b2f69 h1:vn82v0gKhTTm67znr7nxYBNW4mJ8zfY7dywZivUy3tY=
 | 
				
			||||||
github.com/kovetskiy/gopencils v0.0.0-20230119081704-a73db75b2f69/go.mod h1:t7LFI5v8Q5+nl9sqId9PS0C9H9F4c5d4XlhkLve1MCM=
 | 
					github.com/kovetskiy/gopencils v0.0.0-20230119081704-a73db75b2f69/go.mod h1:t7LFI5v8Q5+nl9sqId9PS0C9H9F4c5d4XlhkLve1MCM=
 | 
				
			||||||
github.com/kovetskiy/lorg v0.0.0-20200107130803-9a7136a95634/go.mod h1:B8HeKAukXULNzWWsW5k/SQyDkiQZPn7lTBJDB46MZ9I=
 | 
					github.com/kovetskiy/lorg v0.0.0-20200107130803-9a7136a95634/go.mod h1:B8HeKAukXULNzWWsW5k/SQyDkiQZPn7lTBJDB46MZ9I=
 | 
				
			||||||
github.com/kovetskiy/lorg v1.2.0 h1:wNIUT/VOhcjKOmizDClZLvchbKFGW+dzf9fQXbSVS5E=
 | 
					github.com/kovetskiy/lorg v1.2.0 h1:wNIUT/VOhcjKOmizDClZLvchbKFGW+dzf9fQXbSVS5E=
 | 
				
			||||||
github.com/kovetskiy/lorg v1.2.0/go.mod h1:rdiamaIRUCkX9HtFZd0D9dQqUbad21hipHk+sat7Z6s=
 | 
					github.com/kovetskiy/lorg v1.2.0/go.mod h1:rdiamaIRUCkX9HtFZd0D9dQqUbad21hipHk+sat7Z6s=
 | 
				
			||||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
 | 
					github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
 | 
				
			||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 | 
					github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
 | 
				
			||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 | 
					github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 | 
				
			||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
 | 
					github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 | 
				
			||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 | 
					github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo=
 | 
				
			||||||
 | 
					github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
 | 
				
			||||||
 | 
					github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
 | 
				
			||||||
 | 
					github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
 | 
				
			||||||
 | 
					github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw=
 | 
				
			||||||
 | 
					github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
 | 
				
			||||||
 | 
					github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
 | 
				
			||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 | 
					github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 | 
				
			||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
					github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
				
			||||||
github.com/reconquest/cog v0.0.0-20210820140837-c5c4e8f49c65 h1:IiAAeijD0sU3C6OO9vy/0WUUfRabZ1aH7hijCBteJC4=
 | 
					github.com/reconquest/cog v0.0.0-20210820140837-c5c4e8f49c65 h1:IiAAeijD0sU3C6OO9vy/0WUUfRabZ1aH7hijCBteJC4=
 | 
				
			||||||
github.com/reconquest/cog v0.0.0-20210820140837-c5c4e8f49c65/go.mod h1:iin2k2yhKESAy14B2fXK8gpf1nofl7dTXH5U+VdIlss=
 | 
					github.com/reconquest/cog v0.0.0-20210820140837-c5c4e8f49c65/go.mod h1:iin2k2yhKESAy14B2fXK8gpf1nofl7dTXH5U+VdIlss=
 | 
				
			||||||
github.com/reconquest/karma-go v0.0.0-20200928103525-22da92476de6/go.mod h1:yuQiKpTdmXSX7E+h+3dD4jx09P/gHc67mRxN3eFLt7o=
 | 
					github.com/reconquest/karma-go v0.0.0-20200928103525-22da92476de6/go.mod h1:yuQiKpTdmXSX7E+h+3dD4jx09P/gHc67mRxN3eFLt7o=
 | 
				
			||||||
github.com/reconquest/karma-go v0.0.0-20220904173930-21741aa386a6 h1:wSI9nn6ZDtuA4Coi6oWmhBqSHGLUK2XRhn9x/4QCCMY=
 | 
					github.com/reconquest/karma-go v0.0.0-20230425053540-765a8ab89f64 h1:1ftdBojSr4bnBpe3UkSGG40fI70g5Av3xLQKn9Gkmx4=
 | 
				
			||||||
github.com/reconquest/karma-go v0.0.0-20220904173930-21741aa386a6/go.mod h1:qMQ8twYxBpCJ4IYrAnMDBtdNMj0ZTH+kcu94ZIY6vDU=
 | 
					github.com/reconquest/karma-go v0.0.0-20230425053540-765a8ab89f64/go.mod h1:52XRXXa2ec/VNrlCirwasdJfNmjI1O87q098gmqILh0=
 | 
				
			||||||
github.com/reconquest/pkg v1.3.0 h1:Yuoxiw92rP/srKXMo5qSML2InhJ+xAqHJIx3/y/2zh8=
 | 
					github.com/reconquest/pkg v1.3.0 h1:Yuoxiw92rP/srKXMo5qSML2InhJ+xAqHJIx3/y/2zh8=
 | 
				
			||||||
github.com/reconquest/pkg v1.3.0/go.mod h1:hUQ0SzzBlFRSbo6lFYG2tSpLMjqOuUqm2LtpjR/+1sg=
 | 
					github.com/reconquest/pkg v1.3.0/go.mod h1:hUQ0SzzBlFRSbo6lFYG2tSpLMjqOuUqm2LtpjR/+1sg=
 | 
				
			||||||
github.com/reconquest/regexputil-go v0.0.0-20160905154124-38573e70c1f4 h1:bcDXaTFC09IIg13Z8gfQHk4gSu001ET7ssW/wKRvPzg=
 | 
					github.com/reconquest/regexputil-go v0.0.0-20160905154124-38573e70c1f4 h1:bcDXaTFC09IIg13Z8gfQHk4gSu001ET7ssW/wKRvPzg=
 | 
				
			||||||
github.com/reconquest/regexputil-go v0.0.0-20160905154124-38573e70c1f4/go.mod h1:OI1di2iiFSwX3D70iZjzdmCPPfssjOl+HX40tI3VaXA=
 | 
					github.com/reconquest/regexputil-go v0.0.0-20160905154124-38573e70c1f4/go.mod h1:OI1di2iiFSwX3D70iZjzdmCPPfssjOl+HX40tI3VaXA=
 | 
				
			||||||
 | 
					github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
 | 
				
			||||||
 | 
					github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
 | 
				
			||||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
 | 
					github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
 | 
				
			||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 | 
					github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 | 
				
			||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
					github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
				
			||||||
@ -45,8 +71,12 @@ github.com/yuin/goldmark v1.5.4 h1:2uY/xC0roWy8IBEGLgB1ywIoEJFGmRrX21YQcvGZzjU=
 | 
				
			|||||||
github.com/yuin/goldmark v1.5.4/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 | 
					github.com/yuin/goldmark v1.5.4/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 | 
				
			||||||
github.com/zazab/zhash v0.0.0-20210630080733-6e809466f8d3 h1:BhVaeQJc3xalHGONn215FylzuxdQBIT3d/aRjDg4nXQ=
 | 
					github.com/zazab/zhash v0.0.0-20210630080733-6e809466f8d3 h1:BhVaeQJc3xalHGONn215FylzuxdQBIT3d/aRjDg4nXQ=
 | 
				
			||||||
github.com/zazab/zhash v0.0.0-20210630080733-6e809466f8d3/go.mod h1:NtepZ8TEXErPsmQDMUoN72f8aIy4+xNinSJ3f1giess=
 | 
					github.com/zazab/zhash v0.0.0-20210630080733-6e809466f8d3/go.mod h1:NtepZ8TEXErPsmQDMUoN72f8aIy4+xNinSJ3f1giess=
 | 
				
			||||||
golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4=
 | 
					golang.org/x/sys v0.0.0-20201207223542-d4d67f95c62d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
 | 
					golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
 | 
					golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y=
 | 
				
			||||||
 | 
					golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4=
 | 
				
			||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
					gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
				
			||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
 | 
					gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
 | 
				
			||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
					gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										34
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										34
									
								
								main.go
									
									
									
									
									
								
							@ -13,6 +13,7 @@ import (
 | 
				
			|||||||
	"github.com/kovetskiy/mark/pkg/mark/includes"
 | 
						"github.com/kovetskiy/mark/pkg/mark/includes"
 | 
				
			||||||
	"github.com/kovetskiy/mark/pkg/mark/macro"
 | 
						"github.com/kovetskiy/mark/pkg/mark/macro"
 | 
				
			||||||
	"github.com/kovetskiy/mark/pkg/mark/stdlib"
 | 
						"github.com/kovetskiy/mark/pkg/mark/stdlib"
 | 
				
			||||||
 | 
						"github.com/kovetskiy/mark/pkg/mark/vfs"
 | 
				
			||||||
	"github.com/reconquest/karma-go"
 | 
						"github.com/reconquest/karma-go"
 | 
				
			||||||
	"github.com/reconquest/pkg/log"
 | 
						"github.com/reconquest/pkg/log"
 | 
				
			||||||
	"github.com/urfave/cli/v2"
 | 
						"github.com/urfave/cli/v2"
 | 
				
			||||||
@ -22,8 +23,7 @@ import (
 | 
				
			|||||||
const (
 | 
					const (
 | 
				
			||||||
	version     = "9.1.4"
 | 
						version     = "9.1.4"
 | 
				
			||||||
	usage       = "A tool for updating Atlassian Confluence pages from markdown."
 | 
						usage       = "A tool for updating Atlassian Confluence pages from markdown."
 | 
				
			||||||
	description = `Mark is a tool to update Atlassian Confluence pages from markdown. Documentation is available here: https://github.com/kovetskiy/mark
 | 
						description = `Mark is a tool to update Atlassian Confluence pages from markdown. Documentation is available here: https://github.com/kovetskiy/mark`
 | 
				
			||||||
`
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var flags = []cli.Flag{
 | 
					var flags = []cli.Flag{
 | 
				
			||||||
@ -139,6 +139,12 @@ var flags = []cli.Flag{
 | 
				
			|||||||
		Usage:   "use specified space key. If the space key is not specified, it must be set in the page metadata.",
 | 
							Usage:   "use specified space key. If the space key is not specified, it must be set in the page metadata.",
 | 
				
			||||||
		EnvVars: []string{"MARK_SPACE"},
 | 
							EnvVars: []string{"MARK_SPACE"},
 | 
				
			||||||
	}),
 | 
						}),
 | 
				
			||||||
 | 
						altsrc.NewStringFlag(&cli.StringFlag{
 | 
				
			||||||
 | 
							Name:    "mermaid-provider",
 | 
				
			||||||
 | 
							Value:   "cloudscript",
 | 
				
			||||||
 | 
							Usage:   "defines the mermaid provider to use. Supported options are: cloudscript, mermaid-go.",
 | 
				
			||||||
 | 
							EnvVars: []string{"MARK_MERMAID_PROVIDER"},
 | 
				
			||||||
 | 
						}),
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func main() {
 | 
					func main() {
 | 
				
			||||||
@ -347,7 +353,8 @@ func processFile(
 | 
				
			|||||||
			markdown = mark.DropDocumentLeadingH1(markdown)
 | 
								markdown = mark.DropDocumentLeadingH1(markdown)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		fmt.Println(mark.CompileMarkdown(markdown, stdlib))
 | 
							html, _ := mark.CompileMarkdown(markdown, stdlib, cCtx.String("mermaid-provider"))
 | 
				
			||||||
 | 
							fmt.Println(html)
 | 
				
			||||||
		os.Exit(0)
 | 
							os.Exit(0)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -399,11 +406,16 @@ func processFile(
 | 
				
			|||||||
		target = page
 | 
							target = page
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Resolve attachments created from <!-- Attachment: --> directive
 | 
				
			||||||
 | 
						localAttachments, err := mark.ResolveLocalAttachments(vfs.LocalOS, filepath.Dir(file), meta.Attachments)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Fatalf(err, "unable to locate attachments")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	attaches, err := mark.ResolveAttachments(
 | 
						attaches, err := mark.ResolveAttachments(
 | 
				
			||||||
		api,
 | 
							api,
 | 
				
			||||||
		target,
 | 
							target,
 | 
				
			||||||
		filepath.Dir(file),
 | 
							localAttachments,
 | 
				
			||||||
		meta.Attachments,
 | 
					 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Fatalf(err, "unable to create/update attachments")
 | 
							log.Fatalf(err, "unable to create/update attachments")
 | 
				
			||||||
@ -418,7 +430,17 @@ func processFile(
 | 
				
			|||||||
		markdown = mark.DropDocumentLeadingH1(markdown)
 | 
							markdown = mark.DropDocumentLeadingH1(markdown)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	html := mark.CompileMarkdown(markdown, stdlib)
 | 
						html, inlineAttachments := mark.CompileMarkdown(markdown, stdlib, cCtx.String("mermaid-provider"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Resolve attachements detected from markdown
 | 
				
			||||||
 | 
						_, err = mark.ResolveAttachments(
 | 
				
			||||||
 | 
							api,
 | 
				
			||||||
 | 
							target,
 | 
				
			||||||
 | 
							inlineAttachments,
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Fatalf(err, "unable to create/update attachments")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		var buffer bytes.Buffer
 | 
							var buffer bytes.Buffer
 | 
				
			||||||
 | 
				
			|||||||
@ -8,7 +8,6 @@ import (
 | 
				
			|||||||
	"io"
 | 
						"io"
 | 
				
			||||||
	"mime/multipart"
 | 
						"mime/multipart"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"os"
 | 
					 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/kovetskiy/gopencils"
 | 
						"github.com/kovetskiy/gopencils"
 | 
				
			||||||
@ -52,7 +51,7 @@ type PageInfo struct {
 | 
				
			|||||||
	} `json:"version"`
 | 
						} `json:"version"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Ancestors []struct {
 | 
						Ancestors []struct {
 | 
				
			||||||
		Id    string `json:"id"`
 | 
							ID    string `json:"id"`
 | 
				
			||||||
		Title string `json:"title"`
 | 
							Title string `json:"title"`
 | 
				
			||||||
	} `json:"ancestors"`
 | 
						} `json:"ancestors"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -141,7 +140,7 @@ func (api *API) FindRootPage(space string) (*PageInfo, error) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return &PageInfo{
 | 
						return &PageInfo{
 | 
				
			||||||
		ID:    page.Ancestors[0].Id,
 | 
							ID:    page.Ancestors[0].ID,
 | 
				
			||||||
		Title: page.Ancestors[0].Title,
 | 
							Title: page.Ancestors[0].Title,
 | 
				
			||||||
	}, nil
 | 
						}, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -158,7 +157,7 @@ func (api *API) FindHomePage(space string) (*PageInfo, error) {
 | 
				
			|||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if request.Raw.StatusCode == 404 || request.Raw.StatusCode != 200 {
 | 
						if request.Raw.StatusCode == http.StatusNotFound || request.Raw.StatusCode != http.StatusOK {
 | 
				
			||||||
		return nil, newErrorStatusNotOK(request)
 | 
							return nil, newErrorStatusNotOK(request)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -193,7 +192,7 @@ func (api *API) FindPage(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// allow 404 because it's fine if page is not found,
 | 
						// allow 404 because it's fine if page is not found,
 | 
				
			||||||
	// the function will return nil, nil
 | 
						// the function will return nil, nil
 | 
				
			||||||
	if request.Raw.StatusCode != 404 && request.Raw.StatusCode != 200 {
 | 
						if request.Raw.StatusCode != http.StatusNotFound && request.Raw.StatusCode != http.StatusOK {
 | 
				
			||||||
		return nil, newErrorStatusNotOK(request)
 | 
							return nil, newErrorStatusNotOK(request)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -208,11 +207,11 @@ func (api *API) CreateAttachment(
 | 
				
			|||||||
	pageID string,
 | 
						pageID string,
 | 
				
			||||||
	name string,
 | 
						name string,
 | 
				
			||||||
	comment string,
 | 
						comment string,
 | 
				
			||||||
	path string,
 | 
						reader io.Reader,
 | 
				
			||||||
) (AttachmentInfo, error) {
 | 
					) (AttachmentInfo, error) {
 | 
				
			||||||
	var info AttachmentInfo
 | 
						var info AttachmentInfo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	form, err := getAttachmentPayload(name, comment, path)
 | 
						form, err := getAttachmentPayload(name, comment, reader)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return AttachmentInfo{}, err
 | 
							return AttachmentInfo{}, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -243,7 +242,7 @@ func (api *API) CreateAttachment(
 | 
				
			|||||||
		return info, err
 | 
							return info, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if request.Raw.StatusCode != 200 {
 | 
						if request.Raw.StatusCode != http.StatusOK {
 | 
				
			||||||
		return info, newErrorStatusNotOK(request)
 | 
							return info, newErrorStatusNotOK(request)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -276,11 +275,11 @@ func (api *API) UpdateAttachment(
 | 
				
			|||||||
	attachID string,
 | 
						attachID string,
 | 
				
			||||||
	name string,
 | 
						name string,
 | 
				
			||||||
	comment string,
 | 
						comment string,
 | 
				
			||||||
	path string,
 | 
						reader io.Reader,
 | 
				
			||||||
) (AttachmentInfo, error) {
 | 
					) (AttachmentInfo, error) {
 | 
				
			||||||
	var info AttachmentInfo
 | 
						var info AttachmentInfo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	form, err := getAttachmentPayload(name, comment, path)
 | 
						form, err := getAttachmentPayload(name, comment, reader)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return AttachmentInfo{}, err
 | 
							return AttachmentInfo{}, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -313,7 +312,7 @@ func (api *API) UpdateAttachment(
 | 
				
			|||||||
		return info, err
 | 
							return info, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if request.Raw.StatusCode != 200 {
 | 
						if request.Raw.StatusCode != http.StatusOK {
 | 
				
			||||||
		return info, newErrorStatusNotOK(request)
 | 
							return info, newErrorStatusNotOK(request)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -353,23 +352,12 @@ func (api *API) UpdateAttachment(
 | 
				
			|||||||
	return shortResponse, nil
 | 
						return shortResponse, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func getAttachmentPayload(name, comment, path string) (*form, error) {
 | 
					func getAttachmentPayload(name, comment string, reader io.Reader) (*form, error) {
 | 
				
			||||||
	var (
 | 
						var (
 | 
				
			||||||
		payload = bytes.NewBuffer(nil)
 | 
							payload = bytes.NewBuffer(nil)
 | 
				
			||||||
		writer  = multipart.NewWriter(payload)
 | 
							writer  = multipart.NewWriter(payload)
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	file, err := os.Open(path)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return nil, karma.Format(
 | 
					 | 
				
			||||||
			err,
 | 
					 | 
				
			||||||
			"unable to open file: %q",
 | 
					 | 
				
			||||||
			path,
 | 
					 | 
				
			||||||
		)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	defer file.Close()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	content, err := writer.CreateFormFile("file", name)
 | 
						content, err := writer.CreateFormFile("file", name)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, karma.Format(
 | 
							return nil, karma.Format(
 | 
				
			||||||
@ -378,7 +366,7 @@ func getAttachmentPayload(name, comment, path string) (*form, error) {
 | 
				
			|||||||
		)
 | 
							)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_, err = io.Copy(content, file)
 | 
						_, err = io.Copy(content, reader)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return nil, karma.Format(
 | 
							return nil, karma.Format(
 | 
				
			||||||
			err,
 | 
								err,
 | 
				
			||||||
@ -436,7 +424,7 @@ func (api *API) GetAttachments(pageID string) ([]AttachmentInfo, error) {
 | 
				
			|||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if request.Raw.StatusCode != 200 {
 | 
						if request.Raw.StatusCode != http.StatusOK {
 | 
				
			||||||
		return nil, newErrorStatusNotOK(request)
 | 
							return nil, newErrorStatusNotOK(request)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -459,7 +447,7 @@ func (api *API) GetPageByID(pageID string) (*PageInfo, error) {
 | 
				
			|||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if request.Raw.StatusCode != 200 {
 | 
						if request.Raw.StatusCode != http.StatusOK {
 | 
				
			||||||
		return nil, newErrorStatusNotOK(request)
 | 
							return nil, newErrorStatusNotOK(request)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -507,7 +495,7 @@ func (api *API) CreatePage(
 | 
				
			|||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if request.Raw.StatusCode != 200 {
 | 
						if request.Raw.StatusCode != http.StatusOK {
 | 
				
			||||||
		return nil, newErrorStatusNotOK(request)
 | 
							return nil, newErrorStatusNotOK(request)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -521,7 +509,7 @@ func (api *API) UpdatePage(page *PageInfo, newContent string, minorEdit bool, ne
 | 
				
			|||||||
	if page.Type != "blogpost" && len(page.Ancestors) > 0 {
 | 
						if page.Type != "blogpost" && len(page.Ancestors) > 0 {
 | 
				
			||||||
		// picking only the last one, which is required by confluence
 | 
							// picking only the last one, which is required by confluence
 | 
				
			||||||
		oldAncestors = []map[string]interface{}{
 | 
							oldAncestors = []map[string]interface{}{
 | 
				
			||||||
			{"id": page.Ancestors[len(page.Ancestors)-1].Id},
 | 
								{"id": page.Ancestors[len(page.Ancestors)-1].ID},
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -547,7 +535,7 @@ func (api *API) UpdatePage(page *PageInfo, newContent string, minorEdit bool, ne
 | 
				
			|||||||
		"ancestors": oldAncestors,
 | 
							"ancestors": oldAncestors,
 | 
				
			||||||
		"body": map[string]interface{}{
 | 
							"body": map[string]interface{}{
 | 
				
			||||||
			"storage": map[string]interface{}{
 | 
								"storage": map[string]interface{}{
 | 
				
			||||||
				"value":          string(newContent),
 | 
									"value":          newContent,
 | 
				
			||||||
				"representation": "storage",
 | 
									"representation": "storage",
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
@ -573,7 +561,7 @@ func (api *API) UpdatePage(page *PageInfo, newContent string, minorEdit bool, ne
 | 
				
			|||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if request.Raw.StatusCode != 200 {
 | 
						if request.Raw.StatusCode != http.StatusOK {
 | 
				
			||||||
		return newErrorStatusNotOK(request)
 | 
							return newErrorStatusNotOK(request)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -654,7 +642,7 @@ func (api *API) RestrictPageUpdatesCloud(
 | 
				
			|||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if request.Raw.StatusCode != 200 {
 | 
						if request.Raw.StatusCode != http.StatusOK {
 | 
				
			||||||
		return newErrorStatusNotOK(request)
 | 
							return newErrorStatusNotOK(request)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -685,7 +673,7 @@ func (api *API) RestrictPageUpdatesServer(
 | 
				
			|||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if request.Raw.StatusCode != 200 {
 | 
						if request.Raw.StatusCode != http.StatusOK {
 | 
				
			||||||
		return newErrorStatusNotOK(request)
 | 
							return newErrorStatusNotOK(request)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -715,13 +703,13 @@ func (api *API) RestrictPageUpdates(
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func newErrorStatusNotOK(request *gopencils.Resource) error {
 | 
					func newErrorStatusNotOK(request *gopencils.Resource) error {
 | 
				
			||||||
	if request.Raw.StatusCode == 401 {
 | 
						if request.Raw.StatusCode == http.StatusUnauthorized {
 | 
				
			||||||
		return errors.New(
 | 
							return errors.New(
 | 
				
			||||||
			"Confluence API returned unexpected status: 401 (Unauthorized)",
 | 
								"Confluence API returned unexpected status: 401 (Unauthorized)",
 | 
				
			||||||
		)
 | 
							)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if request.Raw.StatusCode == 404 {
 | 
						if request.Raw.StatusCode == http.StatusNotFound {
 | 
				
			||||||
		return errors.New(
 | 
							return errors.New(
 | 
				
			||||||
			"Confluence API returned unexpected status: 404 (Not Found)",
 | 
								"Confluence API returned unexpected status: 404 (Not Found)",
 | 
				
			||||||
		)
 | 
							)
 | 
				
			||||||
 | 
				
			|||||||
@ -6,13 +6,13 @@ import (
 | 
				
			|||||||
	"encoding/hex"
 | 
						"encoding/hex"
 | 
				
			||||||
	"io"
 | 
						"io"
 | 
				
			||||||
	"net/url"
 | 
						"net/url"
 | 
				
			||||||
	"os"
 | 
					 | 
				
			||||||
	"path"
 | 
						"path"
 | 
				
			||||||
	"path/filepath"
 | 
						"path/filepath"
 | 
				
			||||||
	"sort"
 | 
						"sort"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/kovetskiy/mark/pkg/confluence"
 | 
						"github.com/kovetskiy/mark/pkg/confluence"
 | 
				
			||||||
 | 
						"github.com/kovetskiy/mark/pkg/mark/vfs"
 | 
				
			||||||
	"github.com/reconquest/karma-go"
 | 
						"github.com/reconquest/karma-go"
 | 
				
			||||||
	"github.com/reconquest/pkg/log"
 | 
						"github.com/reconquest/pkg/log"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@ -22,36 +22,32 @@ const (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Attachment struct {
 | 
					type Attachment struct {
 | 
				
			||||||
	ID       string
 | 
						ID        string
 | 
				
			||||||
	Name     string
 | 
						Name      string
 | 
				
			||||||
	Filename string
 | 
						Filename  string
 | 
				
			||||||
	Path     string
 | 
						FileBytes []byte
 | 
				
			||||||
	Checksum string
 | 
						Checksum  string
 | 
				
			||||||
	Link     string
 | 
						Link      string
 | 
				
			||||||
	Replace  string
 | 
						Width     string
 | 
				
			||||||
 | 
						Height    string
 | 
				
			||||||
 | 
						Replace   string
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func ResolveAttachments(
 | 
					func ResolveAttachments(
 | 
				
			||||||
	api *confluence.API,
 | 
						api *confluence.API,
 | 
				
			||||||
	page *confluence.PageInfo,
 | 
						page *confluence.PageInfo,
 | 
				
			||||||
	base string,
 | 
						attachments []Attachment,
 | 
				
			||||||
	replacements []string,
 | 
					 | 
				
			||||||
) ([]Attachment, error) {
 | 
					) ([]Attachment, error) {
 | 
				
			||||||
	attaches, err := prepareAttachments(base, replacements)
 | 
						for i := range attachments {
 | 
				
			||||||
	if err != nil {
 | 
							checksum, err := GetChecksum(bytes.NewReader(attachments[i].FileBytes))
 | 
				
			||||||
		return nil, err
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	for i := range attaches {
 | 
					 | 
				
			||||||
		checksum, err := getChecksum(attaches[i].Path)
 | 
					 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			return nil, karma.Format(
 | 
								return nil, karma.Format(
 | 
				
			||||||
				err,
 | 
									err,
 | 
				
			||||||
				"unable to get checksum for attachment: %q", attaches[i].Name,
 | 
									"unable to get checksum for attachment: %q", attachments[i].Name,
 | 
				
			||||||
			)
 | 
								)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		attaches[i].Checksum = checksum
 | 
							attachments[i].Checksum = checksum
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	remotes, err := api.GetAttachments(page.ID)
 | 
						remotes, err := api.GetAttachments(page.ID)
 | 
				
			||||||
@ -62,18 +58,18 @@ func ResolveAttachments(
 | 
				
			|||||||
	existing := []Attachment{}
 | 
						existing := []Attachment{}
 | 
				
			||||||
	creating := []Attachment{}
 | 
						creating := []Attachment{}
 | 
				
			||||||
	updating := []Attachment{}
 | 
						updating := []Attachment{}
 | 
				
			||||||
	for _, attach := range attaches {
 | 
						for _, attachment := range attachments {
 | 
				
			||||||
		var found bool
 | 
							var found bool
 | 
				
			||||||
		var same bool
 | 
							var same bool
 | 
				
			||||||
		for _, remote := range remotes {
 | 
							for _, remote := range remotes {
 | 
				
			||||||
			if remote.Filename == attach.Filename {
 | 
								if remote.Filename == attachment.Filename {
 | 
				
			||||||
				same = attach.Checksum == strings.TrimPrefix(
 | 
									same = attachment.Checksum == strings.TrimPrefix(
 | 
				
			||||||
					remote.Metadata.Comment,
 | 
										remote.Metadata.Comment,
 | 
				
			||||||
					AttachmentChecksumPrefix,
 | 
										AttachmentChecksumPrefix,
 | 
				
			||||||
				)
 | 
									)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				attach.ID = remote.ID
 | 
									attachment.ID = remote.ID
 | 
				
			||||||
				attach.Link = path.Join(
 | 
									attachment.Link = path.Join(
 | 
				
			||||||
					remote.Links.Context,
 | 
										remote.Links.Context,
 | 
				
			||||||
					remote.Links.Download,
 | 
										remote.Links.Download,
 | 
				
			||||||
				)
 | 
									)
 | 
				
			||||||
@ -86,109 +82,143 @@ func ResolveAttachments(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		if found {
 | 
							if found {
 | 
				
			||||||
			if same {
 | 
								if same {
 | 
				
			||||||
				existing = append(existing, attach)
 | 
									existing = append(existing, attachment)
 | 
				
			||||||
			} else {
 | 
								} else {
 | 
				
			||||||
				updating = append(updating, attach)
 | 
									updating = append(updating, attachment)
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			creating = append(creating, attach)
 | 
								creating = append(creating, attachment)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for i, attach := range creating {
 | 
						for i, attachment := range creating {
 | 
				
			||||||
		log.Infof(nil, "creating attachment: %q", attach.Name)
 | 
							log.Infof(nil, "creating attachment: %q", attachment.Name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		info, err := api.CreateAttachment(
 | 
							info, err := api.CreateAttachment(
 | 
				
			||||||
			page.ID,
 | 
								page.ID,
 | 
				
			||||||
			attach.Filename,
 | 
								attachment.Filename,
 | 
				
			||||||
			AttachmentChecksumPrefix+attach.Checksum,
 | 
								AttachmentChecksumPrefix+attachment.Checksum,
 | 
				
			||||||
			attach.Path,
 | 
								bytes.NewReader(attachment.FileBytes),
 | 
				
			||||||
		)
 | 
							)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			return nil, karma.Format(
 | 
								return nil, karma.Format(
 | 
				
			||||||
				err,
 | 
									err,
 | 
				
			||||||
				"unable to create attachment %q",
 | 
									"unable to create attachment %q",
 | 
				
			||||||
				attach.Name,
 | 
									attachment.Name,
 | 
				
			||||||
			)
 | 
								)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		attach.ID = info.ID
 | 
							attachment.ID = info.ID
 | 
				
			||||||
		attach.Link = path.Join(
 | 
							attachment.Link = path.Join(
 | 
				
			||||||
			info.Links.Context,
 | 
								info.Links.Context,
 | 
				
			||||||
			info.Links.Download,
 | 
								info.Links.Download,
 | 
				
			||||||
		)
 | 
							)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		creating[i] = attach
 | 
							creating[i] = attachment
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for i, attach := range updating {
 | 
						for i, attachment := range updating {
 | 
				
			||||||
		log.Infof(nil, "updating attachment: %q", attach.Name)
 | 
							log.Infof(nil, "updating attachment: %q", attachment.Name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		info, err := api.UpdateAttachment(
 | 
							info, err := api.UpdateAttachment(
 | 
				
			||||||
			page.ID,
 | 
								page.ID,
 | 
				
			||||||
			attach.ID,
 | 
								attachment.ID,
 | 
				
			||||||
			attach.Filename,
 | 
								attachment.Filename,
 | 
				
			||||||
			AttachmentChecksumPrefix+attach.Checksum,
 | 
								AttachmentChecksumPrefix+attachment.Checksum,
 | 
				
			||||||
			attach.Path,
 | 
								bytes.NewReader(attachment.FileBytes),
 | 
				
			||||||
		)
 | 
							)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			return nil, karma.Format(
 | 
								return nil, karma.Format(
 | 
				
			||||||
				err,
 | 
									err,
 | 
				
			||||||
				"unable to update attachment %q",
 | 
									"unable to update attachment %q",
 | 
				
			||||||
				attach.Name,
 | 
									attachment.Name,
 | 
				
			||||||
			)
 | 
								)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		attach.Link = path.Join(
 | 
							attachment.Link = path.Join(
 | 
				
			||||||
			info.Links.Context,
 | 
								info.Links.Context,
 | 
				
			||||||
			info.Links.Download,
 | 
								info.Links.Download,
 | 
				
			||||||
		)
 | 
							)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		updating[i] = attach
 | 
							updating[i] = attachment
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for i := range existing {
 | 
						for i := range existing {
 | 
				
			||||||
		log.Infof(nil, "keeping unmodified attachment: %q", attaches[i].Name)
 | 
							log.Infof(nil, "keeping unmodified attachment: %q", attachments[i].Name)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	attaches = []Attachment{}
 | 
						attachments = []Attachment{}
 | 
				
			||||||
	attaches = append(attaches, existing...)
 | 
						attachments = append(attachments, existing...)
 | 
				
			||||||
	attaches = append(attaches, creating...)
 | 
						attachments = append(attachments, creating...)
 | 
				
			||||||
	attaches = append(attaches, updating...)
 | 
						attachments = append(attachments, updating...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return attaches, nil
 | 
						return attachments, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func prepareAttachments(base string, replacements []string) ([]Attachment, error) {
 | 
					func ResolveLocalAttachments(opener vfs.Opener, base string, replacements []string) ([]Attachment, error) {
 | 
				
			||||||
	attaches := []Attachment{}
 | 
						attachments, err := prepareAttachments(opener, base, replacements)
 | 
				
			||||||
	for _, name := range replacements {
 | 
						if err != nil {
 | 
				
			||||||
		attach := Attachment{
 | 
							return nil, err
 | 
				
			||||||
			Name:     name,
 | 
						}
 | 
				
			||||||
			Filename: strings.ReplaceAll(name, "/", "_"),
 | 
					
 | 
				
			||||||
			Path:     filepath.Join(base, name),
 | 
						for _, attachment := range attachments {
 | 
				
			||||||
			Replace:  name,
 | 
							checksum, err := GetChecksum(bytes.NewReader(attachment.FileBytes))
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, karma.Format(
 | 
				
			||||||
 | 
									err,
 | 
				
			||||||
 | 
									"unable to get checksum for attachment: %q", attachment.Name,
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		attaches = append(attaches, attach)
 | 
							attachment.Checksum = checksum
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						return attachments, err
 | 
				
			||||||
	return attaches, nil
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func CompileAttachmentLinks(markdown []byte, attaches []Attachment) []byte {
 | 
					// prepareAttachements creates an array of attachement objects based on an array of filepaths
 | 
				
			||||||
 | 
					func prepareAttachments(opener vfs.Opener, base string, replacements []string) ([]Attachment, error) {
 | 
				
			||||||
 | 
						attachments := []Attachment{}
 | 
				
			||||||
 | 
						for _, name := range replacements {
 | 
				
			||||||
 | 
							attachment, err := prepareAttachment(opener, base, name)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							attachments = append(attachments, attachment)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return attachments, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// prepareAttachement opens the file, reads its content and creates an attachement object
 | 
				
			||||||
 | 
					func prepareAttachment(opener vfs.Opener, base, name string) (Attachment, error) {
 | 
				
			||||||
 | 
						attachmentPath := filepath.Join(base, name)
 | 
				
			||||||
 | 
						file, err := opener.Open(attachmentPath)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return Attachment{}, karma.Format(err, "unable to open file: %q", attachmentPath)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer file.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fileBytes, err := io.ReadAll(file)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return Attachment{}, karma.Format(err, "unable to read file: %q", attachmentPath)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return Attachment{
 | 
				
			||||||
 | 
							Name:      name,
 | 
				
			||||||
 | 
							Filename:  strings.ReplaceAll(name, "/", "_"),
 | 
				
			||||||
 | 
							FileBytes: fileBytes,
 | 
				
			||||||
 | 
							Replace:   name,
 | 
				
			||||||
 | 
						}, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func CompileAttachmentLinks(markdown []byte, attachments []Attachment) []byte {
 | 
				
			||||||
	links := map[string]string{}
 | 
						links := map[string]string{}
 | 
				
			||||||
	replaces := []string{}
 | 
						replaces := []string{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, attach := range attaches {
 | 
						for _, attachment := range attachments {
 | 
				
			||||||
		uri, err := url.ParseRequestURI(attach.Link)
 | 
							links[attachment.Replace] = parseAttachmentLink(attachment.Link)
 | 
				
			||||||
		if err != nil {
 | 
							replaces = append(replaces, attachment.Replace)
 | 
				
			||||||
			links[attach.Replace] = strings.ReplaceAll("&", "&", attach.Link)
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			links[attach.Replace] = uri.Path +
 | 
					 | 
				
			||||||
				"?" + url.QueryEscape(uri.Query().Encode())
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		replaces = append(replaces, attach.Replace)
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// sort by length so first items will have bigger length
 | 
						// sort by length so first items will have bigger length
 | 
				
			||||||
@ -240,20 +270,21 @@ func CompileAttachmentLinks(markdown []byte, attaches []Attachment) []byte {
 | 
				
			|||||||
	return markdown
 | 
						return markdown
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func getChecksum(filename string) (string, error) {
 | 
					func GetChecksum(reader io.Reader) (string, error) {
 | 
				
			||||||
	file, err := os.Open(filename)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		return "", karma.Format(
 | 
					 | 
				
			||||||
			err,
 | 
					 | 
				
			||||||
			"unable to open file",
 | 
					 | 
				
			||||||
		)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	defer file.Close()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	hash := sha256.New()
 | 
						hash := sha256.New()
 | 
				
			||||||
	if _, err := io.Copy(hash, file); err != nil {
 | 
						if _, err := io.Copy(hash, reader); err != nil {
 | 
				
			||||||
		return "", err
 | 
							return "", err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return hex.EncodeToString(hash.Sum(nil)), nil
 | 
						return hex.EncodeToString(hash.Sum(nil)), nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func parseAttachmentLink(attachLink string) string {
 | 
				
			||||||
 | 
						uri, err := url.ParseRequestURI(attachLink)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return strings.ReplaceAll("&", "&", attachLink)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							return uri.Path +
 | 
				
			||||||
 | 
								"?" + url.QueryEscape(uri.Query().Encode())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,9 @@
 | 
				
			|||||||
package mark
 | 
					package mark
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/stretchr/testify/assert"
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
@ -14,43 +17,74 @@ var (
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type bufferCloser struct {
 | 
				
			||||||
 | 
						*bytes.Buffer
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (bufferCloser) Close() error { return nil }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type virtualOpener struct {
 | 
				
			||||||
 | 
						PathToBuf map[string]*bufferCloser
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (o *virtualOpener) Open(name string) (io.ReadWriteCloser, error) {
 | 
				
			||||||
 | 
						if buf, ok := o.PathToBuf[name]; ok {
 | 
				
			||||||
 | 
							return buf, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil, os.ErrNotExist
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestPrepareAttachmentsWithWorkDirBase(t *testing.T) {
 | 
					func TestPrepareAttachmentsWithWorkDirBase(t *testing.T) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	attaches, err := prepareAttachments(".", replacements)
 | 
						testingOpener := &virtualOpener{
 | 
				
			||||||
 | 
							PathToBuf: map[string]*bufferCloser{
 | 
				
			||||||
 | 
								"image1.jpg":        {bytes.NewBuffer(nil)},
 | 
				
			||||||
 | 
								"images/image2.jpg": {bytes.NewBuffer(nil)},
 | 
				
			||||||
 | 
								"../image3.jpg":     {bytes.NewBuffer(nil)},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						attaches, err := prepareAttachments(testingOpener, ".", replacements)
 | 
				
			||||||
 | 
						t.Logf("attatches: %s", err)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		println(err.Error())
 | 
							println(err.Error())
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	assert.Equal(t, "image1.jpg", attaches[0].Name)
 | 
						assert.Equal(t, "image1.jpg", attaches[0].Name)
 | 
				
			||||||
	assert.Equal(t, "image1.jpg", attaches[0].Replace)
 | 
						assert.Equal(t, "image1.jpg", attaches[0].Replace)
 | 
				
			||||||
	assert.Equal(t, "image1.jpg", attaches[0].Path)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	assert.Equal(t, "images/image2.jpg", attaches[1].Name)
 | 
						assert.Equal(t, "images/image2.jpg", attaches[1].Name)
 | 
				
			||||||
	assert.Equal(t, "images/image2.jpg", attaches[1].Replace)
 | 
						assert.Equal(t, "images/image2.jpg", attaches[1].Replace)
 | 
				
			||||||
	assert.Equal(t, "images/image2.jpg", attaches[1].Path)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	assert.Equal(t, "../image3.jpg", attaches[2].Name)
 | 
						assert.Equal(t, "../image3.jpg", attaches[2].Name)
 | 
				
			||||||
	assert.Equal(t, "../image3.jpg", attaches[2].Replace)
 | 
						assert.Equal(t, "../image3.jpg", attaches[2].Replace)
 | 
				
			||||||
	assert.Equal(t, "../image3.jpg", attaches[2].Path)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	assert.Equal(t, len(attaches), 3)
 | 
						assert.Equal(t, len(attaches), 3)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestPrepareAttachmentsWithSubDirBase(t *testing.T) {
 | 
					func TestPrepareAttachmentsWithSubDirBase(t *testing.T) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	attaches, _ := prepareAttachments("a/b", replacements)
 | 
						testingOpener := &virtualOpener{
 | 
				
			||||||
 | 
							PathToBuf: map[string]*bufferCloser{
 | 
				
			||||||
 | 
								"a/b/image1.jpg":        {bytes.NewBuffer(nil)},
 | 
				
			||||||
 | 
								"a/b/images/image2.jpg": {bytes.NewBuffer(nil)},
 | 
				
			||||||
 | 
								"a/image3.jpg":          {bytes.NewBuffer(nil)},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						attaches, err := prepareAttachments(testingOpener, "a/b", replacements)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							t.Fatal(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	assert.Equal(t, "image1.jpg", attaches[0].Name)
 | 
						assert.Equal(t, "image1.jpg", attaches[0].Name)
 | 
				
			||||||
	assert.Equal(t, "image1.jpg", attaches[0].Replace)
 | 
						assert.Equal(t, "image1.jpg", attaches[0].Replace)
 | 
				
			||||||
	assert.Equal(t, "a/b/image1.jpg", attaches[0].Path)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	assert.Equal(t, "images/image2.jpg", attaches[1].Name)
 | 
						assert.Equal(t, "images/image2.jpg", attaches[1].Name)
 | 
				
			||||||
	assert.Equal(t, "images/image2.jpg", attaches[1].Replace)
 | 
						assert.Equal(t, "images/image2.jpg", attaches[1].Replace)
 | 
				
			||||||
	assert.Equal(t, "a/b/images/image2.jpg", attaches[1].Path)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	assert.Equal(t, "../image3.jpg", attaches[2].Name)
 | 
						assert.Equal(t, "../image3.jpg", attaches[2].Name)
 | 
				
			||||||
	assert.Equal(t, "../image3.jpg", attaches[2].Replace)
 | 
						assert.Equal(t, "../image3.jpg", attaches[2].Replace)
 | 
				
			||||||
	assert.Equal(t, "a/image3.jpg", attaches[2].Path)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	assert.Equal(t, len(attaches), 3)
 | 
						assert.Equal(t, len(attaches), 3)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -48,17 +48,20 @@ func (m BlockQuoteLevelMap) Level(node ast.Node) int {
 | 
				
			|||||||
// Renderer renders anchor [Node]s.
 | 
					// Renderer renders anchor [Node]s.
 | 
				
			||||||
type ConfluenceRenderer struct {
 | 
					type ConfluenceRenderer struct {
 | 
				
			||||||
	html.Config
 | 
						html.Config
 | 
				
			||||||
	Stdlib *stdlib.Lib
 | 
						Stdlib          *stdlib.Lib
 | 
				
			||||||
 | 
						MermaidProvider string
 | 
				
			||||||
	LevelMap BlockQuoteLevelMap
 | 
						LevelMap        BlockQuoteLevelMap
 | 
				
			||||||
 | 
						Attachments     []Attachment
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewConfluenceRenderer creates a new instance of the ConfluenceRenderer
 | 
					// NewConfluenceRenderer creates a new instance of the ConfluenceRenderer
 | 
				
			||||||
func NewConfluenceRenderer(stdlib *stdlib.Lib, opts ...html.Option) renderer.NodeRenderer {
 | 
					func NewConfluenceRenderer(stdlib *stdlib.Lib, mermaidProvider string, opts ...html.Option) renderer.NodeRenderer {
 | 
				
			||||||
	return &ConfluenceRenderer{
 | 
						return &ConfluenceRenderer{
 | 
				
			||||||
		Config:   html.NewConfig(),
 | 
							Config:          html.NewConfig(),
 | 
				
			||||||
		Stdlib:   stdlib,
 | 
							Stdlib:          stdlib,
 | 
				
			||||||
		LevelMap: nil,
 | 
							MermaidProvider: mermaidProvider,
 | 
				
			||||||
 | 
							LevelMap:        nil,
 | 
				
			||||||
 | 
							Attachments:     []Attachment{},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -355,29 +358,59 @@ func (r *ConfluenceRenderer) renderFencedCodeBlock(writer util.BufWriter, source
 | 
				
			|||||||
		line := node.Lines().At(i)
 | 
							line := node.Lines().At(i)
 | 
				
			||||||
		lval = append(lval, line.Value(source)...)
 | 
							lval = append(lval, line.Value(source)...)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	err := r.Stdlib.Templates.ExecuteTemplate(
 | 
					
 | 
				
			||||||
		writer,
 | 
						if lang == "mermaid" && r.MermaidProvider == "mermaid-go" {
 | 
				
			||||||
		"ac:code",
 | 
							attachment, err := processMermaidLocally(title, lval)
 | 
				
			||||||
		struct {
 | 
							if err != nil {
 | 
				
			||||||
			Language    string
 | 
								return ast.WalkStop, err
 | 
				
			||||||
			Collapse    bool
 | 
							}
 | 
				
			||||||
			Title       string
 | 
							r.Attachments = append(r.Attachments, attachment)
 | 
				
			||||||
			Theme       string
 | 
							err = r.Stdlib.Templates.ExecuteTemplate(
 | 
				
			||||||
			Linenumbers bool
 | 
								writer,
 | 
				
			||||||
			Firstline   int
 | 
								"ac:image",
 | 
				
			||||||
			Text        string
 | 
								struct {
 | 
				
			||||||
		}{
 | 
									Width      string
 | 
				
			||||||
			lang,
 | 
									Height     string
 | 
				
			||||||
			collapse,
 | 
									Title      string
 | 
				
			||||||
			title,
 | 
									Attachment string
 | 
				
			||||||
			theme,
 | 
								}{
 | 
				
			||||||
			linenumbers,
 | 
									attachment.Width,
 | 
				
			||||||
			firstline,
 | 
									attachment.Height,
 | 
				
			||||||
			strings.TrimSuffix(string(lval), "\n"),
 | 
									attachment.Name,
 | 
				
			||||||
		},
 | 
									attachment.Filename,
 | 
				
			||||||
	)
 | 
								},
 | 
				
			||||||
	if err != nil {
 | 
							)
 | 
				
			||||||
		return ast.WalkStop, err
 | 
					
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return ast.WalkStop, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							err := r.Stdlib.Templates.ExecuteTemplate(
 | 
				
			||||||
 | 
								writer,
 | 
				
			||||||
 | 
								"ac:code",
 | 
				
			||||||
 | 
								struct {
 | 
				
			||||||
 | 
									Language    string
 | 
				
			||||||
 | 
									Collapse    bool
 | 
				
			||||||
 | 
									Title       string
 | 
				
			||||||
 | 
									Theme       string
 | 
				
			||||||
 | 
									Linenumbers bool
 | 
				
			||||||
 | 
									Firstline   int
 | 
				
			||||||
 | 
									Text        string
 | 
				
			||||||
 | 
								}{
 | 
				
			||||||
 | 
									lang,
 | 
				
			||||||
 | 
									collapse,
 | 
				
			||||||
 | 
									title,
 | 
				
			||||||
 | 
									theme,
 | 
				
			||||||
 | 
									linenumbers,
 | 
				
			||||||
 | 
									firstline,
 | 
				
			||||||
 | 
									strings.TrimSuffix(string(lval), "\n"),
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return ast.WalkStop, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return ast.WalkContinue, nil
 | 
						return ast.WalkContinue, nil
 | 
				
			||||||
@ -430,9 +463,11 @@ func (r *ConfluenceRenderer) renderCodeBlock(writer util.BufWriter, source []byt
 | 
				
			|||||||
	return ast.WalkContinue, nil
 | 
						return ast.WalkContinue, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func CompileMarkdown(markdown []byte, stdlib *stdlib.Lib) string {
 | 
					func CompileMarkdown(markdown []byte, stdlib *stdlib.Lib, mermaidProvider string) (string, []Attachment) {
 | 
				
			||||||
	log.Tracef(nil, "rendering markdown:\n%s", string(markdown))
 | 
						log.Tracef(nil, "rendering markdown:\n%s", string(markdown))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						confluenceRenderer := NewConfluenceRenderer(stdlib, mermaidProvider)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	converter := goldmark.New(
 | 
						converter := goldmark.New(
 | 
				
			||||||
		goldmark.WithExtensions(
 | 
							goldmark.WithExtensions(
 | 
				
			||||||
			extension.GFM,
 | 
								extension.GFM,
 | 
				
			||||||
@ -455,7 +490,7 @@ func CompileMarkdown(markdown []byte, stdlib *stdlib.Lib) string {
 | 
				
			|||||||
	))
 | 
						))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	converter.Renderer().AddOptions(renderer.WithNodeRenderers(
 | 
						converter.Renderer().AddOptions(renderer.WithNodeRenderers(
 | 
				
			||||||
		util.Prioritized(NewConfluenceRenderer(stdlib), 100),
 | 
							util.Prioritized(confluenceRenderer, 100),
 | 
				
			||||||
	))
 | 
						))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var buf bytes.Buffer
 | 
						var buf bytes.Buffer
 | 
				
			||||||
@ -469,7 +504,8 @@ func CompileMarkdown(markdown []byte, stdlib *stdlib.Lib) string {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	log.Tracef(nil, "rendered markdown to html:\n%s", string(html))
 | 
						log.Tracef(nil, "rendered markdown to html:\n%s", string(html))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return string(html)
 | 
						return string(html), confluenceRenderer.(*ConfluenceRenderer).Attachments
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// DropDocumentLeadingH1 will drop leading H1 headings to prevent
 | 
					// DropDocumentLeadingH1 will drop leading H1 headings to prevent
 | 
				
			||||||
 | 
				
			|||||||
@ -36,7 +36,7 @@ func TestCompileMarkdown(t *testing.T) {
 | 
				
			|||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			panic(err)
 | 
								panic(err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		actual := CompileMarkdown(markdown, lib)
 | 
							actual, _ := CompileMarkdown(markdown, lib, "")
 | 
				
			||||||
		test.EqualValues(string(html), actual, filename+" vs "+htmlname)
 | 
							test.EqualValues(string(html), actual, filename+" vs "+htmlname)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										50
									
								
								pkg/mark/mermaid.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								pkg/mark/mermaid.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,50 @@
 | 
				
			|||||||
 | 
					package mark
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mermaid "github.com/dreampuf/mermaid.go"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var renderTimeout = 60 * time.Second
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func processMermaidLocally(title string, mermaidDiagram []byte) (attachement Attachment, err error) {
 | 
				
			||||||
 | 
						ctx, cancel := context.WithTimeout(context.TODO(), renderTimeout)
 | 
				
			||||||
 | 
						defer cancel()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						renderer, err := mermaid.NewRenderEngine(ctx)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return Attachment{}, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pngBytes, boxModel, err := renderer.RenderAsPng(string(mermaidDiagram))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return Attachment{}, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						checkSum, err := GetChecksum(bytes.NewReader(mermaidDiagram))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return Attachment{}, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if title == "" {
 | 
				
			||||||
 | 
							title = checkSum
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fileName := title + ".png"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return Attachment{
 | 
				
			||||||
 | 
							ID:        "",
 | 
				
			||||||
 | 
							Name:      title,
 | 
				
			||||||
 | 
							Filename:  fileName,
 | 
				
			||||||
 | 
							FileBytes: pngBytes,
 | 
				
			||||||
 | 
							Checksum:  checkSum,
 | 
				
			||||||
 | 
							Replace:   title,
 | 
				
			||||||
 | 
							Width:     strconv.FormatInt(boxModel.Width, 10),
 | 
				
			||||||
 | 
							Height:    strconv.FormatInt(boxModel.Height, 10),
 | 
				
			||||||
 | 
						}, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										38
									
								
								pkg/mark/mermaid_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								pkg/mark/mermaid_test.go
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@ -2,11 +2,12 @@ package parser
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"bytes"
 | 
						"bytes"
 | 
				
			||||||
 | 
						"regexp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/yuin/goldmark/ast"
 | 
						"github.com/yuin/goldmark/ast"
 | 
				
			||||||
	"github.com/yuin/goldmark/parser"
 | 
						"github.com/yuin/goldmark/parser"
 | 
				
			||||||
	"github.com/yuin/goldmark/text"
 | 
						"github.com/yuin/goldmark/text"
 | 
				
			||||||
	"github.com/yuin/goldmark/util"
 | 
						"github.com/yuin/goldmark/util"
 | 
				
			||||||
	"regexp"
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewConfluenceTagParser returns an inline parser that parses <ac:* /> and <ri:* /> tags to ensure that Confluence specific tags are parsed
 | 
					// NewConfluenceTagParser returns an inline parser that parses <ac:* /> and <ri:* /> tags to ensure that Confluence specific tags are parsed
 | 
				
			||||||
 | 
				
			|||||||
@ -42,13 +42,13 @@ func macros(templates *template.Template) ([]macro.Macro, error) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	macros, _, err := macro.ExtractMacros(
 | 
						macros, _, err := macro.ExtractMacros(
 | 
				
			||||||
		"",
 | 
							"",
 | 
				
			||||||
		[]byte(text(
 | 
							text(
 | 
				
			||||||
			`<!-- Macro: @\{([^}]+)\}`,
 | 
								`<!-- Macro: @\{([^}]+)\}`,
 | 
				
			||||||
			`     Template: ac:link:user`,
 | 
								`     Template: ac:link:user`,
 | 
				
			||||||
			`     Name: ${1} -->`,
 | 
								`     Name: ${1} -->`,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// TODO(seletskiy): more macros here
 | 
								// TODO(seletskiy): more macros here
 | 
				
			||||||
		)),
 | 
							),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		templates,
 | 
							templates,
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
@ -218,7 +218,9 @@ func templates(api *confluence.API) (*template.Template, error) {
 | 
				
			|||||||
			`<ac:emoticon ac:name="{{ .Name }}"/>`,
 | 
								`<ac:emoticon ac:name="{{ .Name }}"/>`,
 | 
				
			||||||
		),
 | 
							),
 | 
				
			||||||
		`ac:image`: text(
 | 
							`ac:image`: text(
 | 
				
			||||||
			`<ac:image {{ if .Width}}ac:width="{{ .Width }}"{{end}}><ri:attachment ri:filename="{{ .Attachment | convertAttachment }}"/></ac:image>`,
 | 
								`<ac:image{{ if .Width}} ac:width="{{ .Width }}"{{end}}{{ if .Height }} ac:height="{{ .Height }}"{{end}}{{ if .Title }} ac:title="{{ .Title }}"{{end}}>{{printf "\n"}}`,
 | 
				
			||||||
 | 
								`<ri:attachment ri:filename="{{ .Attachment | convertAttachment }}"/>{{printf "\n"}}`,
 | 
				
			||||||
 | 
								`</ac:image>{{printf "\n"}}`,
 | 
				
			||||||
		),
 | 
							),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/* https://confluence.atlassian.com/doc/widget-connector-macro-171180449.html#WidgetConnectorMacro-YouTube */
 | 
							/* https://confluence.atlassian.com/doc/widget-connector-macro-171180449.html#WidgetConnectorMacro-YouTube */
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										19
									
								
								pkg/mark/vfs/vfs.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								pkg/mark/vfs/vfs.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					package vfs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"io"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Opener interface {
 | 
				
			||||||
 | 
						Open(name string) (io.ReadWriteCloser, error)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type LocalOSOpener struct {
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (o LocalOSOpener) Open(name string) (io.ReadWriteCloser, error) {
 | 
				
			||||||
 | 
						return os.Open(name)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var LocalOS = LocalOSOpener{}
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user