diff --git a/API.md b/API.md index 4d15bc941..04c663326 100644 --- a/API.md +++ b/API.md @@ -35,10 +35,13 @@ * [Templates](#Templates) * [Render a template](#Render-a-template) * [File](#File) - * [Get File](#Get-file) - * [Put File](#Put-file) + * [Get file](#Get-file) + * [Put file](#Put-file) * [Export](#Export) * [Export Markdown](#Export-Markdown) +* [Notification](#Notification) + * [Push message](#Push-message) + * [Push error message](#Push-error-message) * [System](#System) * [Get boot progress](#Get-boot-progress) * [Get system version](#Get-system-version) @@ -846,6 +849,60 @@ View API token in Settings - About, request header: `Authorization: T * `hPath`: human-readable path * `content`: Markdown content +## Notification + +### Push message + +* `/api/notification/pushMsg` +* Parameters + + ```json + { + "msg": "test", + "timeout": 7000 + } + ``` + * `timeout`: The duration of the message display in milliseconds. This field can be omitted, the default is 7000 + milliseconds +* Return value + + ```json + { + "code": 0, + "msg": "", + "data": { + "id": "62jtmqi" + } + } + ``` + * `id`: Message ID + +### Push error message + +* `/api/notification/pushErrMsg` +* Parameters + + ```json + { + "msg": "test", + "timeout": 7000 + } + ``` + * `timeout`: The duration of the message display in milliseconds. This field can be omitted, the default is 7000 + milliseconds +* Return value + + ```json + { + "code": 0, + "msg": "", + "data": { + "id": "qc9znut" + } + } + ``` + * `id`:Message ID + ## System ### Get boot progress diff --git a/API_zh_CN.md b/API_zh_CN.md index 89f36e1f3..bbe29299b 100644 --- a/API_zh_CN.md +++ b/API_zh_CN.md @@ -34,11 +34,14 @@ * [执行 SQL 查询](#执行-SQL-查询) * [模板](#模板) * [渲染模板](#渲染模板) -* [导出](#导出) - * [导出 Markdown 文本](#导出-markdown-文本) * [文件](#文件) * [获取文件](#获取文件) * [写入文件](#写入文件) +* [导出](#导出) + * [导出 Markdown 文本](#导出-markdown-文本) +* [通知](#通知) + * [推送消息](#推送消息) + * [推送报错消息](#推送报错消息) * [系统](#系统) * [获取启动进度](#获取启动进度) * [获取系统版本](#获取系统版本) @@ -799,7 +802,7 @@ * 参数为 HTTP Multipart 表单 * `path`:工作空间路径下的文件路径 - * `isDir`:是否为创建文件夹,为 `true` 时仅创建文件夹,忽略 `file` + * `isDir`:是否为创建文件夹,为 `true` 时仅创建文件夹,忽略 `file` * `modTime`:最近访问和修改时间,Unix time * `file`:上传的文件 * 返回值 @@ -842,6 +845,58 @@ * `hPath`:人类可读的路径 * `content`:Markdown 内容 +## 通知 + +### 推送消息 + +* `/api/notification/pushMsg` +* 参数 + + ```json + { + "msg": "test", + "timeout": 7000 + } + ``` + * `timeout`:消息持续显示时间,单位为毫秒。可以不传入该字段,默认为 7000 毫秒 +* 返回值 + + ```json + { + "code": 0, + "msg": "", + "data": { + "id": "62jtmqi" + } + } + ``` + * `id`:消息 ID + +### 推送报错消息 + +* `/api/notification/pushErrMsg` +* 参数 + + ```json + { + "msg": "test", + "timeout": 7000 + } + ``` + * `timeout`:消息持续显示时间,单位为毫秒。可以不传入该字段,默认为 7000 毫秒 +* 返回值 + + ```json + { + "code": 0, + "msg": "", + "data": { + "id": "qc9znut" + } + } + ``` + * `id`:消息 ID + ## 系统 ### 获取启动进度 diff --git a/kernel/api/notebook.go b/kernel/api/notebook.go index c75112b07..ef8d81f5b 100644 --- a/kernel/api/notebook.go +++ b/kernel/api/notebook.go @@ -155,7 +155,8 @@ func openNotebook(c *gin.Context) { } notebook := arg["notebook"].(string) - util.PushMsg(model.Conf.Language(45), 1000*60*15) + msgId := util.PushMsg(model.Conf.Language(45), 1000*60*15) + defer util.PushClearMsg(msgId) existed, err := model.Mount(notebook) if nil != err { ret.Code = -1 diff --git a/kernel/api/notification.go b/kernel/api/notification.go new file mode 100644 index 000000000..1c63f7760 --- /dev/null +++ b/kernel/api/notification.go @@ -0,0 +1,67 @@ +// SiYuan - Build Your Eternal Digital Garden +// 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 . + +package api + +import ( + "net/http" + + "github.com/88250/gulu" + "github.com/gin-gonic/gin" + "github.com/siyuan-note/siyuan/kernel/util" +) + +func pushMsg(c *gin.Context) { + ret := gulu.Ret.NewResult() + defer c.JSON(http.StatusOK, ret) + + arg, ok := util.JsonArg(c, ret) + if !ok { + return + } + + msg := arg["msg"].(string) + timeout := 7000 + if nil != arg["timeout"] { + timeout = int(arg["timeout"].(float64)) + } + msgId := util.PushMsg(msg, timeout) + + ret.Data = map[string]interface{}{ + "id": msgId, + } +} + +func pushErrMsg(c *gin.Context) { + ret := gulu.Ret.NewResult() + defer c.JSON(http.StatusOK, ret) + + arg, ok := util.JsonArg(c, ret) + if !ok { + return + } + + msg := arg["msg"].(string) + timeout := 7000 + if nil != arg["timeout"] { + timeout = int(arg["timeout"].(float64)) + } + msgId := util.PushErrMsg(msg, timeout) + + ret.Data = map[string]interface{}{ + "id": msgId, + } +} diff --git a/kernel/api/router.go b/kernel/api/router.go index 04cf706e4..cd8c74ab0 100644 --- a/kernel/api/router.go +++ b/kernel/api/router.go @@ -245,4 +245,7 @@ func ServeAPI(ginServer *gin.Engine) { ginServer.Handle("POST", "/api/bazaar/installBazaarTheme", model.CheckAuth, installBazaarTheme) ginServer.Handle("POST", "/api/bazaar/uninstallBazaarTheme", model.CheckAuth, uninstallBazaarTheme) ginServer.Handle("POST", "/api/bazaar/getBazaarPackageREAME", model.CheckAuth, getBazaarPackageREAME) + + ginServer.Handle("POST", "/api/notification/pushMsg", model.CheckAuth, pushMsg) + ginServer.Handle("POST", "/api/notification/pushErrMsg", model.CheckAuth, pushErrMsg) } diff --git a/kernel/model/file.go b/kernel/model/file.go index cc9647314..b49465ea8 100644 --- a/kernel/model/file.go +++ b/kernel/model/file.go @@ -837,8 +837,8 @@ func renameWriteJSONQueue(tree *parse.Tree, oldHPath string) (err error) { } func DuplicateDoc(rootID string) (err error) { - util.PushMsg(Conf.Language(116), 30000) - defer util.PushClearMsg() + msgId := util.PushMsg(Conf.Language(116), 30000) + defer util.PushClearMsg(msgId) WaitForWritingFiles() tree, err := loadTreeByBlockID(rootID) diff --git a/kernel/model/sync.go b/kernel/model/sync.go index 1c2842a25..d8de3feb2 100644 --- a/kernel/model/sync.go +++ b/kernel/model/sync.go @@ -248,7 +248,7 @@ func SyncData(boot, exit, byHand bool) { wroteFiles, transferSize, err := ossUpload(localSyncDirPath, "sync/"+Conf.Sync.CloudName, device, boot, removedSyncList, upsertedSyncList) if nil != err { - util.PushClearMsg() + util.PushClearProgress() IncWorkspaceDataVer() // 上传失败的话提升本地版本,以备下次上传 msg := fmt.Sprintf(Conf.Language(80), formatErrorMsg(err)) @@ -263,7 +263,7 @@ func SyncData(boot, exit, byHand bool) { return } - util.PushClearMsg() + util.PushClearProgress() elapsed := time.Now().Sub(start).Seconds() stat := fmt.Sprintf(Conf.Language(130), wroteFiles, humanize.Bytes(transferSize)) + fmt.Sprintf(Conf.Language(132), elapsed) util.LogInfof("sync [cloud=%d, local=%d, wroteFiles=%d, transferSize=%s] uploaded in [%.2fs]", cloudSyncVer, syncConf.SyncVer, wroteFiles, humanize.Bytes(transferSize), elapsed) @@ -293,7 +293,7 @@ func SyncData(boot, exit, byHand bool) { var tmpTransferSize uint64 err = ossDownload0(util.TempDir+"/sync", "sync/"+Conf.Sync.CloudName, "/"+pathJSON, &tmpFetchedFiles, &tmpTransferSize, boot || exit) if nil != err { - util.PushClearMsg() + util.PushClearProgress() msg := fmt.Sprintf(Conf.Language(80), formatErrorMsg(err)) Conf.Sync.Stat = msg util.PushErrMsg(msg, 7000) @@ -314,7 +314,7 @@ func SyncData(boot, exit, byHand bool) { } data, err = encryption.AESGCMDecryptBinBytes(data, Conf.E2EEPasswd) if nil != err { - util.PushClearMsg() + util.PushClearProgress() msg := Conf.Language(28) Conf.Sync.Stat = msg util.PushErrMsg(fmt.Sprintf(Conf.Language(80), msg), 7000) @@ -331,7 +331,7 @@ func SyncData(boot, exit, byHand bool) { // 解密验证成功后将其移动到 sync/ 文件夹下 if err = os.Rename(tmpPathJSON, filepath.Join(localSyncDirPath, pathJSON)); nil != err { - util.PushClearMsg() + util.PushClearProgress() msg := fmt.Sprintf(Conf.Language(80), formatErrorMsg(err)) Conf.Sync.Stat = msg util.PushErrMsg(msg, 7000) @@ -347,7 +347,7 @@ func SyncData(boot, exit, byHand bool) { fetchedFilesCount, transferSize, downloadedFiles, err := ossDownload(localSyncDirPath, "sync/"+Conf.Sync.CloudName, boot || exit) if nil != err { - util.PushClearMsg() + util.PushClearProgress() msg := fmt.Sprintf(Conf.Language(80), formatErrorMsg(err)) Conf.Sync.Stat = msg util.PushErrMsg(msg, 7000) @@ -366,7 +366,7 @@ func SyncData(boot, exit, byHand bool) { syncDownloadErrCount++ return } - util.PushClearMsg() + util.PushClearProgress() // 恢复 var upsertFiles, removeFiles []string diff --git a/kernel/util/websocket.go b/kernel/util/websocket.go index e78ee7227..c1b19eab8 100644 --- a/kernel/util/websocket.go +++ b/kernel/util/websocket.go @@ -19,6 +19,7 @@ package util import ( "sync" + "github.com/88250/gulu" "github.com/88250/melody" ) @@ -126,19 +127,16 @@ func PushTxErr(msg string, code int, data interface{}) { BroadcastByType("main", "txerr", code, msg, data) } -func PushMsg(msg string, timeout int) { - evt := NewCmdResult("msg", 0, PushModeBroadcast, 0) - evt.Msg = msg - evt.Data = map[string]interface{}{"closeTimeout": timeout} - PushEvent(evt) +func PushMsg(msg string, timeout int) (msgId string) { + msgId = gulu.Rand.String(7) + BroadcastByType("main", "msg", 0, msg, map[string]interface{}{"id": msgId, "closeTimeout": timeout}) + return } -func PushErrMsg(msg string, timeout int) { - evt := NewCmdResult("msg", 0, PushModeBroadcast, 0) - evt.Code = -1 - evt.Msg = msg - evt.Data = map[string]interface{}{"closeTimeout": timeout} - PushEvent(evt) +func PushErrMsg(msg string, timeout int) (msgId string) { + msgId = gulu.Rand.String(7) + BroadcastByType("main", "msg", -1, msg, map[string]interface{}{"id": msgId, "closeTimeout": timeout}) + return } const ( @@ -156,20 +154,20 @@ func PushEndlessProgress(msg string) { } func PushProgress(code, current, total int, msg string) { - evt := NewCmdResult("progress", 0, PushModeBroadcast, 0) - evt.Msg = msg - evt.Code = code - evt.Data = map[string]interface{}{ + BroadcastByType("main", "progress", code, msg, map[string]interface{}{ "current": current, "total": total, - } - PushEvent(evt) + }) } -// PushClearMsg 会清空消息提示以及进度遮罩。 -func PushClearMsg() { - evt := NewCmdResult("cmsg", 0, PushModeBroadcast, 0) - PushEvent(evt) +// PushClearMsg 会清空指定消息。 +func PushClearMsg(msgId string) { + BroadcastByType("main", "cmsg", 0, "", map[string]interface{}{"id": msgId}) +} + +// PushClearProgress 取消进度遮罩。 +func PushClearProgress() { + BroadcastByType("main", "cprogress", 0, "", nil) } func PushDownloadProgress(id string, percent float32) {