diff --git a/app/appearance/langs/es_ES.json b/app/appearance/langs/es_ES.json index 06c61d6520f..933603eb647 100644 --- a/app/appearance/langs/es_ES.json +++ b/app/appearance/langs/es_ES.json @@ -1063,7 +1063,7 @@ "export20": "La exportación de archivos Word .docx requiere la conversión del formato mediante Pandoc", "export21": "Exportar plantilla de pie de página PDF", "export22": "%page es el número de página actual, %pages es el número de página total y es compatible con las funciones de plantilla de Sprig ", - "export23": "Exportar descuento con YAML front-matter", + "export23": "Exportar Markdown con YAML front-matter", "export24": "Después de habilitar, agregue información general de metadatos al comienzo del archivo Markdown exportado", "export25": "Exportar ruta de plantilla .docx de Word", "export26": "La ruta absoluta de la plantilla utilizada al exportar archivos .docx de Word, es decir, Pandoc --reference-doc valor del parámetro", diff --git a/app/src/assets/scss/business/_drag.scss b/app/src/assets/scss/business/_drag.scss index 75e3df422fb..17424611479 100644 --- a/app/src/assets/scss/business/_drag.scss +++ b/app/src/assets/scss/business/_drag.scss @@ -1,5 +1,5 @@ .dragover { - background-color: var(--b3-theme-primary-lightest); + background-color: var(--b3-theme-primary-lightest) !important; // 需要 !important,否则拖拽到闪卡无效果 &__top { diff --git a/app/src/assets/scss/protyle/_wysiwyg.scss b/app/src/assets/scss/protyle/_wysiwyg.scss index de8b02d9467..dd52c91bc77 100644 --- a/app/src/assets/scss/protyle/_wysiwyg.scss +++ b/app/src/assets/scss/protyle/_wysiwyg.scss @@ -55,6 +55,18 @@ } } + .bq { + .dragover { + &__top:not(.av__row) { + box-shadow: 0 -3px 0 var(--b3-theme-primary-lighter), inset 0 1px 0 var(--b3-theme-primary-lighter) !important; + } + + &__bottom:not(.av__row) { + box-shadow: 0 3px 0 var(--b3-theme-primary-lighter), inset 0 -1px 0 var(--b3-theme-primary-lighter) !important; + } + } + } + &.list { padding-left: 0; @@ -449,6 +461,10 @@ &--select { background-color: var(--b3-theme-primary-lightest) !important; + + [data-node-id][style*="background-color"] { + opacity: .86; + } } // https://github.com/siyuan-note/siyuan/issues/11589 diff --git a/app/src/block/Panel.ts b/app/src/block/Panel.ts index 49f996ae63b..ec10d0f3275 100644 --- a/app/src/block/Panel.ts +++ b/app/src/block/Panel.ts @@ -124,7 +124,7 @@ export class BlockPanel { openFileById({ app: options.app, id: this.nodeIds[0], - action: this.editors[0].protyle.block.rootID !== this.nodeIds[0] ? [Constants.CB_GET_ALL] : [Constants.CB_GET_CONTEXT], + action: this.editors[0].protyle.block.rootID !== this.nodeIds[0] ? [Constants.CB_GET_ALL, Constants.CB_GET_FOCUS] : [Constants.CB_GET_CONTEXT], }); /// #endif } @@ -237,7 +237,7 @@ export class BlockPanel { let openHTML = ""; /// #if !BROWSER if (this.nodeIds.length === 1) { - openHTML = ` + openHTML = ` `; diff --git a/app/src/config/keymap.ts b/app/src/config/keymap.ts index 46fbc7b49c2..01f0cba4256 100644 --- a/app/src/config/keymap.ts +++ b/app/src/config/keymap.ts @@ -230,7 +230,8 @@ export const keymap = { }); }, search(value: string, keymapString: string) { - keymap.element.querySelectorAll("#keymapList .b3-list-item--hide-action > .b3-list-item__text").forEach(item => { + const keymapListElement = keymap.element.querySelector("#keymapList") + keymapListElement.querySelectorAll(".b3-list-item--hide-action > .b3-list-item__text").forEach(item => { const liElement = item.parentElement; let matchedKeymap = false; if (keymapString === "" || liElement.querySelector(".b3-text-field").getAttribute("data-value").indexOf(keymapString) > -1) { @@ -245,10 +246,9 @@ export const keymap = { } if (!liElement.nextElementSibling) { const toggleElement = liElement.parentElement.previousElementSibling; - const toggleIconElement = toggleElement.querySelector(".b3-list-item__arrow"); if (value === "" && keymapString === "") { // 复原折叠状态 - if (toggleIconElement.classList.contains("b3-list-item__arrow--open")) { + if (toggleElement.querySelector(".b3-list-item__arrow").classList.contains("b3-list-item__arrow--open")) { liElement.parentElement.classList.remove("fn__none"); } else { liElement.parentElement.classList.add("fn__none"); @@ -262,8 +262,14 @@ export const keymap = { } } }); - // 编辑器中三级菜单单独处理 - const editorKeymapElement = keymap.element.querySelector("#keymapList").lastElementChild; + // 编辑器单独处理 + this._toggleSearchItem(keymapListElement.lastElementChild, value, keymapString); + // 插件单独处理 + if (keymapListElement.childElementCount === 5) { + this._toggleSearchItem(keymapListElement.lastElementChild.previousElementSibling, value, keymapString); + } + }, + _toggleSearchItem(editorKeymapElement: HTMLElement, value: string, keymapString: string) { if (value === "" && keymapString === "") { // 复原折叠状态 if (editorKeymapElement.querySelector(".b3-list-item__arrow").classList.contains("b3-list-item__arrow--open")) { diff --git a/app/src/dialog/processSystem.ts b/app/src/dialog/processSystem.ts index d78a5dad3ac..b371a0cc3c3 100644 --- a/app/src/dialog/processSystem.ts +++ b/app/src/dialog/processSystem.ts @@ -201,6 +201,11 @@ export const setDefRefCount = (data: { attrElement.innerHTML = `
${data.refCount}
${Constants.ZWSP}`; } } + if (data.refCount === 0) { + item.removeAttribute("refcount"); + } else { + item.setAttribute("refcount", data.refCount.toString()); + } }); }); diff --git a/app/src/layout/dock/Files.ts b/app/src/layout/dock/Files.ts index 2e9b0be90d6..0fb2d656d36 100644 --- a/app/src/layout/dock/Files.ts +++ b/app/src/layout/dock/Files.ts @@ -20,6 +20,7 @@ import {hasClosestByAttribute, hasClosestByTag, hasTopClosestByTag} from "../../ import {isTouchDevice} from "../../util/functions"; import {App} from "../../index"; import {refreshFileTree} from "../../dialog/processSystem"; +import {hideTooltip} from "../../dialog/tooltip"; export class Files extends Model { public element: HTMLElement; @@ -382,6 +383,7 @@ export class Files extends Model { return; } window.getSelection().removeAllRanges(); + hideTooltip(); const liElement = hasClosestByTag(event.target, "LI"); if (liElement) { let selectElements: Element[] = Array.from(this.element.querySelectorAll(".b3-list-item--focus")); diff --git a/app/src/menus/Menu.ts b/app/src/menus/Menu.ts index 3e51baa9eaf..4a66455830d 100644 --- a/app/src/menus/Menu.ts +++ b/app/src/menus/Menu.ts @@ -256,7 +256,12 @@ export class MenuItem { const getActionMenu = (element: Element, next: boolean) => { let actionMenuElement = element; - while (actionMenuElement && (actionMenuElement.classList.contains("b3-menu__separator") || actionMenuElement.classList.contains("b3-menu__item--readonly"))) { + while (actionMenuElement && + (actionMenuElement.classList.contains("b3-menu__separator") || + actionMenuElement.classList.contains("b3-menu__item--readonly") || + // https://github.com/siyuan-note/siyuan/issues/12518 + actionMenuElement.getBoundingClientRect().height === 0) + ) { if (actionMenuElement.querySelector(".b3-text-field")) { break; } diff --git a/app/src/menus/protyle.ts b/app/src/menus/protyle.ts index 9bbc223d66b..0830ca69c38 100644 --- a/app/src/menus/protyle.ts +++ b/app/src/menus/protyle.ts @@ -25,7 +25,7 @@ import {transaction, updateTransaction} from "../protyle/wysiwyg/transaction"; import {openMenu} from "./commonMenuItem"; import {fetchPost} from "../util/fetch"; import {Constants} from "../constants"; -import {copyPlainText, readText, setStorageVal, writeText} from "../protyle/util/compatibility"; +import {copyPlainText, readText, setStorageVal, updateHotkeyTip, writeText} from "../protyle/util/compatibility"; import {preventScroll} from "../protyle/scroll/preventScroll"; import {onGet} from "../protyle/util/onGet"; import {getAllModels} from "../layout/getAll"; @@ -382,7 +382,7 @@ export const refMenu = (protyle: IProtyle, element: HTMLElement) => { window.siyuan.menus.menu.append(new MenuItem({ label: window.siyuan.languages.refTab, icon: "iconEyeoff", - accelerator: window.siyuan.config.keymap.editor.general.refTab.custom + "/⌘" + window.siyuan.languages.click, + accelerator: window.siyuan.config.keymap.editor.general.refTab.custom + "/" + updateHotkeyTip("⌘") + window.siyuan.languages.click, click() { checkFold(refBlockId, (zoomIn) => { openFileById({ @@ -398,7 +398,7 @@ export const refMenu = (protyle: IProtyle, element: HTMLElement) => { window.siyuan.menus.menu.append(new MenuItem({ label: window.siyuan.languages.insertRight, icon: "iconLayoutRight", - accelerator: window.siyuan.config.keymap.editor.general.insertRight.custom + "/⌥" + window.siyuan.languages.click, + accelerator: window.siyuan.config.keymap.editor.general.insertRight.custom + "/" + updateHotkeyTip("⌥") + window.siyuan.languages.click, click() { checkFold(refBlockId, (zoomIn, action, isRoot) => { if (!isRoot) { @@ -417,7 +417,7 @@ export const refMenu = (protyle: IProtyle, element: HTMLElement) => { window.siyuan.menus.menu.append(new MenuItem({ label: window.siyuan.languages.insertBottom, icon: "iconLayoutBottom", - accelerator: window.siyuan.config.keymap.editor.general.insertBottom.custom + (window.siyuan.config.keymap.editor.general.insertBottom.custom ? "/" : "") + "⇧" + window.siyuan.languages.click, + accelerator: window.siyuan.config.keymap.editor.general.insertBottom.custom + (window.siyuan.config.keymap.editor.general.insertBottom.custom ? "/" : "") + updateHotkeyTip("⇧") + window.siyuan.languages.click, click() { checkFold(refBlockId, (zoomIn, action, isRoot) => { if (!isRoot) { @@ -1086,7 +1086,6 @@ export const imgMenu = (protyle: IProtyle, range: Range, assetElement: HTMLEleme } }).element); } - /// #if !BROWSER window.siyuan.menus.menu.append(new MenuItem({ label: "OCR", submenu: [{ @@ -1116,7 +1115,6 @@ export const imgMenu = (protyle: IProtyle, range: Range, assetElement: HTMLEleme } }], }).element); - /// #endif window.siyuan.menus.menu.append(new MenuItem({ icon: "iconAlignCenter", label: window.siyuan.languages.alignCenter, diff --git a/app/src/protyle/gutter/index.ts b/app/src/protyle/gutter/index.ts index c3734cd0da4..4ac1f37be01 100644 --- a/app/src/protyle/gutter/index.ts +++ b/app/src/protyle/gutter/index.ts @@ -2139,6 +2139,7 @@ export class Gutter { label: window.siyuan.languages.height, submenu: styles.concat([{ iconHTML: "", + type: "readonly", label: `
`, diff --git a/app/src/protyle/header/openTitleMenu.ts b/app/src/protyle/header/openTitleMenu.ts index 267b283950a..fa47f9ab316 100644 --- a/app/src/protyle/header/openTitleMenu.ts +++ b/app/src/protyle/header/openTitleMenu.ts @@ -200,13 +200,13 @@ export const openTitleMenu = (protyle: IProtyle, position: IPosition) => { /// #if !MOBILE if (!protyle.model) { window.siyuan.menus.menu.append(new MenuItem({ - label: window.siyuan.languages.openInNewTab, - icon: "iconLayoutRight", + label: window.siyuan.languages.openBy, + icon: "iconOpen", click() { openFileById({ app: protyle.app, id: protyle.block.id, - action: protyle.block.rootID !== protyle.block.id ? [Constants.CB_GET_ALL] : [Constants.CB_GET_CONTEXT], + action: protyle.block.rootID !== protyle.block.id ? [Constants.CB_GET_ALL, Constants.CB_GET_FOCUS] : [Constants.CB_GET_CONTEXT], }); } }).element); diff --git a/app/src/protyle/ui/initUI.ts b/app/src/protyle/ui/initUI.ts index 572a51bca78..166b0634db9 100644 --- a/app/src/protyle/ui/initUI.ts +++ b/app/src/protyle/ui/initUI.ts @@ -8,6 +8,10 @@ import {fetchPost} from "../../util/fetch"; import {lineNumberRender} from "../render/highlightRender"; import {hideMessage, showMessage} from "../../dialog/message"; import {genUUID} from "../../util/genID"; +import {getContenteditableElement, getLastBlock} from "../wysiwyg/getBlock"; +import {genEmptyElement} from "../../block/util"; +import {transaction} from "../wysiwyg/transaction"; +import {focusByRange} from "../util/selection"; export const initUI = (protyle: IProtyle) => { protyle.contentElement = document.createElement("div"); @@ -91,6 +95,48 @@ export const initUI = (protyle: IProtyle) => { }); }, Constants.TIMEOUT_LOAD); }, {passive: false}); + protyle.contentElement.addEventListener("click", (event: MouseEvent & { target: HTMLElement }) => { + // wysiwyg 元素下方点击无效果 https://github.com/siyuan-note/siyuan/issues/12009 + if (protyle.disabled || + (!event.target.classList.contains("protyle-content") && !event.target.classList.contains("protyle-wysiwyg"))) { + return + } + const lastRect = protyle.wysiwyg.element.lastElementChild.getBoundingClientRect(); + const range = document.createRange(); + if (event.y > lastRect.bottom) { + const lastEditElement = getContenteditableElement(getLastBlock(protyle.wysiwyg.element.lastElementChild)); + if (!lastEditElement || + (protyle.wysiwyg.element.lastElementChild.getAttribute("data-type") !== "NodeParagraph" && protyle.wysiwyg.element.getAttribute("data-doc-type") !== "NodeListItem") || + (protyle.wysiwyg.element.lastElementChild.getAttribute("data-type") === "NodeParagraph" && getContenteditableElement(lastEditElement).innerHTML !== "")) { + const emptyElement = genEmptyElement(false, false); + protyle.wysiwyg.element.insertAdjacentElement("beforeend", emptyElement); + transaction(protyle, [{ + action: "insert", + data: emptyElement.outerHTML, + id: emptyElement.getAttribute("data-node-id"), + previousID: emptyElement.previousElementSibling.getAttribute("data-node-id"), + parentID: protyle.block.parentID + }], [{ + action: "delete", + id: emptyElement.getAttribute("data-node-id") + }]); + const emptyEditElement = getContenteditableElement(emptyElement) as HTMLInputElement; + range.selectNodeContents(emptyEditElement); + range.collapse(true); + focusByRange(range); + // 需等待 range 更新再次进行渲染 + if (protyle.options.render.breadcrumb) { + setTimeout(() => { + protyle.breadcrumb.render(protyle); + }, Constants.TIMEOUT_TRANSITION); + } + } else if (lastEditElement) { + range.selectNodeContents(lastEditElement); + range.collapse(false); + focusByRange(range); + } + } + }); }; export const addLoading = (protyle: IProtyle, msg?: string) => { diff --git a/app/src/protyle/util/editorCommonEvent.ts b/app/src/protyle/util/editorCommonEvent.ts index 5c55d0ae168..5da609b8024 100644 --- a/app/src/protyle/util/editorCommonEvent.ts +++ b/app/src/protyle/util/editorCommonEvent.ts @@ -797,7 +797,7 @@ export const dropEvent = (protyle: IProtyle, editorElement: HTMLElement) => { } const targetElement = editorElement.querySelector(".dragover__left, .dragover__right, .dragover__bottom, .dragover__top"); if (targetElement) { - targetElement.classList.remove("protyle-wysiwyg--select"); + targetElement.classList.remove("protyle-wysiwyg--select", "dragover"); targetElement.removeAttribute("select-start"); targetElement.removeAttribute("select-end"); } @@ -1176,6 +1176,18 @@ export const dropEvent = (protyle: IProtyle, editorElement: HTMLElement) => { event.preventDefault(); let targetElement = hasClosestByClassName(event.target, "av__row") || hasClosestByClassName(event.target, "av__row--util") || hasClosestBlock(event.target); const point = {x: event.clientX, y: event.clientY, className: ""}; + + // 超级块中有a,b两个段落块,移动到 ab 之间的间隙 targetElement 会变为超级块,需修正为 a + if (targetElement && (targetElement.classList.contains("bq") || targetElement.classList.contains("sb") || targetElement.classList.contains("list") || targetElement.classList.contains("li"))) { + let prevElement = hasClosestBlock(document.elementFromPoint(point.x, point.y - 6)) + while (prevElement && targetElement.contains(prevElement)) { + if (prevElement.nextElementSibling?.getAttribute("data-node-id")) { + targetElement = prevElement; + } + prevElement = prevElement.parentElement; + } + } + if (!targetElement) { if (event.clientY > editorElement.lastElementChild.getBoundingClientRect().bottom) { // 命中底部 @@ -1227,7 +1239,8 @@ export const dropEvent = (protyle: IProtyle, editorElement: HTMLElement) => { } } else if (targetElement && gutterType && gutterType.startsWith(`${Constants.SIYUAN_DROP_GUTTER}NodeAttributeViewRowMenu${Constants.ZWSP}`.toLowerCase())) { // 行只能拖拽当前 av 中 - if (!targetElement.classList.contains("av__row") || !window.siyuan.dragElement.contains(targetElement)) { + if ((!targetElement.classList.contains("av__row") && !targetElement.classList.contains("av__row--util")) || + !window.siyuan.dragElement.contains(targetElement)) { targetElement = false; } } @@ -1247,14 +1260,17 @@ export const dropEvent = (protyle: IProtyle, editorElement: HTMLElement) => { return; } if (point.className) { - targetElement.classList.add(point.className, "dragover"); + targetElement.classList.add(point.className); + addDragover(targetElement); return; } if (targetElement.getAttribute("data-type") === "NodeListItem" || fileTreeIds.indexOf("-") > -1) { if (event.clientY > nodeRect.top + nodeRect.height / 2) { - targetElement.classList.add("dragover__bottom", "dragover"); + targetElement.classList.add("dragover__bottom"); + addDragover(targetElement); } else if (!targetElement.classList.contains("av__row--header")) { - targetElement.classList.add("dragover__top", "dragover"); + targetElement.classList.add("dragover__top"); + addDragover(targetElement); } return; } @@ -1275,19 +1291,23 @@ export const dropEvent = (protyle: IProtyle, editorElement: HTMLElement) => { } if (event.clientX < nodeRect.left + 32 && event.clientX >= nodeRect.left - 1 && !targetElement.classList.contains("av__row")) { - targetElement.classList.add("dragover__left", "dragover"); + targetElement.classList.add("dragover__left"); + addDragover(targetElement); } else if (event.clientX > nodeRect.right - 32 && event.clientX < nodeRect.right && !targetElement.classList.contains("av__row")) { - targetElement.classList.add("dragover__right", "dragover"); + targetElement.classList.add("dragover__right"); + addDragover(targetElement); } else if (targetElement.classList.contains("av__row--header")) { - targetElement.classList.add("dragover__bottom", "dragover"); + targetElement.classList.add("dragover__bottom"); } else if (targetElement.classList.contains("av__row--util")) { - targetElement.previousElementSibling.classList.add("dragover__bottom", "dragover"); + targetElement.previousElementSibling.classList.add("dragover__bottom"); } else { if (event.clientY > nodeRect.top + nodeRect.height / 2 && disabledPosition !== "bottom") { - targetElement.classList.add("dragover__bottom", "dragover"); + targetElement.classList.add("dragover__bottom"); + addDragover(targetElement); } else if (disabledPosition !== "top") { - targetElement.classList.add("dragover__top", "dragover"); + targetElement.classList.add("dragover__top"); + addDragover(targetElement); } } return; @@ -1362,3 +1382,12 @@ export const dropEvent = (protyle: IProtyle, editorElement: HTMLElement) => { }); }); }; + +const addDragover = (element: HTMLElement) => { + if (element.classList.contains("sb") || + element.classList.contains("li") || + element.classList.contains("list") || + element.classList.contains("bq")) { + element.classList.add("dragover") + } +} diff --git a/app/src/protyle/wysiwyg/index.ts b/app/src/protyle/wysiwyg/index.ts index 4de0f96e104..9b213af423a 100644 --- a/app/src/protyle/wysiwyg/index.ts +++ b/app/src/protyle/wysiwyg/index.ts @@ -2155,6 +2155,7 @@ export class WYSIWYG { action, zoomIn }); + window.dispatchEvent(new KeyboardEvent("keydown", {key: "Escape"})); } else if (event.altKey) { openFileById({ app: protyle.app, @@ -2226,8 +2227,11 @@ export class WYSIWYG { return; } - // 如果aLink 为空时,当 data-type="a inline-math" 可继续后续操作 - if (aElement && range.toString() === "" && aLink) { + if (aElement && + // https://github.com/siyuan-note/siyuan/issues/11980 + (event.shiftKey || range.toString() === "") && + // 如果aLink 为空时,当 data-type="a inline-math" 可继续后续操作 + aLink) { event.stopPropagation(); event.preventDefault(); openLink(protyle, aLink, event, ctrlIsPressed); @@ -2515,44 +2519,6 @@ export class WYSIWYG { return; } - // 点击空白 - if (event.target.contains(this.element) && this.element.lastElementChild && !protyle.disabled) { - const lastRect = this.element.lastElementChild.getBoundingClientRect(); - if (event.y > lastRect.bottom) { - const lastEditElement = getContenteditableElement(getLastBlock(this.element.lastElementChild)); - if (!lastEditElement || - (this.element.lastElementChild.getAttribute("data-type") !== "NodeParagraph" && protyle.wysiwyg.element.getAttribute("data-doc-type") !== "NodeListItem") || - (this.element.lastElementChild.getAttribute("data-type") === "NodeParagraph" && getContenteditableElement(lastEditElement).innerHTML !== "")) { - const emptyElement = genEmptyElement(false, false); - this.element.insertAdjacentElement("beforeend", emptyElement); - transaction(protyle, [{ - action: "insert", - data: emptyElement.outerHTML, - id: emptyElement.getAttribute("data-node-id"), - previousID: emptyElement.previousElementSibling.getAttribute("data-node-id"), - parentID: protyle.block.parentID - }], [{ - action: "delete", - id: emptyElement.getAttribute("data-node-id") - }]); - const emptyEditElement = getContenteditableElement(emptyElement) as HTMLInputElement; - range.selectNodeContents(emptyEditElement); - range.collapse(true); - focusByRange(range); - // 需等待 range 更新再次进行渲染 - if (protyle.options.render.breadcrumb) { - setTimeout(() => { - protyle.breadcrumb.render(protyle); - }, Constants.TIMEOUT_TRANSITION); - } - } else if (lastEditElement) { - range.selectNodeContents(lastEditElement); - range.collapse(false); - focusByRange(range); - } - } - } - setTimeout(() => { // 选中后,在选中的文字上点击需等待 range 更新 let newRange = getEditorRange(this.element); diff --git a/kernel/api/filetree.go b/kernel/api/filetree.go index 405b7d20d34..d1a9c576277 100644 --- a/kernel/api/filetree.go +++ b/kernel/api/filetree.go @@ -673,6 +673,12 @@ func createDocWithMd(c *gin.Context) { return } + tagsArg := arg["tags"] + var tags string + if nil != tagsArg { + tags = tagsArg.(string) + } + var parentID string parentIDArg := arg["parentID"] if nil != parentIDArg { @@ -706,7 +712,7 @@ func createDocWithMd(c *gin.Context) { withMath = withMathArg.(bool) } - id, err := model.CreateWithMarkdown(notebook, hPath, markdown, parentID, id, withMath) + id, err := model.CreateWithMarkdown(tags, notebook, hPath, markdown, parentID, id, withMath) if err != nil { ret.Code = -1 ret.Msg = err.Error() diff --git a/kernel/model/asset_content.go b/kernel/model/asset_content.go index 628f72383e4..f41e3ef89d6 100644 --- a/kernel/model/asset_content.go +++ b/kernel/model/asset_content.go @@ -880,7 +880,7 @@ func (parser *PdfAssetParser) Parse(absPath string) (ret *AssetParseResult) { res := <-results pageText[res.pageNo] = res.text if nil != res.err { - logging.LogErrorf("convert [%s] of page %d failed: [%s]", tmp, res.pageNo, err) + logging.LogErrorf("convert [%s] of page %d failed: [%s]", tmp, res.pageNo, res.err) } } close(results) diff --git a/kernel/model/file.go b/kernel/model/file.go index 5765b5d93f0..320eba633f1 100644 --- a/kernel/model/file.go +++ b/kernel/model/file.go @@ -1159,7 +1159,7 @@ func CreateDocByMd(boxID, p, title, md string, sorts []string) (tree *parse.Tree return } -func CreateWithMarkdown(boxID, hPath, md, parentID, id string, withMath bool) (retID string, err error) { +func CreateWithMarkdown(tags, boxID, hPath, md, parentID, id string, withMath bool) (retID string, err error) { createDocLock.Lock() defer createDocLock.Unlock() @@ -1177,6 +1177,19 @@ func CreateWithMarkdown(boxID, hPath, md, parentID, id string, withMath bool) (r luteEngine.SetHTMLTag2TextMark(true) dom := luteEngine.Md2BlockDOM(md, false) retID, err = createDocsByHPath(box.ID, hPath, dom, parentID, id) + + nameValues := map[string]string{} + tags = strings.TrimSpace(tags) + tags = strings.ReplaceAll(tags, ",", ",") + tagArray := strings.Split(tags, ",") + var tmp []string + for _, tag := range tagArray { + tmp = append(tmp, strings.TrimSpace(tag)) + } + tags = strings.Join(tmp, ",") + nameValues["tags"] = tags + SetBlockAttrs(retID, nameValues) + WaitForWritingFiles() return } diff --git a/kernel/model/search.go b/kernel/model/search.go index a7f7a0f0350..7a0949ae1e4 100644 --- a/kernel/model/search.go +++ b/kernel/model/search.go @@ -824,8 +824,30 @@ func replaceNodeTextMarkTextContent(n *ast.Node, method int, keyword string, rep // Supports replacing text elements with other elements https://github.com/siyuan-note/siyuan/issues/11058 func replaceTextNode(text *ast.Node, method int, keyword string, replacement string, r *regexp.Regexp, luteEngine *lute.Lute) bool { if 0 == method { - if bytes.Contains(text.Tokens, []byte(keyword)) { - newContent := bytes.ReplaceAll(text.Tokens, []byte(keyword), []byte(replacement)) + newContent := text.Tokens + if Conf.Search.CaseSensitive { + if bytes.Contains(text.Tokens, []byte(keyword)) { + newContent = bytes.ReplaceAll(text.Tokens, []byte(keyword), []byte(replacement)) + } + } else { + // 当搜索结果中的文本元素包含大小写混合时替换失败 + // Replace fails when search results contain mixed case in text elements https://github.com/siyuan-note/siyuan/issues/9171 + keywords := strings.Split(keyword, " ") + // keyword 可能是 "foo Foo" 使用空格分隔的大小写命中情况,这里统一转换小写后去重 + if 1 < len(keywords) { + var lowerKeywords []string + for _, k := range keywords { + lowerKeywords = append(lowerKeywords, strings.ToLower(k)) + } + lowerKeywords = gulu.Str.RemoveDuplicatedElem(lowerKeywords) + keyword = strings.Join(lowerKeywords, " ") + } + + if bytes.Contains(bytes.ToLower(text.Tokens), []byte(keyword)) { + newContent = replaceCaseInsensitive(text.Tokens, []byte(keyword), []byte(replacement)) + } + } + if !bytes.Equal(newContent, text.Tokens) { tree := parse.Inline("", newContent, luteEngine.ParseOptions) if nil == tree.Root.FirstChild { return false @@ -1818,3 +1840,8 @@ func filterQueryInvisibleChars(query string) string { query = strings.ReplaceAll(query, "_@full_width_space@_", " ") return query } + +func replaceCaseInsensitive(input, old, new []byte) []byte { + re := regexp.MustCompile("(?i)" + regexp.QuoteMeta(string(old))) + return []byte(re.ReplaceAllString(string(input), string(new))) +} diff --git a/kernel/model/template.go b/kernel/model/template.go index 2015711d83d..be8a6a1191d 100644 --- a/kernel/model/template.go +++ b/kernel/model/template.go @@ -42,7 +42,7 @@ import ( func RenderGoTemplate(templateContent string) (ret string, err error) { tmpl := template.New("") - tplFuncMap := util.BuiltInTemplateFuncs() + tplFuncMap := treenode.BuiltInTemplateFuncs() sql.SQLTemplateFuncs(&tplFuncMap) tmpl = tmpl.Funcs(tplFuncMap) tpl, err := tmpl.Parse(templateContent) @@ -224,7 +224,7 @@ func RenderTemplate(p, id string, preview bool) (tree *parse.Tree, dom string, e } goTpl := template.New("").Delims(".action{", "}") - tplFuncMap := util.BuiltInTemplateFuncs() + tplFuncMap := treenode.BuiltInTemplateFuncs() sql.SQLTemplateFuncs(&tplFuncMap) goTpl = goTpl.Funcs(tplFuncMap) tpl, err := goTpl.Funcs(tplFuncMap).Parse(gulu.Str.FromBytes(md)) diff --git a/kernel/sql/av.go b/kernel/sql/av.go index fa185f0fdd9..4c9ace3ff59 100644 --- a/kernel/sql/av.go +++ b/kernel/sql/av.go @@ -404,7 +404,7 @@ func RenderTemplateCol(ial map[string]string, rowValues []*av.KeyValues, tplCont } goTpl := template.New("").Delims(".action{", "}") - tplFuncMap := util.BuiltInTemplateFuncs() + tplFuncMap := treenode.BuiltInTemplateFuncs() SQLTemplateFuncs(&tplFuncMap) goTpl = goTpl.Funcs(tplFuncMap) tpl, err := goTpl.Parse(tplContent) diff --git a/kernel/util/template.go b/kernel/treenode/template.go similarity index 84% rename from kernel/util/template.go rename to kernel/treenode/template.go index 2f133364b33..84561fc6106 100644 --- a/kernel/util/template.go +++ b/kernel/treenode/template.go @@ -14,7 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -package util +package treenode import ( "math" @@ -25,21 +25,23 @@ import ( "github.com/Masterminds/sprig/v3" "github.com/araddon/dateparse" "github.com/siyuan-note/logging" + "github.com/siyuan-note/siyuan/kernel/util" "github.com/spf13/cast" ) func BuiltInTemplateFuncs() (ret template.FuncMap) { ret = sprig.TxtFuncMap() - ret["Weekday"] = Weekday - ret["WeekdayCN"] = WeekdayCN - ret["WeekdayCN2"] = WeekdayCN2 - ret["ISOWeek"] = ISOWeek + ret["Weekday"] = util.Weekday + ret["WeekdayCN"] = util.WeekdayCN + ret["WeekdayCN2"] = util.WeekdayCN2 + ret["ISOWeek"] = util.ISOWeek ret["pow"] = pow ret["powf"] = powf ret["log"] = log ret["logf"] = logf ret["parseTime"] = parseTime ret["FormatFloat"] = FormatFloat + ret["getHPathByID"] = getHPathByID return } @@ -63,3 +65,12 @@ func parseTime(dateStr string) time.Time { func FormatFloat(format string, n float64) string { return humanize.FormatFloat(format, n) } + +func getHPathByID(id string) (ret string) { + bt := GetBlockTree(id) + if nil == bt { + return + } + ret = bt.HPath + return +}