export.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663
  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 err != nil {
  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([]string{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([]string{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([]string{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([]string{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([]string{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([]string{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([]string{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([]string{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([]string{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 err != nil {
  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 err != nil {
  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 err != nil {
  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 err != nil {
  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 exportNotebookMd(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. zipPath := model.ExportNotebookMarkdown(notebook)
  270. ret.Data = map[string]interface{}{
  271. "name": path.Base(zipPath),
  272. "zip": zipPath,
  273. }
  274. }
  275. func exportMds(c *gin.Context) {
  276. ret := gulu.Ret.NewResult()
  277. defer c.JSON(http.StatusOK, ret)
  278. arg, ok := util.JsonArg(c, ret)
  279. if !ok {
  280. return
  281. }
  282. idsArg := arg["ids"].([]interface{})
  283. var ids []string
  284. for _, id := range idsArg {
  285. ids = append(ids, id.(string))
  286. }
  287. name, zipPath := model.ExportPandocConvertZip(ids, "", ".md")
  288. ret.Data = map[string]interface{}{
  289. "name": name,
  290. "zip": zipPath,
  291. }
  292. }
  293. func exportMd(c *gin.Context) {
  294. ret := gulu.Ret.NewResult()
  295. defer c.JSON(http.StatusOK, ret)
  296. arg, ok := util.JsonArg(c, ret)
  297. if !ok {
  298. return
  299. }
  300. id := arg["id"].(string)
  301. name, zipPath := model.ExportPandocConvertZip([]string{id}, "", ".md")
  302. ret.Data = map[string]interface{}{
  303. "name": name,
  304. "zip": zipPath,
  305. }
  306. }
  307. func exportNotebookSY(c *gin.Context) {
  308. ret := gulu.Ret.NewResult()
  309. defer c.JSON(http.StatusOK, ret)
  310. arg, ok := util.JsonArg(c, ret)
  311. if !ok {
  312. return
  313. }
  314. id := arg["id"].(string)
  315. zipPath := model.ExportNotebookSY(id)
  316. ret.Data = map[string]interface{}{
  317. "zip": zipPath,
  318. }
  319. }
  320. func exportSY(c *gin.Context) {
  321. ret := gulu.Ret.NewResult()
  322. defer c.JSON(http.StatusOK, ret)
  323. arg, ok := util.JsonArg(c, ret)
  324. if !ok {
  325. return
  326. }
  327. id := arg["id"].(string)
  328. name, zipPath := model.ExportSY(id)
  329. ret.Data = map[string]interface{}{
  330. "name": name,
  331. "zip": zipPath,
  332. }
  333. }
  334. func exportMdContent(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. if util.InvalidIDPattern(id, ret) {
  343. return
  344. }
  345. hPath, content := model.ExportMarkdownContent(id)
  346. ret.Data = map[string]interface{}{
  347. "hPath": hPath,
  348. "content": content,
  349. }
  350. }
  351. func exportDocx(c *gin.Context) {
  352. ret := gulu.Ret.NewResult()
  353. defer c.JSON(http.StatusOK, ret)
  354. arg, ok := util.JsonArg(c, ret)
  355. if !ok {
  356. return
  357. }
  358. id := arg["id"].(string)
  359. savePath := arg["savePath"].(string)
  360. removeAssets := arg["removeAssets"].(bool)
  361. merge := false
  362. if nil != arg["merge"] {
  363. merge = arg["merge"].(bool)
  364. }
  365. fullPath, err := model.ExportDocx(id, savePath, removeAssets, merge)
  366. if err != nil {
  367. ret.Code = -1
  368. ret.Msg = err.Error()
  369. ret.Data = map[string]interface{}{"closeTimeout": 7000}
  370. return
  371. }
  372. ret.Data = map[string]interface{}{
  373. "path": fullPath,
  374. }
  375. }
  376. func exportMdHTML(c *gin.Context) {
  377. ret := gulu.Ret.NewResult()
  378. defer c.JSON(http.StatusOK, ret)
  379. arg, ok := util.JsonArg(c, ret)
  380. if !ok {
  381. return
  382. }
  383. id := arg["id"].(string)
  384. savePath := arg["savePath"].(string)
  385. name, content := model.ExportMarkdownHTML(id, savePath, false, false)
  386. ret.Data = map[string]interface{}{
  387. "id": id,
  388. "name": name,
  389. "content": content,
  390. }
  391. }
  392. func exportTempContent(c *gin.Context) {
  393. ret := gulu.Ret.NewResult()
  394. defer c.JSON(http.StatusOK, ret)
  395. arg, ok := util.JsonArg(c, ret)
  396. if !ok {
  397. return
  398. }
  399. content := arg["content"].(string)
  400. tmpExport := filepath.Join(util.TempDir, "export", "temp")
  401. if err := os.MkdirAll(tmpExport, 0755); err != nil {
  402. ret.Code = 1
  403. ret.Msg = err.Error()
  404. ret.Data = map[string]interface{}{"closeTimeout": 7000}
  405. return
  406. }
  407. p := filepath.Join(tmpExport, gulu.Rand.String(7))
  408. if err := os.WriteFile(p, []byte(content), 0644); err != nil {
  409. ret.Code = 1
  410. ret.Msg = err.Error()
  411. ret.Data = map[string]interface{}{"closeTimeout": 7000}
  412. return
  413. }
  414. url := path.Join("/export/temp/", filepath.Base(p))
  415. ret.Data = map[string]interface{}{
  416. "url": "http://" + util.LocalHost + ":" + util.ServerPort + url,
  417. }
  418. }
  419. func exportPreviewHTML(c *gin.Context) {
  420. ret := gulu.Ret.NewResult()
  421. defer c.JSON(http.StatusOK, ret)
  422. arg, ok := util.JsonArg(c, ret)
  423. if !ok {
  424. return
  425. }
  426. id := arg["id"].(string)
  427. keepFold := false
  428. if nil != arg["keepFold"] {
  429. keepFold = arg["keepFold"].(bool)
  430. }
  431. merge := false
  432. if nil != arg["merge"] {
  433. merge = arg["merge"].(bool)
  434. }
  435. image := false
  436. if nil != arg["image"] {
  437. image = arg["image"].(bool)
  438. }
  439. name, content, node := model.ExportHTML(id, "", true, image, keepFold, merge)
  440. // 导出 PDF 预览时点击块引转换后的脚注跳转不正确 https://github.com/siyuan-note/siyuan/issues/5894
  441. content = strings.ReplaceAll(content, "http://"+util.LocalHost+":"+util.ServerPort+"/#", "#")
  442. // Add `data-doc-type` and attribute when exporting image and PDF https://github.com/siyuan-note/siyuan/issues/9497
  443. attrs := map[string]string{}
  444. var typ string
  445. if nil != node {
  446. attrs = parse.IAL2Map(node.KramdownIAL)
  447. typ = node.Type.String()
  448. }
  449. ret.Data = map[string]interface{}{
  450. "id": id,
  451. "name": name,
  452. "content": content,
  453. "attrs": attrs,
  454. "type": typ,
  455. }
  456. }
  457. func exportHTML(c *gin.Context) {
  458. ret := gulu.Ret.NewResult()
  459. defer c.JSON(http.StatusOK, ret)
  460. arg, ok := util.JsonArg(c, ret)
  461. if !ok {
  462. return
  463. }
  464. id := arg["id"].(string)
  465. pdf := arg["pdf"].(bool)
  466. savePath := arg["savePath"].(string)
  467. keepFold := false
  468. if nil != arg["keepFold"] {
  469. keepFold = arg["keepFold"].(bool)
  470. }
  471. merge := false
  472. if nil != arg["merge"] {
  473. merge = arg["merge"].(bool)
  474. }
  475. name, content, _ := model.ExportHTML(id, savePath, pdf, false, keepFold, merge)
  476. ret.Data = map[string]interface{}{
  477. "id": id,
  478. "name": name,
  479. "content": content,
  480. }
  481. }
  482. func processPDF(c *gin.Context) {
  483. ret := gulu.Ret.NewResult()
  484. defer c.JSON(http.StatusOK, ret)
  485. arg, ok := util.JsonArg(c, ret)
  486. if !ok {
  487. return
  488. }
  489. id := arg["id"].(string)
  490. path := arg["path"].(string)
  491. merge := false
  492. if nil != arg["merge"] {
  493. merge = arg["merge"].(bool)
  494. }
  495. removeAssets := arg["removeAssets"].(bool)
  496. watermark := arg["watermark"].(bool)
  497. err := model.ProcessPDF(id, path, merge, removeAssets, watermark)
  498. if err != nil {
  499. ret.Code = -1
  500. ret.Msg = err.Error()
  501. return
  502. }
  503. }
  504. func exportPreview(c *gin.Context) {
  505. ret := gulu.Ret.NewResult()
  506. defer c.JSON(http.StatusOK, ret)
  507. arg, ok := util.JsonArg(c, ret)
  508. if !ok {
  509. return
  510. }
  511. id := arg["id"].(string)
  512. stdHTML := model.Preview(id)
  513. ret.Data = map[string]interface{}{
  514. "html": stdHTML,
  515. }
  516. }
  517. func exportAsFile(c *gin.Context) {
  518. ret := gulu.Ret.NewResult()
  519. defer c.JSON(http.StatusOK, ret)
  520. form, err := c.MultipartForm()
  521. if err != nil {
  522. logging.LogErrorf("export as file failed: %s", err)
  523. ret.Code = -1
  524. ret.Msg = err.Error()
  525. return
  526. }
  527. file := form.File["file"][0]
  528. reader, err := file.Open()
  529. if err != nil {
  530. logging.LogErrorf("export as file failed: %s", err)
  531. ret.Code = -1
  532. ret.Msg = err.Error()
  533. return
  534. }
  535. defer reader.Close()
  536. data, err := io.ReadAll(reader)
  537. if err != nil {
  538. logging.LogErrorf("export as file failed: %s", err)
  539. ret.Code = -1
  540. ret.Msg = err.Error()
  541. return
  542. }
  543. name := "file-" + file.Filename
  544. name = util.FilterFileName(name)
  545. tmpDir := filepath.Join(util.TempDir, "export")
  546. if err = os.MkdirAll(tmpDir, 0755); err != nil {
  547. logging.LogErrorf("export as file failed: %s", err)
  548. ret.Code = -1
  549. ret.Msg = err.Error()
  550. return
  551. }
  552. tmp := filepath.Join(tmpDir, name)
  553. err = os.WriteFile(tmp, data, 0644)
  554. if err != nil {
  555. logging.LogErrorf("export as file failed: %s", err)
  556. ret.Code = -1
  557. ret.Msg = err.Error()
  558. return
  559. }
  560. ret.Data = map[string]interface{}{
  561. "name": name,
  562. "file": path.Join("/export/", name),
  563. }
  564. }