mirror of
https://github.com/siyuan-note/siyuan.git
synced 2025-09-22 08:30:42 +02:00
🎨 导入 Markdown 时将 Base64 编码的图片转换为文件 Fix https://github.com/siyuan-note/siyuan/issues/6671
This commit is contained in:
parent
5f5d70740e
commit
31be236557
2 changed files with 108 additions and 7 deletions
|
@ -30,6 +30,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/88250/gulu"
|
"github.com/88250/gulu"
|
||||||
|
"github.com/88250/lute"
|
||||||
"github.com/88250/lute/ast"
|
"github.com/88250/lute/ast"
|
||||||
"github.com/88250/lute/parse"
|
"github.com/88250/lute/parse"
|
||||||
"github.com/dustin/go-humanize"
|
"github.com/dustin/go-humanize"
|
||||||
|
@ -439,10 +440,30 @@ func moveTree(tree *parse.Tree) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseStdMd(markdown []byte) (ret *parse.Tree) {
|
||||||
|
luteEngine := lute.New()
|
||||||
|
luteEngine.SetFootnotes(false)
|
||||||
|
luteEngine.SetToC(false)
|
||||||
|
luteEngine.SetIndentCodeBlock(false)
|
||||||
|
luteEngine.SetAutoSpace(false)
|
||||||
|
luteEngine.SetHeadingID(false)
|
||||||
|
luteEngine.SetSetext(false)
|
||||||
|
luteEngine.SetYamlFrontMatter(false)
|
||||||
|
luteEngine.SetLinkRef(false)
|
||||||
|
ret = parse.Parse("", markdown, luteEngine.ParseOptions)
|
||||||
|
genTreeID(ret)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func parseKTree(kramdown []byte) (ret *parse.Tree) {
|
func parseKTree(kramdown []byte) (ret *parse.Tree) {
|
||||||
luteEngine := NewLute()
|
luteEngine := NewLute()
|
||||||
ret = parse.Parse("", kramdown, luteEngine.ParseOptions)
|
ret = parse.Parse("", kramdown, luteEngine.ParseOptions)
|
||||||
ast.Walk(ret.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
|
genTreeID(ret)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func genTreeID(tree *parse.Tree) {
|
||||||
|
ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
|
||||||
if !entering {
|
if !entering {
|
||||||
return ast.WalkContinue
|
return ast.WalkContinue
|
||||||
}
|
}
|
||||||
|
@ -475,7 +496,7 @@ func parseKTree(kramdown []byte) (ret *parse.Tree) {
|
||||||
}
|
}
|
||||||
return ast.WalkContinue
|
return ast.WalkContinue
|
||||||
})
|
})
|
||||||
ret.Root.KramdownIAL = parse.Tokens2IAL(ret.Root.LastChild.Tokens)
|
tree.Root.KramdownIAL = parse.Tokens2IAL(tree.Root.LastChild.Tokens)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,9 +18,13 @@ package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"image/jpeg"
|
||||||
|
"image/png"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
@ -403,6 +407,9 @@ func ImportFromLocalPath(boxID, localPath string, toPath string) (err error) {
|
||||||
}
|
}
|
||||||
boxLocalPath = filepath.Join(util.DataDir, boxID)
|
boxLocalPath = filepath.Join(util.DataDir, boxID)
|
||||||
|
|
||||||
|
base64TmpDir := filepath.Join(util.TempDir, "base64")
|
||||||
|
os.MkdirAll(base64TmpDir, 0755)
|
||||||
|
|
||||||
luteEngine := NewLute()
|
luteEngine := NewLute()
|
||||||
if gulu.File.IsDir(localPath) {
|
if gulu.File.IsDir(localPath) {
|
||||||
// 收集所有资源文件
|
// 收集所有资源文件
|
||||||
|
@ -419,8 +426,7 @@ func ImportFromLocalPath(boxID, localPath string, toPath string) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.HasSuffix(info.Name(), ".md") && !strings.HasSuffix(info.Name(), ".markdown") {
|
if !strings.HasSuffix(info.Name(), ".md") && !strings.HasSuffix(info.Name(), ".markdown") {
|
||||||
dest := currentPath
|
assets[currentPath] = currentPath
|
||||||
assets[dest] = currentPath
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -480,7 +486,7 @@ func ImportFromLocalPath(boxID, localPath string, toPath string) (err error) {
|
||||||
return io.EOF
|
return io.EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
tree = parseKTree(data)
|
tree = parseStdMd(data)
|
||||||
if nil == tree {
|
if nil == tree {
|
||||||
logging.LogErrorf("parse tree [%s] failed", currentPath)
|
logging.LogErrorf("parse tree [%s] failed", currentPath)
|
||||||
return nil
|
return nil
|
||||||
|
@ -507,6 +513,11 @@ func ImportFromLocalPath(boxID, localPath string, toPath string) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
dest := n.TokensStr()
|
dest := n.TokensStr()
|
||||||
|
if strings.HasPrefix(dest, "data:image") && strings.Contains(dest, ";base64,") {
|
||||||
|
processBase64Img(n, dest, base64TmpDir, assetDirPath, err)
|
||||||
|
return ast.WalkContinue
|
||||||
|
}
|
||||||
|
|
||||||
if !util.IsRelativePath(dest) || "" == dest {
|
if !util.IsRelativePath(dest) || "" == dest {
|
||||||
return ast.WalkContinue
|
return ast.WalkContinue
|
||||||
}
|
}
|
||||||
|
@ -570,7 +581,7 @@ func ImportFromLocalPath(boxID, localPath string, toPath string) (err error) {
|
||||||
if nil != err {
|
if nil != err {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
tree := parseKTree(data)
|
tree := parseStdMd(data)
|
||||||
if nil == tree {
|
if nil == tree {
|
||||||
msg := fmt.Sprintf("parse tree [%s] failed", localPath)
|
msg := fmt.Sprintf("parse tree [%s] failed", localPath)
|
||||||
logging.LogErrorf(msg)
|
logging.LogErrorf(msg)
|
||||||
|
@ -595,6 +606,11 @@ func ImportFromLocalPath(boxID, localPath string, toPath string) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
dest := n.TokensStr()
|
dest := n.TokensStr()
|
||||||
|
if strings.HasPrefix(dest, "data:image") && strings.Contains(dest, ";base64,") {
|
||||||
|
processBase64Img(n, dest, base64TmpDir, assetDirPath, err)
|
||||||
|
return ast.WalkContinue
|
||||||
|
}
|
||||||
|
|
||||||
if !util.IsRelativePath(dest) {
|
if !util.IsRelativePath(dest) {
|
||||||
return ast.WalkContinue
|
return ast.WalkContinue
|
||||||
}
|
}
|
||||||
|
@ -641,6 +657,71 @@ func ImportFromLocalPath(boxID, localPath string, toPath string) (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func processBase64Img(n *ast.Node, dest string, base64TmpDir string, assetDirPath string, err error) {
|
||||||
|
sep := strings.Index(dest, ";base64,")
|
||||||
|
var decodeErr error
|
||||||
|
unbased, decodeErr := base64.StdEncoding.DecodeString(dest[sep+8:])
|
||||||
|
if nil != decodeErr {
|
||||||
|
logging.LogErrorf("decode base64 image failed: %s", decodeErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dataReader := bytes.NewReader(unbased)
|
||||||
|
var img image.Image
|
||||||
|
var ext string
|
||||||
|
typ := dest[5:sep]
|
||||||
|
switch typ {
|
||||||
|
case "image/png":
|
||||||
|
img, decodeErr = png.Decode(dataReader)
|
||||||
|
ext = ".png"
|
||||||
|
case "image/jpeg":
|
||||||
|
img, decodeErr = jpeg.Decode(dataReader)
|
||||||
|
ext = ".jpg"
|
||||||
|
default:
|
||||||
|
logging.LogWarnf("unsupported base64 image type [%s]", typ)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if nil != decodeErr {
|
||||||
|
logging.LogErrorf("decode base64 image failed: %s", decodeErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
name := "image" + ext
|
||||||
|
alt := n.Parent.ChildByType(ast.NodeLinkText)
|
||||||
|
if nil != alt {
|
||||||
|
name = alt.TokensStr() + ext
|
||||||
|
}
|
||||||
|
name = util.FilterFileName(name)
|
||||||
|
name = util.AssetName(name)
|
||||||
|
|
||||||
|
tmp := filepath.Join(base64TmpDir, name)
|
||||||
|
tmpFile, openErr := os.OpenFile(tmp, os.O_RDWR|os.O_CREATE, 0644)
|
||||||
|
if nil != openErr {
|
||||||
|
logging.LogErrorf("open temp file [%s] failed: %s", tmp, openErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var encodeErr error
|
||||||
|
switch typ {
|
||||||
|
case "image/png":
|
||||||
|
encodeErr = png.Encode(tmpFile, img)
|
||||||
|
case "image/jpeg":
|
||||||
|
encodeErr = jpeg.Encode(tmpFile, img, &jpeg.Options{Quality: 100})
|
||||||
|
}
|
||||||
|
if nil != encodeErr {
|
||||||
|
logging.LogErrorf("encode base64 image failed: %s", encodeErr)
|
||||||
|
tmpFile.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tmpFile.Close()
|
||||||
|
|
||||||
|
assetTargetPath := filepath.Join(assetDirPath, name)
|
||||||
|
if err = filelock.Copy(tmp, assetTargetPath); nil != err {
|
||||||
|
logging.LogErrorf("copy asset from [%s] to [%s] failed: %s", tmp, assetTargetPath, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
n.Tokens = []byte("assets/" + name)
|
||||||
|
}
|
||||||
|
|
||||||
func imgHtmlBlock2InlineImg(tree *parse.Tree) {
|
func imgHtmlBlock2InlineImg(tree *parse.Tree) {
|
||||||
imgHtmlBlocks := map[*ast.Node]*html.Node{}
|
imgHtmlBlocks := map[*ast.Node]*html.Node{}
|
||||||
ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
|
ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
|
||||||
|
@ -691,7 +772,6 @@ func imgHtmlBlock2InlineImg(tree *parse.Tree) {
|
||||||
n.InsertBefore(p)
|
n.InsertBefore(p)
|
||||||
n.Unlink()
|
n.Unlink()
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue