diff --git a/argilla-frontend/components/features/annotation/container/fields/chat-field/ChatField.vue b/argilla-frontend/components/features/annotation/container/fields/chat-field/ChatField.vue
index 35a25a695c..9cc922ce63 100644
--- a/argilla-frontend/components/features/annotation/container/fields/chat-field/ChatField.vue
+++ b/argilla-frontend/components/features/annotation/container/fields/chat-field/ChatField.vue
@@ -31,17 +31,21 @@
}"
>
-
-
-
+
+
+
+
diff --git a/argilla-frontend/components/features/annotation/container/fields/span-annotation/SpanAnnotationTextField.vue b/argilla-frontend/components/features/annotation/container/fields/span-annotation/SpanAnnotationTextField.vue
index c5818b8f24..490d490506 100644
--- a/argilla-frontend/components/features/annotation/container/fields/span-annotation/SpanAnnotationTextField.vue
+++ b/argilla-frontend/components/features/annotation/container/fields/span-annotation/SpanAnnotationTextField.vue
@@ -63,47 +63,53 @@
:entity-name="selectedEntity.text"
:message="$t('spanAnnotation.shortcutHelper')"
/>
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/argilla-frontend/components/features/annotation/container/fields/span-annotation/components/highlighting.ts b/argilla-frontend/components/features/annotation/container/fields/span-annotation/components/highlighting.ts
index e8f43132d3..65f537d6df 100644
--- a/argilla-frontend/components/features/annotation/container/fields/span-annotation/components/highlighting.ts
+++ b/argilla-frontend/components/features/annotation/container/fields/span-annotation/components/highlighting.ts
@@ -50,6 +50,8 @@ type Styles = {
type InitialConfiguration = Partial>;
+const isCSSHighlightsSupported = !!CSS.highlights;
+
export class Highlighting {
private readonly spanSelection = SpanSelection.getInstance();
private node: HTMLElement | undefined;
@@ -108,7 +110,7 @@ export class Highlighting {
}
mount(selections: LoadedSpan[] = []) {
- if (!CSS.highlights) {
+ if (!isCSSHighlightsSupported) {
throw new Error(
"The CSS Custom Highlight API is not supported in this browser!"
);
@@ -292,7 +294,7 @@ export class Highlighting {
}
private applyStyles() {
- if (!CSS.highlights) return;
+ if (!isCSSHighlightsSupported) return;
this.applyHighlightStyle();
this.applyEntityStyle();
diff --git a/argilla-frontend/components/features/annotation/container/fields/text-field/TextField.vue b/argilla-frontend/components/features/annotation/container/fields/text-field/TextField.vue
index ed1f3c3cda..a5258e2556 100644
--- a/argilla-frontend/components/features/annotation/container/fields/text-field/TextField.vue
+++ b/argilla-frontend/components/features/annotation/container/fields/text-field/TextField.vue
@@ -20,14 +20,17 @@
-
-
-
+
+
+
diff --git a/argilla-frontend/components/features/annotation/container/fields/useSearchTextHighlight.ts b/argilla-frontend/components/features/annotation/container/fields/useSearchTextHighlight.ts
index 2b23ab4d57..b2e3b33522 100644
--- a/argilla-frontend/components/features/annotation/container/fields/useSearchTextHighlight.ts
+++ b/argilla-frontend/components/features/annotation/container/fields/useSearchTextHighlight.ts
@@ -10,35 +10,38 @@ declare namespace CSS {
};
}
+type Indexes = { start: number; end: number }[];
+type Coincidences = {
+ textNode: Node;
+ indexes: Indexes;
+}[];
+
const DSLChars = ["|", "+", "-", "*"];
+const isCSSHighlightsSupported = !!CSS.highlights;
export const useSearchTextHighlight = (fieldId: string) => {
const FIELD_ID_TO_HIGHLIGHT = `fields-content-${fieldId}`;
const HIGHLIGHT_CLASS = `search-text-highlight-${fieldId}`;
- const scapeDSLChars = (value: string) => {
- let output = value;
-
- for (const char of DSLChars) {
- output = output.replaceAll(char, " ");
- }
-
- return output
- .split(" ")
- .map((w) => w.trim())
- .filter(Boolean);
- };
-
- const createRangesToHighlight = (
+ const createIndexesToHighlight = (
fieldComponent: HTMLElement,
searchText: string
- ) => {
- CSS.highlights.delete(HIGHLIGHT_CLASS);
+ ): Coincidences => {
+ const scapeDSLChars = (value: string) => {
+ let output = value;
- const ranges = [];
+ for (const char of DSLChars) {
+ output = output.replaceAll(char, " ");
+ }
+
+ return output
+ .split(" ")
+ .map((w) => w.trim())
+ .filter(Boolean);
+ };
const getTextNodesUnder = (el) => {
- const textNodes = [];
+ const textNodes: Node[] = [];
const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT);
while (walker.nextNode()) {
@@ -48,8 +51,12 @@ export const useSearchTextHighlight = (fieldId: string) => {
return textNodes.filter((node) => node.nodeValue.trim().length > 0);
};
- const getAllCoincidences = (textNode, word, mode: "PARTIAL" | "WORD") => {
- const indexes = [];
+ const getAllCoincidences = (
+ textNode: Node,
+ word: string,
+ mode: "PARTIAL" | "WORD"
+ ) => {
+ const indexes: Indexes = [];
if (mode === "PARTIAL") {
let startIndex = 0;
@@ -88,46 +95,102 @@ export const useSearchTextHighlight = (fieldId: string) => {
return indexes;
};
- const createRanges = (textNode, indexes) => {
- const ranges = [];
+ const textNodes = getTextNodesUnder(fieldComponent);
+ const words = scapeDSLChars(searchText);
- for (const index of indexes) {
- const range = new Range();
+ const coincidences = [];
- range.setStart(textNode, index.start);
- range.setEnd(textNode, index.end);
+ for (const textNode of textNodes) {
+ const indexes = [];
+ for (const word of words) {
+ const index = getAllCoincidences(textNode, word, "WORD");
- ranges.push(range);
+ indexes.push(...index);
}
- return ranges;
- };
+ coincidences.push({
+ textNode,
+ indexes,
+ });
+ }
- const textNodes = getTextNodesUnder(fieldComponent);
- const words = scapeDSLChars(searchText);
+ return coincidences;
+ };
- for (const textNode of textNodes) {
- for (const word of words) {
- const indexes = getAllCoincidences(textNode, word, "WORD");
+ const highlightCoincidences = (coincidences: Coincidences) => {
+ if (isCSSHighlightsSupported) {
+ const createRanges = (coincidences: Coincidences) => {
+ const ranges = [];
- const newRanges = createRanges(textNode, indexes);
+ for (const coincidence of coincidences) {
+ for (const index of coincidence.indexes) {
+ const range = new Range();
- ranges.push(...newRanges);
- }
+ range.setStart(coincidence.textNode, index.start);
+ range.setEnd(coincidence.textNode, index.end);
+
+ ranges.push(range);
+ }
+ }
+
+ return ranges;
+ };
+
+ const ranges = createRanges(coincidences);
+
+ return CSS.highlights.set(HIGHLIGHT_CLASS, new Highlight(...ranges));
}
- return ranges;
+ for (const coincidence of coincidences) {
+ let highlightedHTML = "";
+ let offset = 0;
+ const textContent = coincidence.textNode.nodeValue;
+
+ coincidence.indexes
+ .sort((a, b) => a.start - b.start)
+ .forEach(({ start, end }) => {
+ highlightedHTML += textContent.slice(offset, start);
+
+ highlightedHTML += `${textContent.slice(
+ start,
+ end
+ )}`;
+
+ offset = end;
+ });
+
+ highlightedHTML += textContent.slice(offset);
+
+ if (coincidence.textNode.parentElement)
+ coincidence.textNode.parentElement.innerHTML = highlightedHTML;
+ }
};
const highlightText = (searchText: string) => {
const fieldComponent = document.getElementById(FIELD_ID_TO_HIGHLIGHT);
+
+ if (isCSSHighlightsSupported) CSS.highlights.delete(HIGHLIGHT_CLASS);
+ else {
+ const currentSpans = document.getElementsByClassName(HIGHLIGHT_CLASS);
+
+ for (const span of Array.from(currentSpans)) {
+ const parent = span.parentElement;
+ if (!parent) continue;
+
+ parent.innerHTML = parent.innerHTML.replaceAll(
+ span.outerHTML,
+ span.innerHTML
+ );
+ }
+ }
+
if (!searchText || !fieldComponent) {
- CSS.highlights.delete(HIGHLIGHT_CLASS);
return;
}
- const ranges = createRangesToHighlight(fieldComponent, searchText);
- CSS.highlights.set(HIGHLIGHT_CLASS, new Highlight(...ranges));
+ const coincidences = createIndexesToHighlight(fieldComponent, searchText);
+
+ highlightCoincidences(coincidences);
};
return {
diff --git a/argilla-frontend/package-lock.json b/argilla-frontend/package-lock.json
index cb75a08577..9930e08761 100644
--- a/argilla-frontend/package-lock.json
+++ b/argilla-frontend/package-lock.json
@@ -60,6 +60,7 @@
"jest": "27.5.1",
"jest-serializer-vue": "2.0.2",
"jest-transform-stub": "2.0.0",
+ "nuxt-compress": "5.0.0",
"prettier": "2.8.8",
"sass-loader": "10.5.2",
"typescript": "5.7.2",
@@ -12296,6 +12297,56 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/compression-webpack-plugin": {
+ "version": "6.1.2",
+ "resolved": "https://registry.npmjs.org/compression-webpack-plugin/-/compression-webpack-plugin-6.1.2.tgz",
+ "integrity": "sha512-z6xtgKP3Uds2lyrkx2PGwrE9FZT8raHTC3ImFrY3e0faAfSfVIV63JmR+sfk5pf4OhUj3E4XdjZBCKpjYWIw6Q==",
+ "dev": true,
+ "dependencies": {
+ "cacache": "^15.0.5",
+ "find-cache-dir": "^3.3.1",
+ "schema-utils": "^3.0.0",
+ "serialize-javascript": "^5.0.1",
+ "webpack-sources": "^1.4.3"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "webpack": "^4.0.0 || ^5.0.0"
+ }
+ },
+ "node_modules/compression-webpack-plugin/node_modules/schema-utils": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
+ "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
+ "dev": true,
+ "dependencies": {
+ "@types/json-schema": "^7.0.8",
+ "ajv": "^6.12.5",
+ "ajv-keywords": "^3.5.2"
+ },
+ "engines": {
+ "node": ">= 10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/compression-webpack-plugin/node_modules/serialize-javascript": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz",
+ "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==",
+ "dev": true,
+ "dependencies": {
+ "randombytes": "^2.1.0"
+ }
+ },
"node_modules/compression/node_modules/bytes": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
@@ -22342,6 +22393,21 @@
"nuxt": "bin/nuxt.js"
}
},
+ "node_modules/nuxt-compress": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/nuxt-compress/-/nuxt-compress-5.0.0.tgz",
+ "integrity": "sha512-KxsYzgRTMJhqQfGfgLbam0gOTw8GtmrhJhQjUfcShCmBpVaL2sFM6urOKW3I/fIx+aRvF9uE0FZCFviF1TLZww==",
+ "dev": true,
+ "dependencies": {
+ "compression-webpack-plugin": "^6.1.1"
+ },
+ "engines": {
+ "node": ">= 12.10.0"
+ },
+ "peerDependencies": {
+ "nuxt": ">=2.9.0"
+ }
+ },
"node_modules/nuxt-highlightjs": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/nuxt-highlightjs/-/nuxt-highlightjs-1.0.3.tgz",