Bläddra i källkod

Merge branch 'dev' into dev-av-style

Jeffrey Chen 7 månader sedan
förälder
incheckning
0e8d1e12c0
72 ändrade filer med 543 tillägg och 189 borttagningar
  1. 1 0
      app/appearance/langs/de_DE.json
  2. 1 0
      app/appearance/langs/en_US.json
  3. 1 0
      app/appearance/langs/es_ES.json
  4. 1 0
      app/appearance/langs/fr_FR.json
  5. 1 0
      app/appearance/langs/he_IL.json
  6. 1 0
      app/appearance/langs/it_IT.json
  7. 1 0
      app/appearance/langs/ja_JP.json
  8. 1 0
      app/appearance/langs/pl_PL.json
  9. 1 0
      app/appearance/langs/ru_RU.json
  10. 1 0
      app/appearance/langs/zh_CHT.json
  11. 1 0
      app/appearance/langs/zh_CN.json
  12. 1 1
      app/appearance/themes/daylight/theme.css
  13. 1 1
      app/appearance/themes/midnight/theme.css
  14. 47 1
      app/guide/20210808180117-6v0mkxr/20200923234011-ieuun1p.sy
  15. 47 1
      app/guide/20210808180117-czj9bvb/20200812220555-lj3enxa.sy
  16. 47 1
      app/guide/20211226090932-5lcq56f/20211226115423-d5z1joq.sy
  17. 47 1
      app/guide/20240530133126-axarxgx/20240530101000-4qitucx.sy
  18. 3 3
      app/src/assets/scss/base.scss
  19. 1 1
      app/src/assets/scss/business/_av.scss
  20. 12 1
      app/src/assets/scss/business/_drag.scss
  21. 1 1
      app/src/assets/scss/business/_export.scss
  22. 8 8
      app/src/assets/scss/business/_layout.scss
  23. 3 3
      app/src/assets/scss/business/_search.scss
  24. 1 1
      app/src/assets/scss/component/_list.scss
  25. 1 1
      app/src/assets/scss/component/_snackbar.scss
  26. 10 10
      app/src/assets/scss/component/_switch.scss
  27. 2 2
      app/src/assets/scss/component/_tooltips.scss
  28. 2 2
      app/src/assets/scss/component/_typography.scss
  29. 2 2
      app/src/assets/scss/pdf/_pdf.scss
  30. 3 3
      app/src/assets/scss/protyle/_protyle.scss
  31. 22 29
      app/src/assets/scss/protyle/_wysiwyg.scss
  32. 3 3
      app/src/block/Panel.ts
  33. 18 0
      app/src/config/keymap.ts
  34. 3 2
      app/src/config/search.ts
  35. 1 1
      app/src/dialog/tooltip.ts
  36. 1 1
      app/src/layout/dock/Files.ts
  37. 4 1
      app/src/menus/util.ts
  38. 1 1
      app/src/mobile/index.ts
  39. 24 0
      app/src/plugin/index.ts
  40. 6 2
      app/src/protyle/gutter/index.ts
  41. 48 12
      app/src/protyle/render/av/relation.ts
  42. 1 1
      app/src/protyle/render/flowchartRender.ts
  43. 18 4
      app/src/protyle/toolbar/index.ts
  44. 1 1
      app/src/protyle/ui/initUI.ts
  45. 16 6
      app/src/protyle/util/compatibility.ts
  46. 6 1
      app/src/protyle/util/paste.ts
  47. 17 18
      app/src/protyle/wysiwyg/index.ts
  48. 1 0
      app/src/protyle/wysiwyg/input.ts
  49. 5 3
      app/src/protyle/wysiwyg/keydown.ts
  50. 2 0
      app/src/protyle/wysiwyg/remove.ts
  51. 0 1
      app/src/protyle/wysiwyg/transaction.ts
  52. 9 0
      app/src/types/index.d.ts
  53. 5 3
      app/src/util/assets.ts
  54. 4 2
      app/src/util/highlightById.ts
  55. 1 1
      app/stage/auth.html
  56. 1 0
      app/stage/protyle/js/flowchart.js/flowchart.min.js
  57. 0 0
      app/stage/protyle/js/lute/lute.min.js
  58. 1 1
      app/stage/service-worker.js
  59. 2 2
      app/webpack.config.js
  60. 2 2
      app/webpack.desktop.js
  61. 2 2
      app/webpack.export.js
  62. 2 2
      app/webpack.mobile.js
  63. 1 1
      kernel/go.mod
  64. 2 2
      kernel/go.sum
  65. 2 2
      kernel/harmony/kernel.go
  66. 25 9
      kernel/model/assets.go
  67. 1 5
      kernel/model/backlink.go
  68. 1 0
      kernel/model/blockinfo.go
  69. 9 1
      kernel/model/export.go
  70. 13 12
      kernel/model/import.go
  71. 2 1
      kernel/model/render.go
  72. 10 10
      kernel/util/file.go

+ 1 - 0
app/appearance/langs/de_DE.json

@@ -1,5 +1,6 @@
 {
   "empty": "Leer",
+  "newRowInRelation": "Neue Zeile in ${x} erstellen <b class='ft__on-surface'>${y}</b>",
   "keyContent": "Schlüsselinhalt",
   "addDesc": "Beschreibung hinzufügen",
   "dataRepoAutoPurgeIndexRetentionDays": "Daten-Snapshot-Aufbewahrungstage",

+ 1 - 0
app/appearance/langs/en_US.json

@@ -1,5 +1,6 @@
 {
   "empty": "Empty",
+  "newRowInRelation": "Create a new row in ${x} <b class='ft__on-surface'>${y}</b>",
   "keyContent": "Key content",
   "addDesc": "Add description",
   "dataRepoAutoPurgeIndexRetentionDays": "Data snapshot retention days",

+ 1 - 0
app/appearance/langs/es_ES.json

@@ -1,5 +1,6 @@
 {
   "empty": "Vacío",
+  "newRowInRelation": "Crear una nueva fila en ${x} <b class='ft__on-surface'>${y}</b>",
   "keyContent": "Contenido de la clave",
   "addDesc": "Agregar descripción",
   "dataRepoAutoPurgeIndexRetentionDays": "Días de retención de instantáneas de datos",

+ 1 - 0
app/appearance/langs/fr_FR.json

@@ -1,4 +1,5 @@
 {
+  "newRowInRelation": "Créer une nouvelle ligne dans ${x} <b class='ft__on-surface'>${y}</b>",
   "keyContent": "Contenu de la clé",
   "addDesc": "Ajouter une description",
   "dataRepoAutoPurgeIndexRetentionDays": "Jours de rétention des instantanés de données",

+ 1 - 0
app/appearance/langs/he_IL.json

@@ -1,5 +1,6 @@
 {
   "empty": "ריק",
+  "newRowInRelation": "צור שורה חדשה ב-${x} <b class='ft__on-surface'>${y}</b>",
   "keyContent": "תוכן המפתח",
   "addDesc": "הוסף תיאור",
   "dataRepoAutoPurgeIndexRetentionDays": "ימי שמירת תמונות נתונים",

+ 1 - 0
app/appearance/langs/it_IT.json

@@ -1,5 +1,6 @@
 {
   "empty": "Vuoto",
+  "newRowInRelation": "Crea una nuova riga in ${x} <b class='ft__on-surface'>${y}</b>",
   "keyContent": "Contenuto della chiave",
   "addDesc": "Aggiungi descrizione",
   "dataRepoAutoPurgeIndexRetentionDays": "Giorni di conservazione degli snapshot dei dati",

+ 1 - 0
app/appearance/langs/ja_JP.json

@@ -1,5 +1,6 @@
 {
   "empty": "空白",
+  "newRowInRelation": "${x} に新しい行を作成 <b class='ft__on-surface'>${y}</b>",
   "keyContent": "キーコンテンツ",
   "addDesc": "説明を追加",
   "dataRepoAutoPurgeIndexRetentionDays": "データスナップショットの保持日数",

+ 1 - 0
app/appearance/langs/pl_PL.json

@@ -1,5 +1,6 @@
 {
   "empty": "Pusty",
+  "newRowInRelation": "Utwórz nowy wiersz w ${x} <b class='ft__on-surface'>${y}</b>",
   "keyContent": "Zawartość klucza",
   "addDesc": "Dodaj opis",
   "dataRepoAutoPurgeIndexRetentionDays": "Dni przechowywania migawek danych",

+ 1 - 0
app/appearance/langs/ru_RU.json

@@ -1,5 +1,6 @@
 {
   "empty": "Пусто",
+  "newRowInRelation": "Создать новую строку в ${x} <b class='ft__on-surface'>${y}</b>",
   "keyContent": "Содержимое ключа",
   "addDesc": "Добавить описание",
   "dataRepoAutoPurgeIndexRetentionDays": "Срок хранения снимков данных",

+ 1 - 0
app/appearance/langs/zh_CHT.json

@@ -1,5 +1,6 @@
 {
   "empty": "空白",
+  "newRowInRelation": "在 ${x} 中新建行 <b class='ft__on-surface'>${y}</b>",
   "keyContent": "主鍵內容",
   "addDesc": "添加描述",
   "dataRepoAutoPurgeIndexRetentionDays": "數據快照保留天數",

+ 1 - 0
app/appearance/langs/zh_CN.json

@@ -1,5 +1,6 @@
 {
   "empty": "空白",
+  "newRowInRelation": "在 ${x} 中新建行 <b class='ft__on-surface'>${y}</b>",
   "keyContent": "主键内容",
   "addDesc": "添加描述",
   "dataRepoAutoPurgeIndexRetentionDays": "数据快照保留天数",

+ 1 - 1
app/appearance/themes/daylight/theme.css

@@ -200,7 +200,7 @@
 }
 
 /* https://github.com/siyuan-note/siyuan/issues/6440 */
-.protyle-action--order:after {
+.protyle-action--order::after {
     mix-blend-mode: multiply;
 }
 

+ 1 - 1
app/appearance/themes/midnight/theme.css

@@ -199,7 +199,7 @@
 }
 
 /* https://github.com/siyuan-note/siyuan/issues/6440 */
-.protyle-action--order:after {
+.protyle-action--order::after {
     mix-blend-mode: screen;
 }
 

+ 47 - 1
app/guide/20210808180117-6v0mkxr/20200923234011-ieuun1p.sy

@@ -8,9 +8,55 @@
 		"title": "Please Start Here",
 		"title-img": "background-color:#556;background-image: linear-gradient(30deg, #445 12%, transparent 12.5%, transparent 87%, #445 87.5%, #445),linear-gradient(150deg, #445 12%, transparent 12.5%, transparent 87%, #445 87.5%, #445),linear-gradient(30deg, #445 12%, transparent 12.5%, transparent 87%, #445 87.5%, #445),linear-gradient(150deg, #445 12%, transparent 12.5%, transparent 87%, #445 87.5%, #445),linear-gradient(60deg, #99a 25%, transparent 25.5%, transparent 75%, #99a 75%, #99a),linear-gradient(60deg, #99a 25%, transparent 25.5%, transparent 75%, #99a 75%, #99a);background-size:80px 140px;background-position: 0 0, 0 0, 40px 70px, 40px 70px, 0 0, 40px 70px;",
 		"type": "doc",
-		"updated": "20241018102340"
+		"updated": "20241125224626"
 	},
 	"Children": [
+		{
+			"ID": "20241125224615-dwfqru1",
+			"Type": "NodeBlockquote",
+			"Properties": {
+				"id": "20241125224615-dwfqru1",
+				"updated": "20241125224626"
+			},
+			"Children": [
+				{
+					"Type": "NodeBlockquoteMarker",
+					"Data": "\u003e"
+				},
+				{
+					"ID": "20241125224615-z2fevdf",
+					"Type": "NodeParagraph",
+					"Properties": {
+						"id": "20241125224615-z2fevdf",
+						"updated": "20241125224626"
+					},
+					"Children": [
+						{
+							"Type": "NodeText",
+							"Data": "When you close SiYuan Notes, the User Guide will be automatically closed. You can click "
+						},
+						{
+							"Type": "NodeTextMark",
+							"TextMarkType": "kbd",
+							"TextMarkTextContent": "Main Menu"
+						},
+						{
+							"Type": "NodeText",
+							"Data": " - "
+						},
+						{
+							"Type": "NodeTextMark",
+							"TextMarkType": "kbd",
+							"TextMarkTextContent": "User Guide"
+						},
+						{
+							"Type": "NodeText",
+							"Data": " to view it again"
+						}
+					]
+				}
+			]
+		},
 		{
 			"ID": "20210528120135-bznvpp6",
 			"Type": "NodeSuperBlock",

+ 47 - 1
app/guide/20210808180117-czj9bvb/20200812220555-lj3enxa.sy

@@ -8,9 +8,55 @@
 		"title": "请从这里开始",
 		"title-img": "background-color:#556;background-image: linear-gradient(30deg, #445 12%, transparent 12.5%, transparent 87%, #445 87.5%, #445),linear-gradient(150deg, #445 12%, transparent 12.5%, transparent 87%, #445 87.5%, #445),linear-gradient(30deg, #445 12%, transparent 12.5%, transparent 87%, #445 87.5%, #445),linear-gradient(150deg, #445 12%, transparent 12.5%, transparent 87%, #445 87.5%, #445),linear-gradient(60deg, #99a 25%, transparent 25.5%, transparent 75%, #99a 75%, #99a),linear-gradient(60deg, #99a 25%, transparent 25.5%, transparent 75%, #99a 75%, #99a);background-size:80px 140px;background-position: 0 0, 0 0, 40px 70px, 40px 70px, 0 0, 40px 70px;",
 		"type": "doc",
-		"updated": "20241018102134"
+		"updated": "20241125224159"
 	},
 	"Children": [
+		{
+			"ID": "20241125224159-8zf3bos",
+			"Type": "NodeBlockquote",
+			"Properties": {
+				"id": "20241125224159-8zf3bos",
+				"updated": "20241125224159"
+			},
+			"Children": [
+				{
+					"Type": "NodeBlockquoteMarker",
+					"Data": "\u003e"
+				},
+				{
+					"ID": "20241125224159-vcaj2bv",
+					"Type": "NodeParagraph",
+					"Properties": {
+						"id": "20241125224159-vcaj2bv",
+						"updated": "20241125224159"
+					},
+					"Children": [
+						{
+							"Type": "NodeText",
+							"Data": "关闭思源笔记时会自动关闭用户指南,可以点击 "
+						},
+						{
+							"Type": "NodeTextMark",
+							"TextMarkType": "kbd",
+							"TextMarkTextContent": "主菜单"
+						},
+						{
+							"Type": "NodeText",
+							"Data": " - "
+						},
+						{
+							"Type": "NodeTextMark",
+							"TextMarkType": "kbd",
+							"TextMarkTextContent": "用户指南"
+						},
+						{
+							"Type": "NodeText",
+							"Data": " 再次查看"
+						}
+					]
+				}
+			]
+		},
 		{
 			"ID": "20210528115012-vst5lwt",
 			"Type": "NodeSuperBlock",

+ 47 - 1
app/guide/20211226090932-5lcq56f/20211226115423-d5z1joq.sy

@@ -8,9 +8,55 @@
 		"title": "請從這裡開始",
 		"title-img": "background-color:#556;background-image: linear-gradient(30deg, #445 12%, transparent 12.5%, transparent 87%, #445 87.5%, #445),linear-gradient(150deg, #445 12%, transparent 12.5%, transparent 87%, #445 87.5%, #445),linear-gradient(30deg, #445 12%, transparent 12.5%, transparent 87%, #445 87.5%, #445),linear-gradient(150deg, #445 12%, transparent 12.5%, transparent 87%, #445 87.5%, #445),linear-gradient(60deg, #99a 25%, transparent 25.5%, transparent 75%, #99a 75%, #99a),linear-gradient(60deg, #99a 25%, transparent 25.5%, transparent 75%, #99a 75%, #99a);background-size:80px 140px;background-position: 0 0, 0 0, 40px 70px, 40px 70px, 0 0, 40px 70px;",
 		"type": "doc",
-		"updated": "20241018105300"
+		"updated": "20241125224503"
 	},
 	"Children": [
+		{
+			"ID": "20241125224503-8vqkbc9",
+			"Type": "NodeBlockquote",
+			"Properties": {
+				"id": "20241125224503-8vqkbc9",
+				"updated": "20241125224503"
+			},
+			"Children": [
+				{
+					"Type": "NodeBlockquoteMarker",
+					"Data": "\u003e"
+				},
+				{
+					"ID": "20241125224503-fqq0875",
+					"Type": "NodeParagraph",
+					"Properties": {
+						"id": "20241125224503-fqq0875",
+						"updated": "20241125224503"
+					},
+					"Children": [
+						{
+							"Type": "NodeText",
+							"Data": "關閉思源筆記時會自動關閉用戶指南,可以點擊 "
+						},
+						{
+							"Type": "NodeTextMark",
+							"TextMarkType": "kbd",
+							"TextMarkTextContent": "主菜單"
+						},
+						{
+							"Type": "NodeText",
+							"Data": " - "
+						},
+						{
+							"Type": "NodeTextMark",
+							"TextMarkType": "kbd",
+							"TextMarkTextContent": "用戶指南"
+						},
+						{
+							"Type": "NodeText",
+							"Data": " ​再次查看"
+						}
+					]
+				}
+			]
+		},
 		{
 			"ID": "20211226115745-rorv31l",
 			"Type": "NodeSuperBlock",

+ 47 - 1
app/guide/20240530133126-axarxgx/20240530101000-4qitucx.sy

@@ -9,9 +9,55 @@
 		"title": "スタートガイド",
 		"title-img": "background-color:#556;background-image: linear-gradient(30deg, #445 12%, transparent 12.5%, transparent 87%, #445 87.5%, #445),linear-gradient(150deg, #445 12%, transparent 12.5%, transparent 87%, #445 87.5%, #445),linear-gradient(30deg, #445 12%, transparent 12.5%, transparent 87%, #445 87.5%, #445),linear-gradient(150deg, #445 12%, transparent 12.5%, transparent 87%, #445 87.5%, #445),linear-gradient(60deg, #99a 25%, transparent 25.5%, transparent 75%, #99a 75%, #99a),linear-gradient(60deg, #99a 25%, transparent 25.5%, transparent 75%, #99a 75%, #99a);background-size:80px 140px;background-position: 0 0, 0 0, 40px 70px, 40px 70px, 0 0, 40px 70px;",
 		"type": "doc",
-		"updated": "20241018102426"
+		"updated": "20241125225136"
 	},
 	"Children": [
+		{
+			"ID": "20241125224702-5a9o34q",
+			"Type": "NodeBlockquote",
+			"Properties": {
+				"id": "20241125224702-5a9o34q",
+				"updated": "20241125225136"
+			},
+			"Children": [
+				{
+					"Type": "NodeBlockquoteMarker",
+					"Data": "\u003e"
+				},
+				{
+					"ID": "20241125225100-s0akx3f",
+					"Type": "NodeParagraph",
+					"Properties": {
+						"id": "20241125225100-s0akx3f",
+						"updated": "20241125225136"
+					},
+					"Children": [
+						{
+							"Type": "NodeText",
+							"Data": "SiYuan を閉じると、ユーザーガイドは自動的に閉じられます。"
+						},
+						{
+							"Type": "NodeTextMark",
+							"TextMarkType": "kbd",
+							"TextMarkTextContent": "メインメニュー"
+						},
+						{
+							"Type": "NodeText",
+							"Data": " - "
+						},
+						{
+							"Type": "NodeTextMark",
+							"TextMarkType": "kbd",
+							"TextMarkTextContent": "ユーザーガイド"
+						},
+						{
+							"Type": "NodeText",
+							"Data": " ​をクリックすると、再度表示できます"
+						}
+					]
+				}
+			]
+		},
 		{
 			"ID": "20240530101000-x3xk6o7",
 			"Type": "NodeSuperBlock",

+ 3 - 3
app/src/assets/scss/base.scss

@@ -329,8 +329,8 @@ html {
     opacity: .38;
   }
 
-  .dragover__top:after,
-  .dragover__bottom:after {
+  .dragover__top::after,
+  .dragover__bottom::after {
     content: "";
     width: var(--file-toggle-width);
     height: 4px;
@@ -341,7 +341,7 @@ html {
     z-index: 1;
   }
 
-  .dragover__top:after {
+  .dragover__top::after {
     top: -3px;
     bottom: auto;
   }

+ 1 - 1
app/src/assets/scss/business/_av.scss

@@ -29,7 +29,7 @@
     background: var(--b3-border-color);
     border-radius: var(--b3-border-radius);
 
-    &:after {
+    &::after {
       content: " ";
       border-radius: var(--b3-border-radius);
       position: absolute;

+ 12 - 1
app/src/assets/scss/business/_drag.scss

@@ -1,5 +1,16 @@
 .dragover {
-  background-color: var(--b3-theme-primary-lightest) !important;
+
+  &::after {
+    background-color: var(--b3-theme-primary-lightest);
+    position: absolute;
+    width: 100%;
+    height: 100%;
+    content: " ";
+    left: 0;
+    top: 0;
+    z-index: 3;
+    pointer-events: none;
+  }
 
   // 需要 !important,否则拖拽到闪卡无效果
   &__top {

+ 1 - 1
app/src/assets/scss/business/_export.scss

@@ -7,7 +7,7 @@
     position: relative;
     z-index: 1;
 
-    [data-node-id].li[fold="1"] > .protyle-action:after {
+    [data-node-id].li[fold="1"] > .protyle-action::after {
       background-color: transparent;
       border: 1px solid var(--b3-list-hover);
       box-sizing: border-box;

+ 8 - 8
app/src/assets/scss/business/_layout.scss

@@ -2,7 +2,7 @@
   overflow-y: hidden; // https://github.com/siyuan-note/siyuan/issues/6706
 
   &__wnd--active .layout-tab-bar .item--focus {
-    &:after {
+    &::after {
       background-color: var(--b3-theme-primary);
     }
 
@@ -111,11 +111,11 @@
     position: relative;
     z-index: 4; // 需大于 #sidebarContainer https://github.com/siyuan-note/siyuan/issues/11759
 
-    &:hover:after {
+    &:hover::after {
       background-color: var(--b3-scroll-color);
     }
 
-    &:after {
+    &::after {
       content: "";
       width: 100%;
       height: .5px;
@@ -133,7 +133,7 @@
       margin: 0 -6px 0 0;
       height: auto;
 
-      &:after {
+      &::after {
         top: 0;
         width: .5px;
         left: 0;
@@ -226,7 +226,7 @@
           max-width: none;
           flex: 1;
 
-          &.item--focus:after {
+          &.item--focus::after {
             background-color: var(--b3-theme-primary);
           }
         }
@@ -268,7 +268,7 @@
           }
         }
 
-        &--focus:after {
+        &--focus::after {
           content: "";
           width: 100%;
           height: 3px;
@@ -384,12 +384,12 @@
     padding: 0;
 
     .dock__item {
-      &[data-index="0"]:after {
+      &[data-index="0"]::after {
         top: -32px;
         bottom: auto;
       }
 
-      &[data-index="1"]:after {
+      &[data-index="1"]::after {
         bottom: 100%;
       }
     }

+ 3 - 3
app/src/assets/scss/business/_search.scss

@@ -24,7 +24,7 @@
         margin: 0 -6px 0 0;
         height: auto;
 
-        &:after {
+        &::after {
           width: .5px;
           height: 100%;
         }
@@ -104,7 +104,7 @@
     position: relative;
     z-index: 2;
 
-    &:after {
+    &::after {
       content: "";
       display: block;
       background-color: var(--b3-theme-surface-lighter);
@@ -116,7 +116,7 @@
       height: .5px;
     }
 
-    &:hover:after {
+    &:hover::after {
       background-color: var(--b3-scroll-color);
     }
   }

+ 1 - 1
app/src/assets/scss/component/_list.scss

@@ -27,7 +27,7 @@
       position: relative;
     }
 
-    .b3-list__panel:before {
+    .b3-list__panel::before {
       content: "";
       height: 100%;
       top: 0;

+ 1 - 1
app/src/assets/scss/component/_snackbar.scss

@@ -41,7 +41,7 @@
   &--error .b3-snackbar__content {
     padding-left: 47px;
 
-    &:after {
+    &::after {
       content: "❗";
       position: absolute;
       left: 16px;

+ 10 - 10
app/src/assets/scss/component/_switch.scss

@@ -24,7 +24,7 @@
     align-self: center;
   }
 
-  &:after {
+  &::after {
     width: 8px;
     height: 8px;
     border-radius: 50%;
@@ -40,7 +40,7 @@
     transition: left 80ms linear, background-color 80ms linear, width 80ms linear, height 80ms linear;
   }
 
-  &:before {
+  &::before {
     z-index: 2;
     content: "";
     left: 7px;
@@ -59,28 +59,28 @@
     background-color: var(--b3-switch-checked-background);
     border-color: transparent;
 
-    &:after {
+    &::after {
       background-color: var(--b3-switch-checked);
       height: 12px;
       width: 12px;
     }
 
-    &:before,
-    &:after {
+    &::before,
+    &::after {
       left: 17px;
     }
 
     &:hover:not(:disabled) {
-      &:after {
+      &::after {
         background-color: var(--b3-switch-checked-hover);
       }
 
-      &:before {
+      &::before {
         background-color: var(--b3-switch-checked-hover2);
       }
     }
 
-    &:active:not(:disabled):after {
+    &:active:not(:disabled)::after {
       height: 14px;
       width: 14px;
     }
@@ -91,12 +91,12 @@
     cursor: auto;
   }
 
-  &:active:not(:disabled):after {
+  &:active:not(:disabled)::after {
     height: 10px;
     width: 10px;
   }
 
-  &:hover:not(:disabled):before {
+  &:hover:not(:disabled)::before {
     display: inline-block;
   }
 }

+ 2 - 2
app/src/assets/scss/component/_tooltips.scss

@@ -197,8 +197,8 @@
 }
 
 @media screen and (max-width: 520px) {
-  .b3-tooltips:before,
-  .b3-tooltips:after {
+  .b3-tooltips::before,
+  .b3-tooltips::after {
     content: none;
   }
 }

+ 2 - 2
app/src/assets/scss/component/_typography.scss

@@ -123,7 +123,7 @@
 
   blockquote,
   .bq {
-    &:before {
+    &::before {
       content: '';
       background-color: var(--b3-theme-surface-lighter);
       width: .25em;
@@ -345,7 +345,7 @@
       display: flex;
       align-items: baseline; // https://ld246.com/article/1645933216334
 
-      &:before {
+      &::before {
         content: "";
         flex: 1;
       }

+ 2 - 2
app/src/assets/scss/pdf/_pdf.scss

@@ -234,7 +234,7 @@
   cursor: ew-resize;
   right: -6px;
 
-  &:after {
+  &::after {
     content: "";
     width: 1px;
     height: 100%;
@@ -244,7 +244,7 @@
     left: 3px;
   }
 
-  &:hover:after {
+  &:hover::after {
     background-color: var(--b3-scroll-color);
   }
 }

+ 3 - 3
app/src/assets/scss/protyle/_protyle.scss

@@ -390,7 +390,7 @@
     padding: 0 4px 0 2px;
     white-space: break-spaces;
 
-    &:empty:after {
+    &:empty::after {
       content: attr(data-tip);
       color: var(--b3-theme-on-surface-light);
       cursor: text;
@@ -420,11 +420,11 @@ table[contenteditable="true"] + .protyle-action__table {
     cursor: col-resize;
     z-index: 2;
 
-    &:hover:after {
+    &:hover::after {
       background-color: var(--b3-theme-primary-light);
     }
 
-    &:after {
+    &::after {
       top: 0;
       width: 2px;
       left: 2px;

+ 22 - 29
app/src/assets/scss/protyle/_wysiwyg.scss

@@ -5,7 +5,7 @@
   user-select: auto;
   overflow-x: clip;
 
-  &--empty:empty:before {
+  &--empty:empty::before {
     color: var(--b3-empty-color);
     content: attr(placeholder);
   }
@@ -43,7 +43,7 @@
         position: relative;
         height: 26px;
 
-        &:after {
+        &::after {
           position: absolute;
           content: " ";
           height: 1px;
@@ -86,14 +86,14 @@
       display: flex;
       flex-direction: column;
 
-      &:before {
+      &::before {
         content: "";
         position: absolute;
         border-left: .5px solid var(--b3-theme-background-light);
         left: 17px;
       }
 
-      &:hover:before {
+      &:hover::before {
         border-left-color: var(--b3-scroll-color);
       }
 
@@ -111,11 +111,11 @@
       }
 
       &[fold="1"] {
-        &:before {
+        &::before {
           content: none;
         }
 
-        & > .protyle-action:after {
+        & > .protyle-action::after {
           background-color: var(--b3-list-hover);
         }
 
@@ -147,7 +147,7 @@
           padding-bottom: 0;
         }
 
-        &:after {
+        &::after {
           content: "";
           position: absolute;
           border-radius: 50%;
@@ -163,7 +163,7 @@
           position: relative;
         }
 
-        &--order:after {
+        &--order::after {
           border-radius: var(--b3-border-radius);
         }
       }
@@ -177,11 +177,11 @@
         display: flex;
         flex-wrap: wrap;
         justify-content: space-between;
+        column-gap: 1.5em;
 
         & > div {
-          flex: 1;
+          flex: 1 0 10%;
           box-sizing: border-box;
-          margin-right: 24px;
           min-width: 1px;
 
           &:nth-last-child(2),
@@ -334,7 +334,7 @@
         filter: brightness(.68);
       }
 
-      &--drag > span:after {
+      &--drag > span::after {
         content: "";
         position: absolute;
         top: 0;
@@ -363,8 +363,8 @@
           box-shadow: 0 0 1px 1px var(--b3-theme-on-background);
         }
 
-        &:before,
-        &:after {
+        &::before,
+        &::after {
           position: absolute;
           width: 4px;
           content: "";
@@ -372,7 +372,7 @@
           left: -4px;
         }
 
-        &:after {
+        &::after {
           left: 4px;
           width: 8px
         }
@@ -409,7 +409,7 @@
       }
 
       &--drag {
-        .iframe-content:after {
+        .iframe-content::after {
           content: "";
           position: absolute;
           top: 0;
@@ -433,7 +433,7 @@
         box-shadow: 0 0 1px 1px var(--b3-theme-on-surface);
         box-sizing: border-box;
 
-        &:after {
+        &::after {
           content: "";
           background-color: var(--b3-theme-surface);
           width: 32px;
@@ -449,7 +449,7 @@
         }
 
         &:hover,
-        &:hover:after {
+        &:hover::after {
           background-color: var(--b3-theme-background);
           box-shadow: 0 0 1px 1px var(--b3-theme-on-background);
         }
@@ -459,17 +459,10 @@
 
   &--select,
   &--hl {
-    &:after {
-      background-color: var(--b3-theme-primary-lightest);
-      position: absolute;
-      width: 100%;
-      height: 100%;
-      content: " ";
-      left: 0;
-      top: 0;
+    @extend .dragover;
+
+    &::after {
       border-radius: var(--b3-border-radius);
-      z-index: 3;
-      pointer-events: none;
     }
   }
 
@@ -515,7 +508,7 @@
     min-height: 26px !important;
   }
 
-  div[fold="1"][data-type="NodeHeading"]:before {
+  div[fold="1"][data-type="NodeHeading"]::before {
     content: "";
     height: 16px !important;
     width: 16px;
@@ -600,7 +593,7 @@
     cursor: pointer;
 
     &:hover {
-      &:after {
+      &::after {
         background-color: var(--b3-theme-background-light);
       }
 

+ 3 - 3
app/src/block/Panel.ts

@@ -337,8 +337,8 @@ export class BlockPanel {
 
         this.element.querySelector(".block__content").addEventListener("scroll", () => {
             this.editors.forEach(item => {
-                hideElements(["gutter"], item.protyle)
-            })
-        })
+                hideElements(["gutter"], item.protyle);
+            });
+        });
     }
 }

+ 18 - 0
app/src/config/keymap.ts

@@ -54,6 +54,24 @@ export const keymap = {
     <input data-key="plugin${Constants.ZWSP}${item.name}${Constants.ZWSP}${command.langKey}" data-value="${command.customHotkey}" data-default="${command.hotkey}" class="b3-text-field fn__none" value="${keyValue}" spellcheck="false">
 </label>`;
             });
+            item.updateProtyleToolbar([]).forEach(toolbarItem => {
+                if (typeof toolbarItem === "string" || Constants.INLINE_TYPE.concat("|").includes(toolbarItem.name) || !toolbarItem.hotkey) {
+                    return
+                }
+                const dockKeymap = window.siyuan.config.keymap.plugin[item.name][toolbarItem.name];
+                const keyValue = updateHotkeyTip(dockKeymap.custom);
+                commandHTML += `<label class="b3-list-item b3-list-item--narrow b3-list-item--hide-action">
+    <span class="b3-list-item__text">${toolbarItem.tip||window.siyuan.languages[toolbarItem.lang]}</span>
+    <span data-type="reset" class="b3-list-item__action b3-tooltips b3-tooltips__w" aria-label="${window.siyuan.languages.reset}">
+        <svg><use xlink:href="#iconUndo"></use></svg>
+    </span>
+    <span data-type="clear" class="b3-list-item__action b3-tooltips b3-tooltips__w" aria-label="${window.siyuan.languages.remove}">
+        <svg><use xlink:href="#iconTrashcan"></use></svg>
+    </span>
+    <span data-type="update" class="config-keymap__key">${keyValue}</span>
+    <input data-key="plugin${Constants.ZWSP}${item.name}${Constants.ZWSP}${toolbarItem.name}" data-value="${dockKeymap.custom}" data-default="${dockKeymap.default}" class="b3-text-field fn__none" value="${keyValue}" spellcheck="false">
+</label>`;
+            })
             Object.keys(item.docks).forEach(key => {
                 const dockConfig = item.docks[key].config;
                 if (!dockConfig.hotkey) {

+ 3 - 2
app/src/config/search.ts

@@ -86,7 +86,7 @@ export const initConfigSearch = (element: HTMLElement, app: App) => {
         getLang(["cloudStorage", "trafficStat", "sync", "backup", "cdn", "total", "sizeLimit", "cloudBackup",
             "cloudBackupTip", "updatePath", "cloudSync", "upload", "download", "syncMode", "syncModeTip",
             "generateConflictDoc", "generateConflictDocTip", "syncProvider", "syncProviderTip",
-            "syncMode1", "syncMode2", "reposTip", "openSyncTip1", "openSyncTip2", "cloudSyncDir", "config"]),
+            "syncMode1", "syncMode2", "reposTip", "openSyncTip1", "openSyncTip2", "cloudSyncDir", "cloudSyncDirTip", "config"]),
 
         // 发布
         getLang(["publishService", "publishServiceTip", "publishServicePort", "publishServicePortTip",
@@ -100,7 +100,8 @@ export const initConfigSearch = (element: HTMLElement, app: App) => {
             "systemLog", "importKey", "genKey", "genKeyByPW", "copyKey", "resetRepo", "systemLogTip", "export",
             "downloadLatestVer", "safeQuit", "directConnection", "siyuanNote", "key", "password", "copied", "resetRepoTip",
             "autoDownloadUpdatePkg", "autoDownloadUpdatePkgTip", "networkProxy", "keyPlaceholder", "initRepoKeyTip",
-            "googleAnalytics", "googleAnalyticsTip"]),
+            "googleAnalytics", "googleAnalyticsTip", "dataRepoPurge", "dataRepoPurgeTip", "dataRepoAutoPurgeIndexRetentionDays",
+            "dataRepoAutoPurgeRetentionIndexesDaily"]),
     ];
     const inputElement = element.querySelector(".b3-form__icon input") as HTMLInputElement;
     /// #if !BROWSER

+ 1 - 1
app/src/dialog/tooltip.ts

@@ -44,7 +44,7 @@ export const showTooltip = (message: string, target: Element, tooltipClass?: str
         left = parentRect.right + 8;
     } else if (position?.endsWith("parentW")) {
         // 数据库属性视图
-        top = parentRect.top + parseInt(position) || 8;
+        top = parentRect.top + (parseInt(position) || 8);
         left = parentRect.left - messageElement.clientWidth;
     }
 

+ 1 - 1
app/src/layout/dock/Files.ts

@@ -737,7 +737,7 @@ export class Files extends Model {
     }
 
     private updateDocInfo(data: IWebSocketData) {
-        const liElement = this.element.querySelector(`li[data-node-id="${data.data.rootID}"]`)
+        const liElement = this.element.querySelector(`li[data-node-id="${data.data.rootID}"]`);
         if (liElement) {
             liElement.setAttribute("data-count", data.data.subFileCount);
             liElement.querySelector(".ariaLabel")?.setAttribute("aria-label", this.genDocAriaLabel(data.data, escapeGreat));

+ 4 - 1
app/src/menus/util.ts

@@ -172,9 +172,12 @@ export const openEditorTab = (app: App, ids: string[], notebookId?: string, path
 };
 
 export const copyPNGByLink = (link: string) => {
-    if (isInAndroid() || isInHarmony()) {
+    if (isInAndroid()) {
         window.JSAndroid.writeImageClipboard(link);
         return;
+    } else if (isInHarmony()) {
+        window.JSHarmony.writeImageClipboard(link);
+        return;
     } else {
         const canvas = document.createElement("canvas");
         const tempElement = document.createElement("img");

+ 1 - 1
app/src/mobile/index.ts

@@ -34,7 +34,7 @@ class App {
     public appId: string;
 
     constructor() {
-        if (!window.webkit?.messageHandlers && !window.JSAndroid) {
+        if (!window.webkit?.messageHandlers && !window.JSAndroid && !window.JSHarmony) {
             registerServiceWorker(`${Constants.SERVICE_WORKER_PATH}?v=${Constants.SIYUAN_VERSION}`);
         }
         addBaseURL();

+ 24 - 0
app/src/plugin/index.ts

@@ -15,6 +15,7 @@ import {hasClosestByAttribute} from "../protyle/util/hasClosest";
 import {BlockPanel} from "../block/Panel";
 import {Setting} from "./Setting";
 import {clearOBG} from "../layout/dock/util";
+import {Constants} from "../constants";
 
 export class Plugin {
     private app: App;
@@ -75,6 +76,29 @@ export class Plugin {
             value: options.name,
             writable: false,
         });
+
+        this.updateProtyleToolbar([]).forEach(toolbarItem => {
+            if (typeof toolbarItem === "string" || Constants.INLINE_TYPE.concat("|").includes(toolbarItem.name) || !toolbarItem.hotkey) {
+                return
+            }
+            if (!window.siyuan.config.keymap.plugin) {
+                window.siyuan.config.keymap.plugin = {};
+            }
+            if (!window.siyuan.config.keymap.plugin[options.name]) {
+                window.siyuan.config.keymap.plugin[options.name] = {
+                    [toolbarItem.name]: {
+                        default: toolbarItem.hotkey,
+                        custom: toolbarItem.hotkey,
+                    }
+                };
+            }
+            if (!window.siyuan.config.keymap.plugin[options.name][toolbarItem.name]) {
+                window.siyuan.config.keymap.plugin[options.name][toolbarItem.name] = {
+                    default: toolbarItem.hotkey,
+                    custom: toolbarItem.hotkey,
+                }
+            }
+        })
     }
 
     public onload() {

+ 6 - 2
app/src/protyle/gutter/index.ts

@@ -1653,8 +1653,10 @@ export class Gutter {
                 label: `${window.siyuan.languages.copy} ${window.siyuan.languages.headings1}`,
                 click() {
                     fetchPost("/api/block/getHeadingChildrenDOM", {id}, (response) => {
-                        if (isInAndroid() || isInHarmony()) {
+                        if (isInAndroid()) {
                             window.JSAndroid.writeHTMLClipboard(protyle.lute.BlockDOM2StdMd(response.data).trimEnd(), response.data + Constants.ZWSP);
+                        } else if (isInHarmony()) {
+                            window.JSHarmony.writeHTMLClipboard(protyle.lute.BlockDOM2StdMd(response.data).trimEnd(), response.data + Constants.ZWSP);
                         } else {
                             writeText(response.data + Constants.ZWSP);
                         }
@@ -1667,8 +1669,10 @@ export class Gutter {
                 label: `${window.siyuan.languages.cut} ${window.siyuan.languages.headings1}`,
                 click() {
                     fetchPost("/api/block/getHeadingChildrenDOM", {id}, (response) => {
-                        if (isInAndroid() || isInHarmony()) {
+                        if (isInAndroid()) {
                             window.JSAndroid.writeHTMLClipboard(protyle.lute.BlockDOM2StdMd(response.data).trimEnd(), response.data + Constants.ZWSP);
+                        } else if (isInHarmony()) {
+                            window.JSHarmony.writeHTMLClipboard(protyle.lute.BlockDOM2StdMd(response.data).trimEnd(), response.data + Constants.ZWSP);
                         } else {
                             writeText(response.data + Constants.ZWSP);
                         }

+ 48 - 12
app/src/protyle/render/av/relation.ts

@@ -8,6 +8,7 @@ import {updateCellsValue} from "./cell";
 import {updateAttrViewCellAnimation} from "./action";
 import {focusBlock} from "../../util/selection";
 import {setPosition} from "../../../util/setPosition";
+import * as dayjs from "dayjs";
 
 const genSearchList = (element: Element, keyword: string, avId?: string, excludes = true, cb?: () => void) => {
     fetchPost("/api/av/searchAttributeView", {
@@ -213,6 +214,11 @@ const genSelectItemHTML = (type: "selected" | "empty" | "unselect", id?: string,
 <svg class="b3-menu__action"><use xlink:href="#iconMin"></use></svg>`;
     }
     if (type === "empty") {
+        if (id) {
+            return `<button class="b3-menu__item" data-type="setRelationCell">
+    <span class="b3-menu__label">${window.siyuan.languages.newRowInRelation.replace('${x}', text).replace('${y}', id)}</span>
+</button>`;
+        }
         return `<button class="b3-menu__item">
     <span class="b3-menu__label">${window.siyuan.languages.emptyContent}</span>
 </button>`;
@@ -234,8 +240,12 @@ const filterItem = (menuElement: Element, cellElement: HTMLElement, keyword: str
         let html = "";
         let selectHTML = "";
         const hasIds: string[] = [];
+        let selectHasShowItem = false;
         cellElement.querySelectorAll("span").forEach((item) => {
             hasIds.push(item.dataset.id);
+            if (item.textContent.indexOf(keyword) > -1) {
+                selectHasShowItem = true;
+            }
             selectHTML += `<button data-id="${item.dataset.id}" data-type="setRelationCell" class="b3-menu__item${item.textContent.indexOf(keyword) > -1 ? "" : " fn__none"}" draggable="true">${genSelectItemHTML("selected", item.dataset.id, !item.classList.contains("av__celltext--ref"), item.textContent || window.siyuan.languages.untitled)}</button>`;
         });
         cells.forEach((item) => {
@@ -243,9 +253,9 @@ const filterItem = (menuElement: Element, cellElement: HTMLElement, keyword: str
                 html += genSelectItemHTML("unselect", item.block.id, item.isDetached, item.block.content || window.siyuan.languages.untitled);
             }
         });
-        menuElement.querySelector(".b3-menu__items").innerHTML = `${selectHTML || genSelectItemHTML("empty")}
+        menuElement.querySelector(".b3-menu__items").innerHTML = `${selectHTML}
 <button class="b3-menu__separator"></button>
-${html || genSelectItemHTML("empty")}`;
+${html || genSelectItemHTML("empty", selectHasShowItem ? "" : keyword, undefined, menuElement.querySelector(".popover__block").outerHTML)}`;
         menuElement.querySelector(".b3-menu__items .b3-menu__item:not(.fn__none)").classList.add("b3-menu__item--current");
     });
 };
@@ -273,7 +283,7 @@ export const bindRelationEvent = (options: {
                 html += genSelectItemHTML("unselect", item.block.id, item.isDetached, item.block.content || window.siyuan.languages.untitled);
             }
         });
-        options.menuElement.querySelector(".b3-menu__items").innerHTML = `${selectHTML || genSelectItemHTML("empty")}
+        options.menuElement.querySelector(".b3-menu__items").innerHTML = `${selectHTML}
 <button class="b3-menu__separator"></button>
 ${html || genSelectItemHTML("empty")}`;
         const cellRect = options.cellElements[options.cellElements.length - 1].getBoundingClientRect();
@@ -340,7 +350,7 @@ export const getRelationHTML = (data: IAV, cellElements?: HTMLElement[]) => {
 };
 
 export const setRelationCell = (protyle: IProtyle, nodeElement: HTMLElement, target: HTMLElement, cellElements: HTMLElement[]) => {
-    const menuElement = hasClosestByClassName(target, "b3-menu__items");
+    const menuElement = hasClosestByClassName(target, "b3-menu");
     if (!menuElement) {
         return;
     }
@@ -353,7 +363,7 @@ export const setRelationCell = (protyle: IProtyle, nodeElement: HTMLElement, tar
     }
     if (!nodeElement.contains(cellElements[0])) {
         cellElements[0] = (nodeElement.querySelector(`.av__row[data-id="${rowElement.dataset.id}"] .av__cell[data-col-id="${cellElements[0].dataset.colId}"]`) ||
-            nodeElement.querySelector(`.fn__flex-1[data-col-id="${cellElements[0].dataset.colId}"]`) ) as HTMLElement;
+            nodeElement.querySelector(`.fn__flex-1[data-col-id="${cellElements[0].dataset.colId}"]`)) as HTMLElement;
     }
     const newValue: IAVCellRelationValue = {blockIDs: [], contents: []};
     menuElement.querySelectorAll('[draggable="true"]').forEach(item => {
@@ -380,13 +390,7 @@ export const setRelationCell = (protyle: IProtyle, nodeElement: HTMLElement, tar
             newValue.contents.splice(removeIndex, 1);
             separatorElement.after(target);
             target.outerHTML = genSelectItemHTML("unselect", targetId, !target.querySelector(".popover__block"), target.querySelector(".b3-menu__label").textContent);
-            if (!separatorElement.previousElementSibling) {
-                separatorElement.insertAdjacentHTML("beforebegin", genSelectItemHTML("empty"));
-            }
-        } else {
-            if (!separatorElement.previousElementSibling.getAttribute("data-id")) {
-                separatorElement.previousElementSibling.remove();
-            }
+        } else if (targetId) {
             newValue.blockIDs.push(targetId);
             newValue.contents.push({
                 type: "block",
@@ -401,6 +405,38 @@ export const setRelationCell = (protyle: IProtyle, nodeElement: HTMLElement, tar
             if (!separatorElement.nextElementSibling) {
                 separatorElement.insertAdjacentHTML("afterend", genSelectItemHTML("empty"));
             }
+        } else {
+            const blockID = target.querySelector(".popover__block").getAttribute("data-id");
+            const content = target.querySelector("b").textContent
+            const rowId = Lute.NewNodeID();
+            transaction(protyle, [{
+                action: "insertAttrViewBlock",
+                avID: menuElement.firstElementChild.getAttribute("data-av-id"),
+                srcs: [{
+                    id: rowId,
+                    isDetached: true,
+                    content
+                }],
+                blockID,
+            }, {
+                action: "doUpdateUpdated",
+                id: blockID,
+                data: dayjs().format("YYYYMMDDHHmmss"),
+            }]);
+            newValue.blockIDs.push(rowId);
+            newValue.contents.push({
+                type: "block",
+                block: {
+                    id: rowId,
+                    content
+                },
+                isDetached: true
+            });
+            separatorElement.before(target);
+            target.outerHTML = `<button data-id="${rowId}" data-type="setRelationCell" class="b3-menu__item" draggable="true">${genSelectItemHTML("selected", rowId, true, content)}</button>`;
+            if (!separatorElement.nextElementSibling) {
+                separatorElement.insertAdjacentHTML("afterend", genSelectItemHTML("empty"));
+            }
         }
         menuElement.querySelector(".b3-menu__item--current")?.classList.remove("b3-menu__item--current");
         menuElement.querySelector(".b3-menu__items .b3-menu__item:not(.fn__none)").classList.add("b3-menu__item--current");

+ 1 - 1
app/src/protyle/render/flowchartRender.ts

@@ -18,7 +18,7 @@ export const flowchartRender = (element: Element, cdn = Constants.PROTYLE_CDN) =
     if (flowchartElements.length === 0) {
         return;
     }
-    addScript(`${cdn}/js/flowchart.js/flowchart.min.js?v=0.0.0`, "protyleFlowchartScript").then(() => {
+    addScript(`${cdn}/js/flowchart.js/flowchart.min.js?v=1.18.0`, "protyleFlowchartScript").then(() => {
         if (flowchartElements[0].firstElementChild.clientWidth === 0) {
             const observer = new MutationObserver(() => {
                 initFlowchart(flowchartElements);

+ 18 - 4
app/src/protyle/toolbar/index.ts

@@ -69,7 +69,14 @@ export class Toolbar {
         /// #endif
         this.toolbarHeight = 29;
         protyle.app.plugins.forEach(item => {
-            options.toolbar = toolbarKeyToMenu(item.updateProtyleToolbar(options.toolbar));
+            const pluginToolbar = item.updateProtyleToolbar(options.toolbar)
+            pluginToolbar.forEach(toolbarItem => {
+                if (typeof toolbarItem === "string" || Constants.INLINE_TYPE.concat("|").includes(toolbarItem.name) || !toolbarItem.hotkey) {
+                    return
+                }
+                toolbarItem.hotkey = window.siyuan.config.keymap.plugin[item.name][toolbarItem.name].custom;
+            })
+            options.toolbar = toolbarKeyToMenu(pluginToolbar);
         });
         options.toolbar.forEach((menuItem: IMenuItem) => {
             const itemElement = this.genItem(protyle, menuItem);
@@ -81,7 +88,14 @@ export class Toolbar {
         this.element.innerHTML = "";
         protyle.options.toolbar = toolbarKeyToMenu(Constants.PROTYLE_TOOLBAR);
         protyle.app.plugins.forEach(item => {
-            protyle.options.toolbar = toolbarKeyToMenu(item.updateProtyleToolbar(protyle.options.toolbar));
+            const pluginToolbar = item.updateProtyleToolbar(protyle.options.toolbar)
+            pluginToolbar.forEach(toolbarItem => {
+                if (typeof toolbarItem === "string" || Constants.INLINE_TYPE.concat("|").includes(toolbarItem.name) || !toolbarItem.hotkey) {
+                    return
+                }
+                toolbarItem.hotkey = window.siyuan.config.keymap.plugin[item.name][toolbarItem.name].custom;
+            })
+            protyle.options.toolbar = toolbarKeyToMenu(pluginToolbar);
         });
         protyle.options.toolbar.forEach((menuItem: IMenuItem) => {
             const itemElement = this.genItem(protyle, menuItem);
@@ -153,7 +167,7 @@ export class Toolbar {
         this.toolbarHeight = this.element.clientHeight;
         const y = rangePosition.top - this.toolbarHeight - 4;
         this.element.setAttribute("data-inity", y + Constants.ZWSP + protyle.contentElement.scrollTop.toString());
-        setPosition(this.element, rangePosition.left - 52, Math.max(y, protyle.element.getBoundingClientRect().top));
+        setPosition(this.element, rangePosition.left - 52, Math.max(y, protyle.element.getBoundingClientRect().top + 30));
         this.element.querySelectorAll(".protyle-toolbar__item--current").forEach(item => {
             item.classList.remove("protyle-toolbar__item--current");
         });
@@ -291,7 +305,7 @@ export class Toolbar {
         )) {
             // 移除
             if (type === "clear") {
-                toolbarElement.querySelectorAll('[data-type="em"],[data-type="u"],[data-type="s"],[data-type="mark"],[data-type="sup"],[data-type="sub"],[data-type="strong"]').forEach(item => {
+                toolbarElement.querySelectorAll('[data-type="strong"],[data-type="em"],[data-type="u"],[data-type="s"],[data-type="mark"],[data-type="sup"],[data-type="sub"],[data-type="kbd"],[data-type="mark"],[data-type="code"]').forEach(item => {
                     item.classList.remove("protyle-toolbar__item--current");
                 });
             } else if (actionBtn) {

+ 1 - 1
app/src/protyle/ui/initUI.ts

@@ -184,7 +184,7 @@ export const setPadding = (protyle: IProtyle) => {
     if (window.siyuan.config.editor.displayBookmarkIcon) {
         const editorAttrElement = document.getElementById("editorAttr");
         if (editorAttrElement) {
-            editorAttrElement.innerHTML = `.protyle-wysiwyg--attr .b3-tooltips:after { max-width: ${protyle.wysiwyg.element.clientWidth - left - right}px; }`;
+            editorAttrElement.innerHTML = `.protyle-wysiwyg--attr .b3-tooltips::after { max-width: ${protyle.wysiwyg.element.clientWidth - left - right}px; }`;
         }
     }
     const oldWidth = protyle.wysiwyg.element.getAttribute("data-realwidth");

+ 16 - 6
app/src/protyle/util/compatibility.ts

@@ -21,16 +21,20 @@ export const openByMobile = (uri: string) => {
                 window.webkit.messageHandlers.openLink.postMessage("https://" + uri);
             }
         }
-    } else if (isInAndroid() || isInHarmony()) {
+    } else if (isInAndroid()) {
         window.JSAndroid.openExternal(uri);
+    } else if (isInHarmony()) {
+        window.JSHarmony.openExternal(uri);
     } else {
         window.open(uri);
     }
 };
 
 export const readText = () => {
-    if (isInAndroid() || isInHarmony()) {
+    if (isInAndroid()) {
         return window.JSAndroid.readClipboard();
+    } else if (isInHarmony()) {
+        return window.JSHarmony.readClipboard();
     }
     return navigator.clipboard.readText();
 };
@@ -42,10 +46,14 @@ export const writeText = (text: string) => {
     }
     try {
         // navigator.clipboard.writeText 抛出异常不进入 catch,这里需要先处理移动端复制
-        if (isInAndroid() || isInHarmony()) {
+        if (isInAndroid()) {
             window.JSAndroid.writeClipboard(text);
             return;
         }
+        if (isInHarmony()) {
+            window.JSHarmony.writeClipboard(text);
+            return;
+        }
         if (isInIOS()) {
             window.webkit.messageHandlers.setClipboard.postMessage(text);
             return;
@@ -54,8 +62,10 @@ export const writeText = (text: string) => {
     } catch (e) {
         if (isInIOS()) {
             window.webkit.messageHandlers.setClipboard.postMessage(text);
-        } else if (isInAndroid() || isInHarmony()) {
+        } else if (isInAndroid()) {
             window.JSAndroid.writeClipboard(text);
+        } else if (isInHarmony()) {
+            window.JSHarmony.writeClipboard(text);
         } else {
             const textElement = document.createElement("textarea");
             textElement.value = text;
@@ -137,8 +147,8 @@ export const isInIOS = () => {
 };
 
 export const isInHarmony = () => {
-    return window.siyuan.config.system.container === "harmony";
-}
+    return window.siyuan.config.system.container === "harmony" && window.JSHarmony;
+};
 
 // Mac,Windows 快捷键展示
 export const updateHotkeyTip = (hotkey: string) => {

+ 6 - 1
app/src/protyle/util/paste.ts

@@ -452,9 +452,14 @@ export const paste = async (protyle: IProtyle, event: (ClipboardEvent | DragEven
             )) {
                 isHTML = false;
             } else {
-                // 需注意 Edge 中的选不应识别为图片 https://github.com/siyuan-note/siyuan/issues/7021
+                // 需注意 Edge 中的选不应识别为图片 https://github.com/siyuan-note/siyuan/issues/7021
                 isHTML = true;
             }
+
+            if (textPlain && "" !== textPlain.trim() && textHTML.startsWith("<span") && -1 < textHTML.indexOf("white-space: pre;")) {
+                // 豆包复制粘贴问题 https://github.com/siyuan-note/siyuan/issues/13265
+                isHTML = false;
+            }
         }
         if (isHTML) {
             const tempElement = document.createElement("div");

+ 17 - 18
app/src/protyle/wysiwyg/index.ts

@@ -4,7 +4,8 @@ import {
     hasClosestByAttribute,
     hasClosestByClassName,
     hasClosestByMatchTag,
-    hasTopClosestByClassName, isInEmbedBlock,
+    hasTopClosestByClassName,
+    isInEmbedBlock,
 } from "../util/hasClosest";
 import {
     focusBlock,
@@ -12,7 +13,8 @@ import {
     focusByWbr,
     focusSideBlock,
     getEditorRange,
-    getSelectionOffset, setFirstNodeRange,
+    getSelectionOffset,
+    setFirstNodeRange,
     setLastNodeRange,
 } from "../util/selection";
 import {Constants} from "../../constants";
@@ -23,7 +25,8 @@ import {
     contentMenu,
     enterBack,
     fileAnnotationRefMenu,
-    imgMenu, inlineMathMenu,
+    imgMenu,
+    inlineMathMenu,
     linkMenu,
     refMenu,
     setFold,
@@ -62,7 +65,7 @@ import {openGlobalSearch} from "../../search/util";
 import {popSearch} from "../../mobile/menu/search";
 /// #endif
 import {BlockPanel} from "../../block/Panel";
-import {isInIOS, isOnlyMeta, readText} from "../util/compatibility";
+import {isInIOS, isMac, isOnlyMeta, readText} from "../util/compatibility";
 import {MenuItem} from "../../menus/Menu";
 import {fetchPost} from "../../util/fetch";
 import {onGet} from "../util/onGet";
@@ -2005,14 +2008,6 @@ export class WYSIWYG {
         // 输入法测试点 https://github.com/siyuan-note/siyuan/issues/3027
         let isComposition = false; // for iPhone
         this.element.addEventListener("compositionstart", (event) => {
-            // 搜狗输入法划选输入后无 data https://github.com/siyuan-note/siyuan/issues/4672
-            const range = getEditorRange(protyle.wysiwyg.element);
-            const nodeElement = hasClosestBlock(range.startContainer);
-            if (nodeElement && typeof protyle.wysiwyg.lastHTMLs[nodeElement.getAttribute("data-node-id")] === "undefined") {
-                range.insertNode(document.createElement("wbr"));
-                protyle.wysiwyg.lastHTMLs[nodeElement.getAttribute("data-node-id")] = nodeElement.outerHTML;
-                nodeElement.querySelector("wbr").remove();
-            }
             isComposition = true;
             event.stopPropagation();
         });
@@ -2085,22 +2080,26 @@ export class WYSIWYG {
         this.element.addEventListener("keyup", (event) => {
             const range = getEditorRange(this.element).cloneRange();
             const nodeElement = hasClosestBlock(range.startContainer);
-            if (event.key !== "PageUp" && event.key !== "PageDown" && event.key !== "Home" && event.key !== "End" && event.key.indexOf("Arrow") === -1 &&
-                event.key !== "Alt" && event.key !== "Shift" && event.key !== "CapsLock" && event.key !== "Escape" && event.key !== "Meta" && !/^F\d{1,2}$/.test(event.key) &&
-                (!event.isComposing || (event.isComposing && range.toString() !== "")) // https://github.com/siyuan-note/siyuan/issues/4341
-            ) {
+
+            if (event.key !== "PageUp" && event.key !== "PageDown" && event.key !== "Home" && event.key !== "End" &&
+                event.key.indexOf("Arrow") === -1 && event.key !== "Escape" && event.key !== "Shift" &&
+                event.key !== "Meta" && event.key !== "Alt" && event.key !== "Control" && event.key !== "CapsLock" &&
+                !event.ctrlKey && !event.shiftKey && !event.metaKey && !event.altKey &&
+                !/^F\d{1,2}$/.test(event.key)) {
                 // 搜狗输入法不走 keydown,需重新记录历史状态
-                if (range.toString() === "" &&  // windows 下回车新建块输入abc,选中 bc ctrl+m 后光标错误
-                    nodeElement && typeof protyle.wysiwyg.lastHTMLs[nodeElement.getAttribute("data-node-id")] === "undefined") {
+                if (!isMac() && nodeElement &&
+                    (typeof protyle.wysiwyg.lastHTMLs[nodeElement.getAttribute("data-node-id")] === "undefined" || range.toString() !== "" || !this.preventKeyup)) {
                     range.insertNode(document.createElement("wbr"));
                     protyle.wysiwyg.lastHTMLs[nodeElement.getAttribute("data-node-id")] = nodeElement.outerHTML;
                     nodeElement.querySelector("wbr").remove();
                 }
+                this.preventKeyup = false;
                 return;
             }
 
             // 需放在 lastHTMLs 后,否则 https://github.com/siyuan-note/siyuan/issues/4388
             if (this.preventKeyup) {
+                this.preventKeyup = false;
                 return;
             }
 

+ 1 - 0
app/src/protyle/wysiwyg/input.ts

@@ -298,6 +298,7 @@ const updateInput = (html: string, protyle: IProtyle, id: string) => {
                 data: protyle.wysiwyg.lastHTMLs[id],
                 action: "update"
             });
+            protyle.wysiwyg.lastHTMLs[id] = item.outerHTML;
         } else {
             let firstElement;
             if (index === 0) {

+ 5 - 3
app/src/protyle/wysiwyg/keydown.ts

@@ -166,15 +166,17 @@ export const keydown = (protyle: IProtyle, editorElement: HTMLElement) => {
         // 有可能输入 shift+. ,因此需要使用 event.key 来进行判断
         if (event.key !== "PageUp" && event.key !== "PageDown" && event.key !== "Home" && event.key !== "End" && event.key.indexOf("Arrow") === -1 &&
             event.key !== "Escape" && event.key !== "Shift" && event.key !== "Meta" && event.key !== "Alt" && event.key !== "Control" && event.key !== "CapsLock" &&
-            !isNotEditBlock(nodeElement) &&
-            !/^F\d{1,2}$/.test(event.key) && typeof protyle.wysiwyg.lastHTMLs[nodeElement.getAttribute("data-node-id")] === "undefined") {
+            !isNotEditBlock(nodeElement) && !/^F\d{1,2}$/.test(event.key)) {
             const cloneRange = range.cloneRange();
+            range.collapse(false);
             range.insertNode(document.createElement("wbr"));
             protyle.wysiwyg.lastHTMLs[nodeElement.getAttribute("data-node-id")] = nodeElement.outerHTML;
             nodeElement.querySelector("wbr").remove();
             // 光标位于引用结尾后 ctrl+b 偶尔会失效
             range = cloneRange;
-            // 会导致  protyle.toolbar.range 和 range 不一致,先在有问题的地方重置一下 https://github.com/siyuan-note/siyuan/issues/10933
+            focusByRange(cloneRange);
+            protyle.toolbar.range = cloneRange;
+            protyle.wysiwyg.preventKeyup = true;
         }
 
         if (!window.siyuan.menus.menu.element.classList.contains("fn__none") &&

+ 2 - 0
app/src/protyle/wysiwyg/remove.ts

@@ -387,6 +387,8 @@ export const removeBlock = (protyle: IProtyle, blockElement: Element, range: Ran
         // 需先移除 removeElement,否则 side 会选中 removeElement
         removeElement.remove();
         focusBlock(previousLastElement, undefined, false);
+        // https://github.com/siyuan-note/siyuan/issues/13254
+        undoOperations.splice(0, 1);
     } else {
         const previousLastEditElement = getContenteditableElement(previousLastElement);
         if (editableElement && (editableElement.textContent !== "" || editableElement.querySelector(".emoji"))) {

+ 0 - 1
app/src/protyle/wysiwyg/transaction.ts

@@ -1121,7 +1121,6 @@ export const transaction = (protyle: IProtyle, doOperations: IOperation[], undoO
         protyle.transactionTime - time < Constants.TIMEOUT_INPUT) {
         needDebounce = true;
     }
-    protyle.wysiwyg.lastHTMLs = {};
     if (undoOperations) {
         if (window.siyuan.config.fileTree.openFilesUseCurrentTab && protyle.model) {
             protyle.model.headElement.classList.remove("item--unupdate");

+ 9 - 0
app/src/types/index.d.ts

@@ -178,6 +178,15 @@ interface Window {
         readClipboard(): string
         getBlockURL(): string
     }
+    JSHarmony: {
+        openExternal(url: string): void
+        changeStatusBarColor(color: string, mode: number): void
+        writeClipboard(text: string): void
+        writeHTMLClipboard(text: string, html: string): void
+        writeImageClipboard(uri: string): void
+        readClipboard(): string
+        getBlockURL(): string
+    }
 
     Protyle: import("../protyle/method").default
 

+ 5 - 3
app/src/util/assets.ts

@@ -205,9 +205,9 @@ export const setInlineStyle = (set = true) => {
 .b3-typography code:not(.hljs), .protyle-wysiwyg span[data-type~=code] { font-variant-ligatures: ${window.siyuan.config.editor.codeLigatures ? "normal" : "none"} }
 .li > .protyle-action {height:${height + 8}px;line-height: ${height + 8}px}
 .protyle-wysiwyg [data-node-id].li > .protyle-action ~ .h1, .protyle-wysiwyg [data-node-id].li > .protyle-action ~ .h2, .protyle-wysiwyg [data-node-id].li > .protyle-action ~ .h3, .protyle-wysiwyg [data-node-id].li > .protyle-action ~ .h4, .protyle-wysiwyg [data-node-id].li > .protyle-action ~ .h5, .protyle-wysiwyg [data-node-id].li > .protyle-action ~ .h6 {line-height:${height + 8}px;}
-.protyle-wysiwyg [data-node-id].li > .protyle-action:after {height: ${window.siyuan.config.editor.fontSize}px;width: ${window.siyuan.config.editor.fontSize}px;margin:-${window.siyuan.config.editor.fontSize / 2}px 0 0 -${window.siyuan.config.editor.fontSize / 2}px}
+.protyle-wysiwyg [data-node-id].li > .protyle-action::after {height: ${window.siyuan.config.editor.fontSize}px;width: ${window.siyuan.config.editor.fontSize}px;margin:-${window.siyuan.config.editor.fontSize / 2}px 0 0 -${window.siyuan.config.editor.fontSize / 2}px}
 .protyle-wysiwyg [data-node-id].li > .protyle-action svg {height: ${Math.max(14, window.siyuan.config.editor.fontSize - 8)}px}
-.protyle-wysiwyg [data-node-id].li:before {height: calc(100% - ${height + 8}px);top:${(height + 8)}px}
+.protyle-wysiwyg [data-node-id].li::before {height: calc(100% - ${height + 8}px);top:${(height + 8)}px}
 .protyle-wysiwyg [data-node-id] [spellcheck] {min-height:${height}px;}
 .protyle-wysiwyg .p,
 .protyle-wysiwyg .code-block .hljs,
@@ -342,8 +342,10 @@ const updateMobileTheme = (OSTheme: string) => {
             }
             if (isInIOS()) {
                 window.webkit.messageHandlers.changeStatusBar.postMessage((backgroundColor || (mode === 0 ? "#fff" : "#1e1e1e")) + " " + mode);
-            } else if (isInAndroid() || isInHarmony()) {
+            } else if (isInAndroid()) {
                 window.JSAndroid.changeStatusBarColor(backgroundColor, mode);
+            } else if ( isInHarmony()) {
+                window.JSHarmony.changeStatusBarColor(backgroundColor, mode);
             }
         }, 500); // 移动端需要加载完才可以获取到颜色
     }

+ 4 - 2
app/src/util/highlightById.ts

@@ -1,5 +1,5 @@
 import {hasClosestBlock, hasClosestByAttribute} from "../protyle/util/hasClosest";
-import {getEditorRange} from "../protyle/util/selection";
+import {focusByRange, getEditorRange} from "../protyle/util/selection";
 
 export const bgFade = (element: Element) => {
     element.classList.add("protyle-wysiwyg--hl");
@@ -56,7 +56,8 @@ export const scrollCenter = (protyle: IProtyle, nodeElement?: Element, top = fal
                 (blockElement.querySelector(".av__row--header").getAttribute("style")?.indexOf("transform") > -1 || blockElement.querySelector(".av__row--footer").getAttribute("style")?.indexOf("transform") > -1)) {
                 return;
             }
-
+            // 撤销时 br 插入删除会导致 rang 被修改 https://github.com/siyuan-note/siyuan/issues/12679
+            const cloneRange = range.cloneRange();
             const br2Element = document.createElement("br");
             range.insertNode(br2Element);
             const editorElement = protyle.contentElement;
@@ -71,6 +72,7 @@ export const scrollCenter = (protyle: IProtyle, nodeElement?: Element, top = fal
                 editorElement.scroll({top: scrollTop, behavior});
             }
             br2Element.remove();
+            focusByRange(cloneRange);
             return;
         }
     }

+ 1 - 1
app/stage/auth.html

@@ -416,7 +416,7 @@
             const {ipcRenderer} = require('electron')
             ipcRenderer.send('siyuan-quit', window.location.port)
         } catch (e) {
-            if ((window.webkit && window.webkit.messageHandlers) || window.JSAndroid) {
+            if ((window.webkit && window.webkit.messageHandlers) || window.JSAndroid || window.JSHarmony) {
                 window.location.href = 'siyuan://api/system/exit'
             } else {
                 window.location.reload()

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 1 - 0
app/stage/protyle/js/flowchart.js/flowchart.min.js


Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
app/stage/protyle/js/lute/lute.min.js


+ 1 - 1
app/stage/service-worker.js

@@ -6,7 +6,7 @@ const INITIAL_CACHED_RESOURCES = [
     "/stage/icon-large.png",
     "/stage/icon.png",
     "/stage/loading-pure.svg",
-    "/stage/build/fonts/JetBrainsMono-Regular.woff",
+    "/stage/build/fonts/JetBrainsMono-Regular.woff2",
     "/stage/protyle/js/lute/lute.min.js",
     "/stage/protyle/js/protyle-html.js"
 ];

+ 2 - 2
app/webpack.config.js

@@ -76,10 +76,10 @@ module.exports = (env, argv) => {
                     ],
                 },
                 {
-                    test: /\.woff$/,
+                    test: /\.woff2$/,
                     type: "asset/resource",
                     generator: {
-                        filename: "../fonts/JetBrainsMono-Regular.woff",
+                        filename: "../fonts/JetBrainsMono-Regular.woff2",
                     },
                 },
                 {

+ 2 - 2
app/webpack.desktop.js

@@ -79,10 +79,10 @@ module.exports = (env, argv) => {
                     ],
                 },
                 {
-                    test: /\.woff$/,
+                    test: /\.woff2$/,
                     type: "asset/resource",
                     generator: {
-                        filename: "../fonts/JetBrainsMono-Regular.woff",
+                        filename: "../fonts/JetBrainsMono-Regular.woff2",
                     },
                 },
                 {

+ 2 - 2
app/webpack.export.js

@@ -72,10 +72,10 @@ module.exports = (env, argv) => {
                     ],
                 },
                 {
-                    test: /\.woff$/,
+                    test: /\.woff2$/,
                     type: "asset/resource",
                     generator: {
-                        filename: "../fonts/JetBrainsMono-Regular.woff",
+                        filename: "../fonts/JetBrainsMono-Regular.woff2",
                     },
                 },
             ],

+ 2 - 2
app/webpack.mobile.js

@@ -80,10 +80,10 @@ module.exports = (env, argv) => {
                     ],
                 },
                 {
-                    test: /\.woff$/,
+                    test: /\.woff2$/,
                     type: "asset/resource",
                     generator: {
-                        filename: "../fonts/JetBrainsMono-Regular.woff",
+                        filename: "../fonts/JetBrainsMono-Regular.woff2",
                     },
                 },
                 {

+ 1 - 1
kernel/go.mod

@@ -10,7 +10,7 @@ require (
 	github.com/88250/epub v0.0.0-20230830085737-c19055cd1f48
 	github.com/88250/go-humanize v0.0.0-20240424102817-4f78fac47ea7
 	github.com/88250/gulu v1.2.3-0.20240612035750-c9cf5f7a4d02
-	github.com/88250/lute v1.7.7-0.20241123145043-7cfd2f597705
+	github.com/88250/lute v1.7.7-0.20241126013711-d6892e61b9f4
 	github.com/88250/pdfcpu v0.3.14-0.20230401044135-c7369a99720c
 	github.com/88250/vitess-sqlparser v0.0.0-20210205111146-56a2ded2aba1
 	github.com/ClarkThan/ahocorasick v0.0.0-20231011042242-30d1ef1347f4

+ 2 - 2
kernel/go.sum

@@ -14,8 +14,8 @@ github.com/88250/go-sqlite3 v1.14.13-0.20231214121541-e7f54c482950 h1:Pa5hMiBceT
 github.com/88250/go-sqlite3 v1.14.13-0.20231214121541-e7f54c482950/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
 github.com/88250/gulu v1.2.3-0.20240612035750-c9cf5f7a4d02 h1:3e5+yobj655pTeKOYMbJrnc1mE51ZkbXIxquTYZuYCY=
 github.com/88250/gulu v1.2.3-0.20240612035750-c9cf5f7a4d02/go.mod h1:MUfzyfmbPrRDZLqxc7aPrVYveatTHRfoUa5TynPS0i8=
-github.com/88250/lute v1.7.7-0.20241123145043-7cfd2f597705 h1:3MMyy7v8OaGG2UuaYZs7vtdBcOwZzS1ehnSL/sP5gZ8=
-github.com/88250/lute v1.7.7-0.20241123145043-7cfd2f597705/go.mod h1:VDAzL8b+oCh+e3NAlmwwLzC53ten0rZlS8NboB7ljtk=
+github.com/88250/lute v1.7.7-0.20241126013711-d6892e61b9f4 h1:H6+9W7fPUolYhg/2bGl6OKHya3qfYb1gpEkk196gJ3w=
+github.com/88250/lute v1.7.7-0.20241126013711-d6892e61b9f4/go.mod h1:VDAzL8b+oCh+e3NAlmwwLzC53ten0rZlS8NboB7ljtk=
 github.com/88250/pdfcpu v0.3.14-0.20230401044135-c7369a99720c h1:Dl/8S9iLyPMTElnWIBxmjaLiWrkI5P4a21ivwAn5pU0=
 github.com/88250/pdfcpu v0.3.14-0.20230401044135-c7369a99720c/go.mod h1:S5YT38L/GCjVjmB4PB84PymA1qfopjEhfhTNQilLpv4=
 github.com/88250/vitess-sqlparser v0.0.0-20210205111146-56a2ded2aba1 h1:48T899JQDwyyRu9yXHePYlPdHtpJfrJEUGBMH3SMBWY=

+ 2 - 2
kernel/harmony/kernel.go

@@ -126,8 +126,8 @@ func SetTimezone(container, appDir, timezoneID string) {
 }
 
 //export DisableFeature
-func DisableFeature(feature string) {
-	util.DisableFeature(feature)
+func DisableFeature(feature *C.char) {
+	util.DisableFeature(C.GoString(feature))
 }
 
 func main() {}

+ 25 - 9
kernel/model/assets.go

@@ -249,7 +249,9 @@ func NetAssets2LocalAssets(rootID string, onlyImg bool, originalURL string) (err
 
 func SearchAssetsByName(keyword string, exts []string) (ret []*cache.Asset) {
 	ret = []*cache.Asset{}
+	keywords := strings.Split(keyword, " ")
 
+	pathHitCount := map[string]int{}
 	count := 0
 	filterByExt := 0 < len(exts)
 	for _, asset := range cache.GetAssets() {
@@ -269,16 +271,24 @@ func SearchAssetsByName(keyword string, exts []string) (ret []*cache.Asset) {
 
 		lowerHName := strings.ToLower(asset.HName)
 		lowerPath := strings.ToLower(asset.Path)
-		lowerKeyword := strings.ToLower(keyword)
-		hitName := strings.Contains(lowerHName, lowerKeyword)
-		hitPath := strings.Contains(lowerPath, lowerKeyword)
-		if !hitName && !hitPath {
+		var hitNameCount, hitPathCount int
+		for _, k := range keywords {
+			lowerKeyword := strings.ToLower(k)
+			hitNameCount += strings.Count(lowerHName, lowerKeyword)
+			hitPathCount += strings.Count(lowerPath, lowerKeyword)
+			if 1 > hitNameCount && 1 > hitPathCount {
+				continue
+			}
+		}
+
+		if 1 > hitNameCount+hitPathCount {
 			continue
 		}
+		pathHitCount[asset.Path] += hitNameCount + hitPathCount
 
 		hName := asset.HName
-		if hitName {
-			_, hName = search.MarkText(asset.HName, keyword, 64, Conf.Search.CaseSensitive)
+		if 0 < hitNameCount {
+			_, hName = search.MarkText(asset.HName, strings.Join(keywords, search.TermSep), 64, Conf.Search.CaseSensitive)
 		}
 		ret = append(ret, &cache.Asset{
 			HName:   hName,
@@ -291,9 +301,15 @@ func SearchAssetsByName(keyword string, exts []string) (ret []*cache.Asset) {
 		}
 	}
 
-	sort.Slice(ret, func(i, j int) bool {
-		return ret[i].Updated > ret[j].Updated
-	})
+	if 0 < len(pathHitCount) {
+		sort.Slice(ret, func(i, j int) bool {
+			return pathHitCount[ret[i].Path] > pathHitCount[ret[j].Path]
+		})
+	} else {
+		sort.Slice(ret, func(i, j int) bool {
+			return ret[i].Updated > ret[j].Updated
+		})
+	}
 	return
 }
 

+ 1 - 5
kernel/model/backlink.go

@@ -614,11 +614,7 @@ func matchBacklinkKeyword(block *Block, keywords []string) bool {
 
 	for _, k := range keywords {
 		k = strings.ToLower(k)
-		content := block.Content
-		if block.IsContainerBlock() {
-			content = block.FContent
-		}
-		if strings.Contains(strings.ToLower(content), k) ||
+		if strings.Contains(strings.ToLower(block.Content), k) ||
 			strings.Contains(strings.ToLower(path.Base(block.HPath)), k) ||
 			strings.Contains(strings.ToLower(block.Name), k) ||
 			strings.Contains(strings.ToLower(block.Alias), k) ||

+ 1 - 0
kernel/model/blockinfo.go

@@ -493,6 +493,7 @@ func buildBlockBreadcrumb(node *ast.Node, excludeTypes []string, isEmbedBlock bo
 		}
 
 		name = strings.ReplaceAll(name, editor.Caret, "")
+		name = util.UnescapeHTML(name)
 		name = util.EscapeHTML(name)
 
 		if !isEmbedBlock && parent == node {

+ 9 - 1
kernel/model/export.go

@@ -1944,6 +1944,8 @@ func exportMarkdownContent0(tree *parse.Tree, cloudAssetsBase string, assetsDest
 		})
 	}
 
+	currentDocDir := path.Dir(tree.HPath)
+
 	var unlinks []*ast.Node
 	ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
 		if !entering {
@@ -1979,11 +1981,17 @@ func exportMarkdownContent0(tree *parse.Tree, cloudAssetsBase string, assetsDest
 					var href string
 					bt := treenode.GetBlockTree(defID)
 					if nil != bt {
-						href += strings.TrimPrefix(bt.HPath, "/") + ".md"
+						href += bt.HPath + ".md"
 						if "d" != bt.Type {
 							href += "#" + defID
 						}
+						if tree.ID == bt.RootID {
+							href = "#" + defID
+						}
 					}
+					href = strings.TrimPrefix(href, currentDocDir)
+					href = util.FilterFilePath(href)
+					href = strings.TrimPrefix(href, "/")
 					blockRefLink := &ast.Node{Type: ast.NodeTextMark, TextMarkType: "a", TextMarkTextContent: linkText, TextMarkAHref: href}
 					blockRefLink.KramdownIAL = n.KramdownIAL
 					n.InsertBefore(blockRefLink)

+ 13 - 12
kernel/model/import.go

@@ -695,6 +695,7 @@ func ImportFromLocalPath(boxID, localPath string, toPath string) (err error) {
 
 	hPathsIDs := map[string]string{}
 	idPaths := map[string]string{}
+	moveIDs := map[string]string{}
 
 	if gulu.File.IsDir(localPath) { // 导入文件夹
 		// 收集所有资源文件
@@ -793,6 +794,7 @@ func ImportFromLocalPath(boxID, localPath string, toPath string) (err error) {
 			}
 
 			if "" != yfmRootID {
+				moveIDs[id] = yfmRootID
 				id = yfmRootID
 			}
 			if "" != yfmTitle {
@@ -999,6 +1001,13 @@ func ImportFromLocalPath(boxID, localPath string, toPath string) (err error) {
 	}
 
 	if 0 < len(importTrees) {
+		for id, newID := range moveIDs {
+			for _, importTree := range importTrees {
+				importTree.ID = strings.ReplaceAll(importTree.ID, id, newID)
+				importTree.Path = strings.ReplaceAll(importTree.Path, id, newID)
+			}
+		}
+
 		initSearchLinks()
 		convertWikiLinksAndTags()
 		buildBlockRefInText()
@@ -1184,16 +1193,6 @@ func imgHtmlBlock2InlineImg(tree *parse.Tree) {
 }
 
 func reassignIDUpdated(tree *parse.Tree, rootID, updated string) {
-	var blockCount int
-	ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
-		if !entering || "" == n.ID {
-			return ast.WalkContinue
-		}
-
-		blockCount++
-		return ast.WalkContinue
-	})
-
 	ast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {
 		if !entering || "" == n.ID {
 			return ast.WalkContinue
@@ -1207,8 +1206,10 @@ func reassignIDUpdated(tree *parse.Tree, rootID, updated string) {
 		n.SetIALAttr("id", n.ID)
 		if "" != updated {
 			n.SetIALAttr("updated", updated)
-			n.ID = updated + "-" + gulu.Rand.String(7)
-			n.SetIALAttr("id", n.ID)
+			if "" == rootID {
+				n.ID = updated + "-" + gulu.Rand.String(7)
+				n.SetIALAttr("id", n.ID)
+			}
 		} else {
 			n.SetIALAttr("updated", util.TimeFromID(n.ID))
 		}

+ 2 - 1
kernel/model/render.go

@@ -18,13 +18,13 @@ package model
 
 import (
 	"bytes"
-	"github.com/88250/lute/editor"
 	"regexp"
 	"strings"
 
 	"github.com/88250/gulu"
 	"github.com/88250/lute"
 	"github.com/88250/lute/ast"
+	"github.com/88250/lute/editor"
 	"github.com/88250/lute/html"
 	"github.com/88250/lute/parse"
 	"github.com/88250/lute/render"
@@ -110,6 +110,7 @@ func renderBlockText(node *ast.Node, excludeTypes []string) (ret string) {
 	ret = sql.NodeStaticContent(node, excludeTypes, false, false, false)
 	ret = strings.TrimSpace(ret)
 	ret = strings.ReplaceAll(ret, "\n", "")
+	ret = util.UnescapeHTML(ret)
 	ret = util.EscapeHTML(ret)
 	ret = strings.TrimSpace(ret)
 	if "" == ret {

+ 10 - 10
kernel/util/file.go

@@ -231,16 +231,16 @@ func FilterFilePath(p string) (ret string) {
 }
 
 func FilterFileName(name string) string {
-	name = strings.ReplaceAll(name, "\\", "")
-	name = strings.ReplaceAll(name, "/", "")
-	name = strings.ReplaceAll(name, ":", "")
-	name = strings.ReplaceAll(name, "*", "")
-	name = strings.ReplaceAll(name, "?", "")
-	name = strings.ReplaceAll(name, "\"", "")
-	name = strings.ReplaceAll(name, "'", "")
-	name = strings.ReplaceAll(name, "<", "")
-	name = strings.ReplaceAll(name, ">", "")
-	name = strings.ReplaceAll(name, "|", "")
+	name = strings.ReplaceAll(name, "\\", "_")
+	name = strings.ReplaceAll(name, "/", "_")
+	name = strings.ReplaceAll(name, ":", "_")
+	name = strings.ReplaceAll(name, "*", "_")
+	name = strings.ReplaceAll(name, "?", "_")
+	name = strings.ReplaceAll(name, "\"", "_")
+	name = strings.ReplaceAll(name, "'", "_")
+	name = strings.ReplaceAll(name, "<", "_")
+	name = strings.ReplaceAll(name, ">", "_")
+	name = strings.ReplaceAll(name, "|", "_")
 	name = strings.TrimSpace(name)
 	name = gulu.Str.RemoveInvisible(name) // Remove invisible characters from file names when uploading assets https://github.com/siyuan-note/siyuan/issues/11683
 	return name

Vissa filer visades inte eftersom för många filer har ändrats