From d159c72321a6765d7974d09e11a5f1d34cd501eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Pumar?= Date: Wed, 11 Dec 2024 18:28:15 +0100 Subject: [PATCH 1/4] =?UTF-8?q?=F0=9F=94=A5=20Allow=20highlighting=20for?= =?UTF-8?q?=20old=20browsers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../container/fields/chat-field/ChatField.vue | 18 ++- .../container/fields/text-field/TextField.vue | 17 ++- .../fields/useSearchTextHighlight.ts | 138 ++++++++++++------ 3 files changed, 118 insertions(+), 55 deletions(-) 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/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..f419dcabf2 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 = ["|", "+", "-", "*"]; export const useSearchTextHighlight = (fieldId: string) => { + const isCSSHighlightsSupported = !!CSS.highlights; 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,95 @@ 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 applyRangesToHighlight = (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); + + const span = document.createElement("span"); + + span.innerHTML = highlightedHTML; + + coincidence.textNode.parentElement?.replaceChild( + span, + coincidence.textNode + ); + } }; const highlightText = (searchText: string) => { const fieldComponent = document.getElementById(FIELD_ID_TO_HIGHLIGHT); + + // CSS.highlights.delete(HIGHLIGHT_CLASS); + 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); + + applyRangesToHighlight(coincidences); }; return { From 8f57cbe9e9d03a86c158d483ab55c4a1fe2d2ee8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Pumar?= Date: Thu, 12 Dec 2024 09:17:01 +0100 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=90=9B=20Fix=20highlight?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SpanAnnotationTextField.vue | 86 ++++++++++--------- .../components/highlighting.ts | 6 +- .../fields/useSearchTextHighlight.ts | 8 +- 3 files changed, 54 insertions(+), 46 deletions(-) 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/useSearchTextHighlight.ts b/argilla-frontend/components/features/annotation/container/fields/useSearchTextHighlight.ts index f419dcabf2..7df9810ab4 100644 --- a/argilla-frontend/components/features/annotation/container/fields/useSearchTextHighlight.ts +++ b/argilla-frontend/components/features/annotation/container/fields/useSearchTextHighlight.ts @@ -17,9 +17,9 @@ type Coincidences = { }[]; const DSLChars = ["|", "+", "-", "*"]; +const isCSSHighlightsSupported = !!CSS.highlights; export const useSearchTextHighlight = (fieldId: string) => { - const isCSSHighlightsSupported = !!CSS.highlights; const FIELD_ID_TO_HIGHLIGHT = `fields-content-${fieldId}`; const HIGHLIGHT_CLASS = `search-text-highlight-${fieldId}`; @@ -117,7 +117,7 @@ export const useSearchTextHighlight = (fieldId: string) => { return coincidences; }; - const applyRangesToHighlight = (coincidences: Coincidences) => { + const highlightCoincidences = (coincidences: Coincidences) => { if (isCSSHighlightsSupported) { const createRanges = (coincidences: Coincidences) => { const ranges = []; @@ -175,7 +175,7 @@ export const useSearchTextHighlight = (fieldId: string) => { const highlightText = (searchText: string) => { const fieldComponent = document.getElementById(FIELD_ID_TO_HIGHLIGHT); - // CSS.highlights.delete(HIGHLIGHT_CLASS); + if (isCSSHighlightsSupported) CSS.highlights.delete(HIGHLIGHT_CLASS); if (!searchText || !fieldComponent) { return; @@ -183,7 +183,7 @@ export const useSearchTextHighlight = (fieldId: string) => { const coincidences = createIndexesToHighlight(fieldComponent, searchText); - applyRangesToHighlight(coincidences); + highlightCoincidences(coincidences); }; return { From 8756990512c192bd16bcc0bd81149f0eca839fa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Pumar?= Date: Thu, 12 Dec 2024 10:20:14 +0100 Subject: [PATCH 3/4] =?UTF-8?q?=F0=9F=93=9D=20Update=20nuxt-compress?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- argilla-frontend/package-lock.json | 66 ++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) 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", From 9194f190d86cb1157f54d69e23d9ab74ed55d085 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dami=C3=A1n=20Pumar?= Date: Wed, 18 Dec 2024 12:02:06 +0100 Subject: [PATCH 4/4] =?UTF-8?q?=F0=9F=9A=80=20Improve=20highlight?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fields/useSearchTextHighlight.ts | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/argilla-frontend/components/features/annotation/container/fields/useSearchTextHighlight.ts b/argilla-frontend/components/features/annotation/container/fields/useSearchTextHighlight.ts index 7df9810ab4..b2e3b33522 100644 --- a/argilla-frontend/components/features/annotation/container/fields/useSearchTextHighlight.ts +++ b/argilla-frontend/components/features/annotation/container/fields/useSearchTextHighlight.ts @@ -161,14 +161,8 @@ export const useSearchTextHighlight = (fieldId: string) => { highlightedHTML += textContent.slice(offset); - const span = document.createElement("span"); - - span.innerHTML = highlightedHTML; - - coincidence.textNode.parentElement?.replaceChild( - span, - coincidence.textNode - ); + if (coincidence.textNode.parentElement) + coincidence.textNode.parentElement.innerHTML = highlightedHTML; } }; @@ -176,6 +170,19 @@ export const useSearchTextHighlight = (fieldId: 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) { return;