Something went wrong. Try again.
A privacy-first, self-hosted, fully open source personal knowledge management software, written in typescript and golang. (PERSONAL FORK)
Something went wrong. Try again.
Go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854// SiYuan - Refactor your thinking// Copyright (c) 2020-present, b3log.org//// This program is free software: you can redistribute it and/or modify// it under the terms of the GNU Affero General Public License as published by// the Free Software Foundation, either version 3 of the License, or// (at your option) any later version.//// This program is distributed in the hope that it will be useful,// but WITHOUT ANY WARRANTY; without even the implied warranty of// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the// GNU Affero General Public License for more details.//// You should have received a copy of the GNU Affero General Public License// along with this program. If not, see <https://www.gnu.org/licenses/>.
package api
import ( "io" "mime" "net/http" "net/url" "os" "path" "path/filepath" "strings" "time"
"github.com/88250/gulu" "github.com/88250/lute/parse" "github.com/gin-gonic/gin" "github.com/mssola/useragent" "github.com/siyuan-note/filelock" "github.com/siyuan-note/logging" "github.com/siyuan-note/siyuan/kernel/model" "github.com/siyuan-note/siyuan/kernel/util")
func exportCodeBlock(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret) if !ok { return }
id := arg["id"].(string) filePath, err := model.ExportCodeBlock(id) if err != nil { ret.Code = 1 ret.Msg = err.Error() ret.Data = map[string]interface{}{"closeTimeout": 7000} return }
ret.Data = map[string]interface{}{ "path": filePath, }}
func exportAttributeView(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret) if !ok { return }
avID := arg["id"].(string) blockID := arg["blockID"].(string) zipPath, err := model.ExportAv2CSV(avID, blockID) if err != nil { ret.Code = 1 ret.Msg = err.Error() ret.Data = map[string]interface{}{"closeTimeout": 7000} return }
ret.Data = map[string]interface{}{ "zip": zipPath, }}
func exportEPUB(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret) if !ok { return }
id := arg["id"].(string) name, zipPath := model.ExportPandocConvertZip([]string{id}, "epub", ".epub") ret.Data = map[string]interface{}{ "name": name, "zip": zipPath, }}
func exportRTF(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret) if !ok { return }
id := arg["id"].(string) name, zipPath := model.ExportPandocConvertZip([]string{id}, "rtf", ".rtf") ret.Data = map[string]interface{}{ "name": name, "zip": zipPath, }}
func exportODT(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret) if !ok { return }
id := arg["id"].(string) name, zipPath := model.ExportPandocConvertZip([]string{id}, "odt", ".odt") ret.Data = map[string]interface{}{ "name": name, "zip": zipPath, }}
func exportMediaWiki(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret) if !ok { return }
id := arg["id"].(string) name, zipPath := model.ExportPandocConvertZip([]string{id}, "mediawiki", ".wiki") ret.Data = map[string]interface{}{ "name": name, "zip": zipPath, }}
func exportOrgMode(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret) if !ok { return }
id := arg["id"].(string) name, zipPath := model.ExportPandocConvertZip([]string{id}, "org", ".org") ret.Data = map[string]interface{}{ "name": name, "zip": zipPath, }}
func exportOPML(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret) if !ok { return }
id := arg["id"].(string) name, zipPath := model.ExportPandocConvertZip([]string{id}, "opml", ".opml") ret.Data = map[string]interface{}{ "name": name, "zip": zipPath, }}
func exportTextile(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret) if !ok { return }
id := arg["id"].(string) name, zipPath := model.ExportPandocConvertZip([]string{id}, "textile", ".textile") ret.Data = map[string]interface{}{ "name": name, "zip": zipPath, }}
func exportAsciiDoc(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret) if !ok { return }
id := arg["id"].(string) name, zipPath := model.ExportPandocConvertZip([]string{id}, "asciidoc", ".adoc") ret.Data = map[string]interface{}{ "name": name, "zip": zipPath, }}
func exportReStructuredText(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret) if !ok { return }
id := arg["id"].(string) name, zipPath := model.ExportPandocConvertZip([]string{id}, "rst", ".rst") ret.Data = map[string]interface{}{ "name": name, "zip": zipPath, }}
func export2Liandi(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret) if !ok { return }
id := arg["id"].(string) err := model.Export2Liandi(id) if err != nil { ret.Code = -1 ret.Msg = err.Error() return }}
func exportDataInFolder(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret) if !ok { return }
exportFolder := arg["folder"].(string) name, err := model.ExportDataInFolder(exportFolder) if err != nil { ret.Code = -1 ret.Msg = err.Error() ret.Data = map[string]interface{}{"closeTimeout": 7000} return } ret.Data = map[string]interface{}{ "name": name, }}
func exportData(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret)
zipPath, err := model.ExportData() if err != nil { ret.Code = 1 ret.Msg = err.Error() ret.Data = map[string]interface{}{"closeTimeout": 7000} return } ret.Data = map[string]interface{}{ "zip": zipPath, }}
func exportResources(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret) if !ok { return }
var name string if nil != arg["name"] { name = util.TruncateLenFileName(arg["name"].(string)) } if name == "" { name = time.Now().Format("export-2006-01-02_15-04-05") // 生成的 *.zip 文件主文件名 }
if nil == arg["paths"] { ret.Code = 1 ret.Data = "" ret.Msg = "paths is required" return }
var resourcePaths []string // 文件/文件夹在工作空间中的路径 for _, resourcePath := range arg["paths"].([]interface{}) { resourcePaths = append(resourcePaths, resourcePath.(string)) }
zipFilePath, err := model.ExportResources(resourcePaths, name) if err != nil { ret.Code = 1 ret.Msg = err.Error() ret.Data = map[string]interface{}{"closeTimeout": 7000} return } ret.Data = map[string]interface{}{ "path": zipFilePath, // 相对于工作空间目录的路径 }}
func exportNotebookMd(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret) if !ok { return }
notebook := arg["notebook"].(string) zipPath := model.ExportNotebookMarkdown(notebook) ret.Data = map[string]interface{}{ "name": path.Base(zipPath), "zip": zipPath, }}
func exportMds(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret) if !ok { return }
idsArg := arg["ids"].([]interface{}) var ids []string for _, id := range idsArg { ids = append(ids, id.(string)) }
name, zipPath := model.ExportPandocConvertZip(ids, "", ".md") ret.Data = map[string]interface{}{ "name": name, "zip": zipPath, }}
func exportMd(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret) if !ok { return }
id := arg["id"].(string) name, zipPath := model.ExportPandocConvertZip([]string{id}, "", ".md") ret.Data = map[string]interface{}{ "name": name, "zip": zipPath, }}
func exportNotebookSY(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret) if !ok { return }
id := arg["id"].(string) zipPath := model.ExportNotebookSY(id) ret.Data = map[string]interface{}{ "zip": zipPath, }}
func exportSYs(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret) if !ok { return }
idsArg := arg["ids"].([]interface{}) var ids []string for _, id := range idsArg { ids = append(ids, id.(string)) }
zipPath := model.ExportSYs(ids) ret.Data = map[string]interface{}{ "zip": zipPath, }}
func exportSY(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret) if !ok { return }
id := arg["id"].(string) zipPath := model.ExportSYs([]string{id}) ret.Data = map[string]interface{}{ "zip": zipPath, }}
func exportMdContent(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret) if !ok { return }
id := arg["id"].(string) if util.InvalidIDPattern(id, ret) { return }
refMode := model.Conf.Export.BlockRefMode if nil != arg["refMode"] { refMode = int(arg["refMode"].(float64)) }
embedMode := model.Conf.Export.BlockEmbedMode if nil != arg["embedMode"] { embedMode = int(arg["embedMode"].(float64)) }
yfm := true if nil != arg["yfm"] { yfm = arg["yfm"].(bool) }
fillCSSVar := false if nil != arg["fillCSSVar"] { fillCSSVar = arg["fillCSSVar"].(bool) }
adjustHeadingLevel := false if nil != arg["adjustHeadingLevel"] { adjustHeadingLevel = arg["adjustHeadingLevel"].(bool) }
imgTag := false if nil != arg["imgTag"] { imgTag = arg["imgTag"].(bool) }
addTitle := model.Conf.Export.AddTitle if nil != arg["addTitle"] { if arg["addTitle"].(bool) { addTitle = true } else { addTitle = false } }
hPath, content := model.ExportMarkdownContent(id, refMode, embedMode, yfm, fillCSSVar, adjustHeadingLevel, imgTag, addTitle) ret.Data = map[string]interface{}{ "hPath": hPath, "content": content, }}
func exportDocx(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret) if !ok { return }
id := arg["id"].(string) savePath := arg["savePath"].(string) removeAssets := arg["removeAssets"].(bool) merge := false if nil != arg["merge"] { merge = arg["merge"].(bool) }
fullPath, err := model.ExportDocx(id, savePath, removeAssets, merge) if err != nil { ret.Code = 1 ret.Msg = err.Error() ret.Data = map[string]interface{}{"closeTimeout": 7000} return } ret.Data = map[string]interface{}{ "path": fullPath, }}
func exportMdHTML(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret) if !ok { return }
id := arg["id"].(string) savePath := arg["savePath"].(string)
savePath = strings.TrimSpace(savePath) if savePath == "" { folderName := "htmlmd-" + id + "-" + util.CurrentTimeSecondsStr() tmpDir := filepath.Join(util.TempDir, "export", folderName) name, content := model.ExportMarkdownHTML(id, tmpDir, false, false) ret.Data = map[string]interface{}{ "id": id, "name": name, "content": content, "folder": folderName, } return }
name, content := model.ExportMarkdownHTML(id, savePath, false, false) ret.Data = map[string]interface{}{ "id": id, "name": name, "content": content, }}
func exportTempContent(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret) if !ok { return }
content := arg["content"].(string) tmpExport := filepath.Join(util.TempDir, "export", "temp") if err := os.MkdirAll(tmpExport, 0755); err != nil { ret.Code = 1 ret.Msg = err.Error() ret.Data = map[string]interface{}{"closeTimeout": 7000} return } p := filepath.Join(tmpExport, gulu.Rand.String(7)) if err := os.WriteFile(p, []byte(content), 0644); err != nil { ret.Code = 1 ret.Msg = err.Error() ret.Data = map[string]interface{}{"closeTimeout": 7000} return } url := path.Join("/export/temp/", filepath.Base(p)) ret.Data = map[string]interface{}{ "url": "http://" + util.LocalHost + ":" + util.ServerPort + url, }}
func exportBrowserHTML(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret) if !ok { return }
folder := arg["folder"].(string) htmlContent := arg["html"].(string) name := arg["name"].(string)
tmpDir := filepath.Join(util.TempDir, "export", folder)
htmlPath := filepath.Join(tmpDir, "index.html") if err := filelock.WriteFile(htmlPath, []byte(htmlContent)); err != nil { ret.Code = -1 ret.Msg = err.Error() ret.Data = nil return }
zipFileName := util.FilterFileName(name) + ".zip" zipPath := filepath.Join(util.TempDir, "export", zipFileName) zip, err := gulu.Zip.Create(zipPath) if err != nil { ret.Code = -1 ret.Msg = err.Error() ret.Data = nil return }
err = zip.AddDirectory("", tmpDir, func(string) {}) if err != nil { ret.Code = -1 ret.Msg = err.Error() ret.Data = nil return }
if err = zip.Close(); err != nil { ret.Code = -1 ret.Msg = err.Error() ret.Data = nil return }
os.RemoveAll(tmpDir)
zipURL := "/export/" + url.PathEscape(filepath.Base(zipPath)) ret.Data = map[string]interface{}{ "zip": zipURL, }}
func exportPreviewHTML(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret) if !ok { return }
id := arg["id"].(string) keepFold := false if nil != arg["keepFold"] { keepFold = arg["keepFold"].(bool) } merge := false if nil != arg["merge"] { merge = arg["merge"].(bool) } image := false if nil != arg["image"] { image = arg["image"].(bool) } name, content, node := model.ExportHTML(id, "", true, image, keepFold, merge) // 导出 PDF 预览时点击块引转换后的脚注跳转不正确 https://github.com/siyuan-note/siyuan/issues/5894 content = strings.ReplaceAll(content, "http://"+util.LocalHost+":"+util.ServerPort+"/#", "#")
// Add `data-doc-type` and attribute when exporting image and PDF https://github.com/siyuan-note/siyuan/issues/9497 attrs := map[string]string{} var typ string if nil != node { attrs = parse.IAL2Map(node.KramdownIAL) typ = node.Type.String() }
ret.Data = map[string]interface{}{ "id": id, "name": name, "content": content, "attrs": attrs, "type": typ, }}
func exportHTML(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret) if !ok { return }
id := arg["id"].(string) pdf := arg["pdf"].(bool) savePath := arg["savePath"].(string) keepFold := false if nil != arg["keepFold"] { keepFold = arg["keepFold"].(bool) } merge := false if nil != arg["merge"] { merge = arg["merge"].(bool) }
savePath = strings.TrimSpace(savePath) if savePath == "" { folderName := "html-" + id + "-" + util.CurrentTimeSecondsStr() tmpDir := filepath.Join(util.TempDir, "export", folderName) name, content, _ := model.ExportHTML(id, tmpDir, pdf, false, keepFold, merge) ret.Data = map[string]interface{}{ "id": id, "name": name, "content": content, "folder": folderName, } return }
name, content, _ := model.ExportHTML(id, savePath, pdf, false, keepFold, merge) ret.Data = map[string]interface{}{ "id": id, "name": name, "content": content, }}
func processPDF(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret) if !ok { return }
id := arg["id"].(string) path := arg["path"].(string) merge := false if nil != arg["merge"] { merge = arg["merge"].(bool) } removeAssets := arg["removeAssets"].(bool) watermark := arg["watermark"].(bool) err := model.ProcessPDF(id, path, merge, removeAssets, watermark) if err != nil { ret.Code = -1 ret.Msg = err.Error() return }}
func exportPreview(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret)
arg, ok := util.JsonArg(c, ret) if !ok { return }
id := arg["id"].(string)
userAgentStr := c.GetHeader("User-Agent") fillCSSVar := true if userAgentStr != "" { ua := useragent.New(userAgentStr) name, _ := ua.Browser() // Chrome、Edge、SiYuan 桌面端不需要替换 CSS 变量 if !ua.Mobile() && (name == "Chrome" || name == "Edge" || strings.Contains(userAgentStr, "Electron") || strings.Contains(userAgentStr, "SiYuan/")) { fillCSSVar = false } }
stdHTML := model.ExportPreview(id, fillCSSVar) ret.Data = map[string]interface{}{ "html": stdHTML, "fillCSSVar": fillCSSVar, }}
func exportAsFile(c *gin.Context) { ret := gulu.Ret.NewResult() defer c.JSON(http.StatusOK, ret)
form, err := c.MultipartForm() if err != nil { logging.LogErrorf("export as file failed: %s", err) ret.Code = -1 ret.Msg = err.Error() return }
file := form.File["file"][0] reader, err := file.Open() if err != nil { logging.LogErrorf("export as file failed: %s", err) ret.Code = -1 ret.Msg = err.Error() return } defer reader.Close()
data, err := io.ReadAll(reader) if err != nil { logging.LogErrorf("export as file failed: %s", err) ret.Code = -1 ret.Msg = err.Error() return }
name := "file-" + file.Filename typ := form.Value["type"][0] exts, _ := mime.ExtensionsByType(typ) if 0 < len(exts) && filepath.Ext(name) != exts[0] { name += exts[0] } name = util.FilterFileName(name) name = strings.ReplaceAll(name, "#", "_") tmpDir := filepath.Join(util.TempDir, "export") if err = os.MkdirAll(tmpDir, 0755); err != nil { logging.LogErrorf("export as file failed: %s", err) ret.Code = -1 ret.Msg = err.Error() return }
tmp := filepath.Join(tmpDir, name) err = os.WriteFile(tmp, data, 0644) if err != nil { logging.LogErrorf("export as file failed: %s", err) ret.Code = -1 ret.Msg = err.Error() return }
ret.Data = map[string]interface{}{ "file": path.Join("/export/", name), }}