From d6c71c7aa745715120e688a59f86da9e1dcc66fb Mon Sep 17 00:00:00 2001 From: Vincent Emonet Date: Fri, 18 Mar 2022 09:54:50 +0100 Subject: [PATCH 1/8] Add onClick callback for tags --- src/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index 6b388de..2807889 100644 --- a/src/index.js +++ b/src/index.js @@ -1,7 +1,7 @@ import React from 'react' // Define functional component. Destructure the props. -const Taggy = ({ text = '', spans = [], ents = []}) => { +const Taggy = ({ text = '', spans = [], ents = [], onClick = (event, tag, index) => {}}) => { // Find the correct color of the given entity type. If the given entity is not found, set the color to grey. const findRed = (type) => { @@ -85,6 +85,7 @@ const Taggy = ({ text = '', spans = [], ents = []}) => { else { jsx.push( onClick(e, t, i)} style={{ padding: '0.25em 0.35em', margin: '0px 0.25em', @@ -178,6 +179,7 @@ const Taggy = ({ text = '', spans = [], ents = []}) => { else { jsx.push( onClick(e, t, i)} style={{ padding: '0.25em 0.35em', margin: '0px 0.25em', From f9857efe25020f65c6f40916de2a8bc0cbbd935c Mon Sep 17 00:00:00 2001 From: Vincent Emonet Date: Fri, 18 Mar 2022 10:16:07 +0100 Subject: [PATCH 2/8] add tag index to onClick callback --- src/index.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/index.js b/src/index.js index 2807889..d0a99d9 100644 --- a/src/index.js +++ b/src/index.js @@ -1,7 +1,7 @@ import React from 'react' // Define functional component. Destructure the props. -const Taggy = ({ text = '', spans = [], ents = [], onClick = (event, tag, index) => {}}) => { +const Taggy = ({ text = '', spans = [], ents = [], onClick = (event, tag, elemIndex, tagIndex) => {}}) => { // Find the correct color of the given entity type. If the given entity is not found, set the color to grey. const findRed = (type) => { @@ -77,6 +77,7 @@ const Taggy = ({ text = '', spans = [], ents = [], onClick = (event, tag, index) } // Filter out the consecutive entities that were marked as duplicates elements = elements.filter(val => !!val) + let tagIndex = 0; // Loop through our 'elements' array. Push strings directly to the 'jsx' array. Convert entity objects to jsx markup, then push to the 'jsx' array. elements.forEach(t => { if (typeof t === 'string') { @@ -85,7 +86,7 @@ const Taggy = ({ text = '', spans = [], ents = [], onClick = (event, tag, index) else { jsx.push( onClick(e, t, i)} + onClick={(e) => onClick(e, t, i, tagIndex)} style={{ padding: '0.25em 0.35em', margin: '0px 0.25em', @@ -129,6 +130,7 @@ const Taggy = ({ text = '', spans = [], ents = [], onClick = (event, tag, index) ) + tagIndex++ } }) } @@ -171,6 +173,7 @@ const Taggy = ({ text = '', spans = [], ents = [], onClick = (event, tag, index) } return t }) + let tagIndex = 0; // Loop through our 'tokens' array. Push strings directly to the 'jsx' array. Convert entity objects to jsx markup, then push to the 'jsx' array. tokensWithSpaces.forEach(t => { if (typeof t === 'string') { @@ -179,7 +182,7 @@ const Taggy = ({ text = '', spans = [], ents = [], onClick = (event, tag, index) else { jsx.push( onClick(e, t, i)} + onClick={(e) => onClick(e, t, i, tagIndex)} style={{ padding: '0.25em 0.35em', margin: '0px 0.25em', @@ -223,6 +226,7 @@ const Taggy = ({ text = '', spans = [], ents = [], onClick = (event, tag, index) ) + tagIndex++ } }) } From 6f073a187271a7b1f9a0abbf10bc1bbabf18d3d4 Mon Sep 17 00:00:00 2001 From: Vincent Emonet Date: Fri, 18 Mar 2022 10:18:58 +0100 Subject: [PATCH 3/8] add index to token loop --- src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index d0a99d9..8bd8d5a 100644 --- a/src/index.js +++ b/src/index.js @@ -175,7 +175,7 @@ const Taggy = ({ text = '', spans = [], ents = [], onClick = (event, tag, elemIn }) let tagIndex = 0; // Loop through our 'tokens' array. Push strings directly to the 'jsx' array. Convert entity objects to jsx markup, then push to the 'jsx' array. - tokensWithSpaces.forEach(t => { + tokensWithSpaces.forEach((t, i) => { if (typeof t === 'string') { jsx.push(t) } From b2fac1b2efc609002181a60a1fec5140362dde57 Mon Sep 17 00:00:00 2001 From: Vincent Emonet Date: Fri, 18 Mar 2022 10:42:34 +0100 Subject: [PATCH 4/8] remove tagIndex, let only elemIndex --- src/index.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/index.js b/src/index.js index 8bd8d5a..635b43c 100644 --- a/src/index.js +++ b/src/index.js @@ -1,7 +1,7 @@ import React from 'react' // Define functional component. Destructure the props. -const Taggy = ({ text = '', spans = [], ents = [], onClick = (event, tag, elemIndex, tagIndex) => {}}) => { +const Taggy = ({ text = '', spans = [], ents = [], onClick = (event, tag, elemIndex) => {}}) => { // Find the correct color of the given entity type. If the given entity is not found, set the color to grey. const findRed = (type) => { @@ -77,16 +77,15 @@ const Taggy = ({ text = '', spans = [], ents = [], onClick = (event, tag, elemIn } // Filter out the consecutive entities that were marked as duplicates elements = elements.filter(val => !!val) - let tagIndex = 0; // Loop through our 'elements' array. Push strings directly to the 'jsx' array. Convert entity objects to jsx markup, then push to the 'jsx' array. - elements.forEach(t => { + elements.forEach((t, i) => { if (typeof t === 'string') { jsx.push(t) } else { jsx.push( onClick(e, t, i, tagIndex)} + onClick={(e) => onClick(e, t, i)} style={{ padding: '0.25em 0.35em', margin: '0px 0.25em', @@ -130,7 +129,6 @@ const Taggy = ({ text = '', spans = [], ents = [], onClick = (event, tag, elemIn ) - tagIndex++ } }) } @@ -173,7 +171,6 @@ const Taggy = ({ text = '', spans = [], ents = [], onClick = (event, tag, elemIn } return t }) - let tagIndex = 0; // Loop through our 'tokens' array. Push strings directly to the 'jsx' array. Convert entity objects to jsx markup, then push to the 'jsx' array. tokensWithSpaces.forEach((t, i) => { if (typeof t === 'string') { @@ -182,7 +179,7 @@ const Taggy = ({ text = '', spans = [], ents = [], onClick = (event, tag, elemIn else { jsx.push( onClick(e, t, i, tagIndex)} + onClick={(e) => onClick(e, t, i)} style={{ padding: '0.25em 0.35em', margin: '0px 0.25em', @@ -226,7 +223,6 @@ const Taggy = ({ text = '', spans = [], ents = [], onClick = (event, tag, elemIn ) - tagIndex++ } }) } From 7cfd1382b89af8a035526edc127bafc259577fc1 Mon Sep 17 00:00:00 2001 From: Vincent Emonet Date: Tue, 22 Mar 2022 11:29:46 +0100 Subject: [PATCH 5/8] add onclick callback --- src/index.js | 46 +++++++++++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/src/index.js b/src/index.js index 635b43c..3dfe0c6 100644 --- a/src/index.js +++ b/src/index.js @@ -1,7 +1,10 @@ import React from 'react' // Define functional component. Destructure the props. -const Taggy = ({ text = '', spans = [], ents = [], onClick = (event, tag, elemIndex) => {}}) => { +const Taggy = ({ text = '', spans = [], ents = [], + onClick = (event, tag, elemIndex) => {}, + onHighlight = (event, text, spanIndex, start, end) => {} +}) => { // Find the correct color of the given entity type. If the given entity is not found, set the color to grey. const findRed = (type) => { @@ -29,6 +32,15 @@ const Taggy = ({ text = '', spans = [], ents = [], onClick = (event, tag, elemIn return 220 } + const highlightCallback = (e, spanText, i) => { + // Start and end are relative to the current element, not the whole text + const start = window.getSelection().anchorOffset + const end = window.getSelection().focusOffset + const text = spanText.substring(start, end) + onHighlight(e, text, i, start, end) + } + + // Initialize an empty array that will hold the text and entities let jsx = [] @@ -38,20 +50,20 @@ const Taggy = ({ text = '', spans = [], ents = [], onClick = (event, tag, elemIn let elements = [] // Keep track of location in the string of text let offset = 0 + // Make sure spans are ordered by they start index + spans.sort((a,b) => (a.start > b.start) ? 1 : ((b.start > a.start) ? -1 : 0)) // Loop through the spans, using the span data to construct the 'elements' array - spans.forEach(({ type, start, end }) => { + spans.forEach((span) => { // Create a string of text that does not contain any entities - const fragment = text.slice(offset, start) + const fragment = text.slice(offset, span.start) // Create an entity - const entity = text.slice(start, end) + const entity = text.slice(span.start, span.end) // Push the both of them to the elements array elements.push(fragment) - elements.push({ - token: entity, - type: type.toLowerCase() - }) + span.token = entity + elements.push(span) // Update our position within the string of text - offset = end + offset = span.end }) // After pushing all of the entities to the 'elements' array, push the remaining text to the 'elements' array. Elements should now consist of strings and objects/entities. elements.push(text.slice(offset, text.length)) @@ -80,7 +92,10 @@ const Taggy = ({ text = '', spans = [], ents = [], onClick = (event, tag, elemIn // Loop through our 'elements' array. Push strings directly to the 'jsx' array. Convert entity objects to jsx markup, then push to the 'jsx' array. elements.forEach((t, i) => { if (typeof t === 'string') { - jsx.push(t) + jsx.push( {highlightCallback(e, t, i)}} + onDoubleClick={(e) => {highlightCallback(e, t, i)}} + >{t}) } else { jsx.push( @@ -114,7 +129,6 @@ const Taggy = ({ text = '', spans = [], ents = [], onClick = (event, tag, elemIn lineHeight: '1', padding: '0.35em', borderRadius: '0.35em', - textTransform: 'uppercase', display: 'inline-block', verticalAlign: 'middle', margin: '0px 0px 0.1rem 0.5rem', @@ -141,7 +155,7 @@ const Taggy = ({ text = '', spans = [], ents = [], onClick = (event, tag, elemIn for (let s = 0; s < spans.length; s++) { tokens[spans[s].index] = { token: tokens[spans[s].index], - type: spans[s].type.toLowerCase() + type: spans[s].type } } // Loop through the tokens array, looking for multi-word entities @@ -174,7 +188,10 @@ const Taggy = ({ text = '', spans = [], ents = [], onClick = (event, tag, elemIn // Loop through our 'tokens' array. Push strings directly to the 'jsx' array. Convert entity objects to jsx markup, then push to the 'jsx' array. tokensWithSpaces.forEach((t, i) => { if (typeof t === 'string') { - jsx.push(t) + jsx.push( {highlightCallback(e, t, i)}} + onDoubleClick={(e) => {highlightCallback(e, t, i)}} + >{t}) } else { jsx.push( @@ -208,7 +225,6 @@ const Taggy = ({ text = '', spans = [], ents = [], onClick = (event, tag, elemIn lineHeight: '1', padding: '0.35em', borderRadius: '0.35em', - textTransform: 'uppercase', display: 'inline-block', verticalAlign: 'middle', margin: '0px 0px 0.1rem 0.5rem', @@ -229,7 +245,7 @@ const Taggy = ({ text = '', spans = [], ents = [], onClick = (event, tag, elemIn // Return the markup return ( -
+
{jsx.map((j, i) => ( {j} ))} From 7df2fef81967fa49d84c239d7bb20b283bc75bae Mon Sep 17 00:00:00 2001 From: Vincent Emonet Date: Tue, 22 Mar 2022 11:59:35 +0100 Subject: [PATCH 6/8] fix sort and use full span object with array too --- src/index.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/index.js b/src/index.js index 3dfe0c6..14513d7 100644 --- a/src/index.js +++ b/src/index.js @@ -44,14 +44,15 @@ const Taggy = ({ text = '', spans = [], ents = [], // Initialize an empty array that will hold the text and entities let jsx = [] + // Make sure spans are ordered by they start index + spans.sort((a,b) => (a.start > b.start) ? 1 : ((b.start > a.start) ? -1 : 0)) + // METHOD 1 - STRING if (typeof text === 'string') { // Initialize an empty array. The contents of 'elements' will eventually get pushed to the 'jsx' array, and will be converted to jsx markup in the process. let elements = [] // Keep track of location in the string of text let offset = 0 - // Make sure spans are ordered by they start index - spans.sort((a,b) => (a.start > b.start) ? 1 : ((b.start > a.start) ? -1 : 0)) // Loop through the spans, using the span data to construct the 'elements' array spans.forEach((span) => { // Create a string of text that does not contain any entities @@ -153,11 +154,11 @@ const Taggy = ({ text = '', spans = [], ents = [], let tokens = text // Loop through the 'spans' array. Use the span data to update our 'tokens' array with entities for (let s = 0; s < spans.length; s++) { - tokens[spans[s].index] = { - token: tokens[spans[s].index], - type: spans[s].type - } + tokens[spans[s].index] = spans[s] + tokens[spans[s].index].token = tokens[spans[s].index] + tokens[spans[s].index].type = spans[s].type } + // Loop through the tokens array, looking for multi-word entities for (let t = 0; t < tokens.length; t++) { // Check if we've stopped at an entity From 73b8b199356505fd89bdc6d6f1ddd169b4213561 Mon Sep 17 00:00:00 2001 From: Vincent Emonet Date: Fri, 24 Feb 2023 13:46:24 +0100 Subject: [PATCH 7/8] add onMouseOver callback --- src/index.js | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/index.js b/src/index.js index 14513d7..681c358 100644 --- a/src/index.js +++ b/src/index.js @@ -1,9 +1,10 @@ import React from 'react' // Define functional component. Destructure the props. -const Taggy = ({ text = '', spans = [], ents = [], - onClick = (event, tag, elemIndex) => {}, - onHighlight = (event, text, spanIndex, start, end) => {} +const Taggy = ({ text = '', spans = [], ents = [], + onClick = (event, tag, elemIndex) => {}, + onMouseOver = (event, tag, elemIndex) => {}, + onHighlight = (event, text, spanIndex, start, end) => {}, }) => { // Find the correct color of the given entity type. If the given entity is not found, set the color to grey. @@ -93,15 +94,16 @@ const Taggy = ({ text = '', spans = [], ents = [], // Loop through our 'elements' array. Push strings directly to the 'jsx' array. Convert entity objects to jsx markup, then push to the 'jsx' array. elements.forEach((t, i) => { if (typeof t === 'string') { - jsx.push( {highlightCallback(e, t, i)}} - onDoubleClick={(e) => {highlightCallback(e, t, i)}} + jsx.push( {highlightCallback(e, t, i)}} + onDoubleClick={(e) => {highlightCallback(e, t, i)}} >{t}) } else { jsx.push( onClick(e, t, i)} + onMouseOver={(e) => onMouseOver(e, t, i)} style={{ padding: '0.25em 0.35em', margin: '0px 0.25em', @@ -154,11 +156,11 @@ const Taggy = ({ text = '', spans = [], ents = [], let tokens = text // Loop through the 'spans' array. Use the span data to update our 'tokens' array with entities for (let s = 0; s < spans.length; s++) { - tokens[spans[s].index] = spans[s] + tokens[spans[s].index] = spans[s] tokens[spans[s].index].token = tokens[spans[s].index] tokens[spans[s].index].type = spans[s].type } - + // Loop through the tokens array, looking for multi-word entities for (let t = 0; t < tokens.length; t++) { // Check if we've stopped at an entity @@ -189,15 +191,16 @@ const Taggy = ({ text = '', spans = [], ents = [], // Loop through our 'tokens' array. Push strings directly to the 'jsx' array. Convert entity objects to jsx markup, then push to the 'jsx' array. tokensWithSpaces.forEach((t, i) => { if (typeof t === 'string') { - jsx.push( {highlightCallback(e, t, i)}} - onDoubleClick={(e) => {highlightCallback(e, t, i)}} + jsx.push( {highlightCallback(e, t, i)}} + onDoubleClick={(e) => {highlightCallback(e, t, i)}} >{t}) } else { jsx.push( onClick(e, t, i)} + onMouseOver={(e) => onMouseOver(e, t, i)} style={{ padding: '0.25em 0.35em', margin: '0px 0.25em', From 628a318e653f4bd4709a2163c6b929c52855352a Mon Sep 17 00:00:00 2001 From: Vincent Emonet Date: Sat, 25 Feb 2023 14:53:30 +0100 Subject: [PATCH 8/8] add ts module definition --- package.json | 2 +- src/index.d.ts | 1 + src/index.js | 11 +++++++---- 3 files changed, 9 insertions(+), 5 deletions(-) create mode 100644 src/index.d.ts diff --git a/package.json b/package.json index 6a8b51f..21b6180 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ "sinon": "^1.17.6" }, "peerDependencies": { - "react": "^0.14.7 || ^15.0.0" + "react": "^0.14.7 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" }, "dependencies": { "babel-runtime": "^6.11.6" diff --git a/src/index.d.ts b/src/index.d.ts new file mode 100644 index 0000000..dc68b6d --- /dev/null +++ b/src/index.d.ts @@ -0,0 +1 @@ +export declare module "react-taggy" \ No newline at end of file diff --git a/src/index.js b/src/index.js index 681c358..68692e5 100644 --- a/src/index.js +++ b/src/index.js @@ -1,10 +1,13 @@ import React from 'react' // Define functional component. Destructure the props. -const Taggy = ({ text = '', spans = [], ents = [], - onClick = (event, tag, elemIndex) => {}, - onMouseOver = (event, tag, elemIndex) => {}, - onHighlight = (event, text, spanIndex, start, end) => {}, +const Taggy = ({ + text = '', + spans = [], + ents = [], + onClick = (event, tag, elemIndex) => {}, + onMouseOver = (event, tag, elemIndex) => {}, + onHighlight = (event, text, spanIndex, start, end) => {}, }) => { // Find the correct color of the given entity type. If the given entity is not found, set the color to grey.