diff --git a/app/src/assets/scss/protyle/_wysiwyg.scss b/app/src/assets/scss/protyle/_wysiwyg.scss
index 0079a33e8..d36546c4b 100644
--- a/app/src/assets/scss/protyle/_wysiwyg.scss
+++ b/app/src/assets/scss/protyle/_wysiwyg.scss
@@ -18,6 +18,16 @@
outline: none;
}
+ &::highlight(search-mark) {
+ background-color: var(--b3-protyle-inline-mark-background);
+ color: var(--b3-protyle-inline-mark-color);
+ }
+
+ &::highlight(search-mark-hl) {
+ background-color: var(--b3-theme-primary-lighter);
+ box-shadow: 0 0 0 .5px var(--b3-theme-on-background);
+ }
+
[data-node-id] {
position: relative;
diff --git a/app/src/protyle/index.ts b/app/src/protyle/index.ts
index 0671653eb..e042e38ef 100644
--- a/app/src/protyle/index.ts
+++ b/app/src/protyle/index.ts
@@ -72,6 +72,12 @@ export class Protyle {
element: id,
options: mergedOptions,
block: {},
+ highlight: {
+ mark: new Highlight(),
+ markHL: new Highlight(),
+ ranges: [],
+ rangeIndex: 0,
+ }
};
this.protyle.hint = new Hint(this.protyle);
diff --git a/app/src/protyle/util/destroy.ts b/app/src/protyle/util/destroy.ts
index a592d4e94..6c22d91df 100644
--- a/app/src/protyle/util/destroy.ts
+++ b/app/src/protyle/util/destroy.ts
@@ -5,6 +5,10 @@ export const destroy = (protyle: IProtyle) => {
return;
}
hideElements(["util"], protyle);
+ protyle.highlight.markHL.clear();
+ protyle.highlight.mark.clear();
+ protyle.highlight.ranges = [];
+ protyle.highlight.rangeIndex = 0;
protyle.observer?.disconnect();
protyle.observerLoad?.disconnect();
protyle.element.classList.remove("protyle");
diff --git a/app/src/protyle/util/reload.ts b/app/src/protyle/util/reload.ts
index ff5586d44..8ac9a1cf2 100644
--- a/app/src/protyle/util/reload.ts
+++ b/app/src/protyle/util/reload.ts
@@ -4,6 +4,7 @@ import {getDocByScroll, saveScroll} from "../scroll/saveScroll";
import {renderBacklink} from "../wysiwyg/renderBacklink";
import {hasClosestByClassName} from "./hasClosest";
import {preventScroll} from "../scroll/preventScroll";
+import {highlightMark} from "../../search/util";
export const reloadProtyle = (protyle: IProtyle, focus: boolean, updateReadonly?: boolean) => {
if (!protyle.preview.element.classList.contains("fn__none")) {
@@ -56,7 +57,12 @@ export const reloadProtyle = (protyle: IProtyle, focus: boolean, updateReadonly?
protyle,
focus,
scrollAttr: saveScroll(protyle, true) as IScrollAttr,
- updateReadonly
+ updateReadonly,
+ cb () {
+ if (protyle.query?.key) {
+ highlightMark(protyle, protyle.wysiwyg.element.querySelectorAll(`span[data-type~="search-mark"]`));
+ }
+ }
});
}
};
diff --git a/app/src/search/util.ts b/app/src/search/util.ts
index 7fa8202f2..13eeb1139 100644
--- a/app/src/search/util.ts
+++ b/app/src/search/util.ts
@@ -1159,6 +1159,28 @@ const renderNextSearchMark = (options: {
edit: Protyle,
target: Element,
}) => {
+ const contentRect = options.edit.protyle.contentElement.getBoundingClientRect();
+ if (CSS.highlights) {
+ options.edit.protyle.highlight.markHL.clear();
+ options.edit.protyle.highlight.mark.clear();
+ options.edit.protyle.highlight.rangeIndex++;
+ if (options.edit.protyle.highlight.rangeIndex >= options.edit.protyle.highlight.ranges.length) {
+ options.edit.protyle.highlight.rangeIndex = 0;
+ }
+ let rangeTop
+ options.edit.protyle.highlight.ranges.forEach((item, index) => {
+ if (options.edit.protyle.highlight.rangeIndex === index) {
+ options.edit.protyle.highlight.markHL.add(item);
+ rangeTop = item.getBoundingClientRect().top
+ } else {
+ options.edit.protyle.highlight.mark.add(item);
+ }
+ });
+ if (typeof rangeTop === "number") {
+ options.edit.protyle.contentElement.scrollTop = options.edit.protyle.contentElement.scrollTop + rangeTop - contentRect.top - contentRect.height / 2;
+ }
+ return;
+ }
let matchElement;
const allMatchElements = Array.from(options.edit.protyle.wysiwyg.element.querySelectorAll(`div[data-node-id="${options.id}"] span[data-type~="search-mark"]`));
allMatchElements.find((item, itemIndex) => {
@@ -1173,7 +1195,6 @@ const renderNextSearchMark = (options: {
}
if (matchElement) {
matchElement.classList.add("search-mark--hl");
- const contentRect = options.edit.protyle.contentElement.getBoundingClientRect();
options.edit.protyle.contentElement.scrollTop = options.edit.protyle.contentElement.scrollTop + matchElement.getBoundingClientRect().top - contentRect.top - contentRect.height / 2;
}
};
@@ -1209,18 +1230,23 @@ export const getArticle = (options: {
updateReadonly: true,
data: getResponse,
protyle: options.edit.protyle,
- action: zoomIn ? [Constants.CB_GET_ALL, Constants.CB_GET_HTML] : [Constants.CB_GET_HL, Constants.CB_GET_HTML],
+ action: zoomIn ? [Constants.CB_GET_ALL, Constants.CB_GET_HTML] : [Constants.CB_GET_HTML],
});
- const matchElement = options.edit.protyle.wysiwyg.element.querySelector(`div[data-node-id="${options.id}"] span[data-type~="search-mark"]`);
- if (matchElement) {
- matchElement.classList.add("search-mark--hl");
- const contentRect = options.edit.protyle.contentElement.getBoundingClientRect();
- const matchRectTop = matchElement.getBoundingClientRect().top; // 需前置,否则代码高亮后会移除该元素
- setTimeout(() => {
- // 等待 scrollCenter 定位后再滚动
- options.edit.protyle.contentElement.scrollTop = options.edit.protyle.contentElement.scrollTop + matchRectTop - contentRect.top - contentRect.height / 2;
- });
+ const matchElements = options.edit.protyle.wysiwyg.element.querySelectorAll(`div[data-node-id="${options.id}"] span[data-type~="search-mark"]`);
+ if (matchElements.length === 0) {
+ return;
}
+ const contentRect = options.edit.protyle.contentElement.getBoundingClientRect();
+ let matchRectTop: number
+ if (CSS.highlights) {
+ options.edit.protyle.highlight.rangeIndex = 0;
+ highlightMark(options.edit.protyle, matchElements);
+ matchRectTop = options.edit.protyle.highlight.ranges[0].getBoundingClientRect().top;
+ } else {
+ matchElements[0].classList.add("search-mark--hl");
+ matchRectTop = matchElements[0].getBoundingClientRect().top;
+ }
+ options.edit.protyle.contentElement.scrollTop = options.edit.protyle.contentElement.scrollTop + matchRectTop - contentRect.top - contentRect.height / 2;
});
});
});
@@ -1476,3 +1502,28 @@ ${item.tag ? `${it
`);
};
+
+export const highlightMark = (protyle: IProtyle, matchElements: NodeListOf) => {
+ protyle.highlight.markHL.clear();
+ protyle.highlight.markHL.clear();
+ protyle.highlight.ranges = [];
+ matchElements.forEach((item, index) => {
+ const range = new Range();
+ if (item.getAttribute("data-type") === "search-mark") {
+ const contentElement = item.firstChild
+ item.replaceWith(contentElement)
+ range.selectNodeContents(contentElement);
+ } else {
+ item.setAttribute("data-type", item.getAttribute("data-type").replace(" search-mark", "").replace("search-mark ", ""));
+ range.selectNodeContents(item);
+ }
+ if (index === protyle.highlight.rangeIndex) {
+ protyle.highlight.markHL.add(range);
+ } else {
+ protyle.highlight.mark.add(range);
+ }
+ protyle.highlight.ranges.push(range)
+ })
+ CSS.highlights.set("search-mark", protyle.highlight.mark);
+ CSS.highlights.set("search-mark-hl", protyle.highlight.markHL);
+}
diff --git a/app/src/types/index.d.ts b/app/src/types/index.d.ts
index 5f3365959..d0744f68b 100644
--- a/app/src/types/index.d.ts
+++ b/app/src/types/index.d.ts
@@ -107,8 +107,23 @@ type TAVFilterOperator =
| "Is relative to today"
| "Is true"
| "Is false"
+
declare module "blueimp-md5"
+declare class Highlight {
+ constructor(...range: Range[]);
+
+ add(range: Range): void
+
+ clear(): void
+
+ forEach(callbackfn: (value: Range, key: number) => void): void;
+}
+
+declare namespace CSS {
+ const highlights: Map;
+}
+
interface Window {
echarts: {
init(element: HTMLElement, theme?: string, options?: {
diff --git a/app/src/types/protyle.d.ts b/app/src/types/protyle.d.ts
index fdc667430..b508e1657 100644
--- a/app/src/types/protyle.d.ts
+++ b/app/src/types/protyle.d.ts
@@ -482,6 +482,12 @@ interface IProtyleOptions {
}
interface IProtyle {
+ highlight: {
+ mark: Highlight
+ markHL: Highlight
+ ranges: Range[]
+ rangeIndex: 0
+ }
getInstance: () => import("../protyle").Protyle,
observerLoad?: ResizeObserver,
observer?: ResizeObserver,