Просмотр исходного кода

Merge branch 'dev' into dev-av-style

Jeffrey Chen 7 месяцев назад
Родитель
Сommit
13a88e49e2

+ 22 - 0
API.md

@@ -416,6 +416,28 @@ Rename a document by `id`:
     "data": null
     "data": null
   }
   }
   ```
   ```
+  
+Remove a document by `id`:
+
+* `/api/filetree/removeDocByID`
+* Parameters
+
+  ```json
+  {
+    "id": "20210902210113-0avi12f"
+  }
+  ```
+
+  * `id`: Document ID
+* Return value
+
+  ```json
+  {
+    "code": 0,
+    "msg": "",
+    "data": null
+  }
+  ```
 
 
 ### Move documents
 ### Move documents
 
 

+ 22 - 0
API_zh_CN.md

@@ -416,6 +416,28 @@
     "data": null
     "data": null
   }
   }
   ```
   ```
+  
+通过 `id` 删除文档:
+
+* `/api/filetree/removeDocByID`
+* 参数
+
+  ```json
+  {
+    "id": "20210902210113-0avi12f"
+  }
+  ```
+
+  * `id`:文档 ID
+* 返回值
+
+  ```json
+  {
+    "code": 0,
+    "msg": "",
+    "data": null
+  }
+  ```
 
 
 ### 移动文档
 ### 移动文档
 
 

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

+ 5 - 0
app/src/assets/scss/business/_history.scss

@@ -1,4 +1,9 @@
 .history {
 .history {
+  &__action {
+    overflow: auto;
+    border-bottom: 1px solid var(--b3-border-color);
+  }
+
   &__text {
   &__text {
     background-color: var(--b3-theme-background);
     background-color: var(--b3-theme-background);
     padding: 16px;
     padding: 16px;

+ 5 - 0
app/src/assets/scss/util/_responsive.scss

@@ -71,6 +71,11 @@
     &__panel {
     &__panel {
       flex-direction: column;
       flex-direction: column;
 
 
+      & > .fn__flex-column {
+        min-height: auto;
+        border-radius: var(--b3-border-radius-b);
+      }
+
       & > .history__side {
       & > .history__side {
         height: 40%;
         height: 40%;
         overflow: auto;
         overflow: auto;

+ 1 - 1
app/src/boot/globalEvent/click.ts

@@ -66,7 +66,7 @@ export const globalClick = (event: MouseEvent & { target: HTMLElement }) => {
     }
     }
     const copyElement = hasTopClosestByClassName(event.target, "protyle-action__copy");
     const copyElement = hasTopClosestByClassName(event.target, "protyle-action__copy");
     if (copyElement) {
     if (copyElement) {
-        let text = copyElement.parentElement.nextElementSibling.textContent.replace("\n", "");
+        let text = copyElement.parentElement.nextElementSibling.textContent.replace(/\n$/, "");
         text = text.replace(/\u00A0/g, " "); // Replace non-breaking spaces with normal spaces when copying https://github.com/siyuan-note/siyuan/issues/9382
         text = text.replace(/\u00A0/g, " "); // Replace non-breaking spaces with normal spaces when copying https://github.com/siyuan-note/siyuan/issues/9382
         writeText(text);
         writeText(text);
         showMessage(window.siyuan.languages.copied, 2000);
         showMessage(window.siyuan.languages.copied, 2000);

+ 53 - 32
app/src/history/doc.ts

@@ -15,7 +15,6 @@ let isLoading = false;
 const renderDoc = (element: HTMLElement, currentPage: number, id: string) => {
 const renderDoc = (element: HTMLElement, currentPage: number, id: string) => {
     const previousElement = element.querySelector('[data-type="docprevious"]');
     const previousElement = element.querySelector('[data-type="docprevious"]');
     const nextElement = element.querySelector('[data-type="docnext"]');
     const nextElement = element.querySelector('[data-type="docnext"]');
-    element.setAttribute("data-page", currentPage.toString());
     if (currentPage > 1) {
     if (currentPage > 1) {
         previousElement.removeAttribute("disabled");
         previousElement.removeAttribute("disabled");
     } else {
     } else {
@@ -23,8 +22,9 @@ const renderDoc = (element: HTMLElement, currentPage: number, id: string) => {
     }
     }
     const opElement = element.querySelector('.b3-select[data-type="opselect"]') as HTMLSelectElement;
     const opElement = element.querySelector('.b3-select[data-type="opselect"]') as HTMLSelectElement;
     const listElement = element.querySelector(".b3-list--background");
     const listElement = element.querySelector(".b3-list--background");
+    element.querySelector('.protyle-title__input').classList.add("fn__none");
     element.querySelector('.history__text[data-type="docPanel"]').classList.add("fn__none");
     element.querySelector('.history__text[data-type="docPanel"]').classList.add("fn__none");
-    element.querySelector('.history__text[data-type="mdPanel"]').classList.remove("fn__none");
+    element.querySelector('.history__text[data-type="mdPanel"]').classList.add("fn__none");
     fetchPost("/api/history/searchHistory", {
     fetchPost("/api/history/searchHistory", {
         query: id,
         query: id,
         page: currentPage,
         page: currentPage,
@@ -36,7 +36,16 @@ const renderDoc = (element: HTMLElement, currentPage: number, id: string) => {
         } else {
         } else {
             nextElement.setAttribute("disabled", "disabled");
             nextElement.setAttribute("disabled", "disabled");
         }
         }
-        nextElement.nextElementSibling.nextElementSibling.textContent = `${currentPage}/${response.data.pageCount || 1}`;
+        const pageNumElement = element.querySelector('[data-type="jumpRepoPage"]')
+        if (response.data.pageCount > 1) {
+            pageNumElement.removeAttribute("disabled");
+        } else {
+            pageNumElement.setAttribute("disabled", "disabled");
+        }
+        pageNumElement.setAttribute("data-totalpage", response.data.pageCount.toString());
+        pageNumElement.textContent = currentPage.toString();
+        const pageInfoElement = nextElement.nextElementSibling.nextElementSibling;
+        pageInfoElement.textContent = window.siyuan.languages.pageCountAndHistoryCount.replace("${x}", response.data.pageCount).replace("${y}", response.data.totalCount);
         if (response.data.histories.length === 0) {
         if (response.data.histories.length === 0) {
             listElement.innerHTML = `<li class="b3-list--empty">${window.siyuan.languages.emptyContent}</li>`;
             listElement.innerHTML = `<li class="b3-list--empty">${window.siyuan.languages.emptyContent}</li>`;
             return;
             return;
@@ -61,40 +70,38 @@ export const openDocHistory = (options: {
     notebookId: string,
     notebookId: string,
     pathString: string
     pathString: string
 }) => {
 }) => {
-    const contentHTML = `<div class="fn__flex fn__flex-1 history__panel">
+    const contentHTML = `<div class="history__action">
+    <div class="block__icons">
+        <span data-type="docprevious" class="block__icon block__icon--show b3-tooltips b3-tooltips__e" disabled="disabled" aria-label="${window.siyuan.languages.previousLabel}"><svg><use xlink:href="#iconLeft"></use></svg></span>
+        <button class="b3-button b3-button--text ft__selectnone" data-type="jumpRepoPage" disabled>1</button>
+        <span data-type="docnext" class="block__icon block__icon--show b3-tooltips b3-tooltips__e" disabled="disabled" aria-label="${window.siyuan.languages.nextLabel}"><svg><use xlink:href="#iconRight"></use></svg></span>
+        <span class="fn__space"></span>
+        <span class="ft__on-surface fn__flex-shrink ft__selectnone">${window.siyuan.languages.pageCountAndHistoryCount}</span>
+        <span class="fn__space"></span>
+        <div class="fn__flex-1"></div>
+        <select data-type="opselect" class="b3-select">
+            <option value="all" selected>${window.siyuan.languages.allOp}</option>
+            <option value="update">${window.siyuan.languages.historyUpdate}</option>
+            <option value="format">${window.siyuan.languages.historyFormat}</option>
+            <option value="sync">${window.siyuan.languages.historySync}</option>
+            <option value="replace">${window.siyuan.languages.historyReplace}</option>
+            <option value="outline">${window.siyuan.languages.historyOutline}</option>
+        </select>
+    </div>
+</div>
+<div class="fn__flex fn__flex-1 history__panel">
     <ul class="b3-list b3-list--background history__side" ${isMobile() ? "" : `style="width: ${window.siyuan.storage[Constants.LOCAL_HISTORY].sideDocWidth}"`}>
     <ul class="b3-list b3-list--background history__side" ${isMobile() ? "" : `style="width: ${window.siyuan.storage[Constants.LOCAL_HISTORY].sideDocWidth}"`}>
         <li class="b3-list--empty">${window.siyuan.languages.emptyContent}</li>
         <li class="b3-list--empty">${window.siyuan.languages.emptyContent}</li>
     </ul>
     </ul>
     <div class="history__resize"></div>
     <div class="history__resize"></div>
     <div class="fn__flex-1 fn__flex-column">
     <div class="fn__flex-1 fn__flex-column">
-        <div class="protyle-title__input ft__center ft__breakword"></div>
+        <div class="protyle-title__input fn__none ft__center ft__breakword"></div>
         <textarea class="fn__flex-1 history__text fn__none" readonly data-type="mdPanel"></textarea>
         <textarea class="fn__flex-1 history__text fn__none" readonly data-type="mdPanel"></textarea>
         <div class="fn__flex-1 history__text fn__none" style="padding: 0" data-type="docPanel"></div>
         <div class="fn__flex-1 history__text fn__none" style="padding: 0" data-type="docPanel"></div>
     </div>
     </div>
 </div>`;
 </div>`;
     const dialog = new Dialog({
     const dialog = new Dialog({
-        title:`<div class="block__icons">
-            ${isMobile() ? "" : options.pathString}
-            <span class="fn__space"></span>
-            <div class="fn__flex-1"></div>
-            <select data-type="opselect" class="b3-select">
-                <option value="all" selected>${window.siyuan.languages.allOp}</option>
-                <option value="clean">${window.siyuan.languages.historyClean}</option>
-                <option value="update">${window.siyuan.languages.historyUpdate}</option>
-                <option value="delete">${window.siyuan.languages.historyDelete}</option>
-                <option value="format">${window.siyuan.languages.historyFormat}</option>
-                <option value="sync">${window.siyuan.languages.historySync}</option>
-                <option value="replace">${window.siyuan.languages.historyReplace}</option>
-                <option value="outline">${window.siyuan.languages.historyOutline}</option>
-            </select>
-            <span class="fn__space"></span>
-            <span data-type="docprevious" class="block__icon block__icon--show b3-tooltips b3-tooltips__s" disabled="disabled" aria-label="${window.siyuan.languages.previousLabel}"><svg><use xlink:href="#iconLeft"></use></svg></span>
-            <span class="fn__space"></span>
-            <span data-type="docnext" class="block__icon block__icon--show b3-tooltips b3-tooltips__s" disabled="disabled" aria-label="${window.siyuan.languages.nextLabel}"><svg><use xlink:href="#iconRight"></use></svg></span>
-            <span class="fn__space"></span>
-            <span>1/1</span>
-            ${isMobile() ? '<span class="fn__space"></span><span data-type="close" class="block__icon block__icon--show"><svg><use xlink:href="#iconClose"></use></svg></span>' : ""}
-        </div>`,
+        title: options.pathString,
         content: contentHTML,
         content: contentHTML,
         width: isMobile() ? "100vw" : "90vw",
         width: isMobile() ? "100vw" : "90vw",
         height: isMobile() ? "100vh" : "80vh",
         height: isMobile() ? "100vh" : "80vh",
@@ -127,13 +134,13 @@ export const openDocHistory = (options: {
         typewriterMode: false,
         typewriterMode: false,
     });
     });
     disabledProtyle(historyEditor.protyle);
     disabledProtyle(historyEditor.protyle);
+    const pageNumElement = dialog.element.querySelector('[data-type="jumpRepoPage"]')
+    const titleElement = dialog.element.querySelector(".protyle-title__input")
     dialog.element.addEventListener("click", (event) => {
     dialog.element.addEventListener("click", (event) => {
         let target = event.target as HTMLElement;
         let target = event.target as HTMLElement;
         while (target && !target.isEqualNode(dialog.element)) {
         while (target && !target.isEqualNode(dialog.element)) {
             const type = target.getAttribute("data-type");
             const type = target.getAttribute("data-type");
-            if (type === "close") {
-                dialog.destroy();
-            } else if (type === "rollback" && !isLoading) {
+            if (type === "rollback" && !isLoading) {
                 getHistoryPath(target.parentElement, opElement.value, options.id, (item) => {
                 getHistoryPath(target.parentElement, opElement.value, options.id, (item) => {
                     const dataPath = item.path;
                     const dataPath = item.path;
                     isLoading = false;
                     isLoading = false;
@@ -168,7 +175,8 @@ export const openDocHistory = (options: {
                                 action: [Constants.CB_GET_HISTORY, Constants.CB_GET_HTML],
                                 action: [Constants.CB_GET_HISTORY, Constants.CB_GET_HTML],
                             });
                             });
                         }
                         }
-                        dialog.element.querySelector(".protyle-title__input").textContent = item.title;
+                        titleElement.textContent = item.title;
+                        titleElement.classList.remove("fn__none")
                         isLoading = false;
                         isLoading = false;
                     });
                     });
                     target.parentElement.querySelector(".b3-list-item--focus")?.classList.remove("b3-list-item--focus");
                     target.parentElement.querySelector(".b3-list-item--focus")?.classList.remove("b3-list-item--focus");
@@ -178,11 +186,24 @@ export const openDocHistory = (options: {
                 event.preventDefault();
                 event.preventDefault();
                 break;
                 break;
             } else if ((type === "docprevious" || type === "docnext") && target.getAttribute("disabled") !== "disabled") {
             } else if ((type === "docprevious" || type === "docnext") && target.getAttribute("disabled") !== "disabled") {
-                const currentPage = parseInt(dialog.element.getAttribute("data-page"));
+                const currentPage = parseInt(pageNumElement.textContent);
                 renderDoc(dialog.element, type === "docprevious" ? currentPage - 1 : currentPage + 1, options.id);
                 renderDoc(dialog.element, type === "docprevious" ? currentPage - 1 : currentPage + 1, options.id);
                 event.stopPropagation();
                 event.stopPropagation();
                 event.preventDefault();
                 event.preventDefault();
                 break;
                 break;
+            } else if (type === "jumpRepoPage") {
+                const totalPage = parseInt(target.getAttribute("data-totalpage") || "1");
+                confirmDialog(
+                    window.siyuan.languages.jumpToPage.replace("${x}", totalPage),
+                    `<input class="b3-text-field fn__block" type="number" min="1" max="${totalPage}" value="${pageNumElement.textContent}">`,
+                    (confirmD) => {
+                        const inputElement = confirmD.element.querySelector(".b3-text-field") as HTMLInputElement;
+                        if (inputElement.value === "") {
+                            return;
+                        }
+                        renderDoc(dialog.element, Math.max(1, Math.min(parseInt(inputElement.value), totalPage)), options.id);
+                    }
+                );
             }
             }
             target = target.parentElement;
             target = target.parentElement;
         }
         }

+ 9 - 8
app/src/history/history.ts

@@ -354,7 +354,7 @@ export const openHistory = (app: App) => {
     </div>
     </div>
     <div class="fn__flex-1 fn__flex" id="historyContainer">
     <div class="fn__flex-1 fn__flex" id="historyContainer">
         <div data-type="doc" class="history__repo fn__block" data-init="true">
         <div data-type="doc" class="history__repo fn__block" data-init="true">
-            <div style="overflow:auto;border-bottom: 1px solid var(--b3-border-color);">
+            <div class="history__action">
                 <div class="block__icons">
                 <div class="block__icons">
                     <span data-type="docprevious" class="block__icon block__icon--show b3-tooltips b3-tooltips__e" disabled="disabled" aria-label="${window.siyuan.languages.previousLabel}"><svg><use xlink:href='#iconLeft'></use></svg></span>
                     <span data-type="docprevious" class="block__icon block__icon--show b3-tooltips b3-tooltips__e" disabled="disabled" aria-label="${window.siyuan.languages.previousLabel}"><svg><use xlink:href='#iconLeft'></use></svg></span>
                     <button class="b3-button b3-button--text ft__selectnone" data-type="jumpHistoryPage" data-totalpage="1">1</button>
                     <button class="b3-button b3-button--text ft__selectnone" data-type="jumpHistoryPage" data-totalpage="1">1</button>
@@ -409,7 +409,7 @@ export const openHistory = (app: App) => {
             <li class="b3-list--empty">${window.siyuan.languages.emptyContent}</li>
             <li class="b3-list--empty">${window.siyuan.languages.emptyContent}</li>
         </ul>
         </ul>
         <div data-type="repo" class="fn__none history__repo">
         <div data-type="repo" class="fn__none history__repo">
-            <div style="overflow: auto"">
+            <div class="history__action">
                 <div class="block__icons">
                 <div class="block__icons">
                     <span data-type="previous" class="block__icon block__icon--show b3-tooltips b3-tooltips__e" disabled="disabled" aria-label="${window.siyuan.languages.previousLabel}"><svg><use xlink:href='#iconLeft'></use></svg></span>
                     <span data-type="previous" class="block__icon block__icon--show b3-tooltips b3-tooltips__e" disabled="disabled" aria-label="${window.siyuan.languages.previousLabel}"><svg><use xlink:href='#iconLeft'></use></svg></span>
                     <button class="b3-button b3-button--text ft__selectnone" data-type="jumpRepoPage" data-totalpage="1">1</button>
                     <button class="b3-button b3-button--text ft__selectnone" data-type="jumpRepoPage" data-totalpage="1">1</button>
@@ -445,6 +445,7 @@ export const openHistory = (app: App) => {
             icon: "iconHistory",
             icon: "iconHistory",
             title: window.siyuan.languages.dataHistory,
             title: window.siyuan.languages.dataHistory,
             bindEvent(element) {
             bindEvent(element) {
+                element.firstElementChild.setAttribute("style", "background-color:var(--b3-theme-background);height:100%")
                 bindEvent(app, element.firstElementChild);
                 bindEvent(app, element.firstElementChild);
             }
             }
         });
         });
@@ -839,9 +840,9 @@ const bindEvent = (app: App, element: Element, dialog?: Dialog) => {
                 if (totalPage > 1) {
                 if (totalPage > 1) {
                     confirmDialog(
                     confirmDialog(
                         window.siyuan.languages.jumpToPage.replace("${x}", totalPage),
                         window.siyuan.languages.jumpToPage.replace("${x}", totalPage),
-                        `<input style="width: 100%;" class="b3-text-field fn__flex-center" type="number" min="1" max="${totalPage}" value="${currentPage}">`,
-                        (dialog: Dialog) => {
-                            const inputElement = dialog.element.querySelector(".b3-text-field") as HTMLInputElement;
+                        `<input class="b3-text-field fn__block" type="number" min="1" max="${totalPage}" value="${currentPage}">`,
+                        (confirmD) => {
+                            const inputElement = confirmD.element.querySelector(".b3-text-field") as HTMLInputElement;
                             if (inputElement.value === "") {
                             if (inputElement.value === "") {
                                 return;
                                 return;
                             }
                             }
@@ -858,9 +859,9 @@ const bindEvent = (app: App, element: Element, dialog?: Dialog) => {
                 if (totalPage > 1) {
                 if (totalPage > 1) {
                     confirmDialog(
                     confirmDialog(
                         window.siyuan.languages.jumpToPage.replace("${x}", totalPage),
                         window.siyuan.languages.jumpToPage.replace("${x}", totalPage),
-                        `<input style="width: 100%;" class="b3-text-field fn__flex-center" type="number" min="1" max="${totalPage}" value="${currentPage}">`,
-                        (dialog: Dialog) => {
-                            const inputElement = dialog.element.querySelector(".b3-text-field") as HTMLInputElement;
+                        `<input class="b3-text-field fn__block" type="number" min="1" max="${totalPage}" value="${currentPage}">`,
+                        (confirmD) => {
+                            const inputElement = confirmD.element.querySelector(".b3-text-field") as HTMLInputElement;
                             if (inputElement.value === "") {
                             if (inputElement.value === "") {
                                 return;
                                 return;
                             }
                             }

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

@@ -74,7 +74,9 @@ export class Toolbar {
                 if (typeof toolbarItem === "string" || Constants.INLINE_TYPE.concat("|").includes(toolbarItem.name) || !toolbarItem.hotkey) {
                 if (typeof toolbarItem === "string" || Constants.INLINE_TYPE.concat("|").includes(toolbarItem.name) || !toolbarItem.hotkey) {
                     return
                     return
                 }
                 }
-                toolbarItem.hotkey = window.siyuan.config.keymap.plugin[item.name][toolbarItem.name].custom;
+                if (window.siyuan.config.keymap.plugin && window.siyuan.config.keymap.plugin[item.name] && window.siyuan.config.keymap.plugin[item.name][toolbarItem.name]) {
+                    toolbarItem.hotkey = window.siyuan.config.keymap.plugin[item.name][toolbarItem.name].custom;
+                }
             })
             })
             options.toolbar = toolbarKeyToMenu(pluginToolbar);
             options.toolbar = toolbarKeyToMenu(pluginToolbar);
         });
         });
@@ -93,7 +95,9 @@ export class Toolbar {
                 if (typeof toolbarItem === "string" || Constants.INLINE_TYPE.concat("|").includes(toolbarItem.name) || !toolbarItem.hotkey) {
                 if (typeof toolbarItem === "string" || Constants.INLINE_TYPE.concat("|").includes(toolbarItem.name) || !toolbarItem.hotkey) {
                     return
                     return
                 }
                 }
-                toolbarItem.hotkey = window.siyuan.config.keymap.plugin[item.name][toolbarItem.name].custom;
+                if (window.siyuan.config.keymap.plugin && window.siyuan.config.keymap.plugin[item.name] && window.siyuan.config.keymap.plugin[item.name][toolbarItem.name]) {
+                    toolbarItem.hotkey = window.siyuan.config.keymap.plugin[item.name][toolbarItem.name].custom;
+                }
             })
             })
             protyle.options.toolbar = toolbarKeyToMenu(pluginToolbar);
             protyle.options.toolbar = toolbarKeyToMenu(pluginToolbar);
         });
         });

+ 12 - 1
app/src/protyle/wysiwyg/index.ts

@@ -2009,6 +2009,15 @@ export class WYSIWYG {
         let isComposition = false; // for iPhone
         let isComposition = false; // for iPhone
         this.element.addEventListener("compositionstart", (event) => {
         this.element.addEventListener("compositionstart", (event) => {
             isComposition = true;
             isComposition = true;
+            // 微软双拼由于 focusByRange 导致无法输入文字,因此不再 keydown 中记录了,但 keyup 会记录拼音字符,因此使用 isComposition 阻止 keyup 记录。
+            // 但搜狗输入法选中后继续输入不走 keydown,isComposition 阻止了 keyup 记录,因此需在此记录。
+            const range = getEditorRange(protyle.wysiwyg.element);
+            const nodeElement = hasClosestBlock(range.startContainer);
+            if (!isMac() && nodeElement) {
+                range.insertNode(document.createElement("wbr"));
+                protyle.wysiwyg.lastHTMLs[nodeElement.getAttribute("data-node-id")] = nodeElement.outerHTML;
+                nodeElement.querySelector("wbr").remove();
+            }
             event.stopPropagation();
             event.stopPropagation();
         });
         });
 
 
@@ -2086,8 +2095,10 @@ export class WYSIWYG {
                 event.key !== "Meta" && event.key !== "Alt" && event.key !== "Control" && event.key !== "CapsLock" &&
                 event.key !== "Meta" && event.key !== "Alt" && event.key !== "Control" && event.key !== "CapsLock" &&
                 !event.ctrlKey && !event.shiftKey && !event.metaKey && !event.altKey &&
                 !event.ctrlKey && !event.shiftKey && !event.metaKey && !event.altKey &&
                 !/^F\d{1,2}$/.test(event.key)) {
                 !/^F\d{1,2}$/.test(event.key)) {
-                // 搜狗输入法不走 keydown,需重新记录历史状态
+                // 搜狗输入法不走 keydown,没有选中字符后不走 compositionstart,需重新记录历史状态
                 if (!isMac() && nodeElement &&
                 if (!isMac() && nodeElement &&
+                    // 微软双拼 keyup 会记录拼音字符,因此在 compositionstart 记录
+                    !isComposition &&
                     (typeof protyle.wysiwyg.lastHTMLs[nodeElement.getAttribute("data-node-id")] === "undefined" || range.toString() !== "" || !this.preventKeyup)) {
                     (typeof protyle.wysiwyg.lastHTMLs[nodeElement.getAttribute("data-node-id")] === "undefined" || range.toString() !== "" || !this.preventKeyup)) {
                     range.insertNode(document.createElement("wbr"));
                     range.insertNode(document.createElement("wbr"));
                     protyle.wysiwyg.lastHTMLs[nodeElement.getAttribute("data-node-id")] = nodeElement.outerHTML;
                     protyle.wysiwyg.lastHTMLs[nodeElement.getAttribute("data-node-id")] = nodeElement.outerHTML;

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

@@ -166,7 +166,9 @@ export const keydown = (protyle: IProtyle, editorElement: HTMLElement) => {
         // 有可能输入 shift+. ,因此需要使用 event.key 来进行判断
         // 有可能输入 shift+. ,因此需要使用 event.key 来进行判断
         if (event.key !== "PageUp" && event.key !== "PageDown" && event.key !== "Home" && event.key !== "End" && event.key.indexOf("Arrow") === -1 &&
         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.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)) {
+            !isNotEditBlock(nodeElement) && !/^F\d{1,2}$/.test(event.key) &&
+            // 微软双拼使用 compositionstart,否则 focusByRange 导致无法输入文字
+            event.key !== "Process") {
             const cloneRange = range.cloneRange();
             const cloneRange = range.cloneRange();
             range.collapse(false);
             range.collapse(false);
             range.insertNode(document.createElement("wbr"));
             range.insertNode(document.createElement("wbr"));

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
app/stage/protyle/js/lute/lute.min.js


+ 29 - 0
kernel/api/filetree.go

@@ -502,6 +502,31 @@ func removeDoc(c *gin.Context) {
 	model.RemoveDoc(notebook, p)
 	model.RemoveDoc(notebook, p)
 }
 }
 
 
+func removeDocByID(c *gin.Context) {
+	ret := gulu.Ret.NewResult()
+	defer c.JSON(http.StatusOK, ret)
+
+	arg, ok := util.JsonArg(c, ret)
+	if !ok {
+		return
+	}
+
+	id := arg["id"].(string)
+	if util.InvalidIDPattern(id, ret) {
+		return
+	}
+
+	tree, err := model.LoadTreeByBlockID(id)
+	if err != nil {
+		ret.Code = -1
+		ret.Msg = err.Error()
+		ret.Data = map[string]interface{}{"closeTimeout": 7000}
+		return
+	}
+
+	model.RemoveDoc(tree.Box, tree.Path)
+}
+
 func removeDocs(c *gin.Context) {
 func removeDocs(c *gin.Context) {
 	ret := gulu.Ret.NewResult()
 	ret := gulu.Ret.NewResult()
 	defer c.JSON(http.StatusOK, ret)
 	defer c.JSON(http.StatusOK, ret)
@@ -558,6 +583,10 @@ func renameDocByID(c *gin.Context) {
 	}
 	}
 
 
 	id := arg["id"].(string)
 	id := arg["id"].(string)
+	if util.InvalidIDPattern(id, ret) {
+		return
+	}
+
 	title := arg["title"].(string)
 	title := arg["title"].(string)
 
 
 	tree, err := model.LoadTreeByBlockID(id)
 	tree, err := model.LoadTreeByBlockID(id)

+ 1 - 0
kernel/api/router.go

@@ -105,6 +105,7 @@ func ServeAPI(ginServer *gin.Engine) {
 	ginServer.Handle("POST", "/api/filetree/renameDoc", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, renameDoc)
 	ginServer.Handle("POST", "/api/filetree/renameDoc", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, renameDoc)
 	ginServer.Handle("POST", "/api/filetree/renameDocByID", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, renameDocByID)
 	ginServer.Handle("POST", "/api/filetree/renameDocByID", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, renameDocByID)
 	ginServer.Handle("POST", "/api/filetree/removeDoc", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeDoc)
 	ginServer.Handle("POST", "/api/filetree/removeDoc", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeDoc)
+	ginServer.Handle("POST", "/api/filetree/removeDocByID", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeDocByID)
 	ginServer.Handle("POST", "/api/filetree/removeDocs", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeDocs)
 	ginServer.Handle("POST", "/api/filetree/removeDocs", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, removeDocs)
 	ginServer.Handle("POST", "/api/filetree/moveDocs", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, moveDocs)
 	ginServer.Handle("POST", "/api/filetree/moveDocs", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, moveDocs)
 	ginServer.Handle("POST", "/api/filetree/duplicateDoc", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, duplicateDoc)
 	ginServer.Handle("POST", "/api/filetree/duplicateDoc", model.CheckAuth, model.CheckAdminRole, model.CheckReadonly, duplicateDoc)

+ 1 - 1
kernel/go.mod

@@ -10,7 +10,7 @@ require (
 	github.com/88250/epub v0.0.0-20230830085737-c19055cd1f48
 	github.com/88250/epub v0.0.0-20230830085737-c19055cd1f48
 	github.com/88250/go-humanize v0.0.0-20240424102817-4f78fac47ea7
 	github.com/88250/go-humanize v0.0.0-20240424102817-4f78fac47ea7
 	github.com/88250/gulu v1.2.3-0.20240612035750-c9cf5f7a4d02
 	github.com/88250/gulu v1.2.3-0.20240612035750-c9cf5f7a4d02
-	github.com/88250/lute v1.7.7-0.20241126013711-d6892e61b9f4
+	github.com/88250/lute v1.7.7-0.20241127031345-f772b0ee2be8
 	github.com/88250/pdfcpu v0.3.14-0.20230401044135-c7369a99720c
 	github.com/88250/pdfcpu v0.3.14-0.20230401044135-c7369a99720c
 	github.com/88250/vitess-sqlparser v0.0.0-20210205111146-56a2ded2aba1
 	github.com/88250/vitess-sqlparser v0.0.0-20210205111146-56a2ded2aba1
 	github.com/ClarkThan/ahocorasick v0.0.0-20231011042242-30d1ef1347f4
 	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/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 h1:3e5+yobj655pTeKOYMbJrnc1mE51ZkbXIxquTYZuYCY=
 github.com/88250/gulu v1.2.3-0.20240612035750-c9cf5f7a4d02/go.mod h1:MUfzyfmbPrRDZLqxc7aPrVYveatTHRfoUa5TynPS0i8=
 github.com/88250/gulu v1.2.3-0.20240612035750-c9cf5f7a4d02/go.mod h1:MUfzyfmbPrRDZLqxc7aPrVYveatTHRfoUa5TynPS0i8=
-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/lute v1.7.7-0.20241127031345-f772b0ee2be8 h1:1gWOQT9m2o3E6X//wvI+bexbXgdHheN6YUZcABHMm4k=
+github.com/88250/lute v1.7.7-0.20241127031345-f772b0ee2be8/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 h1:Dl/8S9iLyPMTElnWIBxmjaLiWrkI5P4a21ivwAn5pU0=
 github.com/88250/pdfcpu v0.3.14-0.20230401044135-c7369a99720c/go.mod h1:S5YT38L/GCjVjmB4PB84PymA1qfopjEhfhTNQilLpv4=
 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=
 github.com/88250/vitess-sqlparser v0.0.0-20210205111146-56a2ded2aba1 h1:48T899JQDwyyRu9yXHePYlPdHtpJfrJEUGBMH3SMBWY=

+ 0 - 3
kernel/model/graph.go

@@ -512,9 +512,6 @@ func nodeContentByBlock(block *Block) (ret string) {
 	if ret = block.Name; "" != ret {
 	if ret = block.Name; "" != ret {
 		return
 		return
 	}
 	}
-	if ret = block.Memo; "" != ret {
-		return
-	}
 	ret = block.Content
 	ret = block.Content
 	if maxLen := 48; maxLen < utf8.RuneCountInString(ret) {
 	if maxLen := 48; maxLen < utf8.RuneCountInString(ret) {
 		ret = gulu.Str.SubStr(ret, maxLen) + "..."
 		ret = gulu.Str.SubStr(ret, maxLen) + "..."

Некоторые файлы не были показаны из-за большого количества измененных файлов