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

This commit is contained in:
Vanessa 2024-09-26 09:41:52 +08:00
commit afe993a15c
7 changed files with 169 additions and 21 deletions

View file

@ -472,6 +472,7 @@ export class Gutter {
}
private turnsIntoOne(options: {
menuId?: string,
accelerator?: string,
icon?: string,
label: string,
@ -479,7 +480,6 @@ export class Gutter {
selectsElement: Element[],
type: TTurnIntoOne,
level?: TTurnIntoOneSub,
menuId?: string,
}) {
return {
id: options.menuId,
@ -493,6 +493,7 @@ export class Gutter {
}
private turnsInto(options: {
menuId?: string,
icon?: string,
label: string,
protyle: IProtyle,
@ -501,7 +502,6 @@ export class Gutter {
level?: number,
isContinue?: boolean,
accelerator?: string,
menuId?: string,
}) {
return {
id: options.menuId,
@ -551,6 +551,7 @@ export class Gutter {
const turnIntoSubmenu: IMenu[] = [];
if (isContinue) {
turnIntoSubmenu.push(this.turnsIntoOne({
menuId: "list",
icon: "iconList",
label: window.siyuan.languages.list,
protyle,
@ -559,6 +560,7 @@ export class Gutter {
type: "Blocks2ULs"
}));
turnIntoSubmenu.push(this.turnsIntoOne({
menuId: "orderedList",
icon: "iconOrderedList",
label: window.siyuan.languages["ordered-list"],
accelerator: window.siyuan.config.keymap.editor.insert["ordered-list"].custom,
@ -567,6 +569,7 @@ export class Gutter {
type: "Blocks2OLs"
}));
turnIntoSubmenu.push(this.turnsIntoOne({
menuId: "check",
icon: "iconCheck",
label: window.siyuan.languages.check,
accelerator: window.siyuan.config.keymap.editor.insert.check.custom,
@ -575,6 +578,7 @@ export class Gutter {
type: "Blocks2TLs"
}));
turnIntoSubmenu.push(this.turnsIntoOne({
menuId: "quote",
icon: "iconQuote",
label: window.siyuan.languages.quote,
accelerator: window.siyuan.config.keymap.editor.insert.quote.custom,
@ -586,6 +590,7 @@ export class Gutter {
// 多选引用转换为块的时候 id 不一致
if (!hasEmbedBlock) {
turnIntoSubmenu.push(this.turnsInto({
menuId: "paragraph",
icon: "iconParagraph",
label: window.siyuan.languages.paragraph,
accelerator: window.siyuan.config.keymap.editor.heading.paragraph.custom,
@ -596,6 +601,7 @@ export class Gutter {
}));
}
turnIntoSubmenu.push(this.turnsInto({
menuId: "heading1",
icon: "iconH1",
label: window.siyuan.languages.heading1,
accelerator: window.siyuan.config.keymap.editor.heading.heading1.custom,
@ -606,6 +612,7 @@ export class Gutter {
isContinue
}));
turnIntoSubmenu.push(this.turnsInto({
menuId: "heading2",
icon: "iconH2",
label: window.siyuan.languages.heading2,
accelerator: window.siyuan.config.keymap.editor.heading.heading2.custom,
@ -616,6 +623,7 @@ export class Gutter {
isContinue
}));
turnIntoSubmenu.push(this.turnsInto({
menuId: "heading3",
icon: "iconH3",
label: window.siyuan.languages.heading3,
accelerator: window.siyuan.config.keymap.editor.heading.heading3.custom,
@ -626,6 +634,7 @@ export class Gutter {
isContinue
}));
turnIntoSubmenu.push(this.turnsInto({
menuId: "heading4",
icon: "iconH4",
label: window.siyuan.languages.heading4,
accelerator: window.siyuan.config.keymap.editor.heading.heading4.custom,
@ -636,6 +645,7 @@ export class Gutter {
isContinue
}));
turnIntoSubmenu.push(this.turnsInto({
menuId: "heading5",
icon: "iconH5",
label: window.siyuan.languages.heading5,
accelerator: window.siyuan.config.keymap.editor.heading.heading5.custom,
@ -646,6 +656,7 @@ export class Gutter {
isContinue
}));
turnIntoSubmenu.push(this.turnsInto({
menuId: "heading6",
icon: "iconH6",
label: window.siyuan.languages.heading6,
accelerator: window.siyuan.config.keymap.editor.heading.heading6.custom,
@ -669,6 +680,7 @@ export class Gutter {
label: window.siyuan.languages.merge + " " + window.siyuan.languages.superBlock,
type: "submenu",
submenu: [this.turnsIntoOne({
menuId: "hLayout",
label: window.siyuan.languages.hLayout,
accelerator: window.siyuan.config.keymap.editor.general.hLayout.custom,
icon: "iconSplitLR",
@ -677,6 +689,7 @@ export class Gutter {
type: "BlocksMergeSuperBlock",
level: "col"
}), this.turnsIntoOne({
menuId: "vLayout",
label: window.siyuan.languages.vLayout,
accelerator: window.siyuan.config.keymap.editor.general.vLayout.custom,
icon: "iconSplitTB",
@ -911,6 +924,7 @@ export class Gutter {
// "heading1-6", "list", "ordered-list", "check", "quote", "code", "table", "line", "math", "paragraph"
if (type === "NodeParagraph" && !protyle.disabled) {
turnIntoSubmenu.push(this.turnsIntoOne({
menuId: "list",
icon: "iconList",
label: window.siyuan.languages.list,
accelerator: window.siyuan.config.keymap.editor.insert.list.custom,
@ -919,6 +933,7 @@ export class Gutter {
type: "Blocks2ULs"
}));
turnIntoSubmenu.push(this.turnsIntoOne({
menuId: "orderedList",
icon: "iconOrderedList",
label: window.siyuan.languages["ordered-list"],
accelerator: window.siyuan.config.keymap.editor.insert["ordered-list"].custom,
@ -927,6 +942,7 @@ export class Gutter {
type: "Blocks2OLs"
}));
turnIntoSubmenu.push(this.turnsIntoOne({
menuId: "check",
icon: "iconCheck",
label: window.siyuan.languages.check,
accelerator: window.siyuan.config.keymap.editor.insert.check.custom,
@ -935,6 +951,7 @@ export class Gutter {
type: "Blocks2TLs"
}));
turnIntoSubmenu.push(this.turnsIntoOne({
menuId: "quote",
icon: "iconQuote",
label: window.siyuan.languages.quote,
accelerator: window.siyuan.config.keymap.editor.insert.quote.custom,
@ -943,6 +960,7 @@ export class Gutter {
type: "Blocks2Blockquote"
}));
turnIntoSubmenu.push(this.turnsInto({
menuId: "heading1",
icon: "iconH1",
label: window.siyuan.languages.heading1,
accelerator: window.siyuan.config.keymap.editor.heading.heading1.custom,
@ -952,6 +970,7 @@ export class Gutter {
type: "Blocks2Hs",
}));
turnIntoSubmenu.push(this.turnsInto({
menuId: "heading2",
icon: "iconH2",
label: window.siyuan.languages.heading2,
accelerator: window.siyuan.config.keymap.editor.heading.heading2.custom,
@ -961,6 +980,7 @@ export class Gutter {
type: "Blocks2Hs",
}));
turnIntoSubmenu.push(this.turnsInto({
menuId: "heading3",
icon: "iconH3",
label: window.siyuan.languages.heading3,
accelerator: window.siyuan.config.keymap.editor.heading.heading3.custom,
@ -970,6 +990,7 @@ export class Gutter {
type: "Blocks2Hs",
}));
turnIntoSubmenu.push(this.turnsInto({
menuId: "heading4",
icon: "iconH4",
label: window.siyuan.languages.heading4,
accelerator: window.siyuan.config.keymap.editor.heading.heading4.custom,
@ -979,6 +1000,7 @@ export class Gutter {
type: "Blocks2Hs",
}));
turnIntoSubmenu.push(this.turnsInto({
menuId: "heading5",
icon: "iconH5",
label: window.siyuan.languages.heading5,
accelerator: window.siyuan.config.keymap.editor.heading.heading5.custom,
@ -988,6 +1010,7 @@ export class Gutter {
type: "Blocks2Hs",
}));
turnIntoSubmenu.push(this.turnsInto({
menuId: "heading6",
icon: "iconH6",
label: window.siyuan.languages.heading6,
accelerator: window.siyuan.config.keymap.editor.heading.heading6.custom,
@ -998,6 +1021,7 @@ export class Gutter {
}));
} else if (type === "NodeHeading" && !protyle.disabled) {
turnIntoSubmenu.push(this.turnsInto({
menuId: "paragraph",
icon: "iconParagraph",
label: window.siyuan.languages.paragraph,
accelerator: window.siyuan.config.keymap.editor.heading.paragraph.custom,
@ -1006,6 +1030,7 @@ export class Gutter {
type: "Blocks2Ps",
}));
turnIntoSubmenu.push(this.turnsIntoOne({
menuId: "quote",
icon: "iconQuote",
label: window.siyuan.languages.quote,
accelerator: window.siyuan.config.keymap.editor.insert.quote.custom,
@ -1015,6 +1040,7 @@ export class Gutter {
}));
if (subType !== "h1") {
turnIntoSubmenu.push(this.turnsInto({
menuId: "heading1",
icon: "iconH1",
label: window.siyuan.languages.heading1,
accelerator: window.siyuan.config.keymap.editor.heading.heading1.custom,
@ -1026,6 +1052,7 @@ export class Gutter {
}
if (subType !== "h2") {
turnIntoSubmenu.push(this.turnsInto({
menuId: "heading2",
icon: "iconH2",
label: window.siyuan.languages.heading2,
accelerator: window.siyuan.config.keymap.editor.heading.heading2.custom,
@ -1037,6 +1064,7 @@ export class Gutter {
}
if (subType !== "h3") {
turnIntoSubmenu.push(this.turnsInto({
menuId: "heading3",
icon: "iconH3",
label: window.siyuan.languages.heading3,
accelerator: window.siyuan.config.keymap.editor.heading.heading3.custom,
@ -1048,6 +1076,7 @@ export class Gutter {
}
if (subType !== "h4") {
turnIntoSubmenu.push(this.turnsInto({
menuId: "heading4",
icon: "iconH4",
label: window.siyuan.languages.heading4,
accelerator: window.siyuan.config.keymap.editor.heading.heading4.custom,
@ -1059,6 +1088,7 @@ export class Gutter {
}
if (subType !== "h5") {
turnIntoSubmenu.push(this.turnsInto({
menuId: "heading5",
icon: "iconH5",
label: window.siyuan.languages.heading5,
accelerator: window.siyuan.config.keymap.editor.heading.heading5.custom,
@ -1070,6 +1100,7 @@ export class Gutter {
}
if (subType !== "h6") {
turnIntoSubmenu.push(this.turnsInto({
menuId: "heading6",
icon: "iconH6",
label: window.siyuan.languages.heading6,
accelerator: window.siyuan.config.keymap.editor.heading.heading6.custom,
@ -1081,6 +1112,7 @@ export class Gutter {
}
} else if (type === "NodeList" && !protyle.disabled) {
turnIntoSubmenu.push(this.turnsOneInto({
menuId: "paragraph",
id,
icon: "iconParagraph",
label: window.siyuan.languages.paragraph,
@ -1090,6 +1122,7 @@ export class Gutter {
type: "CancelList"
}));
turnIntoSubmenu.push(this.turnsIntoOne({
menuId: "quote",
icon: "iconQuote",
label: window.siyuan.languages.quote,
accelerator: window.siyuan.config.keymap.editor.insert.quote.custom,
@ -1099,6 +1132,7 @@ export class Gutter {
}));
if (nodeElement.getAttribute("data-subtype") === "o") {
turnIntoSubmenu.push(this.turnsOneInto({
menuId: "list",
id,
icon: "iconList",
label: window.siyuan.languages.list,
@ -1108,6 +1142,7 @@ export class Gutter {
type: "OL2UL"
}));
turnIntoSubmenu.push(this.turnsOneInto({
menuId: "check",
id,
icon: "iconCheck",
label: window.siyuan.languages.check,
@ -1118,6 +1153,7 @@ export class Gutter {
}));
} else if (nodeElement.getAttribute("data-subtype") === "t") {
turnIntoSubmenu.push(this.turnsOneInto({
menuId: "list",
id,
icon: "iconList",
label: window.siyuan.languages.list,
@ -1127,6 +1163,7 @@ export class Gutter {
type: "TL2UL"
}));
turnIntoSubmenu.push(this.turnsOneInto({
menuId: "orderedList",
id,
icon: "iconOrderedList",
label: window.siyuan.languages["ordered-list"],
@ -1137,6 +1174,7 @@ export class Gutter {
}));
} else {
turnIntoSubmenu.push(this.turnsOneInto({
menuId: "orderedList",
id,
icon: "iconOrderedList",
label: window.siyuan.languages["ordered-list"],
@ -1146,6 +1184,7 @@ export class Gutter {
type: "UL2OL"
}));
turnIntoSubmenu.push(this.turnsOneInto({
menuId: "check",
id,
icon: "iconCheck",
label: window.siyuan.languages.check,
@ -1157,6 +1196,7 @@ export class Gutter {
}
} else if (type === "NodeBlockquote" && !protyle.disabled) {
turnIntoSubmenu.push(this.turnsOneInto({
menuId: "paragraph",
id,
icon: "iconParagraph",
label: window.siyuan.languages.paragraph,

View file

@ -63,6 +63,7 @@ func ServeAPI(ginServer *gin.Engine) {
ginServer.Handle("POST", "/api/system/getChangelog", model.CheckAuth, getChangelog)
ginServer.Handle("POST", "/api/system/getNetwork", model.CheckAuth, model.CheckAdminRole, getNetwork)
ginServer.Handle("POST", "/api/system/exportConf", model.CheckAuth, model.CheckAdminRole, exportConf)
ginServer.Handle("POST", "/api/system/importConf", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, importConf)
ginServer.Handle("POST", "/api/storage/setLocalStorage", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, setLocalStorage)
ginServer.Handle("POST", "/api/storage/getLocalStorage", model.CheckAuth, getLocalStorage)

View file

@ -17,7 +17,7 @@
package api
import (
"github.com/jinzhu/copier"
"io"
"net/http"
"os"
"path/filepath"
@ -25,10 +25,10 @@ import (
"sync"
"time"
"github.com/88250/lute"
"github.com/88250/gulu"
"github.com/88250/lute"
"github.com/gin-gonic/gin"
"github.com/jinzhu/copier"
"github.com/siyuan-note/logging"
"github.com/siyuan-note/siyuan/kernel/conf"
"github.com/siyuan-note/siyuan/kernel/model"
@ -211,17 +211,19 @@ func exportConf(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
logging.LogInfof("exporting conf...")
name := "siyuan-conf-" + time.Now().Format("20060102150405") + ".json"
tmpDir := filepath.Join(util.TempDir, "export")
if err := os.MkdirAll(tmpDir, 0755); err != nil {
logging.LogErrorf("export WebDAV provider failed: %s", err)
logging.LogErrorf("export conf failed: %s", err)
ret.Code = -1
ret.Msg = err.Error()
return
}
clonedConf := &model.AppConf{}
if err := copier.Copy(clonedConf, model.Conf); err != nil {
if err := copier.CopyWithOption(clonedConf, model.Conf, copier.Option{IgnoreEmpty: false, DeepCopy: true}); err != nil {
logging.LogErrorf("export conf failed: %s", err)
ret.Code = -1
ret.Msg = err.Error()
@ -291,6 +293,8 @@ func exportConf(c *gin.Context) {
return
}
logging.LogInfof("exported conf")
zipPath := "/export/" + name + ".zip"
ret.Data = map[string]interface{}{
"name": name,
@ -298,6 +302,96 @@ func exportConf(c *gin.Context) {
}
}
func importConf(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(200, ret)
logging.LogInfof("importing conf...")
form, err := c.MultipartForm()
if err != nil {
logging.LogErrorf("read upload file failed: %s", err)
ret.Code = -1
ret.Msg = err.Error()
return
}
files := form.File["file"]
if 1 != len(files) {
ret.Code = -1
ret.Msg = "invalid upload file"
return
}
f := files[0]
fh, err := f.Open()
if err != nil {
logging.LogErrorf("read upload file failed: %s", err)
ret.Code = -1
ret.Msg = err.Error()
return
}
data, err := io.ReadAll(fh)
fh.Close()
if err != nil {
logging.LogErrorf("read upload file failed: %s", err)
ret.Code = -1
ret.Msg = err.Error()
return
}
tmpDir := filepath.Join(util.TempDir, "import")
if err = os.MkdirAll(tmpDir, 0755); err != nil {
logging.LogErrorf("import conf failed: %s", err)
ret.Code = -1
ret.Msg = err.Error()
return
}
tmp := filepath.Join(tmpDir, f.Filename)
if err = os.WriteFile(tmp, data, 0644); err != nil {
logging.LogErrorf("import conf failed: %s", err)
ret.Code = -1
ret.Msg = err.Error()
return
}
if err = gulu.Zip.Unzip(tmp, tmpDir); err != nil {
logging.LogErrorf("import conf failed: %s", err)
ret.Code = -1
ret.Msg = err.Error()
return
}
tmp = filepath.Join(tmpDir, f.Filename[:len(f.Filename)-4])
data, err = os.ReadFile(tmp)
if err != nil {
logging.LogErrorf("import conf failed: %s", err)
ret.Code = -1
ret.Msg = err.Error()
return
}
importedConf := model.NewAppConf()
if err = gulu.JSON.UnmarshalJSON(data, importedConf); err != nil {
logging.LogErrorf("import conf failed: %s", err)
ret.Code = -1
ret.Msg = err.Error()
return
}
if err = copier.CopyWithOption(model.Conf, importedConf, copier.Option{IgnoreEmpty: true, DeepCopy: true}); err != nil {
logging.LogErrorf("import conf failed: %s", err)
ret.Code = -1
ret.Msg = err.Error()
return
}
logging.LogInfof("imported conf")
model.Close(false, true, 1)
}
func getConf(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)

View file

@ -1675,7 +1675,7 @@ func (tx *Transaction) doSetAttrViewViewIcon(operation *Operation) (ret *TxErr)
}
func (tx *Transaction) doSetAttrViewName(operation *Operation) (ret *TxErr) {
err := setAttributeViewName(operation)
err := tx.setAttributeViewName(operation)
if err != nil {
return &TxErr{code: TxErrWriteAttributeView, id: operation.AvID, msg: err.Error()}
}
@ -1684,7 +1684,7 @@ func (tx *Transaction) doSetAttrViewName(operation *Operation) (ret *TxErr) {
const attrAvNameTpl = `<span data-av-id="${avID}" data-popover-url="/api/av/getMirrorDatabaseBlocks" class="popover__block">${avName}</span>`
func setAttributeViewName(operation *Operation) (err error) {
func (tx *Transaction) setAttributeViewName(operation *Operation) (err error) {
avID := operation.ID
attrView, err := av.ParseAttributeView(avID)
if err != nil {
@ -1694,7 +1694,7 @@ func setAttributeViewName(operation *Operation) (err error) {
attrView.Name = strings.TrimSpace(operation.Data.(string))
err = av.SaveAttributeView(attrView)
_, nodes := getAttrViewBoundNodes(attrView)
_, nodes := tx.getAttrViewBoundNodes(attrView)
for _, node := range nodes {
avNames := getAvNames(node.IALAttr(av.NodeAttrNameAvs))
oldAttrs := parse.IAL2Map(node.KramdownIAL)
@ -1731,7 +1731,7 @@ func getAvNames(avIDs string) (ret string) {
return
}
func getAttrViewBoundNodes(attrView *av.AttributeView) (trees []*parse.Tree, nodes []*ast.Node) {
func (tx *Transaction) getAttrViewBoundNodes(attrView *av.AttributeView) (trees []*parse.Tree, nodes []*ast.Node) {
blockKeyValues := attrView.GetBlockKeyValues()
treeCache := map[string]*parse.Tree{}
for _, blockKeyValue := range blockKeyValues.Values {
@ -1742,7 +1742,11 @@ func getAttrViewBoundNodes(attrView *av.AttributeView) (trees []*parse.Tree, nod
var tree *parse.Tree
tree = treeCache[blockKeyValue.BlockID]
if nil == tree {
tree, _ = LoadTreeByBlockID(blockKeyValue.BlockID)
if nil == tx {
tree, _ = LoadTreeByBlockID(blockKeyValue.BlockID)
} else {
tree, _ = tx.loadTree(blockKeyValue.BlockID)
}
}
if nil == tree {
continue

View file

@ -51,7 +51,7 @@ var Conf *AppConf
// AppConf 维护应用元数据,保存在 ~/.siyuan/conf.json。
type AppConf struct {
LogLevel string `json:"logLevel"` // 日志级别:Off, Trace, Debug, Info, Warn, Error, Fatal
LogLevel string `json:"logLevel"` // 日志级别:off, trace, debug, info, warn, error, fatal
Appearance *conf.Appearance `json:"appearance"` // 外观
Langs []*conf.Lang `json:"langs"` // 界面语言列表
Lang string `json:"lang"` // 选择的界面语言,同 Appearance.Lang
@ -87,6 +87,10 @@ type AppConf struct {
m *sync.Mutex
}
func NewAppConf() *AppConf {
return &AppConf{LogLevel: "debug", m: &sync.Mutex{}}
}
func (conf *AppConf) GetUILayout() *conf.UILayout {
conf.m.Lock()
defer conf.m.Unlock()
@ -114,7 +118,7 @@ func (conf *AppConf) SetUser(user *conf.User) {
func InitConf() {
initLang()
Conf = &AppConf{LogLevel: "debug", m: &sync.Mutex{}}
Conf = NewAppConf()
confPath := filepath.Join(util.ConfDir, "conf.json")
if gulu.File.IsExist(confPath) {
if data, err := os.ReadFile(confPath); err != nil {

View file

@ -1704,7 +1704,7 @@ func removeDoc(box *Box, p string, luteEngine *lute.Lute) {
continue
}
syncDelete2AvBlock(removeTree.Root)
syncDelete2AvBlock(removeTree.Root, removeTree, nil)
}
if existChildren {

View file

@ -819,14 +819,14 @@ func (tx *Transaction) doDelete(operation *Operation) (ret *TxErr) {
}
if needSyncDel2AvBlock {
syncDelete2AvBlock(node)
syncDelete2AvBlock(node, tree, tx)
}
return
}
func syncDelete2AvBlock(node *ast.Node) {
func syncDelete2AvBlock(node *ast.Node, nodeTree *parse.Tree, tx *Transaction) {
changedAvIDs := syncDelete2AttributeView(node)
avIDs := syncDelete2Block(node)
avIDs := tx.syncDelete2Block(node, nodeTree)
changedAvIDs = append(changedAvIDs, avIDs...)
changedAvIDs = gulu.Str.RemoveDuplicatedElem(changedAvIDs)
@ -835,7 +835,7 @@ func syncDelete2AvBlock(node *ast.Node) {
}
}
func syncDelete2Block(node *ast.Node) (changedAvIDs []string) {
func (tx *Transaction) syncDelete2Block(node *ast.Node, nodeTree *parse.Tree) (changedAvIDs []string) {
ast.Walk(node, func(n *ast.Node, entering bool) ast.WalkStatus {
if !entering || ast.NodeAttributeView != n.Type {
return ast.WalkContinue
@ -857,7 +857,7 @@ func syncDelete2Block(node *ast.Node) (changedAvIDs []string) {
return ast.WalkContinue
}
trees, nodes := getAttrViewBoundNodes(attrView)
trees, nodes := tx.getAttrViewBoundNodes(attrView)
for _, toChangNode := range nodes {
avs := toChangNode.IALAttr(av.NodeAttrNameAvs)
if "" != avs {
@ -874,8 +874,13 @@ func syncDelete2Block(node *ast.Node) (changedAvIDs []string) {
toChangNode.SetIALAttr(av.NodeAttrViewNames, avNames)
pushBroadcastAttrTransactions(oldAttrs, toChangNode)
}
nodeTreeID := nodeTree.ID
for _, tree := range trees {
indexWriteTreeUpsertQueue(tree)
self := nodeTreeID == tree.ID
if !self {
indexWriteTreeUpsertQueue(tree)
}
}
return ast.WalkContinue
})