export.go 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. // SiYuan - Build Your Eternal Digital Garden
  2. // Copyright (c) 2020-present, b3log.org
  3. //
  4. // This program is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Affero General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // This program is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Affero General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Affero General Public License
  15. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  16. package api
  17. import (
  18. "io"
  19. "net/http"
  20. "os"
  21. "path"
  22. "path/filepath"
  23. "strings"
  24. "github.com/88250/gulu"
  25. "github.com/gin-gonic/gin"
  26. "github.com/siyuan-note/logging"
  27. "github.com/siyuan-note/siyuan/kernel/model"
  28. "github.com/siyuan-note/siyuan/kernel/util"
  29. )
  30. func exportOPML(c *gin.Context) {
  31. ret := gulu.Ret.NewResult()
  32. defer c.JSON(http.StatusOK, ret)
  33. arg, ok := util.JsonArg(c, ret)
  34. if !ok {
  35. return
  36. }
  37. id := arg["id"].(string)
  38. name, zipPath := model.ExportPandocConvertZip(id, "opml", ".opml")
  39. ret.Data = map[string]interface{}{
  40. "name": name,
  41. "zip": zipPath,
  42. }
  43. }
  44. func exportTextile(c *gin.Context) {
  45. ret := gulu.Ret.NewResult()
  46. defer c.JSON(http.StatusOK, ret)
  47. arg, ok := util.JsonArg(c, ret)
  48. if !ok {
  49. return
  50. }
  51. id := arg["id"].(string)
  52. name, zipPath := model.ExportPandocConvertZip(id, "textile", ".textile")
  53. ret.Data = map[string]interface{}{
  54. "name": name,
  55. "zip": zipPath,
  56. }
  57. }
  58. func exportAsciiDoc(c *gin.Context) {
  59. ret := gulu.Ret.NewResult()
  60. defer c.JSON(http.StatusOK, ret)
  61. arg, ok := util.JsonArg(c, ret)
  62. if !ok {
  63. return
  64. }
  65. id := arg["id"].(string)
  66. name, zipPath := model.ExportPandocConvertZip(id, "asciidoc", ".adoc")
  67. ret.Data = map[string]interface{}{
  68. "name": name,
  69. "zip": zipPath,
  70. }
  71. }
  72. func exportReStructuredText(c *gin.Context) {
  73. ret := gulu.Ret.NewResult()
  74. defer c.JSON(http.StatusOK, ret)
  75. arg, ok := util.JsonArg(c, ret)
  76. if !ok {
  77. return
  78. }
  79. id := arg["id"].(string)
  80. name, zipPath := model.ExportPandocConvertZip(id, "rst", ".rst")
  81. ret.Data = map[string]interface{}{
  82. "name": name,
  83. "zip": zipPath,
  84. }
  85. }
  86. func export2Liandi(c *gin.Context) {
  87. ret := gulu.Ret.NewResult()
  88. defer c.JSON(http.StatusOK, ret)
  89. arg, ok := util.JsonArg(c, ret)
  90. if !ok {
  91. return
  92. }
  93. id := arg["id"].(string)
  94. err := model.Export2Liandi(id)
  95. if nil != err {
  96. ret.Code = -1
  97. ret.Msg = err.Error()
  98. return
  99. }
  100. }
  101. func exportDataInFolder(c *gin.Context) {
  102. ret := gulu.Ret.NewResult()
  103. defer c.JSON(http.StatusOK, ret)
  104. arg, ok := util.JsonArg(c, ret)
  105. if !ok {
  106. return
  107. }
  108. exportFolder := arg["folder"].(string)
  109. name, err := model.ExportDataInFolder(exportFolder)
  110. if nil != err {
  111. ret.Code = -1
  112. ret.Msg = err.Error()
  113. ret.Data = map[string]interface{}{"closeTimeout": 7000}
  114. return
  115. }
  116. ret.Data = map[string]interface{}{
  117. "name": name,
  118. }
  119. }
  120. func exportData(c *gin.Context) {
  121. ret := gulu.Ret.NewResult()
  122. defer c.JSON(http.StatusOK, ret)
  123. zipPath, err := model.ExportData()
  124. if nil != err {
  125. ret.Code = 1
  126. ret.Msg = err.Error()
  127. ret.Data = map[string]interface{}{"closeTimeout": 7000}
  128. return
  129. }
  130. ret.Data = map[string]interface{}{
  131. "zip": zipPath,
  132. }
  133. }
  134. func batchExportMd(c *gin.Context) {
  135. ret := gulu.Ret.NewResult()
  136. defer c.JSON(http.StatusOK, ret)
  137. arg, ok := util.JsonArg(c, ret)
  138. if !ok {
  139. return
  140. }
  141. notebook := arg["notebook"].(string)
  142. p := arg["path"].(string)
  143. zipPath := model.BatchExportMarkdown(notebook, p)
  144. ret.Data = map[string]interface{}{
  145. "name": path.Base(zipPath),
  146. "zip": zipPath,
  147. }
  148. }
  149. func exportMd(c *gin.Context) {
  150. ret := gulu.Ret.NewResult()
  151. defer c.JSON(http.StatusOK, ret)
  152. arg, ok := util.JsonArg(c, ret)
  153. if !ok {
  154. return
  155. }
  156. id := arg["id"].(string)
  157. name, zipPath := model.ExportPandocConvertZip(id, "", ".md")
  158. ret.Data = map[string]interface{}{
  159. "name": name,
  160. "zip": zipPath,
  161. }
  162. }
  163. func exportNotebookSY(c *gin.Context) {
  164. ret := gulu.Ret.NewResult()
  165. defer c.JSON(http.StatusOK, ret)
  166. arg, ok := util.JsonArg(c, ret)
  167. if !ok {
  168. return
  169. }
  170. id := arg["id"].(string)
  171. zipPath := model.ExportNotebookSY(id)
  172. ret.Data = map[string]interface{}{
  173. "zip": zipPath,
  174. }
  175. }
  176. func exportSY(c *gin.Context) {
  177. ret := gulu.Ret.NewResult()
  178. defer c.JSON(http.StatusOK, ret)
  179. arg, ok := util.JsonArg(c, ret)
  180. if !ok {
  181. return
  182. }
  183. id := arg["id"].(string)
  184. name, zipPath := model.ExportSY(id)
  185. ret.Data = map[string]interface{}{
  186. "name": name,
  187. "zip": zipPath,
  188. }
  189. }
  190. func exportMdContent(c *gin.Context) {
  191. ret := gulu.Ret.NewResult()
  192. defer c.JSON(http.StatusOK, ret)
  193. arg, ok := util.JsonArg(c, ret)
  194. if !ok {
  195. return
  196. }
  197. id := arg["id"].(string)
  198. if util.InvalidIDPattern(id, ret) {
  199. return
  200. }
  201. hPath, content := model.ExportMarkdownContent(id)
  202. ret.Data = map[string]interface{}{
  203. "hPath": hPath,
  204. "content": content,
  205. }
  206. }
  207. func exportDocx(c *gin.Context) {
  208. ret := gulu.Ret.NewResult()
  209. defer c.JSON(http.StatusOK, ret)
  210. arg, ok := util.JsonArg(c, ret)
  211. if !ok {
  212. return
  213. }
  214. id := arg["id"].(string)
  215. savePath := arg["savePath"].(string)
  216. removeAssets := arg["removeAssets"].(bool)
  217. merge := false
  218. if nil != arg["merge"] {
  219. merge = arg["merge"].(bool)
  220. }
  221. err := model.ExportDocx(id, savePath, removeAssets, merge)
  222. if nil != err {
  223. ret.Code = 1
  224. ret.Msg = err.Error()
  225. ret.Data = map[string]interface{}{"closeTimeout": 7000}
  226. return
  227. }
  228. }
  229. func exportMdHTML(c *gin.Context) {
  230. ret := gulu.Ret.NewResult()
  231. defer c.JSON(http.StatusOK, ret)
  232. arg, ok := util.JsonArg(c, ret)
  233. if !ok {
  234. return
  235. }
  236. id := arg["id"].(string)
  237. savePath := arg["savePath"].(string)
  238. name, content := model.ExportMarkdownHTML(id, savePath, false, false)
  239. ret.Data = map[string]interface{}{
  240. "id": id,
  241. "name": name,
  242. "content": content,
  243. }
  244. }
  245. func exportTempContent(c *gin.Context) {
  246. ret := gulu.Ret.NewResult()
  247. defer c.JSON(http.StatusOK, ret)
  248. arg, ok := util.JsonArg(c, ret)
  249. if !ok {
  250. return
  251. }
  252. content := arg["content"].(string)
  253. tmpExport := filepath.Join(util.TempDir, "export", "temp")
  254. if err := os.MkdirAll(tmpExport, 0755); nil != err {
  255. ret.Code = 1
  256. ret.Msg = err.Error()
  257. ret.Data = map[string]interface{}{"closeTimeout": 7000}
  258. return
  259. }
  260. p := filepath.Join(tmpExport, gulu.Rand.String(7))
  261. if err := os.WriteFile(p, []byte(content), 0644); nil != err {
  262. ret.Code = 1
  263. ret.Msg = err.Error()
  264. ret.Data = map[string]interface{}{"closeTimeout": 7000}
  265. return
  266. }
  267. url := path.Join("/export/temp/", filepath.Base(p))
  268. ret.Data = map[string]interface{}{
  269. "url": "http://" + util.LocalHost + ":" + util.ServerPort + url,
  270. }
  271. }
  272. func exportPreviewHTML(c *gin.Context) {
  273. ret := gulu.Ret.NewResult()
  274. defer c.JSON(http.StatusOK, ret)
  275. arg, ok := util.JsonArg(c, ret)
  276. if !ok {
  277. return
  278. }
  279. id := arg["id"].(string)
  280. keepFold := false
  281. if nil != arg["keepFold"] {
  282. keepFold = arg["keepFold"].(bool)
  283. }
  284. merge := false
  285. if nil != arg["merge"] {
  286. merge = arg["merge"].(bool)
  287. }
  288. image := false
  289. if nil != arg["image"] {
  290. image = arg["image"].(bool)
  291. }
  292. name, content := model.ExportHTML(id, "", true, image, keepFold, merge)
  293. // 导出 PDF 预览时点击块引转换后的脚注跳转不正确 https://github.com/siyuan-note/siyuan/issues/5894
  294. content = strings.ReplaceAll(content, "http://"+util.LocalHost+":"+util.ServerPort+"/#", "#")
  295. ret.Data = map[string]interface{}{
  296. "id": id,
  297. "name": name,
  298. "content": content,
  299. }
  300. }
  301. func exportHTML(c *gin.Context) {
  302. ret := gulu.Ret.NewResult()
  303. defer c.JSON(http.StatusOK, ret)
  304. arg, ok := util.JsonArg(c, ret)
  305. if !ok {
  306. return
  307. }
  308. id := arg["id"].(string)
  309. pdf := arg["pdf"].(bool)
  310. savePath := arg["savePath"].(string)
  311. keepFold := false
  312. if nil != arg["keepFold"] {
  313. keepFold = arg["keepFold"].(bool)
  314. }
  315. merge := false
  316. if nil != arg["merge"] {
  317. merge = arg["merge"].(bool)
  318. }
  319. name, content := model.ExportHTML(id, savePath, pdf, false, keepFold, merge)
  320. ret.Data = map[string]interface{}{
  321. "id": id,
  322. "name": name,
  323. "content": content,
  324. }
  325. }
  326. func processPDF(c *gin.Context) {
  327. ret := gulu.Ret.NewResult()
  328. defer c.JSON(http.StatusOK, ret)
  329. arg, ok := util.JsonArg(c, ret)
  330. if !ok {
  331. return
  332. }
  333. id := arg["id"].(string)
  334. path := arg["path"].(string)
  335. merge := false
  336. if nil != arg["merge"] {
  337. merge = arg["merge"].(bool)
  338. }
  339. removeAssets := arg["removeAssets"].(bool)
  340. err := model.ProcessPDF(id, path, merge, removeAssets)
  341. if nil != err {
  342. ret.Code = -1
  343. ret.Msg = err.Error()
  344. return
  345. }
  346. }
  347. func exportPreview(c *gin.Context) {
  348. ret := gulu.Ret.NewResult()
  349. defer c.JSON(http.StatusOK, ret)
  350. arg, ok := util.JsonArg(c, ret)
  351. if !ok {
  352. return
  353. }
  354. id := arg["id"].(string)
  355. stdHTML := model.Preview(id)
  356. ret.Data = map[string]interface{}{
  357. "html": stdHTML,
  358. }
  359. }
  360. func exportAsFile(c *gin.Context) {
  361. ret := gulu.Ret.NewResult()
  362. defer c.JSON(http.StatusOK, ret)
  363. form, err := c.MultipartForm()
  364. if nil != err {
  365. logging.LogErrorf("export as file failed: %s", err)
  366. ret.Code = -1
  367. ret.Msg = err.Error()
  368. return
  369. }
  370. file := form.File["file"][0]
  371. reader, err := file.Open()
  372. if nil != err {
  373. logging.LogErrorf("export as file failed: %s", err)
  374. ret.Code = -1
  375. ret.Msg = err.Error()
  376. return
  377. }
  378. defer reader.Close()
  379. data, err := io.ReadAll(reader)
  380. if nil != err {
  381. logging.LogErrorf("export as file failed: %s", err)
  382. ret.Code = -1
  383. ret.Msg = err.Error()
  384. return
  385. }
  386. name := "file-" + file.Filename
  387. name = util.FilterFileName(name)
  388. tmpDir := filepath.Join(util.TempDir, "export")
  389. if err = os.MkdirAll(tmpDir, 0755); nil != err {
  390. logging.LogErrorf("export as file failed: %s", err)
  391. ret.Code = -1
  392. ret.Msg = err.Error()
  393. return
  394. }
  395. tmp := filepath.Join(tmpDir, name)
  396. err = os.WriteFile(tmp, data, 0644)
  397. if nil != err {
  398. logging.LogErrorf("export as file failed: %s", err)
  399. ret.Code = -1
  400. ret.Msg = err.Error()
  401. return
  402. }
  403. ret.Data = map[string]interface{}{
  404. "name": name,
  405. "file": path.Join("/export/", name),
  406. }
  407. }