export.go 13 KB

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