Compare commits

...

32 commits

Author SHA1 Message Date
Daniel
96dfe0bea4
🔖 Release v3.6.5
Some checks are pending
Release Docker Image / build (push) Waiting to run
Signed-off-by: Daniel <845765@qq.com>
2026-04-21 11:15:03 +08:00
Vanessa
e87fb431cc 🎨 https://github.com/siyuan-note/siyuan/issues/17560 2026-04-21 10:53:26 +08:00
Vanessa
0312bb7090 🎨 https://github.com/siyuan-note/siyuan/issues/17556 2026-04-21 10:43:59 +08:00
Vanessa
71610f0109 Merge remote-tracking branch 'origin/dev' into dev 2026-04-21 10:34:33 +08:00
Vanessa
f8cc5d5f29 🎨 https://github.com/siyuan-note/siyuan/issues/17556 2026-04-21 10:34:20 +08:00
Daniel
b01d452d2d
🎨 Clean code
Signed-off-by: Daniel <845765@qq.com>
2026-04-21 10:25:40 +08:00
Vanessa
9f8fb571f5 Merge remote-tracking branch 'origin/dev' into dev 2026-04-21 10:20:07 +08:00
Vanessa
5c43e082d5 🎨 https://github.com/siyuan-note/siyuan/issues/17559 2026-04-21 10:19:54 +08:00
Daniel
3999fd33f8
🎨 https://github.com/siyuan-note/siyuan/issues/17561
Signed-off-by: Daniel <845765@qq.com>
2026-04-21 09:50:58 +08:00
Vanessa
e987e3fb1e Merge remote-tracking branch 'origin/dev' into dev 2026-04-21 09:47:27 +08:00
Vanessa
f4dba03828 🎨 https://github.com/siyuan-note/siyuan/issues/17532 2026-04-21 09:47:14 +08:00
Daniel
321abe8785
🎨 https://github.com/siyuan-note/siyuan/issues/17549
Signed-off-by: Daniel <845765@qq.com>
2026-04-21 09:39:35 +08:00
Vanessa
ed799763d4 🎨 https://github.com/siyuan-note/siyuan/issues/17556 2026-04-21 09:08:35 +08:00
Daniel
237ae6ff97
🎨 https://github.com/siyuan-note/siyuan/issues/17349#issuecomment-4282750929
Signed-off-by: Daniel <845765@qq.com>
2026-04-21 08:55:34 +08:00
Daniel
464b72b24d
🎨 Treat the renaming of tags, bookmarks and assets as a data history Replace operation https://github.com/siyuan-note/siyuan/issues/17407#issuecomment-4283539036
Signed-off-by: Daniel <845765@qq.com>
2026-04-21 08:52:22 +08:00
Jeffrey Chen
1cb44c4d24
🐛 Correct the FTS target table branch of indexNode https://github.com/siyuan-note/siyuan/issues/17536 (#17557) 2026-04-20 23:22:17 +08:00
Vanessa
1995e6d421 🎨 https://github.com/siyuan-note/siyuan/issues/17556 2026-04-20 22:34:09 +08:00
Vanessa
4999feb021 🎨 https://github.com/siyuan-note/siyuan/issues/17555 2026-04-20 22:24:11 +08:00
Vanessa
43d6cb26a5 Merge remote-tracking branch 'origin/dev' into dev 2026-04-20 22:02:46 +08:00
Vanessa
0dc545607b 🎨 https://github.com/siyuan-note/siyuan/issues/17555 2026-04-20 22:02:23 +08:00
Daniel
53bcf77106
🎨 https://github.com/siyuan-note/siyuan/issues/17555#issuecomment-4280872035
Signed-off-by: Daniel <845765@qq.com>
2026-04-20 22:00:05 +08:00
Daniel
5cedae40ba
🎨 https://github.com/siyuan-note/siyuan/issues/17555#issuecomment-4280872035
Signed-off-by: Daniel <845765@qq.com>
2026-04-20 21:22:45 +08:00
Daniel
4bbcc82998
🎨 https://github.com/siyuan-note/siyuan/issues/17555#issuecomment-4280872035
Signed-off-by: Daniel <845765@qq.com>
2026-04-20 21:09:41 +08:00
Daniel
7c182042e7
📝 Update changelogs
Signed-off-by: Daniel <845765@qq.com>
2026-04-20 20:36:02 +08:00
Daniel
59019a29c6
🎨 https://github.com/siyuan-note/siyuan/issues/17555
Signed-off-by: Daniel <845765@qq.com>
2026-04-20 20:25:47 +08:00
Daniel
75a6eba371
🎨 https://github.com/siyuan-note/siyuan/issues/17555
Signed-off-by: Daniel <845765@qq.com>
2026-04-20 20:22:00 +08:00
Jeffrey Chen
c8a093fe1d
♻️ Skip subdoc dir scan when parent doc is hidden https://github.com/siyuan-note/siyuan/issues/17533 (#17554) 2026-04-20 20:10:29 +08:00
Daniel
0ddcf82eb2
🎨 Improve input method compatibility https://github.com/siyuan-note/siyuan/issues/17546
Signed-off-by: Daniel <845765@qq.com>
2026-04-20 20:06:11 +08:00
Vanessa
e9cc256964 Merge remote-tracking branch 'origin/dev' into dev 2026-04-20 19:44:27 +08:00
Vanessa
26681f8fb5 🎨 https://github.com/siyuan-note/siyuan/issues/17505 2026-04-20 19:44:14 +08:00
Daniel
bfe9f5cf72
🎨 https://github.com/siyuan-note/siyuan/issues/10313
Signed-off-by: Daniel <845765@qq.com>
2026-04-20 18:23:08 +08:00
Daniel
32c2c242e2
♻️ Extract const
Signed-off-by: Daniel <845765@qq.com>
2026-04-20 12:21:32 +08:00
61 changed files with 369 additions and 221 deletions

View file

@ -1,4 +1,5 @@
{
"hyperlink": "ارتباط تشعبي",
"copyDoc": "نسخ النص كاملًا",
"position": "الموقع",
"insertColumnLeft1": "إدراج ${x} عمودًا إلى اليسار",

View file

@ -1,4 +1,5 @@
{
"hyperlink": "Hyperlink",
"copyDoc": "Gesamten Text kopieren",
"position": "Position",
"insertColumnLeft1": "Füge ${x} Spalten links ein",

View file

@ -1,4 +1,5 @@
{
"hyperlink": "Hyperlink",
"copyDoc": "Copy full document",
"position": "Position",
"insertColumnLeft1": "Insert ${x} column(s) to the left",

View file

@ -1,4 +1,5 @@
{
"hyperlink": "Hipervínculo",
"copyDoc": "Copiar todo el texto",
"position": "Posición",
"insertColumnLeft1": "Insertar ${x} columnas a la izquierda",

View file

@ -1,4 +1,5 @@
{
"hyperlink": "Hyperlien",
"copyDoc": "Copier tout le texte",
"position": "Position",
"insertColumnLeft1": "Insérer ${x} colonnes à gauche",

View file

@ -1,4 +1,5 @@
{
"hyperlink": "היפר־קישור",
"copyDoc": "העתק את כל הטקסט",
"position": "מיקום",
"insertColumnLeft1": "הוסף ${x} עמודות משמאל",

View file

@ -1,4 +1,5 @@
{
"hyperlink": "Collegamento ipertestuale",
"copyDoc": "Copia tutto il testo",
"position": "Posizione",
"insertColumnLeft1": "Inserisci ${x} colonne a sinistra",

View file

@ -1,4 +1,5 @@
{
"hyperlink": "ハイパーリンク",
"copyDoc": "全文をコピー",
"position": "位置",
"insertColumnLeft1": "左側に ${x} 列を挿入",

View file

@ -1,4 +1,5 @@
{
"hyperlink": "하이퍼링크",
"copyDoc": "전체 복사",
"position": "위치",
"insertColumnLeft1": "왼쪽에 ${x} 열 삽입",

View file

@ -1,4 +1,5 @@
{
"hyperlink": "Hiperłącze",
"copyDoc": "Kopiuj cały tekst",
"position": "Pozycja",
"insertColumnLeft1": "Wstaw ${x} kolumn po lewej",

View file

@ -1,4 +1,5 @@
{
"hyperlink": "Hiperlink",
"copyDoc": "Copiar todo o texto",
"position": "Posição",
"insertColumnLeft1": "Inserir ${x} colunas à esquerda",

View file

@ -1,4 +1,5 @@
{
"hyperlink": "Гиперссылка",
"copyDoc": "Копировать весь текст",
"position": "Позиция",
"insertColumnLeft1": "Вставить слева ${x} столбцов",

View file

@ -1,4 +1,5 @@
{
"hyperlink": "Hypertextový odkaz",
"copyDoc": "Kopírovať celý dokument",
"position": "Pozícia",
"insertColumnLeft1": "Vložiť ${x} stĺpec/stĺpce doľava",

View file

@ -1,4 +1,5 @@
{
"hyperlink": "Köprü",
"copyDoc": "Tüm metni kopyala",
"position": "Pozisyon",
"insertColumnLeft1": "Sol tarafa ${x} sütun ekle",

View file

@ -1,4 +1,5 @@
{
"hyperlink": "超鏈接",
"copyDoc": "複製全文",
"position": "位置",
"insertColumnLeft1": "在左側插入 ${x} 欄",

View file

@ -1,4 +1,5 @@
{
"hyperlink": "超链接",
"copyDoc": "复制全文",
"position": "位置",
"insertColumnLeft1": "在左侧插入 ${x} 列",

View file

@ -9,7 +9,7 @@
<Identity Name="89C2A984.SiYuan"
ProcessorArchitecture="arm64"
Publisher="CN=087C656E-C1D9-42D8-8807-CED45A74FC0F"
Version="3.6.4.0"/>
Version="3.6.5.0"/>
<Properties>
<DisplayName>SiYuan</DisplayName>
<PublisherDisplayName>云南链滴科技有限公司</PublisherDisplayName>

View file

@ -9,7 +9,7 @@
<Identity Name="89C2A984.SiYuan"
ProcessorArchitecture="x64"
Publisher="CN=087C656E-C1D9-42D8-8807-CED45A74FC0F"
Version="3.6.4.0"/>
Version="3.6.5.0"/>
<Properties>
<DisplayName>SiYuan</DisplayName>
<PublisherDisplayName>云南链滴科技有限公司</PublisherDisplayName>

View file

@ -12,12 +12,14 @@ Below are the detailed changes in this version.
* [Improve appearance settings for inline text on mobile](https://github.com/siyuan-note/siyuan/issues/17477)
* [Editor toolbar does not hide when clicking outside it on mobile](https://github.com/siyuan-note/siyuan/issues/17478)
* [Improve task list item markdown indexing for `data-task` marker](https://github.com/siyuan-note/siyuan/issues/17502)
* [Easy way to switch between tags](https://github.com/siyuan-note/siyuan/issues/17505)
* [Improve decoding of anchor text when pasting hyperlinks](https://github.com/siyuan-note/siyuan/issues/17513)
* [Improve `kbd` font `--b3-font-family-kbd`](https://github.com/siyuan-note/siyuan/issues/17517)
* [Change default Redo shortcut to `⇧⌘Z` on macOS](https://github.com/siyuan-note/siyuan/issues/17518)
* [Improve cursor positioning after Undo in tables](https://github.com/siyuan-note/siyuan/issues/17532)
* [Optimize code block line number rendering for better performance](https://github.com/siyuan-note/siyuan/issues/17542)
* [Improve data indexing](https://github.com/siyuan-note/siyuan/issues/17543)
* [Improve input method compatibility](https://github.com/siyuan-note/siyuan/issues/17546)
* [Improve the clipping extension to resolve issues where images were too large to be clipped](https://github.com/siyuan-note/siyuan/issues/17547)
### Bugfix

View file

@ -12,12 +12,14 @@
* [改進行動端行級文字的外觀設定](https://github.com/siyuan-note/siyuan/issues/17477)
* [行動端點選編輯器外部時工具列不會隱藏](https://github.com/siyuan-note/siyuan/issues/17478)
* [改進任務清單項目中 `data-task` 標記的 Markdown 索引](https://github.com/siyuan-note/siyuan/issues/17502)
* [改進標籤切換](https://github.com/siyuan-note/siyuan/issues/17505)
* [改進貼上超連結時對錨文本的解碼](https://github.com/siyuan-note/siyuan/issues/17513)
* [改良 `kbd` 字體 `--b3-font-family-kbd`](https://github.com/siyuan-note/siyuan/issues/17517)
* [將 macOS 上預設的重做快捷鍵改為 `⇧⌘Z`](https://github.com/siyuan-note/siyuan/issues/17518)
* [改進表格中撤銷後的遊標定位](https://github.com/siyuan-note/siyuan/issues/17532)
* [最佳化程式碼區塊行號渲染以提升效能](https://github.com/siyuan-note/siyuan/issues/17542)
* [改進資料索引](https://github.com/siyuan-note/siyuan/issues/17543)
* [改進輸入法相容性](https://github.com/siyuan-note/siyuan/issues/17546)
* [改進剪藏擴充解決圖片過大無法剪藏](https://github.com/siyuan-note/siyuan/issues/17547)
### 修復缺陷

View file

@ -12,12 +12,14 @@
* [改进移动端行级文本的外观设置](https://github.com/siyuan-note/siyuan/issues/17477)
* [移动端点击编辑器外部时工具栏不会隐藏](https://github.com/siyuan-note/siyuan/issues/17478)
* [改进任务列表项中 `data-task` 标记的 Markdown 索引](https://github.com/siyuan-note/siyuan/issues/17502)
* [改进标签切换](https://github.com/siyuan-note/siyuan/issues/17505)
* [改进粘贴超链接时对锚文本的解码](https://github.com/siyuan-note/siyuan/issues/17513)
* [改进 `kbd` 字体 `--b3-font-family-kbd`](https://github.com/siyuan-note/siyuan/issues/17517)
* [将 macOS 上默认的重做快捷键改为 `⇧⌘Z`](https://github.com/siyuan-note/siyuan/issues/17518)
* [改进表格中撤销后的光标定位](https://github.com/siyuan-note/siyuan/issues/17532)
* [优化代码块行号渲染以提升性能](https://github.com/siyuan-note/siyuan/issues/17542)
* [改进数据索引](https://github.com/siyuan-note/siyuan/issues/17543)
* [改进输入法兼容性](https://github.com/siyuan-note/siyuan/issues/17546)
* [改进剪藏扩展解决图片过大无法剪藏](https://github.com/siyuan-note/siyuan/issues/17547)
### 修复缺陷

View file

@ -1,6 +1,6 @@
{
"name": "SiYuan",
"version": "3.6.4",
"version": "3.6.5",
"description": "Refactor your thinking",
"homepage": "https://b3log.org/siyuan",
"main": "./electron/main.js",

View file

@ -98,6 +98,9 @@ export const onGetConfig = (isStart: boolean, app: App) => {
}
});
}
window.siyuan.dialogs.forEach(item => {
item.resize();
});
}, Constants.TIMEOUT_RESIZE);
});
};

View file

@ -14,6 +14,7 @@ export class Dialog {
private disableClose: boolean;
public editors: { [key: string]: Protyle };
public data: any;
private resizeCallback: (type: string) => void;
constructor(options: {
positionId?: string,
@ -29,6 +30,7 @@ export class Dialog {
resizeCallback?: (type: string) => void,
containerClassName?: string
}) {
this.resizeCallback = options.resizeCallback;
this.disableClose = options.disableClose;
this.id = genUUID();
window.siyuan.dialogs.push(this);
@ -85,6 +87,15 @@ left:${left || "auto"};top:${top || "auto"}">
/// #endif
}
public resize() {
if (this.resizeCallback) {
const containerElement = this.element.querySelector(".b3-dialog__container") as HTMLElement;
if (containerElement && containerElement.style.maxWidth !== "none") {
this.resizeCallback("l");
}
}
}
public destroy(options?: IObject) {
this.element.classList.remove("b3-dialog--open");
setTimeout(() => {

View file

@ -50,14 +50,15 @@ export const reloadSync = (
hideMessage();
}
/// #if MOBILE
if (window.siyuan.mobile.popEditor) {
if (window.siyuan.mobile.popEditor && window.siyuan.mobile.popEditor.protyle) {
if (data.removeRootIDs.includes(window.siyuan.mobile.popEditor.protyle.block.rootID)) {
hideElements(["dialog"]);
} else {
reloadProtyle(window.siyuan.mobile.popEditor.protyle, false, updateReadonly);
}
}
if (window.siyuan.mobile.editor) {
if (document.getElementById("empty").classList.contains("fn__none") &&
window.siyuan.mobile.editor && window.siyuan.mobile.editor.protyle) {
if (data.removeRootIDs.includes(window.siyuan.mobile.editor.protyle.block.rootID)) {
setEmpty(app);
} else {

View file

@ -5,9 +5,8 @@ import {getInstanceById, getWndByLayout, pdfIsLoading, setPanelFocus} from "../l
import {getDockByType} from "../layout/tabUtil";
import {getAllModels, getAllTabs} from "../layout/getAll";
import {highlightById, scrollCenter} from "../util/highlightById";
import {getDisplayName, useShell, pathPosix} from "../util/pathName";
import {getDisplayName, pathPosix, useShell} from "../util/pathName";
import {Constants} from "../constants";
import {setEditMode} from "../protyle/util/setEditMode";
import {Files} from "../layout/dock/Files";
import {fetchPost, fetchSyncPost} from "../util/fetch";
import {focusBlock, focusByOffset, focusByRange} from "../protyle/util/selection";

View file

@ -993,6 +993,7 @@ export class Wnd {
this.parent.direction = direction;
if (direction === "tb") {
this.parent.element.classList.add("fn__flex-column");
this.parent.element.style.minHeight = "8px";
this.parent.element.classList.remove("fn__flex");
} else {
this.parent.element.classList.remove("fn__flex-column");

View file

@ -1062,7 +1062,7 @@ data-type="navigation-root" data-path="/">
if (!fileItemElement) {
return;
}
fileItemElement.setAttribute("data-name", Lute.EscapeHTMLStr(data.title));
fileItemElement.setAttribute("data-name", data.title);
fileItemElement.querySelector(".b3-list-item__text").innerHTML = escapeHtml(data.title);
}

View file

@ -937,6 +937,13 @@ export const adjustLayout = (layout: Layout = window.siyuan.layout.centerLayout.
} else {
item.element.style.minWidth = "";
}
if (!item.element.style.height && !item.element.classList.contains("layout__center") &&
item.element.classList.contains("fn__flex-column")) {
item.element.style.minHeight = "8px";
} else {
item.element.style.minHeight = "";
}
});
if (layout.direction === "lr" && layout.element.scrollWidth > layout.element.clientWidth + 2) {
let index = Math.ceil(screen.width / 8);

View file

@ -48,7 +48,7 @@ import {pushBack} from "../mobile/util/MobileBackFoward";
import {copyPNGByLink, exportAsset, writeAssetToClipboard} from "./util";
import {removeInlineType} from "../protyle/toolbar/util";
import {alignImgCenter, alignImgLeft} from "../protyle/wysiwyg/commonHotkey";
import {checkFold, renameTag} from "../util/noRelyPCFunction";
import {checkFold, genTagList, renameTag} from "../util/noRelyPCFunction";
import {hideElements} from "../protyle/ui/hideElements";
import {emitOpenMenu} from "../plugin/EventBus";
import {openMobileFileById} from "../mobile/editor";
@ -65,6 +65,7 @@ import {hideTooltip} from "../dialog/tooltip";
import {clearSelect} from "../protyle/util/clear";
import {scrollCenter} from "../util/highlightById";
import {base64ToURL} from "../util/image";
import {setPosition} from "../util/setPosition";
const renderAssetList = (element: Element, k: string, position: IPosition, exts: string[] = []) => {
fetchPost("/api/search/searchAsset", {
@ -572,7 +573,7 @@ export const refMenu = (protyle: IProtyle, element: HTMLElement) => {
}
}, {
id: "link",
label: window.siyuan.languages.link,
label: window.siyuan.languages.hyperlink,
iconHTML: "",
click() {
element.outerHTML = `<span data-type="a" data-href="siyuan://blocks/${element.getAttribute("data-id")}">${element.innerHTML}</span><wbr>`;
@ -1764,55 +1765,84 @@ style="margin:4px 0;width: ${isMobile() ? "100%" : "360px"}" class="b3-text-fiel
export const tagMenu = (protyle: IProtyle, tagElement: HTMLElement) => {
window.siyuan.menus.menu.remove();
window.siyuan.menus.menu.element.setAttribute("data-name", Constants.MENU_INLINE_TAG);
const nodeElement = hasClosestBlock(tagElement);
if (!nodeElement) {
return;
}
hideElements(["util", "toolbar", "hint"], protyle);
const id = nodeElement.getAttribute("data-node-id");
let html = nodeElement.outerHTML;
let inputElement: HTMLInputElement;
const oldHTML = nodeElement.outerHTML;
window.siyuan.menus.menu.removeCB = () => {
tagElement.innerHTML = Constants.ZWSP + Lute.EscapeHTMLStr(inputElement.value || "");
if (!inputElement.value) {
tagElement.insertAdjacentHTML("afterend", "<wbr>");
tagElement.remove();
focusByWbr(nodeElement, protyle.toolbar.range);
} else {
protyle.toolbar.range.selectNodeContents(tagElement);
protyle.toolbar.range.collapse(false);
focusByRange(protyle.toolbar.range);
}
if (nodeElement.outerHTML !== oldHTML) {
nodeElement.setAttribute("updated", dayjs().format("YYYYMMDDHHmmss"));
updateTransaction(protyle, id, nodeElement.outerHTML, oldHTML);
}
};
window.siyuan.menus.menu.element.setAttribute("data-name", Constants.MENU_INLINE_TAG);
window.siyuan.menus.menu.append(new MenuItem({
id: "tag",
iconHTML: "",
type: "readonly",
label: `<input class="b3-text-field fn__block" style="margin: 4px 0" placeholder="${window.siyuan.languages.tag}">`,
label: `<input class="b3-text-field fn__block" style="margin: 4px 0" placeholder="${window.siyuan.languages.tag}">
<div class="fn__none b3-list fn__flex-1 b3-list--background protyle-hint" style="position: fixed"></div>`,
bind(element) {
const inputElement = element.querySelector("input");
const listElement = element.querySelector(".b3-list") as HTMLElement;
inputElement = element.querySelector("input");
inputElement.value = tagElement.textContent.replace(Constants.ZWSP, "");
inputElement.addEventListener("change", () => {
updateTransaction(protyle, id, nodeElement.outerHTML, html);
html = nodeElement.outerHTML;
});
inputElement.addEventListener("compositionend", () => {
tagElement.innerHTML = Constants.ZWSP + Lute.EscapeHTMLStr(inputElement.value || "");
genTagList(listElement, inputElement.value.trim());
setPosition(listElement, inputElementRect.right + 8, inputElementRect[isMobile() ? "bottom" : "top"], inputElementRect.height);
});
inputElement.addEventListener("input", (event: KeyboardEvent) => {
if (!event.isComposing) {
// https://github.com/siyuan-note/siyuan/issues/4511
tagElement.innerHTML = Constants.ZWSP + Lute.EscapeHTMLStr(inputElement.value || "");
listElement.classList.remove("fn__none");
genTagList(listElement, inputElement.value.trim());
setPosition(listElement, inputElementRect.right + 8, inputElementRect[isMobile() ? "bottom" : "top"], inputElementRect.height);
}
});
inputElement.addEventListener("keydown", (event) => {
if ((event.key === "Enter" || event.key === "Escape") && !event.isComposing) {
event.preventDefault();
event.stopPropagation();
if (!inputElement.value) {
const oldHTML = nodeElement.outerHTML;
tagElement.insertAdjacentHTML("afterend", "<wbr>");
tagElement.remove();
nodeElement.setAttribute("updated", dayjs().format("YYYYMMDDHHmmss"));
updateTransaction(protyle, id, nodeElement.outerHTML, oldHTML);
focusByWbr(nodeElement, protyle.toolbar.range);
} else {
protyle.toolbar.range.selectNodeContents(tagElement);
protyle.toolbar.range.collapse(false);
focusByRange(protyle.toolbar.range);
}
window.siyuan.menus.menu.remove();
} else if (electronUndo(event)) {
event.stopPropagation();
if (event.isComposing) {
return;
}
if (event.key === "Enter" || event.key === "Escape") {
if (!listElement.classList.contains("fn__none")) {
listElement.classList.add("fn__none");
if (event.key === "Enter") {
const currentElement = listElement.querySelector(".b3-list-item--focus") as HTMLElement;
inputElement.value = currentElement.dataset.type === "new" ? currentElement.querySelector("mark").textContent.trim() : currentElement.textContent.trim();
}
return;
}
if (event.key === "Escape") {
window.siyuan.menus.menu.removeCB = null;
}
window.siyuan.menus.menu.remove();
event.preventDefault();
} else {
electronUndo(event);
upDownHint(listElement, event);
}
});
listElement.addEventListener("click", (event) => {
const target = event.target as HTMLElement;
const listItemElement = hasClosestByClassName(target, "b3-list-item");
if (!listItemElement) {
return;
}
inputElement.value = listItemElement.dataset.type === "new" ? listItemElement.querySelector("mark").textContent.trim() : listItemElement.textContent.trim();
listElement.classList.add("fn__none");
});
}
}).element);
@ -1885,7 +1915,6 @@ export const tagMenu = (protyle: IProtyle, tagElement: HTMLElement) => {
icon: "iconTrashcan",
label: window.siyuan.languages.remove,
click() {
const oldHTML = nodeElement.outerHTML;
tagElement.insertAdjacentHTML("afterend", "<wbr>");
tagElement.remove();
nodeElement.setAttribute("updated", dayjs().format("YYYYMMDDHHmmss"));
@ -1918,7 +1947,8 @@ export const tagMenu = (protyle: IProtyle, tagElement: HTMLElement) => {
/// #endif
const popoverElement = hasTopClosestByClassName(protyle.element, "block__popover", true);
window.siyuan.menus.menu.element.setAttribute("data-from", popoverElement ? popoverElement.dataset.level + "popover" : "app");
window.siyuan.menus.menu.element.querySelector("input").select();
inputElement.select();
const inputElementRect = inputElement.getBoundingClientRect();
};
export const inlineMathMenu = (protyle: IProtyle, element: Element) => {

View file

@ -527,7 +527,7 @@ export class MobileFiles extends Model {
if (!fileItemElement) {
return;
}
fileItemElement.setAttribute("data-name", Lute.EscapeHTMLStr(data.title));
fileItemElement.setAttribute("data-name", data.title);
fileItemElement.querySelector(".b3-list-item__text").innerHTML = escapeHtml(data.title);
}

View file

@ -748,7 +748,7 @@ export const initKeyboardToolbar = () => {
preventRender = true;
setTimeout(() => {
preventRender = false;
}, 1000)
}, 1000);
}, Constants.TIMEOUT_TRANSITION);
}
return;

View file

@ -1,12 +1,11 @@
/// #if !MOBILE
import {getAllEditor, getAllModels, getAllWnds} from "../../layout/getAll";
import {getAllModels, getAllWnds} from "../../layout/getAll";
/// #endif
import {addLoading} from "../ui/initUI";
import {fetchPost} from "../../util/fetch";
import {Constants} from "../../constants";
import {hideAllElements, hideElements} from "../ui/hideElements";
import {hasClosestByClassName} from "../util/hasClosest";
import {reloadProtyle} from "../util/reload";
import {resize} from "../util/resize";
import {disabledProtyle, enableProtyle} from "../util/onGet";
import {isWindow} from "../../util/functions";
@ -20,16 +19,6 @@ export const net2LocalAssets = (protyle: IProtyle, type: "Assets" | "Img") => {
hideElements(["toolbar"], protyle);
fetchPost(`/api/format/net${type}2LocalAssets`, {
id: protyle.block.rootID
}, () => {
/// #if MOBILE
reloadProtyle(protyle, false);
/// #else
getAllEditor().forEach(item => {
if (item.protyle.block.rootID === protyle.block.rootID) {
reloadProtyle(item.protyle, item.protyle.element === protyle.element);
}
});
/// #endif
});
};
@ -111,7 +100,7 @@ export const fullscreen = (element: Element, btnElement?: Element) => {
};
export const updateReadonly = (target: Element, protyle: IProtyle) => {
if (!window.siyuan.config.readonly) {
if (!window.siyuan.config.readonly && protyle.element.getAttribute("disabled-forever") !== "true") {
const isReadonly = target.querySelector("use").getAttribute("xlink:href") !== "#iconUnlock";
if (window.siyuan.config.editor.readOnly) {
if (isReadonly) {

View file

@ -573,6 +573,9 @@ ${padHTML}
}
public render(protyle: IProtyle, update = false, nodeElement?: Element | false) {
if (protyle.element.getAttribute("disabled-forever") === "true") {
return;
}
/// #if !MOBILE
let range: Range;
let blockElement: Element;

View file

@ -8,7 +8,7 @@ import {Dialog} from "../../dialog";
import {addScript} from "../util/addScript";
import {isMobile} from "../../util/functions";
import {Constants} from "../../constants";
import {highlightRender} from "../render/highlightRender";
import {highlightRender, lineNumberRender} from "../render/highlightRender";
import {processRender} from "../util/processCode";
import {isIPhone, isSafari, openByMobile, setStorageVal} from "../util/compatibility";
import {useShell} from "../../util/pathName";
@ -27,6 +27,7 @@ export const afterExport = (exportPath: string, msgId: string) => {
export const exportImage = (id: string) => {
const exportDialog = new Dialog({
disableAnimation: true,
title: window.siyuan.languages.exportAsImage,
content: `<div class="b3-dialog__content" style="${isMobile() ? "padding:8px;" : ""};background-color: var(--b3-theme-background)">
<div style="${isMobile() ? "margin: 8px 0" : "padding: 48px;margin: 8px 0"}" class="export-img">
@ -51,7 +52,14 @@ export const exportImage = (id: string) => {
</div>
<div class="fn__loading"><img height="128px" width="128px" src="stage/loading-pure.svg"></div>`,
width: isMobile() ? "92vw" : "990px",
height: "70vh"
height: "70vh",
resizeCallback() {
previewElement.querySelectorAll(".code-block .protyle-linenumber__rows").forEach((item: HTMLElement) => {
if ((item.nextElementSibling as HTMLElement).style.wordBreak === "break-word") {
lineNumberRender(item.parentElement);
}
});
}
});
exportDialog.element.setAttribute("data-key", Constants.DIALOG_EXPORTIMAGE);
const btnsElement = exportDialog.element.querySelectorAll(".b3-button");

View file

@ -251,7 +251,12 @@ const setHTML = (options: {
if (protyle.breadcrumb) {
protyle.breadcrumb.element.nextElementSibling.textContent = "";
}
protyle.element.removeAttribute("disabled-forever");
if (protyle.element.hasAttribute("disabled-forever")) {
if (protyle.wysiwyg.element.getAttribute("custom-sy-readonly") !== "true") {
protyle.disabled = false;
}
protyle.element.removeAttribute("disabled-forever");
}
if (options.action.includes(Constants.CB_GET_OPENNEW) && window.siyuan.config.editor.readOnly && !window.siyuan.config.readonly) {
enableProtyle(protyle);
} else {

View file

@ -547,13 +547,13 @@ export const setInsertWbrHTML = (nodeElement: HTMLElement, range: Range, protyle
const offset = getSelectionOffset(cellElement, nodeElement, range);
cellElement.classList.add("range");
const cloneNode = nodeElement.cloneNode(true) as HTMLElement;
cellElement.classList.remove("range");
cellElement.removeAttribute("class");
const cloneCellElement = cloneNode.querySelector(".range");
const cloneRange = focusByOffset(cloneCellElement, offset.end, offset.end, false);
if (cloneRange) {
cloneRange.insertNode(document.createElement("wbr"));
}
cloneCellElement.classList.remove("range");
cloneCellElement.removeAttribute("class");
protyle.wysiwyg.lastHTMLs[nodeElement.getAttribute("data-node-id")] = cloneNode.outerHTML;
}
} else {

View file

@ -2518,7 +2518,10 @@ export class WYSIWYG {
input(protyle, blockElement, range, true, event);
}, Constants.TIMEOUT_INPUT);
} else {
input(protyle, blockElement, range, true, event);
clearTimeout(timeout); // https://github.com/siyuan-note/siyuan/issues/9179
timeout = window.setTimeout(() => {
input(protyle, blockElement, range, true, event);
});
}
}
event.stopPropagation();

View file

@ -241,6 +241,7 @@ export const input = async (protyle: IProtyle, blockElement: HTMLElement, range:
realElement = protyle.wysiwyg.element.querySelector(`[data-node-id="${tempId}"]`);
}
const realType = realElement.getAttribute("data-type");
let itemHTML = "";
if (realType === "NodeCodeBlock") {
const languageElement = realElement.querySelector(".protyle-action__language");
if (languageElement) {
@ -283,6 +284,7 @@ export const input = async (protyle: IProtyle, blockElement: HTMLElement, range:
currentWbrElement.insertAdjacentText("beforebegin", Constants.ZWSP);
}
}
itemHTML = realElement.outerHTML;
focusByWbr(protyle.wysiwyg.element, range);
protyle.hint.render(protyle);
// 表格出现滚动条,输入数字会向前滚 https://github.com/siyuan-note/siyuan/issues/3650
@ -292,7 +294,7 @@ export const input = async (protyle: IProtyle, blockElement: HTMLElement, range:
}
}
// https://github.com/siyuan-note/siyuan/issues/14766
html += realElement.outerHTML;
html += itemHTML || realElement.outerHTML;
});
} else if (blockElement.getAttribute("data-type") === "NodeCodeBlock") {
editElement.parentElement.removeAttribute("data-render");

View file

@ -12,6 +12,29 @@ import {upDownHint} from "./upDownHint";
import {escapeHtml} from "./escape";
import {hasClosestByClassName} from "../protyle/util/hasClosest";
import {isNotCtrl} from "../protyle/util/compatibility";
import {electronUndo} from "../protyle/undo";
export const genTagList = (listElement: Element, k: string) => {
listElement.classList.remove("fn__none");
fetchPost("/api/search/searchTag", {
k,
}, (response) => {
let searchHTML = "";
let hasKey = false;
response.data.tags.forEach((item: string, index: number) => {
searchHTML += `<div class="b3-list-item${index === 0 ? " b3-list-item--focus" : ""}">
<div class="fn__flex-1">${item}</div>
</div>`;
if (item === `<mark>${response.data.k}</mark>`) {
hasKey = true;
}
});
if (!hasKey && response.data.k) {
searchHTML = `<div data-type="new" class="b3-list-item${searchHTML ? "" : " b3-list-item--focus"}"><div class="fn__flex-1">${window.siyuan.languages.new} <mark>${escapeHtml(response.data.k)}</mark></div></div>` + searchHTML;
}
listElement.innerHTML = searchHTML;
});
};
// 需独立出来,否则移动端引用的时候会引入 pc 端大量无用代码
export const renameTag = (labelName: string) => {
@ -72,29 +95,18 @@ export const renameTag = (labelName: string) => {
listElement.classList.add("fn__none");
}
event.preventDefault();
} else {
electronUndo(event);
}
});
inputElement.addEventListener("input", (event) => {
inputElement.addEventListener("input", (event: KeyboardEvent) => {
event.stopPropagation();
listElement.classList.remove("fn__none");
fetchPost("/api/search/searchTag", {
k: inputElement.value.trim(),
}, (response) => {
let searchHTML = "";
let hasKey = false;
response.data.tags.forEach((item: string, index: number) => {
searchHTML += `<div class="b3-list-item${index === 0 ? " b3-list-item--focus" : ""}">
<div class="fn__flex-1">${item}</div>
</div>`;
if (item === `<mark>${response.data.k}</mark>`) {
hasKey = true;
}
});
if (!hasKey && response.data.k) {
searchHTML = `<div data-type="new" class="b3-list-item${searchHTML ? "" : " b3-list-item--focus"}"><div class="fn__flex-1">${window.siyuan.languages.new} <mark>${escapeHtml(response.data.k)}</mark></div></div>` + searchHTML;
}
listElement.innerHTML = searchHTML;
});
if (!event.isComposing) {
genTagList(listElement, inputElement.value.trim());
}
});
inputElement.addEventListener("compositionend", () => {
genTagList(listElement, inputElement.value.trim());
});
listElement.addEventListener("click", (event) => {
const target = event.target as HTMLElement;

View file

@ -648,7 +648,7 @@ const getLeaf = (liElement: HTMLElement, flashcard: boolean) => {
<svg class="b3-list-item__arrow"><use xlink:href="#iconRight"></use></svg>
</span>
${unicode2Emoji(item.icon || (item.subFileCount === 0 ? window.siyuan.storage[Constants.LOCAL_IMAGES].file : window.siyuan.storage[Constants.LOCAL_IMAGES].folder), "b3-list-item__graphic", true)}
<span class="b3-list-item__text ariaLabel" data-position="parentE" aria-label="${getDisplayName(Lute.EscapeHTMLStr(item.name), true, true)} <small class='ft__on-surface'>${item.hSize}</small>${item.bookmark ? "<br>" + window.siyuan.languages.bookmark + " " + item.bookmark : ""}${item.name1 ? "<br>" + window.siyuan.languages.name + " " + item.name1 : ""}${item.alias ? "<br>" + window.siyuan.languages.alias + " " + item.alias : ""}${item.memo ? "<br>" + window.siyuan.languages.memo + " " + item.memo : ""}${item.subFileCount !== 0 ? window.siyuan.languages.includeSubFile.replace("x", item.subFileCount) : ""}<br>${window.siyuan.languages.modifiedAt} ${item.hMtime}<br>${window.siyuan.languages.createdAt} ${item.hCtime}">${getDisplayName(item.name, true, true)}</span>
<span class="b3-list-item__text ariaLabel" data-position="parentE" aria-label="${getDisplayName(Lute.EscapeHTMLStr(item.name), true, true)} <small class='ft__on-surface'>${item.hSize}</small>${item.bookmark ? "<br>" + window.siyuan.languages.bookmark + " " + item.bookmark : ""}${item.name1 ? "<br>" + window.siyuan.languages.name + " " + item.name1 : ""}${item.alias ? "<br>" + window.siyuan.languages.alias + " " + item.alias : ""}${item.memo ? "<br>" + window.siyuan.languages.memo + " " + item.memo : ""}${item.subFileCount !== 0 ? window.siyuan.languages.includeSubFile.replace("x", item.subFileCount) : ""}<br>${window.siyuan.languages.modifiedAt} ${item.hMtime}<br>${window.siyuan.languages.createdAt} ${item.hCtime}">${getDisplayName(Lute.EscapeHTMLStr(item.name), true, true)}</span>
${countHTML}
</li>`;
});

View file

@ -77,6 +77,9 @@ export const init = (app: App) => {
}
});
}
window.siyuan.dialogs.forEach(item => {
item.resize();
});
}, Constants.TIMEOUT_RESIZE);
});
};

View file

@ -668,7 +668,15 @@ func setFollowSystemLockScreen(c *gin.Context) {
func getSysFonts(c *gin.Context) {
ret := gulu.Ret.NewResult()
defer c.JSON(http.StatusOK, ret)
ret.Data = util.LoadSysFonts()
fonts := util.LoadSysFonts()
// TODO: 字重 https://github.com/siyuan-note/siyuan/issues/10313
var families []string
for _, font := range fonts {
families = append(families, font.Family)
}
families = gulu.Str.RemoveDuplicatedElem(families)
ret.Data = families
}
func version(c *gin.Context) {

View file

@ -11,7 +11,7 @@ require (
github.com/88250/lute v1.7.7-0.20260419134724-bb68012f231d
github.com/88250/vitess-sqlparser v0.0.0-20210205111146-56a2ded2aba1
github.com/ClarkThan/ahocorasick v0.0.0-20231011042242-30d1ef1347f4
github.com/ConradIrwin/font v0.2.1
github.com/ConradIrwin/font v0.2.2-0.20260202161408-44ae4cf5fb22
github.com/Masterminds/sprig/v3 v3.3.0
github.com/PuerkitoBio/goquery v1.11.0
github.com/Xuanwo/go-locale v1.1.3
@ -78,7 +78,6 @@ require (
github.com/vmihailenco/msgpack/v5 v5.4.1
github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342
github.com/xuri/excelize/v2 v2.10.1
golang.org/x/image v0.38.0
golang.org/x/mobile v0.0.0-20251209145715-2553ed8ce294
golang.org/x/mod v0.34.0
golang.org/x/net v0.53.0
@ -138,6 +137,7 @@ require (
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.28.0 // indirect
github.com/go-resty/resty/v2 v2.16.5 // indirect
github.com/go-sw/text-codec v0.0.1 // indirect
github.com/goccy/go-json v0.10.6 // indirect
github.com/goccy/go-yaml v1.18.0 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
@ -194,6 +194,7 @@ require (
github.com/yusufpapurcu/wmi v1.2.4 // indirect
golang.org/x/arch v0.23.0 // indirect
golang.org/x/crypto v0.50.0 // indirect
golang.org/x/image v0.38.0 // indirect
golang.org/x/tools v0.43.0 // indirect
google.golang.org/protobuf v1.36.10 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect

View file

@ -24,8 +24,8 @@ github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/ClarkThan/ahocorasick v0.0.0-20231011042242-30d1ef1347f4 h1:r10k4+Lu1mDpiCKa1liAdGJUhB4BJHHJnMvtoli6Hts=
github.com/ClarkThan/ahocorasick v0.0.0-20231011042242-30d1ef1347f4/go.mod h1:a3CzWIqeRxiODAscAIfZ4wbFRXxywBrdCwTENVAWB2g=
github.com/ConradIrwin/font v0.2.1 h1:D4tWi7zyRAdVKOtOys5960HnAAfUSRx/syaf+J9JqlI=
github.com/ConradIrwin/font v0.2.1/go.mod h1:krTLO7JWu6g8RMxG8sl+T1Hf8W93XQacBKJmqFZ2MFY=
github.com/ConradIrwin/font v0.2.2-0.20260202161408-44ae4cf5fb22 h1:xEDrMXxOJsMByKW9Uw2WvwuVhfRd0SN5sOWVR8rYSjc=
github.com/ConradIrwin/font v0.2.2-0.20260202161408-44ae4cf5fb22/go.mod h1:5iRYC36M+hBFrRcE25N9/kioASZaqIkAXbgOyfVDCXg=
github.com/JalfResi/justext v0.0.0-20221106200834-be571e3e3052 h1:8T2zMbhLBbH9514PIQVHdsGhypMrsB4CxwbldKA9sBA=
github.com/JalfResi/justext v0.0.0-20221106200834-be571e3e3052/go.mod h1:0SURuH1rsE8aVWvutuMZghRNrNrYEUzibzJfhEYR8L0=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
@ -186,6 +186,8 @@ github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3
github.com/go-resty/resty/v2 v2.0.0/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8=
github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM=
github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
github.com/go-sw/text-codec v0.0.1 h1:H+wnKj5TuvqYlfhMtbc52m1Oe0YnxD7QPzZPBE8QAn8=
github.com/go-sw/text-codec v0.0.1/go.mod h1:RTuIwijiCSy7wYLZd3h4RmWHBCD1bERsEST7vqnOzmo=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
@ -529,7 +531,6 @@ golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/text v0.0.0-20180302201248-b7ef84aaf62a/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=

View file

@ -227,6 +227,9 @@ func DocAssets(rootID string) (ret []string, err error) {
}
func NetAssets2LocalAssets(rootID string, onlyImg bool, originalURL string) (err error) {
syncingFiles.Store(rootID, true)
defer syncingFiles.Delete(rootID)
tree, err := LoadTreeByBlockID(rootID)
if err != nil {
return
@ -241,6 +244,10 @@ func NetAssets2LocalAssets(rootID string, onlyImg bool, originalURL string) (err
}
err = netAssets2LocalAssets0(tree, onlyImg, originalURL, assetsDirPath, true)
go func() {
time.Sleep(128 * time.Microsecond)
util.PushReloadProtyle(rootID)
}()
return
}
@ -780,7 +787,7 @@ func RemoveUnusedAssets() (ret []string) {
unusedAssets := UnusedAssets(false)
historyDir, err := GetHistoryDir(HistoryOpClean)
historyDir, err := getHistoryDir(HistoryOpClean)
if err != nil {
logging.LogErrorf("get history dir failed: %s", err)
return
@ -850,7 +857,7 @@ func RemoveUnusedAsset(p string) (ret string) {
return absPath
}
historyDir, err := GetHistoryDir(HistoryOpClean)
historyDir, err := getHistoryDir(HistoryOpClean)
if err != nil {
logging.LogErrorf("get history dir failed: %s", err)
return
@ -935,7 +942,11 @@ func RenameAsset(oldPath, newName string) (newPath string, err error) {
return
}
historyDir, err := getHistoryDir(HistoryOpReplace, time.Now())
historyDir, err := getHistoryDir(HistoryOpReplace)
if nil != err {
return
}
luteEngine := util.NewLute()
for _, notebook := range notebooks {
pages := pagedPaths(filepath.Join(util.DataDir, notebook.ID), 32)
@ -968,7 +979,7 @@ func RenameAsset(oldPath, newName string) (newPath string, err error) {
continue
}
generateTreeHistory(historyDir, tree)
generateTreeHistory(tree, historyDir)
treenode.UpsertBlockTree(tree)
sql.UpsertTreeQueue(tree)

View file

@ -56,7 +56,7 @@ func RemoveUnusedAttributeView(id string) {
return
}
historyDir, err := GetHistoryDir(HistoryOpClean)
historyDir, err := getHistoryDir(HistoryOpClean)
if err != nil {
logging.LogErrorf("get history dir failed: %s", err)
return
@ -94,7 +94,7 @@ func RemoveUnusedAttributeViews() (ret []string) {
unusedAttributeViews := UnusedAttributeViews(false)
historyDir, err := GetHistoryDir(HistoryOpClean)
historyDir, err := getHistoryDir(HistoryOpClean)
if err != nil {
logging.LogErrorf("get history dir failed: %s", err)
return
@ -3808,7 +3808,7 @@ func removeAttributeViewBlock(srcIDs []string, avID string, tx *Transaction) (er
refreshRelatedSrcAvs(avID, tx)
historyDir, err := GetHistoryDir(HistoryOpUpdate)
historyDir, err := getHistoryDir(HistoryOpUpdate)
if err != nil {
logging.LogErrorf("get history dir failed: %s", err)
return
@ -3844,8 +3844,8 @@ func removeAttributeViewBlock(srcIDs []string, avID string, tx *Transaction) (er
func removeNodeAvID(node *ast.Node, avID string, tx *Transaction, tree *parse.Tree) (err error) {
attrs := parse.IAL2Map(node.KramdownIAL)
if ast.NodeDocument == node.Type {
delete(attrs, "custom-hidden")
node.RemoveIALAttr("custom-hidden")
delete(attrs, DocHiddenAttr)
node.RemoveIALAttr(DocHiddenAttr)
}
if avs := attrs[av.NodeAttrNameAvs]; "" != avs {

View file

@ -182,8 +182,7 @@ func setNodeAttrs(node *ast.Node, tree *parse.Tree, nameValues map[string]string
pushBlockAttrs(oldAttrs, node)
if ("true" == oldAttrs["custom-hidden"] && "true" != nameValues["custom-hidden"]) ||
"true" != oldAttrs["custom-hidden"] && "true" == nameValues["custom-hidden"] {
if ("true" == oldAttrs[DocHiddenAttr]) != ("true" == nameValues[DocHiddenAttr]) {
ReloadFiletree()
}

View file

@ -22,7 +22,6 @@ import (
"path/filepath"
"sort"
"strings"
"time"
"github.com/88250/gulu"
"github.com/88250/lute/parse"
@ -36,6 +35,7 @@ import (
func RemoveBookmark(bookmark string) (err error) {
util.PushEndlessProgress(Conf.Language(116))
defer util.PushClearProgress()
bookmarks := sql.QueryBookmarkBlocks()
treeBlocks := map[string][]string{}
@ -47,13 +47,15 @@ func RemoveBookmark(bookmark string) (err error) {
}
}
historyDir, err := getHistoryDir(HistoryOpReplace, time.Now())
historyDir, err := getHistoryDir(HistoryOpReplace)
if nil != err {
return
}
for treeID, blocks := range treeBlocks {
util.PushEndlessProgress("[" + treeID + "]")
tree, e := LoadTreeByBlockID(treeID)
if nil != e {
util.PushClearProgress()
return e
}
@ -72,7 +74,7 @@ func RemoveBookmark(bookmark string) (err error) {
}
if changed {
generateTreeHistory(historyDir, tree)
generateTreeHistory(tree, historyDir)
util.PushEndlessProgress(fmt.Sprintf(Conf.Language(111), util.EscapeHTML(tree.Root.IALAttr("title"))))
if err = writeTreeUpsertQueue(tree); err != nil {
util.ClearPushProgress(100)
@ -105,6 +107,7 @@ func RenameBookmark(oldBookmark, newBookmark string) (err error) {
}
util.PushEndlessProgress(Conf.Language(110))
defer util.ClearPushProgress(100)
bookmarks := sql.QueryBookmarkBlocks()
treeBlocks := map[string][]string{}
@ -116,12 +119,15 @@ func RenameBookmark(oldBookmark, newBookmark string) (err error) {
}
}
historyDir, err := getHistoryDir(HistoryOpReplace, time.Now())
historyDir, err := getHistoryDir(HistoryOpReplace)
if nil != err {
return
}
for treeID, blocks := range treeBlocks {
util.PushEndlessProgress("[" + treeID + "]")
tree, e := LoadTreeByBlockID(treeID)
if nil != e {
util.ClearPushProgress(100)
return e
}
@ -140,7 +146,7 @@ func RenameBookmark(oldBookmark, newBookmark string) (err error) {
}
if changed {
generateTreeHistory(historyDir, tree)
generateTreeHistory(tree, historyDir)
util.PushEndlessProgress(fmt.Sprintf(Conf.Language(111), util.EscapeHTML(tree.Root.IALAttr("title"))))
if err = writeTreeUpsertQueue(tree); err != nil {
util.ClearPushProgress(100)

View file

@ -300,7 +300,7 @@ func ListDocTree(boxID, listPath string, sortMode int, flashcard, showHidden boo
continue
}
if ial := box.docIAL(parentDocPath); nil != ial {
if !showHidden && "true" == ial["custom-hidden"] {
if !showHidden && "true" == ial[DocHiddenAttr] {
continue
}
@ -309,7 +309,7 @@ func ListDocTree(boxID, listPath string, sortMode int, flashcard, showHidden boo
if err == nil {
for _, subFile := range subFiles {
subDocFilePath := path.Join(file.path, subFile.Name())
if subIAL := box.docIAL(subDocFilePath); "true" == subIAL["custom-hidden"] {
if subIAL := box.docIAL(subDocFilePath); "true" == subIAL[DocHiddenAttr] {
continue
}
@ -347,7 +347,7 @@ func ListDocTree(boxID, listPath string, sortMode int, flashcard, showHidden boo
}
if ial := box.docIAL(file.path); nil != ial {
if !showHidden && "true" == ial["custom-hidden"] {
if !showHidden && "true" == ial[DocHiddenAttr] {
continue
}
@ -1090,6 +1090,7 @@ func CreateWithMarkdown(tags, boxID, hPath, md, parentID, id string, withMath bo
const (
DailyNoteAttrPrefix = "custom-dailynote-"
NodeAttrTitleEmpty = "custom-sy-title-empty"
DocHiddenAttr = "custom-hidden"
)
func CreateDailyNote(boxID string) (p string, existed bool, err error) {
@ -1564,7 +1565,7 @@ func removeDoc(box *Box, p string, luteEngine *lute.Lute) (ret *parse.Tree) {
return
}
historyDir, err := GetHistoryDir(HistoryOpDelete)
historyDir, err := getHistoryDir(HistoryOpDelete)
if err != nil {
logging.LogErrorf("get history dir failed: %s", err)
return

View file

@ -399,7 +399,7 @@ func Heading2Doc(srcHeadingID, targetBoxID, targetPath, previousPath string) (sr
headingNode.SetIALAttr("type", "doc")
headingNode.SetIALAttr("id", srcHeadingID)
headingNode.SetIALAttr("title", headingText)
headingNode.RemoveIALAttr("custom-hidden")
headingNode.RemoveIALAttr(DocHiddenAttr)
newTree.Root.KramdownIAL = headingNode.KramdownIAL
topLevel := treenode.TopHeadingLevel(newTree)

View file

@ -633,7 +633,7 @@ func generateAssetsHistory() {
return
}
historyDir, err := GetHistoryDir(HistoryOpUpdate)
historyDir, err := getHistoryDir(HistoryOpUpdate)
if err != nil {
logging.LogErrorf("get history dir failed: %s", err)
return
@ -662,7 +662,7 @@ func (box *Box) generateDocHistory0() {
return
}
historyDir, err := GetHistoryDir(HistoryOpUpdate)
historyDir, err := getHistoryDir(HistoryOpUpdate)
if err != nil {
logging.LogErrorf("get history dir failed: %s", err)
return
@ -820,23 +820,21 @@ const (
)
func generateOpTypeHistory(tree *parse.Tree, opType string) {
historyDir, err := GetHistoryDir(opType)
historyDir, err := getHistoryDir(opType)
if err != nil {
logging.LogErrorf("get history dir failed: %s", err)
return
}
if err = generateTreeHistory(historyDir, tree); err != nil {
return
}
generateTreeHistory(tree, historyDir)
generateAvHistoryInTree(tree, historyDir)
indexHistoryDir(filepath.Base(historyDir), util.NewLute())
}
func generateTreeHistory(historyDir string, tree *parse.Tree) (err error) {
func generateTreeHistory(tree *parse.Tree, historyDir string) {
historyPath := filepath.Join(historyDir, tree.Box, tree.Path)
var err error
if err = os.MkdirAll(filepath.Dir(historyPath), 0755); err != nil {
logging.LogErrorf("generate history failed: %s", err)
return
@ -866,12 +864,8 @@ func generateAvHistoryInTree(tree *parse.Tree, historyDir string) {
}
}
func GetHistoryDir(suffix string) (ret string, err error) {
return getHistoryDir(suffix, time.Now())
}
func getHistoryDir(suffix string, t time.Time) (ret string, err error) {
ret = filepath.Join(util.HistoryDir, t.Format("2006-01-02-150405")+"-"+suffix)
func getHistoryDir(suffix string) (ret string, err error) {
ret = filepath.Join(util.HistoryDir, time.Now().Format("2006-01-02-150405")+"-"+suffix)
if err = os.MkdirAll(ret, 0755); err != nil {
logging.LogErrorf("make history dir failed: %s", err)
return

View file

@ -111,7 +111,7 @@ func ListItem2Doc(srcListItemID, targetBoxID, targetPath, previousPath string) (
listItemNode.SetIALAttr("id", srcListItemID)
listItemNode.SetIALAttr("title", listItemText)
listItemNode.RemoveIALAttr("fold")
listItemNode.RemoveIALAttr("custom-hidden")
listItemNode.RemoveIALAttr(DocHiddenAttr)
newTree.Root.KramdownIAL = listItemNode.KramdownIAL
srcLiParent := listItemNode.Parent
listItemNode.Unlink()

View file

@ -138,7 +138,7 @@ func RemoveBox(boxID string) (err error) {
if !isUserGuide {
var historyDir string
historyDir, err = GetHistoryDir(HistoryOpDelete)
historyDir, err = getHistoryDir(HistoryOpDelete)
if err != nil {
logging.LogErrorf("get history dir failed: %s", err)
return

View file

@ -135,20 +135,19 @@ func refreshDocInfo0(tree *parse.Tree, size uint64) {
}
subFileCount := 0
subDir := filepath.Join(util.DataDir, tree.Box, strings.TrimSuffix(tree.Path, ".sy"))
subFiles, err := os.ReadDir(subDir)
if err == nil {
for _, subFile := range subFiles {
if "true" == tree.Root.IALAttr("custom-hidden") {
continue
}
if "true" != tree.Root.IALAttr(DocHiddenAttr) {
subDir := filepath.Join(util.DataDir, tree.Box, strings.TrimSuffix(tree.Path, ".sy"))
subFiles, err := os.ReadDir(subDir)
if err == nil {
for _, subFile := range subFiles {
if !strings.HasSuffix(subFile.Name(), ".sy") {
continue
}
subDocIAL := filesys.DocIAL(filepath.Join(subDir, subFile.Name()))
if "true" == subDocIAL["custom-hidden"] {
continue
}
if strings.HasSuffix(subFile.Name(), ".sy") {
subDocIAL := filesys.DocIAL(filepath.Join(subDir, subFile.Name()))
if "true" == subDocIAL[DocHiddenAttr] {
continue
}
subFileCount++
}
}

View file

@ -513,9 +513,8 @@ func FindReplace(keyword, replacement string, replaceTypes map[string]bool, ids
renameRootTitles := map[string]string{}
cachedTrees := map[string]*parse.Tree{}
historyDir, err := getHistoryDir(HistoryOpReplace, time.Now())
historyDir, err := getHistoryDir(HistoryOpReplace)
if err != nil {
logging.LogErrorf("get history dir failed: %s", err)
return
}
@ -543,7 +542,7 @@ func FindReplace(keyword, replacement string, replaceTypes map[string]bool, ids
continue
}
generateTreeHistory(historyDir, tree)
generateTreeHistory(tree, historyDir)
cachedTrees[bt.RootID] = tree
}

View file

@ -22,7 +22,6 @@ import (
"path/filepath"
"sort"
"strings"
"time"
"github.com/88250/gulu"
"github.com/88250/lute/ast"
@ -49,7 +48,10 @@ func RemoveTag(label string) (err error) {
var reloadTreeIDs []string
updateNodes := map[string]*ast.Node{}
historyDir, err := getHistoryDir(HistoryOpReplace, time.Now())
historyDir, err := getHistoryDir(HistoryOpReplace)
if nil != err {
return
}
for treeID, blocks := range treeBlocks {
util.PushEndlessProgress("[" + treeID + "]")
@ -59,7 +61,7 @@ func RemoveTag(label string) (err error) {
return e
}
generateTreeHistory(historyDir, tree)
generateTreeHistory(tree, historyDir)
var unlinks []*ast.Node
for _, blockID := range blocks {
@ -153,7 +155,10 @@ func RenameTag(oldLabel, newLabel string) (err error) {
var reloadTreeIDs []string
updateNodes := map[string]*ast.Node{}
historyDir, err := getHistoryDir(HistoryOpReplace, time.Now())
historyDir, err := getHistoryDir(HistoryOpReplace)
if nil != err {
return
}
for treeID, blocks := range treeBlocks {
util.PushEndlessProgress("[" + treeID + "]")
@ -163,7 +168,7 @@ func RenameTag(oldLabel, newLabel string) (err error) {
return e
}
generateTreeHistory(historyDir, tree)
generateTreeHistory(tree, historyDir)
for _, blockID := range blocks {
node := treenode.GetNodeInTree(tree, blockID)

View file

@ -244,6 +244,9 @@ func LoadTreeByBlockID(id string) (ret *parse.Tree, err error) {
func loadTreeByBlockTree(bt *treenode.BlockTree) (ret *parse.Tree, err error) {
luteEngine := util.NewLute()
ret, needFix, err := filesys.LoadTreeWithFix(bt.BoxID, bt.Path, luteEngine)
if nil != err {
return
}
if needFix {
treenode.UpsertBlockTree(ret)
sql.IndexTreeQueue(ret)

View file

@ -127,13 +127,13 @@ func indexNode(tx *sql.Tx, id string) (err error) {
return
}
if caseSensitive {
stmt = "UPDATE blocks_fts_case_insensitive SET content = ? WHERE id = ?"
stmt = "UPDATE blocks_fts SET content = ? WHERE id = ?"
if err = execStmtTx(tx, stmt, content, id); err != nil {
tx.Rollback()
return
}
} else {
stmt = "UPDATE blocks_fts SET content = ? WHERE id = ?"
stmt = "UPDATE blocks_fts_case_insensitive SET content = ? WHERE id = ?"
if err = execStmtTx(tx, stmt, content, id); err != nil {
tx.Rollback()
return

View file

@ -19,25 +19,24 @@ package util
import (
"os"
"sort"
"strconv"
"strings"
"sync"
"time"
"github.com/88250/gulu"
"github.com/ConradIrwin/font/sfnt"
"github.com/flopp/go-findfont"
"github.com/siyuan-note/logging"
ttc "golang.org/x/image/font/sfnt"
textUnicode "golang.org/x/text/encoding/unicode"
"golang.org/x/text/transform"
)
var (
sysFonts []string
sysFonts []*Font
sysFontsLock = sync.Mutex{}
)
func LoadSysFonts() (ret []string) {
func LoadSysFonts() []*Font {
sysFontsLock.Lock()
defer sysFontsLock.Unlock()
@ -46,21 +45,20 @@ func LoadSysFonts() (ret []string) {
}
start := time.Now()
fonts := loadFonts()
ret = []string{}
for _, font := range fonts {
ret = append(ret, font.Family)
}
ret = gulu.Str.RemoveDuplicatedElem(ret)
sort.Strings(ret)
sysFonts = ret
sysFonts = loadFonts()
sort.Slice(sysFonts, func(i, j int) bool {
return sysFonts[i].DisplayName < sysFonts[j].DisplayName
})
logging.LogInfof("loaded system fonts [%d] in [%dms]", len(sysFonts), time.Since(start).Milliseconds())
return
return sysFonts
}
type Font struct {
Path string
Family string
Family string `json:"family"` // 对应 CSS font-family
Weight int `json:"weight"` // 对应 CSS font-weight
DisplayName string `json:"displayName"` // 给人看的名称 (Family + Subfamily)
}
func loadFonts() (ret []*Font) {
@ -68,22 +66,22 @@ func loadFonts() (ret []*Font) {
for _, fontPath := range findfont.List() {
if strings.HasSuffix(strings.ToLower(fontPath), ".ttc") {
families := parseTTCFontFamily(fontPath)
for _, family := range families {
if existFont(family, ret) {
for _, f := range families {
if existFont(f, ret) {
continue
}
ret = append(ret, &Font{fontPath, family})
ret = append(ret, f)
//LogInfof("[%s] [%s]", fontPath, family)
}
} else if strings.HasSuffix(strings.ToLower(fontPath), ".otf") || strings.HasSuffix(strings.ToLower(fontPath), ".ttf") {
family := parseTTFFontFamily(fontPath)
if "" != family {
if existFont(family, ret) {
f := parseTTFFontFamily(fontPath)
if nil != f {
if existFont(f, ret) {
continue
}
ret = append(ret, &Font{fontPath, family})
ret = append(ret, f)
//logging.LogInfof("[%s] [%s]", fontPath, family)
}
}
@ -91,77 +89,63 @@ func loadFonts() (ret []*Font) {
return
}
func existFont(family string, fonts []*Font) bool {
func existFont(f *Font, fonts []*Font) bool {
for _, font := range fonts {
if strings.EqualFold(family, font.Family) {
if strings.EqualFold(f.Family, font.Family) && f.Weight == font.Weight {
return true
}
}
return false
}
func parseTTCFontFamily(fontPath string) (ret []string) {
func parseTTCFontFamily(fontPath string) (ret []*Font) {
defer logging.Recover()
data, err := os.ReadFile(fontPath)
fontFile, err := os.Open(fontPath)
if err != nil {
//logging.LogErrorf("read font file [%s] failed: %s", fontPath, err)
return
}
collection, err := ttc.ParseCollection(data)
defer fontFile.Close()
fonts, err := sfnt.ParseCollection(fontFile)
if err != nil {
//LogErrorf("parse font collection [%s] failed: %s", fontPath, err)
return
}
for i := 0; i < collection.NumFonts(); i++ {
font, err := collection.Font(i)
if err != nil {
//LogErrorf("get font [%s] failed: %s", fontPath, err)
continue
}
family, _ := font.Name(nil, ttc.NameIDFull)
family = strings.TrimSpace(family)
if "" != family && !strings.HasPrefix(family, ".") {
ret = append(ret, family)
}
family, _ = font.Name(nil, ttc.NameIDFamily)
family = strings.TrimSpace(family)
if "" != family && !strings.HasPrefix(family, ".") {
ret = append(ret, family)
}
family, _ = font.Name(nil, ttc.NameIDTypographicFamily)
family = strings.TrimSpace(family)
if "" != family && !strings.HasPrefix(family, ".") {
ret = append(ret, family)
for _, f := range fonts {
font := parseFont(f)
if nil != font {
ret = append(ret, font)
}
}
ret = gulu.Str.RemoveDuplicatedElem(ret)
return
}
func parseTTFFontFamily(fontPath string) (ret string) {
func parseTTFFontFamily(fontPath string) *Font {
defer logging.Recover()
fontFile, err := os.Open(fontPath)
defer fontFile.Close()
if err != nil {
//LogErrorf("open font file [%s] failed: %s", fontPath, err)
return
return nil
}
defer fontFile.Close()
font, err := sfnt.Parse(fontFile)
if err != nil {
//LogErrorf("parse font [%s] failed: %s", fontPath, err)
return
//logging.LogErrorf("parse font [%s] failed: %s", fontFile.Name(), err)
return nil
}
return parseFont(font)
}
func parseFont(font *sfnt.Font) *Font {
t, err := font.NameTable()
if err != nil {
logging.LogErrorf("get font [%s] name table failed: %s", fontPath, err)
return
//logging.LogErrorf("parse font name table failed: %s", err)
return nil
}
var family, subfamily string
@ -192,15 +176,53 @@ func parseTTFFontFamily(fontPath string) (ret string) {
}
}
//if family != "" && !strings.HasPrefix(family, ".") {
// if subfamily != "" && !strings.Contains(subfamily, "<") && !strings.EqualFold(subfamily, "Regular") {
// ret = family + "(" + subfamily + ")"
// } else {
// ret = family
// }
//}
// TODO: 字重加载方案
_ = subfamily
ret = family
return
weight := 400
os2, err := font.OS2Table()
if nil == err {
weight = int(os2.USWeightClass)
}
if weight == 400 && subfamily != "" {
s := strings.ToLower(subfamily)
// 自动匹配 W01-W09
for i := 1; i <= 9; i++ {
wStr := "w0" + strconv.Itoa(i)
if strings.Contains(s, wStr) {
weight = i * 100
break
}
}
// 自动匹配标准关键词
if weight == 400 { // 如果 W 系列没匹配到
switch {
case strings.Contains(s, "thin"):
weight = 100
case strings.Contains(s, "light"):
weight = 300
case strings.Contains(s, "medium"):
weight = 500
case strings.Contains(s, "semibold") || strings.Contains(s, "demi"):
weight = 600
case strings.Contains(s, "bold"):
weight = 700
case strings.Contains(s, "black") || strings.Contains(s, "heavy"):
weight = 900
}
}
}
if family != "" && !strings.HasPrefix(family, ".") {
displayName := family
if subfamily != "" && !strings.EqualFold(subfamily, "Regular") {
displayName = family + " " + subfamily
}
return &Font{
Family: family,
Weight: weight,
DisplayName: displayName,
}
}
return nil
}

View file

@ -44,7 +44,7 @@ import (
var Mode = "prod"
const (
Ver = "3.6.4"
Ver = "3.6.5"
IsInsider = false
)