export.go 13 KB

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