export.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643
  1. // SiYuan - Refactor your thinking
  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. "time"
  25. "github.com/88250/gulu"
  26. "github.com/88250/lute/parse"
  27. "github.com/gin-gonic/gin"
  28. "github.com/siyuan-note/logging"
  29. "github.com/siyuan-note/siyuan/kernel/model"
  30. "github.com/siyuan-note/siyuan/kernel/util"
  31. )
  32. func exportAttributeView(c *gin.Context) {
  33. ret := gulu.Ret.NewResult()
  34. defer c.JSON(http.StatusOK, ret)
  35. arg, ok := util.JsonArg(c, ret)
  36. if !ok {
  37. return
  38. }
  39. avID := arg["id"].(string)
  40. blockID := arg["blockID"].(string)
  41. zipPath, err := model.ExportAv2CSV(avID, blockID)
  42. if nil != err {
  43. ret.Code = 1
  44. ret.Msg = err.Error()
  45. ret.Data = map[string]interface{}{"closeTimeout": 7000}
  46. return
  47. }
  48. ret.Data = map[string]interface{}{
  49. "zip": zipPath,
  50. }
  51. }
  52. func exportEPUB(c *gin.Context) {
  53. ret := gulu.Ret.NewResult()
  54. defer c.JSON(http.StatusOK, ret)
  55. arg, ok := util.JsonArg(c, ret)
  56. if !ok {
  57. return
  58. }
  59. id := arg["id"].(string)
  60. name, zipPath := model.ExportPandocConvertZip(id, "epub", ".epub")
  61. ret.Data = map[string]interface{}{
  62. "name": name,
  63. "zip": zipPath,
  64. }
  65. }
  66. func exportRTF(c *gin.Context) {
  67. ret := gulu.Ret.NewResult()
  68. defer c.JSON(http.StatusOK, ret)
  69. arg, ok := util.JsonArg(c, ret)
  70. if !ok {
  71. return
  72. }
  73. id := arg["id"].(string)
  74. name, zipPath := model.ExportPandocConvertZip(id, "rtf", ".rtf")
  75. ret.Data = map[string]interface{}{
  76. "name": name,
  77. "zip": zipPath,
  78. }
  79. }
  80. func exportODT(c *gin.Context) {
  81. ret := gulu.Ret.NewResult()
  82. defer c.JSON(http.StatusOK, ret)
  83. arg, ok := util.JsonArg(c, ret)
  84. if !ok {
  85. return
  86. }
  87. id := arg["id"].(string)
  88. name, zipPath := model.ExportPandocConvertZip(id, "odt", ".odt")
  89. ret.Data = map[string]interface{}{
  90. "name": name,
  91. "zip": zipPath,
  92. }
  93. }
  94. func exportMediaWiki(c *gin.Context) {
  95. ret := gulu.Ret.NewResult()
  96. defer c.JSON(http.StatusOK, ret)
  97. arg, ok := util.JsonArg(c, ret)
  98. if !ok {
  99. return
  100. }
  101. id := arg["id"].(string)
  102. name, zipPath := model.ExportPandocConvertZip(id, "mediawiki", ".wiki")
  103. ret.Data = map[string]interface{}{
  104. "name": name,
  105. "zip": zipPath,
  106. }
  107. }
  108. func exportOrgMode(c *gin.Context) {
  109. ret := gulu.Ret.NewResult()
  110. defer c.JSON(http.StatusOK, ret)
  111. arg, ok := util.JsonArg(c, ret)
  112. if !ok {
  113. return
  114. }
  115. id := arg["id"].(string)
  116. name, zipPath := model.ExportPandocConvertZip(id, "org", ".org")
  117. ret.Data = map[string]interface{}{
  118. "name": name,
  119. "zip": zipPath,
  120. }
  121. }
  122. func exportOPML(c *gin.Context) {
  123. ret := gulu.Ret.NewResult()
  124. defer c.JSON(http.StatusOK, ret)
  125. arg, ok := util.JsonArg(c, ret)
  126. if !ok {
  127. return
  128. }
  129. id := arg["id"].(string)
  130. name, zipPath := model.ExportPandocConvertZip(id, "opml", ".opml")
  131. ret.Data = map[string]interface{}{
  132. "name": name,
  133. "zip": zipPath,
  134. }
  135. }
  136. func exportTextile(c *gin.Context) {
  137. ret := gulu.Ret.NewResult()
  138. defer c.JSON(http.StatusOK, ret)
  139. arg, ok := util.JsonArg(c, ret)
  140. if !ok {
  141. return
  142. }
  143. id := arg["id"].(string)
  144. name, zipPath := model.ExportPandocConvertZip(id, "textile", ".textile")
  145. ret.Data = map[string]interface{}{
  146. "name": name,
  147. "zip": zipPath,
  148. }
  149. }
  150. func exportAsciiDoc(c *gin.Context) {
  151. ret := gulu.Ret.NewResult()
  152. defer c.JSON(http.StatusOK, ret)
  153. arg, ok := util.JsonArg(c, ret)
  154. if !ok {
  155. return
  156. }
  157. id := arg["id"].(string)
  158. name, zipPath := model.ExportPandocConvertZip(id, "asciidoc", ".adoc")
  159. ret.Data = map[string]interface{}{
  160. "name": name,
  161. "zip": zipPath,
  162. }
  163. }
  164. func exportReStructuredText(c *gin.Context) {
  165. ret := gulu.Ret.NewResult()
  166. defer c.JSON(http.StatusOK, ret)
  167. arg, ok := util.JsonArg(c, ret)
  168. if !ok {
  169. return
  170. }
  171. id := arg["id"].(string)
  172. name, zipPath := model.ExportPandocConvertZip(id, "rst", ".rst")
  173. ret.Data = map[string]interface{}{
  174. "name": name,
  175. "zip": zipPath,
  176. }
  177. }
  178. func export2Liandi(c *gin.Context) {
  179. ret := gulu.Ret.NewResult()
  180. defer c.JSON(http.StatusOK, ret)
  181. arg, ok := util.JsonArg(c, ret)
  182. if !ok {
  183. return
  184. }
  185. id := arg["id"].(string)
  186. err := model.Export2Liandi(id)
  187. if nil != err {
  188. ret.Code = -1
  189. ret.Msg = err.Error()
  190. return
  191. }
  192. }
  193. func exportDataInFolder(c *gin.Context) {
  194. ret := gulu.Ret.NewResult()
  195. defer c.JSON(http.StatusOK, ret)
  196. arg, ok := util.JsonArg(c, ret)
  197. if !ok {
  198. return
  199. }
  200. exportFolder := arg["folder"].(string)
  201. name, err := model.ExportDataInFolder(exportFolder)
  202. if nil != err {
  203. ret.Code = -1
  204. ret.Msg = err.Error()
  205. ret.Data = map[string]interface{}{"closeTimeout": 7000}
  206. return
  207. }
  208. ret.Data = map[string]interface{}{
  209. "name": name,
  210. }
  211. }
  212. func exportData(c *gin.Context) {
  213. ret := gulu.Ret.NewResult()
  214. defer c.JSON(http.StatusOK, ret)
  215. zipPath, err := model.ExportData()
  216. if nil != err {
  217. ret.Code = 1
  218. ret.Msg = err.Error()
  219. ret.Data = map[string]interface{}{"closeTimeout": 7000}
  220. return
  221. }
  222. ret.Data = map[string]interface{}{
  223. "zip": zipPath,
  224. }
  225. }
  226. func exportResources(c *gin.Context) {
  227. ret := gulu.Ret.NewResult()
  228. defer c.JSON(http.StatusOK, ret)
  229. arg, ok := util.JsonArg(c, ret)
  230. if !ok {
  231. return
  232. }
  233. var name string
  234. if nil != arg["name"] {
  235. name = util.TruncateLenFileName(arg["name"].(string))
  236. }
  237. if name == "" {
  238. name = time.Now().Format("export-2006-01-02_15-04-05") // 生成的 *.zip 文件主文件名
  239. }
  240. if nil == arg["paths"] {
  241. ret.Code = 1
  242. ret.Data = ""
  243. ret.Msg = "paths is required"
  244. return
  245. }
  246. var resourcePaths []string // 文件/文件夹在工作空间中的路径
  247. for _, resourcePath := range arg["paths"].([]interface{}) {
  248. resourcePaths = append(resourcePaths, resourcePath.(string))
  249. }
  250. zipFilePath, err := model.ExportResources(resourcePaths, name)
  251. if nil != err {
  252. ret.Code = 1
  253. ret.Msg = err.Error()
  254. ret.Data = map[string]interface{}{"closeTimeout": 7000}
  255. return
  256. }
  257. ret.Data = map[string]interface{}{
  258. "path": zipFilePath, // 相对于工作空间目录的路径
  259. }
  260. }
  261. func batchExportMd(c *gin.Context) {
  262. ret := gulu.Ret.NewResult()
  263. defer c.JSON(http.StatusOK, ret)
  264. arg, ok := util.JsonArg(c, ret)
  265. if !ok {
  266. return
  267. }
  268. notebook := arg["notebook"].(string)
  269. p := arg["path"].(string)
  270. zipPath := model.BatchExportMarkdown(notebook, p)
  271. ret.Data = map[string]interface{}{
  272. "name": path.Base(zipPath),
  273. "zip": zipPath,
  274. }
  275. }
  276. func exportMd(c *gin.Context) {
  277. ret := gulu.Ret.NewResult()
  278. defer c.JSON(http.StatusOK, ret)
  279. arg, ok := util.JsonArg(c, ret)
  280. if !ok {
  281. return
  282. }
  283. id := arg["id"].(string)
  284. name, zipPath := model.ExportPandocConvertZip(id, "", ".md")
  285. ret.Data = map[string]interface{}{
  286. "name": name,
  287. "zip": zipPath,
  288. }
  289. }
  290. func exportNotebookSY(c *gin.Context) {
  291. ret := gulu.Ret.NewResult()
  292. defer c.JSON(http.StatusOK, ret)
  293. arg, ok := util.JsonArg(c, ret)
  294. if !ok {
  295. return
  296. }
  297. id := arg["id"].(string)
  298. zipPath := model.ExportNotebookSY(id)
  299. ret.Data = map[string]interface{}{
  300. "zip": zipPath,
  301. }
  302. }
  303. func exportSY(c *gin.Context) {
  304. ret := gulu.Ret.NewResult()
  305. defer c.JSON(http.StatusOK, ret)
  306. arg, ok := util.JsonArg(c, ret)
  307. if !ok {
  308. return
  309. }
  310. id := arg["id"].(string)
  311. name, zipPath := model.ExportSY(id)
  312. ret.Data = map[string]interface{}{
  313. "name": name,
  314. "zip": zipPath,
  315. }
  316. }
  317. func exportMdContent(c *gin.Context) {
  318. ret := gulu.Ret.NewResult()
  319. defer c.JSON(http.StatusOK, ret)
  320. arg, ok := util.JsonArg(c, ret)
  321. if !ok {
  322. return
  323. }
  324. id := arg["id"].(string)
  325. if util.InvalidIDPattern(id, ret) {
  326. return
  327. }
  328. hPath, content := model.ExportMarkdownContent(id)
  329. ret.Data = map[string]interface{}{
  330. "hPath": hPath,
  331. "content": content,
  332. }
  333. }
  334. func exportDocx(c *gin.Context) {
  335. ret := gulu.Ret.NewResult()
  336. defer c.JSON(http.StatusOK, ret)
  337. arg, ok := util.JsonArg(c, ret)
  338. if !ok {
  339. return
  340. }
  341. id := arg["id"].(string)
  342. savePath := arg["savePath"].(string)
  343. removeAssets := arg["removeAssets"].(bool)
  344. merge := false
  345. if nil != arg["merge"] {
  346. merge = arg["merge"].(bool)
  347. }
  348. fullPath, err := model.ExportDocx(id, savePath, removeAssets, merge)
  349. if nil != err {
  350. ret.Code = -1
  351. ret.Msg = err.Error()
  352. ret.Data = map[string]interface{}{"closeTimeout": 7000}
  353. return
  354. }
  355. ret.Data = map[string]interface{}{
  356. "path": fullPath,
  357. }
  358. }
  359. func exportMdHTML(c *gin.Context) {
  360. ret := gulu.Ret.NewResult()
  361. defer c.JSON(http.StatusOK, ret)
  362. arg, ok := util.JsonArg(c, ret)
  363. if !ok {
  364. return
  365. }
  366. id := arg["id"].(string)
  367. savePath := arg["savePath"].(string)
  368. name, content := model.ExportMarkdownHTML(id, savePath, false, false)
  369. ret.Data = map[string]interface{}{
  370. "id": id,
  371. "name": name,
  372. "content": content,
  373. }
  374. }
  375. func exportTempContent(c *gin.Context) {
  376. ret := gulu.Ret.NewResult()
  377. defer c.JSON(http.StatusOK, ret)
  378. arg, ok := util.JsonArg(c, ret)
  379. if !ok {
  380. return
  381. }
  382. content := arg["content"].(string)
  383. tmpExport := filepath.Join(util.TempDir, "export", "temp")
  384. if err := os.MkdirAll(tmpExport, 0755); nil != err {
  385. ret.Code = 1
  386. ret.Msg = err.Error()
  387. ret.Data = map[string]interface{}{"closeTimeout": 7000}
  388. return
  389. }
  390. p := filepath.Join(tmpExport, gulu.Rand.String(7))
  391. if err := os.WriteFile(p, []byte(content), 0644); nil != err {
  392. ret.Code = 1
  393. ret.Msg = err.Error()
  394. ret.Data = map[string]interface{}{"closeTimeout": 7000}
  395. return
  396. }
  397. url := path.Join("/export/temp/", filepath.Base(p))
  398. ret.Data = map[string]interface{}{
  399. "url": "http://" + util.LocalHost + ":" + util.ServerPort + url,
  400. }
  401. }
  402. func exportPreviewHTML(c *gin.Context) {
  403. ret := gulu.Ret.NewResult()
  404. defer c.JSON(http.StatusOK, ret)
  405. arg, ok := util.JsonArg(c, ret)
  406. if !ok {
  407. return
  408. }
  409. id := arg["id"].(string)
  410. keepFold := false
  411. if nil != arg["keepFold"] {
  412. keepFold = arg["keepFold"].(bool)
  413. }
  414. merge := false
  415. if nil != arg["merge"] {
  416. merge = arg["merge"].(bool)
  417. }
  418. image := false
  419. if nil != arg["image"] {
  420. image = arg["image"].(bool)
  421. }
  422. name, content, node := model.ExportHTML(id, "", true, image, keepFold, merge)
  423. // 导出 PDF 预览时点击块引转换后的脚注跳转不正确 https://github.com/siyuan-note/siyuan/issues/5894
  424. content = strings.ReplaceAll(content, "http://"+util.LocalHost+":"+util.ServerPort+"/#", "#")
  425. // Add `data-doc-type` and attribute when exporting image and PDF https://github.com/siyuan-note/siyuan/issues/9497
  426. attrs := map[string]string{}
  427. var typ string
  428. if nil != node {
  429. attrs = parse.IAL2Map(node.KramdownIAL)
  430. typ = node.Type.String()
  431. }
  432. ret.Data = map[string]interface{}{
  433. "id": id,
  434. "name": name,
  435. "content": content,
  436. "attrs": attrs,
  437. "type": typ,
  438. }
  439. }
  440. func exportHTML(c *gin.Context) {
  441. ret := gulu.Ret.NewResult()
  442. defer c.JSON(http.StatusOK, ret)
  443. arg, ok := util.JsonArg(c, ret)
  444. if !ok {
  445. return
  446. }
  447. id := arg["id"].(string)
  448. pdf := arg["pdf"].(bool)
  449. savePath := arg["savePath"].(string)
  450. keepFold := false
  451. if nil != arg["keepFold"] {
  452. keepFold = arg["keepFold"].(bool)
  453. }
  454. merge := false
  455. if nil != arg["merge"] {
  456. merge = arg["merge"].(bool)
  457. }
  458. name, content, _ := model.ExportHTML(id, savePath, pdf, false, keepFold, merge)
  459. ret.Data = map[string]interface{}{
  460. "id": id,
  461. "name": name,
  462. "content": content,
  463. }
  464. }
  465. func processPDF(c *gin.Context) {
  466. ret := gulu.Ret.NewResult()
  467. defer c.JSON(http.StatusOK, ret)
  468. arg, ok := util.JsonArg(c, ret)
  469. if !ok {
  470. return
  471. }
  472. id := arg["id"].(string)
  473. path := arg["path"].(string)
  474. merge := false
  475. if nil != arg["merge"] {
  476. merge = arg["merge"].(bool)
  477. }
  478. removeAssets := arg["removeAssets"].(bool)
  479. watermark := arg["watermark"].(bool)
  480. err := model.ProcessPDF(id, path, merge, removeAssets, watermark)
  481. if nil != err {
  482. ret.Code = -1
  483. ret.Msg = err.Error()
  484. return
  485. }
  486. }
  487. func exportPreview(c *gin.Context) {
  488. ret := gulu.Ret.NewResult()
  489. defer c.JSON(http.StatusOK, ret)
  490. arg, ok := util.JsonArg(c, ret)
  491. if !ok {
  492. return
  493. }
  494. id := arg["id"].(string)
  495. stdHTML, outline := model.Preview(id)
  496. ret.Data = map[string]interface{}{
  497. "html": stdHTML,
  498. "outline": outline,
  499. }
  500. }
  501. func exportAsFile(c *gin.Context) {
  502. ret := gulu.Ret.NewResult()
  503. defer c.JSON(http.StatusOK, ret)
  504. form, err := c.MultipartForm()
  505. if nil != err {
  506. logging.LogErrorf("export as file failed: %s", err)
  507. ret.Code = -1
  508. ret.Msg = err.Error()
  509. return
  510. }
  511. file := form.File["file"][0]
  512. reader, err := file.Open()
  513. if nil != err {
  514. logging.LogErrorf("export as file failed: %s", err)
  515. ret.Code = -1
  516. ret.Msg = err.Error()
  517. return
  518. }
  519. defer reader.Close()
  520. data, err := io.ReadAll(reader)
  521. if nil != err {
  522. logging.LogErrorf("export as file failed: %s", err)
  523. ret.Code = -1
  524. ret.Msg = err.Error()
  525. return
  526. }
  527. name := "file-" + file.Filename
  528. name = util.FilterFileName(name)
  529. tmpDir := filepath.Join(util.TempDir, "export")
  530. if err = os.MkdirAll(tmpDir, 0755); nil != err {
  531. logging.LogErrorf("export as file failed: %s", err)
  532. ret.Code = -1
  533. ret.Msg = err.Error()
  534. return
  535. }
  536. tmp := filepath.Join(tmpDir, name)
  537. err = os.WriteFile(tmp, data, 0644)
  538. if nil != err {
  539. logging.LogErrorf("export as file failed: %s", err)
  540. ret.Code = -1
  541. ret.Msg = err.Error()
  542. return
  543. }
  544. ret.Data = map[string]interface{}{
  545. "name": name,
  546. "file": path.Join("/export/", name),
  547. }
  548. }