Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
Vanessa 2023-02-23 13:20:28 +08:00
commit d59c131170
16 changed files with 264 additions and 69 deletions

18
API.md
View File

@ -457,13 +457,12 @@ View API token in <kbd>Settings - About</kbd>, request header: `Authorization: T
* `/api/asset/upload`
* The parameter is an HTTP Multipart form
* `assetsDirPath`: The folder path where the assets are stored. The arguments have the following three cases
* `assetsDirPath`: The folder path where assets are stored, with the data folder as the root path, for example:
* `"/assets/"`: workspace/data/assets/ folder
* `"/assets/sub/"`: workspace/data/assets/sub/ folder
1. `"/assets/"`: Workspace/data/assets folder
2. `"/Test Notebook/assets/"`: Assets folder under `Test Notebook`
3. `"/Test Notebook/foo/assets/"`: Assets folder under foo folder under `Test notebook`
It is recommended to use the first one, which is stored in the workspace assets folder uniformly.
Under normal circumstances, it is recommended to use the first method, which is stored in the assets folder
of the workspace.
* `file[]`: Uploaded file list
* Return value
@ -705,7 +704,7 @@ View API token in <kbd>Settings - About</kbd>, request header: `Authorization: T
}
```
* `id`: ID of the block to be got
* `id`: ID of the block to be got
* Return value
```json
@ -814,8 +813,9 @@ View API token in <kbd>Settings - About</kbd>, request header: `Authorization: T
"path": "F:\\SiYuan\\data\\templates\\foo.md"
}
```
* `id`: The ID of the document where the rendering is called
* `path`: Template file absolute path
* `id`: The ID of the document where the rendering is called
* `path`: Template file absolute path
* Return value
```json

View File

@ -454,13 +454,11 @@
* `/api/asset/upload`
* 参数为 HTTP Multipart 表单
* `assetsDirPath`:资源文件存放的文件夹路径,实参有以下三种情况
* `assetsDirPath`:资源文件存放的文件夹路径,以 data 文件夹作为根路径,比如:
* `"/assets/"`:工作空间/data/assets/ 文件夹
* `"/assets/sub/"`:工作空间/data/assets/sub/ 文件夹
1. `"/assets/"`:工作空间/data/assets 文件夹
2. `"/测试笔记本/assets/"``测试笔记本`下的 assets 文件夹
3. `"/测试笔记本/foo/assets/"``测试笔记本`下 foo 文件夹下的 assets 文件夹
建议用第一种,统一存放到工作空间资源文件夹下。
常规情况下建议用第一种,统一存放到工作空间资源文件夹下。
* `file[]`:上传的文件列表
* 返回值

View File

@ -199,9 +199,10 @@
"exportPDF1": "Landscape page",
"exportPDF2": "Page margins",
"exportPDF3": "Page Scale",
"exportPDF4": "Remove assets directory",
"exportPDF4": "Embed assets",
"exportPDF5": "Keep folded",
"exportPDF6": "Merge subdocuments",
"mergeSubdocs": "Merge subdocuments",
"removeAssetsFolder": "Remove assets directory",
"upload": "Upload",
"reminderTip": "The reminder time cannot be less than the current time",
"wechatTip": "The content block will be sent to the cloud in clear text, and pushed through the WeChat MP template message when it expires",

View File

@ -199,9 +199,10 @@
"exportPDF1": "Página apaisada",
"exportPDF2": "Márgenes de la página",
"exportPDF3": "Escala de la página",
"exportPDF4": "Eliminar directorio de activos",
"exportPDF4": "Activos incrustados",
"exportPDF5": "Mantener doblado",
"exportPDF6": "Fusionar subdocumentos",
"mergeSubdocs": "Fusionar subdocumentos",
"removeAssetsFolder": "Eliminar directorio de activos",
"upload": "Subir",
"reminderTip": "La hora del recordatorio no puede ser inferior a la hora actual",
"wechatTip": "El bloque de contenido se enviará a la nube en texto claro, y se empujará a través del mensaje de plantilla de WeChat MP cuando caduque",

View File

@ -199,9 +199,10 @@
"exportPDF1": "Page paysage",
"exportPDF2": "Marges de page",
"exportPDF3": "Échelle de page",
"exportPDF4": "Supprimer le répertoire des actifs",
"exportPDF4": "Incorporer des ressources",
"exportPDF5": "Garder plié",
"exportPDF6": "Fusionner les sous-documents",
"mergeSubdocs": "Fusionner les sous-documents",
"removeAssetsFolder": "Supprimer le répertoire des actifs",
"upload": "Télécharger",
"reminderTip": "The reminder time cannot be less than the current time",
"wechatTip": "Le bloc de contenu sera envoyé au cloud en texte clair et transmis au message du modèle de compte officiel WeChat à son expiration.",

View File

@ -199,9 +199,10 @@
"exportPDF1": "橫向頁面",
"exportPDF2": "頁面邊距",
"exportPDF3": "頁面縮放",
"exportPDF4": "移除 assets 目錄",
"exportPDF4": "嵌入資源文件",
"exportPDF5": "保持折疊狀態",
"exportPDF6": "合併子文檔",
"mergeSubdocs": "合併子文檔",
"removeAssetsFolder": "移除 assets 目錄",
"upload": "上傳",
"reminderTip": "提醒時間不能小於當前時間",
"wechatTip": "該內容塊將以明文形式發送到雲端,到期時通過微信公眾號模板消息進行推送",

View File

@ -199,9 +199,10 @@
"exportPDF1": "横向页面",
"exportPDF2": "页面边距",
"exportPDF3": "页面缩放",
"exportPDF4": "移除 assets 目录",
"exportPDF4": "嵌入资源文件",
"exportPDF5": "保持折叠状态",
"exportPDF6": "合并子文档",
"mergeSubdocs": "合并子文档",
"removeAssetsFolder": "移除 assets 目录",
"upload": "上传",
"reminderTip": "提醒时间不能小于当前时间",
"wechatTip": "该内容块将以明文形式发送到云端,到期时通过微信公众号模板消息进行推送",

View File

@ -1,7 +1,7 @@
import {hideMessage, showMessage} from "../../dialog/message";
import {Constants} from "../../constants";
/// #if !BROWSER
import {OpenDialogReturnValue, ipcRenderer} from "electron";
import {ipcRenderer, OpenDialogReturnValue} from "electron";
import {app, BrowserWindow, dialog, getCurrentWindow} from "@electron/remote";
import * as fs from "fs";
import * as path from "path";
@ -33,14 +33,14 @@ export const saveExport = (option: { type: string, id: string }) => {
content: `<div class="b3-dialog__content">
<label class="fn__flex b3-label">
<div class="fn__flex-1">
${window.siyuan.languages.exportPDF4}
${window.siyuan.languages.removeAssetsFolder}
</div>
<span class="fn__space"></span>
<input id="removeAssets" class="b3-switch" type="checkbox" ${localData.removeAssets ? "checked" : ""}>
</label>
<label class="fn__flex b3-label">
<div class="fn__flex-1">
${window.siyuan.languages.exportPDF6}
${window.siyuan.languages.mergeSubdocs}
</div>
<span class="fn__space"></span>
<input id="mergeSubdocs" class="b3-switch" type="checkbox" ${localData.mergeSubdocs ? "checked" : ""}>
@ -215,7 +215,7 @@ const renderPDF = (id: string) => {
</label>
<label class="b3-label">
<div>
${window.siyuan.languages.exportPDF6}
${window.siyuan.languages.mergeSubdocs}
</div>
<span class="fn__hr"></span>
<input id="mergeSubdocs" class="b3-switch" type="checkbox" ${localData.mergeSubdocs ? "checked" : ""}>

View File

@ -3,7 +3,7 @@ import {exportLayout, getInstanceById, JSONToLayout, resetLayout, resizeDrag, re
import {hotKey2Electron, setStorageVal, updateHotkeyTip} from "../protyle/util/compatibility";
/// #if !BROWSER
import {dialog, getCurrentWindow} from "@electron/remote";
import {webFrame, ipcRenderer, OpenDialogReturnValue} from "electron";
import {ipcRenderer, OpenDialogReturnValue, webFrame} from "electron";
import * as fs from "fs";
import * as path from "path";
import {afterExport} from "../protyle/export/util";
@ -418,10 +418,11 @@ export const initWindow = () => {
const pdfFilePath = path.join(result.filePaths[0], replaceLocalPath(ipcData.rootTitle) + ".pdf");
fs.writeFileSync(pdfFilePath, pdfData);
window.siyuan.printWin.destroy();
fetchPost("/api/export/addPDFOutline", {
fetchPost("/api/export/processPDF", {
id: ipcData.rootId,
merge: ipcData.mergeSubdocs,
path: pdfFilePath
path: pdfFilePath,
removeAssets: ipcData.removeAssets,
}, () => {
afterExport(pdfFilePath, msgId);
if (ipcData.removeAssets) {

View File

@ -311,7 +311,7 @@ func exportHTML(c *gin.Context) {
}
}
func addPDFOutline(c *gin.Context) {
func processPDF(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
@ -326,7 +326,8 @@ func addPDFOutline(c *gin.Context) {
if nil != arg["merge"] {
merge = arg["merge"].(bool)
}
err := model.AddPDFOutline(id, path, merge)
removeAssets := arg["removeAssets"].(bool)
err := model.ProcessPDF(id, path, merge, removeAssets)
if nil != err {
ret.Code = -1
ret.Msg = err.Error()

View File

@ -233,7 +233,7 @@ func ServeAPI(ginServer *gin.Engine) {
ginServer.Handle("POST", "/api/export/exportPreviewHTML", model.CheckAuth, exportPreviewHTML)
ginServer.Handle("POST", "/api/export/exportMdHTML", model.CheckAuth, exportMdHTML)
ginServer.Handle("POST", "/api/export/exportDocx", model.CheckAuth, exportDocx)
ginServer.Handle("POST", "/api/export/addPDFOutline", model.CheckAuth, addPDFOutline)
ginServer.Handle("POST", "/api/export/processPDF", model.CheckAuth, processPDF)
ginServer.Handle("POST", "/api/export/preview", model.CheckAuth, exportPreview)
ginServer.Handle("POST", "/api/export/exportAsFile", model.CheckAuth, exportAsFile)
ginServer.Handle("POST", "/api/export/exportData", model.CheckAuth, exportData)

View File

@ -7,7 +7,7 @@ require (
github.com/88250/css v0.1.2
github.com/88250/gulu v1.2.3-0.20221117052724-cd06804db798
github.com/88250/lute v1.7.6-0.20230220030205-b0f64d7ba66e
github.com/88250/pdfcpu v0.3.13
github.com/88250/pdfcpu v0.3.14-0.20230223031826-d2ae187e1c38
github.com/88250/vitess-sqlparser v0.0.0-20210205111146-56a2ded2aba1
github.com/ClarkThan/ahocorasick v0.0.0-20230216061320-bccdb98581a3
github.com/ConradIrwin/font v0.0.0-20210318200717-ce8d41cc0732

View File

@ -10,8 +10,8 @@ github.com/88250/gulu v1.2.3-0.20221117052724-cd06804db798 h1:sR/s/Y9wyl79ZRCUER
github.com/88250/gulu v1.2.3-0.20221117052724-cd06804db798/go.mod h1:I1qBzsksFL2ciGSuqDE7R3XW4BUMrfDgOvSXEk7FsAI=
github.com/88250/lute v1.7.6-0.20230220030205-b0f64d7ba66e h1:7UgFzsksh+z6IX2z+BKG3tt1TU7LJNb0zOHDbhLEaUc=
github.com/88250/lute v1.7.6-0.20230220030205-b0f64d7ba66e/go.mod h1:cEoBGi0zArPqAsp0MdG9SKinvH/xxZZWXU7sRx8vHSA=
github.com/88250/pdfcpu v0.3.13 h1:touMWMZkCGalMIbEg9bxYp7rETM+zwb9hXjwhqi4I7Q=
github.com/88250/pdfcpu v0.3.13/go.mod h1:S5YT38L/GCjVjmB4PB84PymA1qfopjEhfhTNQilLpv4=
github.com/88250/pdfcpu v0.3.14-0.20230223031826-d2ae187e1c38 h1:MaFRabDTXOpLBrdP4qkZnjFBIUTu/rk8S6fu7hC6jCY=
github.com/88250/pdfcpu v0.3.14-0.20230223031826-d2ae187e1c38/go.mod h1:S5YT38L/GCjVjmB4PB84PymA1qfopjEhfhTNQilLpv4=
github.com/88250/vitess-sqlparser v0.0.0-20210205111146-56a2ded2aba1 h1:48T899JQDwyyRu9yXHePYlPdHtpJfrJEUGBMH3SMBWY=
github.com/88250/vitess-sqlparser v0.0.0-20210205111146-56a2ded2aba1/go.mod h1:U3pckKQIgxxkmZjV5yXQjHdGxQK0o/vEZeZ6cQsxfHw=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=

View File

@ -654,7 +654,7 @@ func processIFrame(tree *parse.Tree) {
}
}
func AddPDFOutline(id, p string, merge bool) (err error) {
func ProcessPDF(id, p string, merge, removeAssets bool) (err error) {
inFile := p
links, err := api.ListToCLinks(inFile)
if nil != err {
@ -764,36 +764,220 @@ func AddPDFOutline(id, p string, merge bool) (err error) {
}
}
//var assetAbsPaths []string
//for _, dest := range assetDests {
// absPath, _ := GetAssetAbsPath(dest)
// if "" != absPath {
// assetAbsPaths = append(assetAbsPaths, absPath)
// }
//}
//
//if 0 < len(assetAbsPaths) {
// outFile := inFile + ".tmp"
// err = api.AddAttachmentsFile(inFile, outFile, assetAbsPaths, false, nil)
// if nil != err {
// logging.LogErrorf("add attachment failed: %s", err)
// return
// }
//
// err = os.Rename(outFile, inFile)
// if nil != err {
// return
// }
//}
//
//assetLinks, err := api.ListAssetLinks(inFile)
//if nil == err {
// logging.LogInfof("pdf annotation: %+v", assetLinks)
//}
var assetAbsPaths []string
for _, dest := range assetDests {
absPath, _ := GetAssetAbsPath(dest)
if "" != absPath {
assetAbsPaths = append(assetAbsPaths, absPath)
}
}
pdfCtx, ctxErr := api.ReadContextFile(inFile)
if nil != ctxErr {
logging.LogErrorf("read pdf context failed: %s", ctxErr)
return
}
if 0 < len(assetAbsPaths) {
assetLinks, otherLinks, listErr := api.ListLinks(inFile)
if nil != listErr {
logging.LogErrorf("list asset links failed: %s", listErr)
return
}
if _, removeErr := pdfCtx.RemoveAnnotations(nil, nil, nil, false); nil != removeErr {
logging.LogWarnf("remove annotations failed: %s", removeErr)
}
linkMap := map[int][]pdfcpu.AnnotationRenderer{}
for _, link := range otherLinks {
link.URI, _ = url.PathUnescape(link.URI)
if 1 > len(linkMap[link.Page]) {
linkMap[link.Page] = []pdfcpu.AnnotationRenderer{link}
} else {
linkMap[link.Page] = append(linkMap[link.Page], link)
}
}
attachmentMap := map[int][]*pdfcpu.IndirectRef{}
now := pdfcpu.StringLiteral(pdfcpu.DateString(time.Now()))
for _, link := range assetLinks {
link.URI = strings.ReplaceAll(link.URI, "http://127.0.0.1:6806/export/temp/", "")
link.URI, _ = url.PathUnescape(link.URI)
if !removeAssets {
// 不移除资源文件夹的话将超链接指向资源文件夹
if 1 > len(linkMap[link.Page]) {
linkMap[link.Page] = []pdfcpu.AnnotationRenderer{link}
} else {
linkMap[link.Page] = append(linkMap[link.Page], link)
}
continue
}
// 移除资源文件夹的话使用内嵌附件
absPath, getErr := GetAssetAbsPath(link.URI)
if nil != getErr {
continue
}
ir, newErr := pdfCtx.XRefTable.NewEmbeddedFileStreamDict(absPath)
if nil != newErr {
logging.LogWarnf("new embedded file stream dict failed: %s", newErr)
continue
}
fn := filepath.Base(absPath)
fileSpecDict, newErr := pdfCtx.XRefTable.NewFileSpecDict(fn, pdfcpu.EncodeUTF16String(fn), "attached by SiYuan", *ir)
if nil != newErr {
logging.LogWarnf("new file spec dict failed: %s", newErr)
continue
}
ir, indErr := pdfCtx.XRefTable.IndRefForNewObject(fileSpecDict)
if nil != indErr {
logging.LogWarnf("ind ref for new object failed: %s", indErr)
continue
}
lx := link.Rect.LL.X + link.Rect.Width()
ly := link.Rect.LL.Y + link.Rect.Height()/2
ux := lx + link.Rect.Height()/2
uy := ly + link.Rect.Height()/2
d := pdfcpu.Dict(
map[string]pdfcpu.Object{
"Type": pdfcpu.Name("Annot"),
"Subtype": pdfcpu.Name("FileAttachment"),
"Contents": pdfcpu.StringLiteral(""),
"Rect": pdfcpu.Rect(lx, ly, ux, uy).Array(),
"P": link.P,
"M": now,
"F": pdfcpu.Integer(0),
"Border": pdfcpu.NewIntegerArray(0, 0, 1),
"C": pdfcpu.NewNumberArray(0.5, 0.0, 0.5),
"CA": pdfcpu.Float(0.95),
"CreationDate": now,
"Name": pdfcpu.Name("FileAttachment"),
"FS": *ir,
"NM": pdfcpu.StringLiteral(""),
},
)
ann, indErr := pdfCtx.XRefTable.IndRefForNewObject(d)
if nil != indErr {
logging.LogWarnf("ind ref for new object failed: %s", indErr)
continue
}
pageDictIndRef, pageErr := pdfCtx.PageDictIndRef(link.Page)
if nil != pageErr {
logging.LogWarnf("page dict ind ref failed: %s", pageErr)
continue
}
d, defErr := pdfCtx.DereferenceDict(*pageDictIndRef)
if nil != defErr {
logging.LogWarnf("dereference dict failed: %s", defErr)
continue
}
if 1 > len(attachmentMap[link.Page]) {
attachmentMap[link.Page] = []*pdfcpu.IndirectRef{ann}
} else {
attachmentMap[link.Page] = append(attachmentMap[link.Page], ann)
}
}
if 0 < len(linkMap) {
if _, addErr := pdfCtx.AddAnnotationsMap(linkMap, false); nil != addErr {
logging.LogErrorf("add annotations map failed: %s", addErr)
}
}
// 添加附件注解指向内嵌的附件
for page, anns := range attachmentMap {
pageDictIndRef, pageErr := pdfCtx.PageDictIndRef(page)
if nil != pageErr {
logging.LogWarnf("page dict ind ref failed: %s", pageErr)
continue
}
pageDict, defErr := pdfCtx.DereferenceDict(*pageDictIndRef)
if nil != defErr {
logging.LogWarnf("dereference dict failed: %s", defErr)
continue
}
array := pdfcpu.Array{}
for _, ann := range anns {
array = append(array, *ann)
}
obj, found := pageDict.Find("Annots")
if !found {
pageDict.Insert("Annots", array)
pdfCtx.EnsureVersionForWriting()
continue
}
ir, ok := obj.(pdfcpu.IndirectRef)
if !ok {
pageDict.Update("Annots", append(obj.(pdfcpu.Array), array...))
pdfCtx.EnsureVersionForWriting()
continue
}
// Annots array is an IndirectReference.
o, err := pdfCtx.Dereference(ir)
if err != nil || o == nil {
continue
}
annots, _ := o.(pdfcpu.Array)
entry, ok := pdfCtx.FindTableEntryForIndRef(&ir)
if !ok {
continue
}
entry.Object = append(annots, array...)
pdfCtx.EnsureVersionForWriting()
}
}
pdfcpu.VersionStr = "SiYuan v" + util.Ver
if writeErr := api.WriteContextFile(pdfCtx, inFile); nil != writeErr {
logging.LogErrorf("write pdf context failed: %s", writeErr)
return
}
return
}
func annotRect(i int, w, h, d, l float64) *pdfcpu.Rectangle {
// d..distance between annotation rectangles
// l..side length of rectangle
// max number of rectangles fitting into w
xmax := int((w - d) / (l + d))
// max number of rectangles fitting into h
ymax := int((h - d) / (l + d))
col := float64(i % xmax)
row := float64(i / xmax % ymax)
llx := d + col*(l+d)
lly := d + row*(l+d)
urx := llx + l
ury := lly + l
return pdfcpu.Rect(llx, lly, urx, ury)
}
func ExportStdMarkdown(id string) string {
tree, err := loadTreeByBlockID(id)
if nil != err {

View File

@ -674,6 +674,10 @@ func GetDecks() (decks []*riff.Deck) {
if 1 > len(decks) {
decks = []*riff.Deck{}
}
sort.Slice(decks, func(i, j int) bool {
return decks[i].Updated > decks[j].Updated
})
return
}

View File

@ -130,9 +130,11 @@ func Upload(c *gin.Context) {
docDirLocalPath := filepath.Join(util.DataDir, bt.BoxID, path.Dir(bt.Path))
assetsDirPath = getAssetsDir(filepath.Join(util.DataDir, bt.BoxID), docDirLocalPath)
}
relAssetsDirPath := "assets"
if nil != form.Value["assetsDirPath"] {
assetsDirPath = form.Value["assetsDirPath"][0]
assetsDirPath = filepath.Join(util.DataDir, assetsDirPath)
relAssetsDirPath = form.Value["assetsDirPath"][0]
assetsDirPath = filepath.Join(util.DataDir, relAssetsDirPath)
}
if !gulu.File.IsExist(assetsDirPath) {
if err = os.MkdirAll(assetsDirPath, 0755); nil != err {
@ -187,7 +189,7 @@ func Upload(c *gin.Context) {
break
}
f.Close()
succMap[baseName] = "assets/" + fName
succMap[baseName] = path.Join(relAssetsDirPath, fName)
}
}