commit 6973d036124698d1ae9776799d2689aff2499866
Author: Lukáš Vlček <vlcek@net-inout.cz>
Date:   Mon May 9 21:30:15 2022 +0200

    Selected tags highlighting
    
    On click in the middle panel or on click on highlight buttons in the right panel

diff --git a/webapp/components/annotation/AnnotationItem.tsx b/webapp/components/annotation/AnnotationItem.tsx
index 58629ee..b9e91a1 100644
--- a/webapp/components/annotation/AnnotationItem.tsx
+++ b/webapp/components/annotation/AnnotationItem.tsx
@@ -8,6 +8,7 @@ import {
     DownOutlined,
     DeleteOutlined,
     TagOutlined,
+    EyeOutlined,
 } from '@ant-design/icons';
 import { AnnotationContext } from '../../contexts/AnnotationContext';
 import { ETagSentiment, TagInstanceInfo } from '../../api';
@@ -20,8 +21,6 @@ const { Option } = Select;
  * @returns The item that represents an annotation.
  */
 export function AnnotationItem(props: { tag: Tag }) {
-    const { markSelectedText } = useContext(AnnotationContext);
-
     /**
      * Should properties of this annotation be visible?
      */
@@ -37,6 +36,10 @@ export function AnnotationItem(props: { tag: Tag }) {
         changeNote,
         changeSentiment,
         changeLength,
+        selectedOccurrenceId,
+        selectedInstanceId,
+        setSelectedOccurrenceId,
+        setSelectedInstanceId,
     } = useContext(AnnotationContext);
 
     /**
@@ -90,9 +93,30 @@ export function AnnotationItem(props: { tag: Tag }) {
 
     return (
         <Container>
-            <Row className="border rounded">
+            <Row
+                className="border rounded"
+                style={{
+                    backgroundColor:
+                        selectedInstanceId === props.tag.instanceId ? '#FCF3CF' : 'white',
+                }}
+            >
                 <Col sm="auto" className="d-flex align-items-center">
-                    <TagOutlined />
+                    <Button
+                        icon={<TagOutlined style={{ marginLeft: 0 }} />}
+                        onClick={() => {
+                            setSelectedInstanceId(props.tag.instanceId);
+                            setSelectedOccurrenceId(null);
+                        }}
+                        style={{
+                            border: 'none',
+                            paddingLeft: 0,
+                            backgroundColor:
+                                selectedInstanceId === props.tag.instanceId
+                                    ? '#FCF3CF'
+                                    : 'white',
+                        }}
+                        title={'Zvýraznit značky'}
+                    />
                 </Col>
                 <Col className="d-flex align-items-center">
                     {props.tag.tagName}
@@ -119,8 +143,17 @@ export function AnnotationItem(props: { tag: Tag }) {
                     <div>Výskyty:</div>
                     {props.tag.occurrences.map((occurrence, index) => {
                         return (
-                            <Container key={index} className="shadow-sm pb-1 pt-1">
-                                <Row>
+                            <Container
+                                key={index}
+                                className="shadow-sm"
+                                style={{
+                                    backgroundColor:
+                                        selectedOccurrenceId === occurrence.occurenceId
+                                            ? '#D8E1E9'
+                                            : 'white',
+                                }}
+                            >
+                                <Row className="mb-1 mt-1">
                                     <Col>
                                         <Row>
                                             <Col>Pozice: {occurrence.position}</Col>
@@ -180,12 +213,27 @@ export function AnnotationItem(props: { tag: Tag }) {
                                             </Row>
                                         )}
                                     </Col>
-                                    <Col sm="auto" className="d-flex align-items-center">
+                                    <Col
+                                        sm="auto"
+                                        className="d-flex align-items-center flex-column justify-content-sm-evenly"
+                                    >
+                                        <Button
+                                            icon={<EyeOutlined />}
+                                            onClick={() => {
+                                                setSelectedOccurrenceId(
+                                                    occurrence.occurenceId ?? null
+                                                );
+                                                setSelectedInstanceId(
+                                                    props.tag.instanceId
+                                                );
+                                            }}
+                                            title={'Zvýraznit výskyt'}
+                                        />
                                         <Button
                                             icon={<DeleteOutlined />}
                                             onClick={onDeleteOccurrence(occurrence)}
                                             danger
-                                        ></Button>
+                                        />
                                     </Col>
                                 </Row>
                             </Container>
diff --git a/webapp/components/annotation/DocumentAnnotationView.tsx b/webapp/components/annotation/DocumentAnnotationView.tsx
index 3424bb7..a611e83 100644
--- a/webapp/components/annotation/DocumentAnnotationView.tsx
+++ b/webapp/components/annotation/DocumentAnnotationView.tsx
@@ -4,18 +4,48 @@
  */
 import { useContext } from 'react';
 import { AnnotationContext } from '../../contexts/AnnotationContext';
-import { Button } from 'antd';
-import { DOCUMENT_INSTANCE_ID_ATTRIBUTE_NAME } from '../../constants';
+import parse from 'html-react-parser';
+import {
+    DOCUMENT_INSTANCE_ID_ATTRIBUTE_NAME,
+    DOCUMENT_OCCURRENCE_ID_ATTRIBUTE_NAME,
+    DOCUMENT_TAG_ID_ATTRIBUTE_NAME,
+} from '../../constants';
 
 export function DocumentAnnotationView() {
-    const { annotation } = useContext(AnnotationContext);
+    const {
+        annotation,
+        setSelectedInstanceId,
+        setSelectedOccurrenceId,
+        selectedInstanceId,
+        selectedOccurrenceId,
+    } = useContext(AnnotationContext);
 
     if (!annotation) {
         return <p>Probíhá načítání anotace ...</p>;
     }
 
-    function getCss() {
+    function detectSelectedElement(e: any) {
+        if (!(e.target instanceof HTMLElement)) {
+            return;
+        }
+
+        const target = e.target as HTMLElement;
+
+        const id = target.getAttribute(DOCUMENT_TAG_ID_ATTRIBUTE_NAME);
+        const idInstance = target.getAttribute(DOCUMENT_INSTANCE_ID_ATTRIBUTE_NAME);
+        const idOccurrence = target.getAttribute(DOCUMENT_OCCURRENCE_ID_ATTRIBUTE_NAME);
+
+        if (!id || !idInstance || !idOccurrence) {
+            setSelectedInstanceId(null);
+            setSelectedOccurrenceId(null);
+            return;
+        }
 
+        setSelectedInstanceId(idInstance);
+        setSelectedOccurrenceId(idOccurrence);
+    }
+
+    function getCss() {
         let css = 'span.annotation {border-bottom: 2px solid;}\n';
         css += 'span {line-height: 30px}\n';
         css +=
@@ -27,15 +57,22 @@ export function DocumentAnnotationView() {
             css += `span[${DOCUMENT_INSTANCE_ID_ATTRIBUTE_NAME}="${info.instanceId}"] { border-color:${info.color}; padding-bottom: ${info.padding}px }\n`;
         }
 
+        if (selectedInstanceId) {
+            css += `span[${DOCUMENT_INSTANCE_ID_ATTRIBUTE_NAME}="${selectedInstanceId}"] { background-color: #FCF3CF;  }\n`;
+        }
+        if (selectedOccurrenceId) {
+            css += `span[${DOCUMENT_OCCURRENCE_ID_ATTRIBUTE_NAME}="${selectedOccurrenceId}"] { border-bottom: 4px dotted;  }\n`;
+        }
+
         return <style dangerouslySetInnerHTML={{ __html: css }}></style>;
     }
 
     return (
         <div>
             {getCss()}
-            <div
-                dangerouslySetInnerHTML={{ __html: annotation.documentToRender ?? '' }}
-            />
+            <div onClick={detectSelectedElement}>
+                {parse(annotation.documentToRender ?? '')}
+            </div>
         </div>
     );
 }
diff --git a/webapp/contexts/AnnotationContext.tsx b/webapp/contexts/AnnotationContext.tsx
index a4474c9..1188815 100644
--- a/webapp/contexts/AnnotationContext.tsx
+++ b/webapp/contexts/AnnotationContext.tsx
@@ -86,6 +86,12 @@ interface IAnnotationContextProvider {
         subtagId: string | null,
         instanceID: string | null
     ) => void;
+
+    selectedInstanceId: string | null;
+    setSelectedInstanceId: (newId: string | null) => void;
+
+    selectedOccurrenceId: string | null;
+    setSelectedOccurrenceId: (newId: string | null) => void;
 }
 
 /**
@@ -178,6 +184,16 @@ export const AnnotationContext = createContext<IAnnotationContextProvider>({
     markSelectedText: () => {
         return;
     },
+
+    selectedInstanceId: null,
+    setSelectedInstanceId: (newId: string | null) => {
+        return;
+    },
+
+    selectedOccurrenceId: null,
+    setSelectedOccurrenceId: (newId: string | null) => {
+        return;
+    },
 });
 
 /**
@@ -197,6 +213,8 @@ const AnnotationProvider = (props: {
     const [tags, setTags] = useState<TagInstanceInfo[] | null>(null);
 
     const [mappedTags, setMappedTags] = useState<Tag[] | null>(null);
+    const [selectedInstanceId, setSelectedInstanceId] = useState<string | null>(null);
+    const [selectedOccurrenceId, setSelectedOccurrenceId] = useState<string | null>(null);
 
     const [submitting, setSubmitting] = useState(false);
 
@@ -405,6 +423,10 @@ const AnnotationProvider = (props: {
                 annotation,
                 mappedTags,
                 markSelectedText,
+                selectedInstanceId,
+                setSelectedInstanceId,
+                selectedOccurrenceId,
+                setSelectedOccurrenceId,
             }}
         >
             {props.children}
diff --git a/webapp/package.json b/webapp/package.json
index df45022..8a37bab 100644
--- a/webapp/package.json
+++ b/webapp/package.json
@@ -18,6 +18,7 @@
     "antd": "^4.19.5",
     "axios": "^0.26.1",
     "bootstrap": "^5.1.3",
+    "html-react-parser": "^1.4.12",
     "http-status-codes": "^2.2.0",
     "next": "12.1.0",
     "react": "17.0.2",
diff --git a/webapp/utils/selectionUtils.ts b/webapp/utils/selectionUtils.ts
index 77bbfdd..ae927fc 100644
--- a/webapp/utils/selectionUtils.ts
+++ b/webapp/utils/selectionUtils.ts
@@ -1,4 +1,5 @@
 import { AnnotationInfo } from '../api';
+import { DOCUMENT_TAG_ID_ATTRIBUTE_NAME } from '../constants';
 
 export interface SelectionInfo {
     startElementId: number;
@@ -9,7 +10,6 @@ export interface SelectionInfo {
 
     selectedText: string;
 }
-const idAttributeName = 'aswi-tag-id';
 
 export function GetSelectionInfo(annotation: AnnotationInfo): SelectionInfo | null {
     const selection = window.getSelection();
@@ -60,8 +60,10 @@ export function GetSelectionInfo(annotation: AnnotationInfo): SelectionInfo | nu
     let startElement = startTag as Element;
     let endElement = endTag as Element;
 
-    let startId: number = Number(startElement.getAttribute(idAttributeName)) ?? -1;
-    let endId: number = Number(endElement.getAttribute(idAttributeName)) ?? -1;
+    let startId: number =
+        Number(startElement.getAttribute(DOCUMENT_TAG_ID_ATTRIBUTE_NAME)) ?? -1;
+    let endId: number =
+        Number(endElement.getAttribute(DOCUMENT_TAG_ID_ATTRIBUTE_NAME)) ?? -1;
 
     let startPosition =
         annotation.tagStartPositions[startId] +
diff --git a/webapp/yarn.lock b/webapp/yarn.lock
index d5dad8e..90a601c 100644
--- a/webapp/yarn.lock
+++ b/webapp/yarn.lock
@@ -908,6 +908,36 @@ dom-helpers@^5.0.1, dom-helpers@^5.2.0, dom-helpers@^5.2.1:
     "@babel/runtime" "^7.8.7"
     csstype "^3.0.2"
 
+dom-serializer@^1.0.1:
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.4.1.tgz#de5d41b1aea290215dc45a6dae8adcf1d32e2d30"
+  integrity sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==
+  dependencies:
+    domelementtype "^2.0.1"
+    domhandler "^4.2.0"
+    entities "^2.0.0"
+
+domelementtype@^2.0.1, domelementtype@^2.2.0:
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d"
+  integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==
+
+domhandler@4.3.1, domhandler@^4.2.0, domhandler@^4.2.2:
+  version "4.3.1"
+  resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c"
+  integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==
+  dependencies:
+    domelementtype "^2.2.0"
+
+domutils@^2.8.0:
+  version "2.8.0"
+  resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135"
+  integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==
+  dependencies:
+    dom-serializer "^1.0.1"
+    domelementtype "^2.2.0"
+    domhandler "^4.2.0"
+
 dotenv-expand@^5.1.0:
   version "5.1.0"
   resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0"
@@ -935,6 +965,16 @@ emoji-regex@^9.2.2:
   resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72"
   integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
 
+entities@^2.0.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
+  integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
+
+entities@^3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/entities/-/entities-3.0.1.tgz#2b887ca62585e96db3903482d336c1006c3001d4"
+  integrity sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==
+
 es-abstract@^1.19.1, es-abstract@^1.19.2:
   version "1.19.5"
   resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.19.5.tgz#a2cb01eb87f724e815b278b0dd0d00f36ca9a7f1"
@@ -1454,6 +1494,34 @@ has@^1.0.3:
   dependencies:
     function-bind "^1.1.1"
 
+html-dom-parser@1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/html-dom-parser/-/html-dom-parser-1.2.0.tgz#8f689b835982ffbf245eda99730e92b8462c111e"
+  integrity sha512-2HIpFMvvffsXHFUFjso0M9LqM+1Lm22BF+Df2ba+7QHJXjk63pWChEnI6YG27eaWqUdfnh5/Vy+OXrNTtepRsg==
+  dependencies:
+    domhandler "4.3.1"
+    htmlparser2 "7.2.0"
+
+html-react-parser@^1.4.12:
+  version "1.4.12"
+  resolved "https://registry.yarnpkg.com/html-react-parser/-/html-react-parser-1.4.12.tgz#5d4336e3853a75e4ac678c9815c15c58581bb30e"
+  integrity sha512-nqYQzr4uXh67G9ejAG7djupTHmQvSTgjY83zbXLRfKHJ0F06751jXx6WKSFARDdXxCngo2/7H4Rwtfeowql4gQ==
+  dependencies:
+    domhandler "4.3.1"
+    html-dom-parser "1.2.0"
+    react-property "2.0.0"
+    style-to-js "1.1.0"
+
+htmlparser2@7.2.0:
+  version "7.2.0"
+  resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-7.2.0.tgz#8817cdea38bbc324392a90b1990908e81a65f5a5"
+  integrity sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==
+  dependencies:
+    domelementtype "^2.0.1"
+    domhandler "^4.2.2"
+    domutils "^2.8.0"
+    entities "^3.0.1"
+
 http-status-codes@^2.2.0:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/http-status-codes/-/http-status-codes-2.2.0.tgz#bb2efe63d941dfc2be18e15f703da525169622be"
@@ -1507,6 +1575,11 @@ inherits@2, inherits@^2.0.3, inherits@^2.0.4:
   resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
   integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
 
+inline-style-parser@0.1.1:
+  version "0.1.1"
+  resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.1.1.tgz#ec8a3b429274e9c0a1f1c4ffa9453a7fef72cea1"
+  integrity sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==
+
 inquirer@8.2.0:
   version "8.2.0"
   resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-8.2.0.tgz#f44f008dd344bbfc4b30031f45d984e034a3ac3a"
@@ -2533,6 +2606,11 @@ react-lifecycles-compat@^3.0.4:
   resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
   integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
 
+react-property@2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/react-property/-/react-property-2.0.0.tgz#2156ba9d85fa4741faf1918b38efc1eae3c6a136"
+  integrity sha512-kzmNjIgU32mO4mmH5+iUyrqlpFQhF8K2k7eZ4fdLSOPFrD1XgEuSBv9LDEgxRXTMBqMd8ppT0x6TIzqE5pdGdw==
+
 react-transition-group@^4.4.2:
   version "4.4.2"
   resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.2.tgz#8b59a56f09ced7b55cbd53c36768b922890d5470"
@@ -2853,6 +2931,20 @@ strip-json-comments@^3.1.0, strip-json-comments@^3.1.1:
   resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
   integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
 
+style-to-js@1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/style-to-js/-/style-to-js-1.1.0.tgz#631cbb20fce204019b3aa1fcb5b69d951ceac4ac"
+  integrity sha512-1OqefPDxGrlMwcbfpsTVRyzwdhr4W0uxYQzeA2F1CBc8WG04udg2+ybRnvh3XYL4TdHQrCahLtax2jc8xaE6rA==
+  dependencies:
+    style-to-object "0.3.0"
+
+style-to-object@0.3.0:
+  version "0.3.0"
+  resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-0.3.0.tgz#b1b790d205991cc783801967214979ee19a76e46"
+  integrity sha512-CzFnRRXhzWIdItT3OmF8SQfWyahHhjq3HwcMNCNLn+N7klOOqPjMeG/4JSu77D7ypZdGvSzvkrbyeTMizz2VrA==
+  dependencies:
+    inline-style-parser "0.1.1"
+
 styled-jsx@5.0.0:
   version "5.0.0"
   resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.0.0.tgz#816b4b92e07b1786c6b7111821750e0ba4d26e77"
