From c2192cda9a0ded2c9da4fea35c0d5b0c3c845b63 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Luka=CC=81s=CC=8C=20Vlc=CC=8Cek?= <vlcek@net-inout.cz>
Date: Sun, 17 Apr 2022 14:12:59 +0200
Subject: [PATCH 01/12] Selection detection and calculation of start and end
 position in original document

---
 .../annotation/DocumentAnnotationView.tsx     | 60 +++++++++++++++++++
 1 file changed, 60 insertions(+)

diff --git a/webapp/components/annotation/DocumentAnnotationView.tsx b/webapp/components/annotation/DocumentAnnotationView.tsx
index f55fe05..8c2090e 100644
--- a/webapp/components/annotation/DocumentAnnotationView.tsx
+++ b/webapp/components/annotation/DocumentAnnotationView.tsx
@@ -15,6 +15,66 @@ export function DocumentAnnotationView() {
 
     return (
         <div>
+            <Button
+                onClick={() => {
+                    const selection = window.getSelection();
+                    if (!selection) {
+                        return;
+                    }
+                    if (!annotation?.tagStartPositions || !annotation.tagLengths) {
+                        console.log('start or lengths not found');
+                        return;
+                    }
+
+                    let startTag = selection.anchorNode;
+                    let endTag = selection.focusNode;
+
+                    if (!startTag || !endTag) {
+                        console.log('Selection not found');
+                        return;
+                    }
+
+                    if (startTag.nodeName.includes('#')) {
+                        startTag = startTag.parentNode;
+                    }
+                    if (endTag.nodeName.includes('#')) {
+                        endTag = endTag.parentNode;
+                    }
+                    if (!startTag || !endTag) {
+                        console.log('Selection element not found');
+                        return;
+                    }
+
+                    if (!(startTag instanceof Element)) {
+                        console.log('StartTag is not instance of Element');
+                        return;
+                    }
+                    if (!(endTag instanceof Element)) {
+                        console.log('EndTag is not instance of Element');
+                        return;
+                    }
+
+                    const startElement = startTag as Element;
+                    const endElement = endTag as Element;
+
+                    const startId: number =
+                        Number(startElement.getAttribute('aswi-tag-id')) ?? -1;
+                    const endId: number =
+                        Number(endElement.getAttribute('aswi-tag-id')) ?? -1;
+
+                    const startPosition =
+                        annotation.tagStartPositions[startId] +
+                        annotation.tagLengths[startId] +
+                        selection.anchorOffset;
+                    const endPosition =
+                        annotation.tagStartPositions[endId] +
+                        annotation.tagLengths[endId] +
+                        selection.focusOffset -
+                        1;
+                }}
+            >
+                Test
+            </Button>
             <div
                 dangerouslySetInnerHTML={{ __html: annotation.documentToRender ?? '' }}
             />
-- 
GitLab


From be4deff84566ef77ef9eed0649c91de0cde01d21 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Vojt=C4=9Bch=20Barti=C4=8Dka?= <aitakaitov@gmail.com>
Date: Sun, 17 Apr 2022 14:38:23 +0200
Subject: [PATCH 02/12] Minor fixes, added annotation instance endpoint -
 untested

---
 .../Controllers/AnnotationController.cs       |  29 ++
 .../Migrations/20220417114429_V3.Designer.cs  | 395 ++++++++++++++++++
 .../Backend/Migrations/20220417114429_V3.cs   | 258 ++++++++++++
 .../DatabaseContextModelSnapshot.cs           |  24 +-
 Backend/Backend/Program.cs                    |   2 +
 Backend/Core/Entities/AnnotationTag.cs        |   2 +-
 Backend/Core/Seeding/Seeder.cs                |   2 +-
 .../AnnotationService/AnnotationServiceEF.cs  |  48 +++
 .../AnnotationService/IAnnotationService.cs   |   2 +
 .../AnnotationInstanceAddRequest.cs           |  20 +
 Backend/Models/Enums/ETagType.cs              |  14 +
 Backend/Models/Tags/TagInstanceInfo.cs        |   2 +-
 12 files changed, 777 insertions(+), 21 deletions(-)
 create mode 100644 Backend/Backend/Migrations/20220417114429_V3.Designer.cs
 create mode 100644 Backend/Backend/Migrations/20220417114429_V3.cs
 create mode 100644 Backend/Models/Annotations/AnnotationInstanceAddRequest.cs
 create mode 100644 Backend/Models/Enums/ETagType.cs

diff --git a/Backend/Backend/Controllers/AnnotationController.cs b/Backend/Backend/Controllers/AnnotationController.cs
index b5cb02a..ccfe949 100644
--- a/Backend/Backend/Controllers/AnnotationController.cs
+++ b/Backend/Backend/Controllers/AnnotationController.cs
@@ -73,5 +73,34 @@ namespace RestAPI.Controllers
             }
 
         }
+
+        [HttpPost("/annotation/{annotationId}")]
+        [ProducesResponseType((int)HttpStatusCode.OK)]
+        [ProducesResponseType((int)HttpStatusCode.Forbidden)]
+        public ActionResult PostAnnotation([FromServices] ClientInfo clientInfo, [FromQuery] Guid annotationId, [FromBody] AnnotationInstanceAddRequest request)
+        {
+            if (clientInfo.LoggedUser == null)
+            {
+                logger.Warning("ClientInfo has null LoggerUser in [Authorized] controller /annotations");
+                return Problem();
+            }
+            
+            // Take care of - non-admin user requesting not-assigned annotation
+            // non-existent annotation
+            try
+            {
+                annotationService.AddAnnotationInstance(annotationId, clientInfo.LoggedUser.Id, clientInfo.LoggedUser.Role, request);
+                return Ok();
+            }
+            catch (InvalidOperationException e)
+            {
+                throw new BadRequestException("Could not find specified annotation");
+            }
+            catch (UnauthorizedAccessException)
+            {
+                return Forbid();
+            }
+
+        }
     }
 }
diff --git a/Backend/Backend/Migrations/20220417114429_V3.Designer.cs b/Backend/Backend/Migrations/20220417114429_V3.Designer.cs
new file mode 100644
index 0000000..8601f5d
--- /dev/null
+++ b/Backend/Backend/Migrations/20220417114429_V3.Designer.cs
@@ -0,0 +1,395 @@
+// <auto-generated />
+using System;
+using Core.Contexts;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
+
+#nullable disable
+
+namespace RestAPI.Migrations
+{
+    [DbContext(typeof(DatabaseContext))]
+    [Migration("20220417114429_V3")]
+    partial class V3
+    {
+        protected override void BuildTargetModel(ModelBuilder modelBuilder)
+        {
+#pragma warning disable 612, 618
+            modelBuilder
+                .HasAnnotation("ProductVersion", "6.0.3")
+                .HasAnnotation("Relational:MaxIdentifierLength", 63);
+
+            NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
+
+            modelBuilder.Entity("AnnotationClass", b =>
+                {
+                    b.Property<Guid>("AnnotationsId")
+                        .HasColumnType("uuid");
+
+                    b.Property<Guid>("ClassesId")
+                        .HasColumnType("uuid");
+
+                    b.HasKey("AnnotationsId", "ClassesId");
+
+                    b.HasIndex("ClassesId");
+
+                    b.ToTable("AnnotationClass");
+                });
+
+            modelBuilder.Entity("Core.Entities.Annotation", b =>
+                {
+                    b.Property<Guid>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("uuid");
+
+                    b.Property<DateTime>("DateAssigned")
+                        .HasColumnType("timestamp with time zone");
+
+                    b.Property<DateTime>("DateLastChanged")
+                        .HasColumnType("timestamp with time zone");
+
+                    b.Property<Guid>("DocumentId")
+                        .HasColumnType("uuid");
+
+                    b.Property<string>("Note")
+                        .IsRequired()
+                        .HasColumnType("text");
+
+                    b.Property<int>("State")
+                        .HasColumnType("integer");
+
+                    b.Property<Guid>("UserAssignedId")
+                        .HasColumnType("uuid");
+
+                    b.Property<Guid>("UserId")
+                        .HasColumnType("uuid");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("DocumentId");
+
+                    b.HasIndex("UserAssignedId");
+
+                    b.HasIndex("UserId");
+
+                    b.ToTable("Annotations");
+                });
+
+            modelBuilder.Entity("Core.Entities.AnnotationTag", b =>
+                {
+                    b.Property<Guid>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("uuid");
+
+                    b.Property<Guid>("AnnotationId")
+                        .HasColumnType("uuid");
+
+                    b.Property<Guid>("Instance")
+                        .HasColumnType("uuid");
+
+                    b.Property<int>("Length")
+                        .HasColumnType("integer");
+
+                    b.Property<string>("Note")
+                        .IsRequired()
+                        .HasColumnType("text");
+
+                    b.Property<int>("Position")
+                        .HasColumnType("integer");
+
+                    b.Property<Guid?>("SubTagId")
+                        .HasColumnType("uuid");
+
+                    b.Property<Guid>("TagId")
+                        .HasColumnType("uuid");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("AnnotationId");
+
+                    b.HasIndex("SubTagId");
+
+                    b.HasIndex("TagId");
+
+                    b.ToTable("AnnotationTags");
+                });
+
+            modelBuilder.Entity("Core.Entities.Class", b =>
+                {
+                    b.Property<Guid>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("uuid");
+
+                    b.Property<string>("Color")
+                        .IsRequired()
+                        .HasColumnType("text");
+
+                    b.Property<string>("Description")
+                        .IsRequired()
+                        .HasColumnType("text");
+
+                    b.Property<string>("Name")
+                        .IsRequired()
+                        .HasColumnType("text");
+
+                    b.HasKey("Id");
+
+                    b.ToTable("Classes");
+                });
+
+            modelBuilder.Entity("Core.Entities.Document", b =>
+                {
+                    b.Property<Guid>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("uuid");
+
+                    b.Property<Guid?>("ContentId")
+                        .HasColumnType("uuid");
+
+                    b.Property<DateTime>("DateAdded")
+                        .HasColumnType("timestamp with time zone");
+
+                    b.Property<int>("Length")
+                        .HasColumnType("integer");
+
+                    b.Property<string>("Name")
+                        .HasColumnType("text");
+
+                    b.Property<int>("RequiredAnnotations")
+                        .HasColumnType("integer");
+
+                    b.Property<Guid?>("UserAddedId")
+                        .HasColumnType("uuid");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("ContentId");
+
+                    b.HasIndex("UserAddedId");
+
+                    b.ToTable("Documents");
+                });
+
+            modelBuilder.Entity("Core.Entities.DocumentContent", b =>
+                {
+                    b.Property<Guid>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("uuid");
+
+                    b.Property<string>("Content")
+                        .IsRequired()
+                        .HasColumnType("text");
+
+                    b.HasKey("Id");
+
+                    b.ToTable("DocumentContents");
+                });
+
+            modelBuilder.Entity("Core.Entities.SubTag", b =>
+                {
+                    b.Property<Guid>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("uuid");
+
+                    b.Property<string>("Description")
+                        .IsRequired()
+                        .HasColumnType("text");
+
+                    b.Property<string>("Name")
+                        .IsRequired()
+                        .HasColumnType("text");
+
+                    b.Property<Guid>("TagId")
+                        .HasColumnType("uuid");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("TagId");
+
+                    b.ToTable("SubTags");
+                });
+
+            modelBuilder.Entity("Core.Entities.Tag", b =>
+                {
+                    b.Property<Guid>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("uuid");
+
+                    b.Property<Guid>("CategoryId")
+                        .HasColumnType("uuid");
+
+                    b.Property<string>("Color")
+                        .IsRequired()
+                        .HasColumnType("text");
+
+                    b.Property<string>("Description")
+                        .IsRequired()
+                        .HasColumnType("text");
+
+                    b.Property<string>("Name")
+                        .IsRequired()
+                        .HasColumnType("text");
+
+                    b.HasKey("Id");
+
+                    b.HasIndex("CategoryId");
+
+                    b.ToTable("Tags");
+                });
+
+            modelBuilder.Entity("Core.Entities.TagCategory", b =>
+                {
+                    b.Property<Guid>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("uuid");
+
+                    b.Property<string>("Color")
+                        .HasColumnType("text");
+
+                    b.Property<string>("Description")
+                        .HasColumnType("text");
+
+                    b.Property<string>("Name")
+                        .HasColumnType("text");
+
+                    b.HasKey("Id");
+
+                    b.ToTable("TagCategories");
+                });
+
+            modelBuilder.Entity("Core.Entities.User", b =>
+                {
+                    b.Property<Guid>("Id")
+                        .ValueGeneratedOnAdd()
+                        .HasColumnType("uuid");
+
+                    b.Property<string>("Name")
+                        .HasColumnType("text");
+
+                    b.Property<string>("Password")
+                        .HasColumnType("text");
+
+                    b.Property<int>("Role")
+                        .HasColumnType("integer");
+
+                    b.Property<string>("Surname")
+                        .HasColumnType("text");
+
+                    b.Property<string>("Username")
+                        .HasColumnType("text");
+
+                    b.HasKey("Id");
+
+                    b.ToTable("Users");
+                });
+
+            modelBuilder.Entity("AnnotationClass", b =>
+                {
+                    b.HasOne("Core.Entities.Annotation", null)
+                        .WithMany()
+                        .HasForeignKey("AnnotationsId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.HasOne("Core.Entities.Class", null)
+                        .WithMany()
+                        .HasForeignKey("ClassesId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+                });
+
+            modelBuilder.Entity("Core.Entities.Annotation", b =>
+                {
+                    b.HasOne("Core.Entities.Document", "Document")
+                        .WithMany()
+                        .HasForeignKey("DocumentId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.HasOne("Core.Entities.User", "UserAssigned")
+                        .WithMany()
+                        .HasForeignKey("UserAssignedId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.HasOne("Core.Entities.User", "User")
+                        .WithMany()
+                        .HasForeignKey("UserId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.Navigation("Document");
+
+                    b.Navigation("User");
+
+                    b.Navigation("UserAssigned");
+                });
+
+            modelBuilder.Entity("Core.Entities.AnnotationTag", b =>
+                {
+                    b.HasOne("Core.Entities.Annotation", "Annotation")
+                        .WithMany()
+                        .HasForeignKey("AnnotationId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.HasOne("Core.Entities.SubTag", "SubTag")
+                        .WithMany()
+                        .HasForeignKey("SubTagId");
+
+                    b.HasOne("Core.Entities.Tag", "Tag")
+                        .WithMany()
+                        .HasForeignKey("TagId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.Navigation("Annotation");
+
+                    b.Navigation("SubTag");
+
+                    b.Navigation("Tag");
+                });
+
+            modelBuilder.Entity("Core.Entities.Document", b =>
+                {
+                    b.HasOne("Core.Entities.DocumentContent", "Content")
+                        .WithMany()
+                        .HasForeignKey("ContentId");
+
+                    b.HasOne("Core.Entities.User", "UserAdded")
+                        .WithMany()
+                        .HasForeignKey("UserAddedId");
+
+                    b.Navigation("Content");
+
+                    b.Navigation("UserAdded");
+                });
+
+            modelBuilder.Entity("Core.Entities.SubTag", b =>
+                {
+                    b.HasOne("Core.Entities.Tag", "Tag")
+                        .WithMany()
+                        .HasForeignKey("TagId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.Navigation("Tag");
+                });
+
+            modelBuilder.Entity("Core.Entities.Tag", b =>
+                {
+                    b.HasOne("Core.Entities.TagCategory", "Category")
+                        .WithMany()
+                        .HasForeignKey("CategoryId")
+                        .OnDelete(DeleteBehavior.Cascade)
+                        .IsRequired();
+
+                    b.Navigation("Category");
+                });
+#pragma warning restore 612, 618
+        }
+    }
+}
diff --git a/Backend/Backend/Migrations/20220417114429_V3.cs b/Backend/Backend/Migrations/20220417114429_V3.cs
new file mode 100644
index 0000000..aecbffb
--- /dev/null
+++ b/Backend/Backend/Migrations/20220417114429_V3.cs
@@ -0,0 +1,258 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace RestAPI.Migrations
+{
+    public partial class V3 : Migration
+    {
+        protected override void Up(MigrationBuilder migrationBuilder)
+        {
+            migrationBuilder.DropForeignKey(
+                name: "FK_Documents_DocumentContents_ContentId",
+                table: "Documents");
+
+            migrationBuilder.DropForeignKey(
+                name: "FK_Documents_Users_UserAddedId",
+                table: "Documents");
+
+            migrationBuilder.AlterColumn<string>(
+                name: "Username",
+                table: "Users",
+                type: "text",
+                nullable: true,
+                oldClrType: typeof(string),
+                oldType: "text");
+
+            migrationBuilder.AlterColumn<string>(
+                name: "Surname",
+                table: "Users",
+                type: "text",
+                nullable: true,
+                oldClrType: typeof(string),
+                oldType: "text");
+
+            migrationBuilder.AlterColumn<string>(
+                name: "Password",
+                table: "Users",
+                type: "text",
+                nullable: true,
+                oldClrType: typeof(string),
+                oldType: "text");
+
+            migrationBuilder.AlterColumn<string>(
+                name: "Name",
+                table: "Users",
+                type: "text",
+                nullable: true,
+                oldClrType: typeof(string),
+                oldType: "text");
+
+            migrationBuilder.AlterColumn<string>(
+                name: "Name",
+                table: "TagCategories",
+                type: "text",
+                nullable: true,
+                oldClrType: typeof(string),
+                oldType: "text");
+
+            migrationBuilder.AlterColumn<string>(
+                name: "Description",
+                table: "TagCategories",
+                type: "text",
+                nullable: true,
+                oldClrType: typeof(string),
+                oldType: "text");
+
+            migrationBuilder.AlterColumn<string>(
+                name: "Color",
+                table: "TagCategories",
+                type: "text",
+                nullable: true,
+                oldClrType: typeof(string),
+                oldType: "text");
+
+            migrationBuilder.AlterColumn<Guid>(
+                name: "UserAddedId",
+                table: "Documents",
+                type: "uuid",
+                nullable: true,
+                oldClrType: typeof(Guid),
+                oldType: "uuid");
+
+            migrationBuilder.AlterColumn<string>(
+                name: "Name",
+                table: "Documents",
+                type: "text",
+                nullable: true,
+                oldClrType: typeof(string),
+                oldType: "text");
+
+            migrationBuilder.AlterColumn<Guid>(
+                name: "ContentId",
+                table: "Documents",
+                type: "uuid",
+                nullable: true,
+                oldClrType: typeof(Guid),
+                oldType: "uuid");
+
+            migrationBuilder.AlterColumn<Guid>(
+                name: "Instance",
+                table: "AnnotationTags",
+                type: "uuid",
+                nullable: false,
+                oldClrType: typeof(int),
+                oldType: "integer");
+
+            migrationBuilder.AddForeignKey(
+                name: "FK_Documents_DocumentContents_ContentId",
+                table: "Documents",
+                column: "ContentId",
+                principalTable: "DocumentContents",
+                principalColumn: "Id");
+
+            migrationBuilder.AddForeignKey(
+                name: "FK_Documents_Users_UserAddedId",
+                table: "Documents",
+                column: "UserAddedId",
+                principalTable: "Users",
+                principalColumn: "Id");
+        }
+
+        protected override void Down(MigrationBuilder migrationBuilder)
+        {
+            migrationBuilder.DropForeignKey(
+                name: "FK_Documents_DocumentContents_ContentId",
+                table: "Documents");
+
+            migrationBuilder.DropForeignKey(
+                name: "FK_Documents_Users_UserAddedId",
+                table: "Documents");
+
+            migrationBuilder.AlterColumn<string>(
+                name: "Username",
+                table: "Users",
+                type: "text",
+                nullable: false,
+                defaultValue: "",
+                oldClrType: typeof(string),
+                oldType: "text",
+                oldNullable: true);
+
+            migrationBuilder.AlterColumn<string>(
+                name: "Surname",
+                table: "Users",
+                type: "text",
+                nullable: false,
+                defaultValue: "",
+                oldClrType: typeof(string),
+                oldType: "text",
+                oldNullable: true);
+
+            migrationBuilder.AlterColumn<string>(
+                name: "Password",
+                table: "Users",
+                type: "text",
+                nullable: false,
+                defaultValue: "",
+                oldClrType: typeof(string),
+                oldType: "text",
+                oldNullable: true);
+
+            migrationBuilder.AlterColumn<string>(
+                name: "Name",
+                table: "Users",
+                type: "text",
+                nullable: false,
+                defaultValue: "",
+                oldClrType: typeof(string),
+                oldType: "text",
+                oldNullable: true);
+
+            migrationBuilder.AlterColumn<string>(
+                name: "Name",
+                table: "TagCategories",
+                type: "text",
+                nullable: false,
+                defaultValue: "",
+                oldClrType: typeof(string),
+                oldType: "text",
+                oldNullable: true);
+
+            migrationBuilder.AlterColumn<string>(
+                name: "Description",
+                table: "TagCategories",
+                type: "text",
+                nullable: false,
+                defaultValue: "",
+                oldClrType: typeof(string),
+                oldType: "text",
+                oldNullable: true);
+
+            migrationBuilder.AlterColumn<string>(
+                name: "Color",
+                table: "TagCategories",
+                type: "text",
+                nullable: false,
+                defaultValue: "",
+                oldClrType: typeof(string),
+                oldType: "text",
+                oldNullable: true);
+
+            migrationBuilder.AlterColumn<Guid>(
+                name: "UserAddedId",
+                table: "Documents",
+                type: "uuid",
+                nullable: false,
+                defaultValue: new Guid("00000000-0000-0000-0000-000000000000"),
+                oldClrType: typeof(Guid),
+                oldType: "uuid",
+                oldNullable: true);
+
+            migrationBuilder.AlterColumn<string>(
+                name: "Name",
+                table: "Documents",
+                type: "text",
+                nullable: false,
+                defaultValue: "",
+                oldClrType: typeof(string),
+                oldType: "text",
+                oldNullable: true);
+
+            migrationBuilder.AlterColumn<Guid>(
+                name: "ContentId",
+                table: "Documents",
+                type: "uuid",
+                nullable: false,
+                defaultValue: new Guid("00000000-0000-0000-0000-000000000000"),
+                oldClrType: typeof(Guid),
+                oldType: "uuid",
+                oldNullable: true);
+
+            migrationBuilder.AlterColumn<int>(
+                name: "Instance",
+                table: "AnnotationTags",
+                type: "integer",
+                nullable: false,
+                oldClrType: typeof(Guid),
+                oldType: "uuid");
+
+            migrationBuilder.AddForeignKey(
+                name: "FK_Documents_DocumentContents_ContentId",
+                table: "Documents",
+                column: "ContentId",
+                principalTable: "DocumentContents",
+                principalColumn: "Id",
+                onDelete: ReferentialAction.Cascade);
+
+            migrationBuilder.AddForeignKey(
+                name: "FK_Documents_Users_UserAddedId",
+                table: "Documents",
+                column: "UserAddedId",
+                principalTable: "Users",
+                principalColumn: "Id",
+                onDelete: ReferentialAction.Cascade);
+        }
+    }
+}
diff --git a/Backend/Backend/Migrations/DatabaseContextModelSnapshot.cs b/Backend/Backend/Migrations/DatabaseContextModelSnapshot.cs
index 489b9ee..5f9ffb3 100644
--- a/Backend/Backend/Migrations/DatabaseContextModelSnapshot.cs
+++ b/Backend/Backend/Migrations/DatabaseContextModelSnapshot.cs
@@ -85,8 +85,8 @@ namespace RestAPI.Migrations
                     b.Property<Guid>("AnnotationId")
                         .HasColumnType("uuid");
 
-                    b.Property<int>("Instance")
-                        .HasColumnType("integer");
+                    b.Property<Guid>("Instance")
+                        .HasColumnType("uuid");
 
                     b.Property<int>("Length")
                         .HasColumnType("integer");
@@ -144,7 +144,7 @@ namespace RestAPI.Migrations
                         .ValueGeneratedOnAdd()
                         .HasColumnType("uuid");
 
-                    b.Property<Guid>("ContentId")
+                    b.Property<Guid?>("ContentId")
                         .HasColumnType("uuid");
 
                     b.Property<DateTime>("DateAdded")
@@ -154,13 +154,12 @@ namespace RestAPI.Migrations
                         .HasColumnType("integer");
 
                     b.Property<string>("Name")
-                        .IsRequired()
                         .HasColumnType("text");
 
                     b.Property<int>("RequiredAnnotations")
                         .HasColumnType("integer");
 
-                    b.Property<Guid>("UserAddedId")
+                    b.Property<Guid?>("UserAddedId")
                         .HasColumnType("uuid");
 
                     b.HasKey("Id");
@@ -246,15 +245,12 @@ namespace RestAPI.Migrations
                         .HasColumnType("uuid");
 
                     b.Property<string>("Color")
-                        .IsRequired()
                         .HasColumnType("text");
 
                     b.Property<string>("Description")
-                        .IsRequired()
                         .HasColumnType("text");
 
                     b.Property<string>("Name")
-                        .IsRequired()
                         .HasColumnType("text");
 
                     b.HasKey("Id");
@@ -269,22 +265,18 @@ namespace RestAPI.Migrations
                         .HasColumnType("uuid");
 
                     b.Property<string>("Name")
-                        .IsRequired()
                         .HasColumnType("text");
 
                     b.Property<string>("Password")
-                        .IsRequired()
                         .HasColumnType("text");
 
                     b.Property<int>("Role")
                         .HasColumnType("integer");
 
                     b.Property<string>("Surname")
-                        .IsRequired()
                         .HasColumnType("text");
 
                     b.Property<string>("Username")
-                        .IsRequired()
                         .HasColumnType("text");
 
                     b.HasKey("Id");
@@ -363,15 +355,11 @@ namespace RestAPI.Migrations
                 {
                     b.HasOne("Core.Entities.DocumentContent", "Content")
                         .WithMany()
-                        .HasForeignKey("ContentId")
-                        .OnDelete(DeleteBehavior.Cascade)
-                        .IsRequired();
+                        .HasForeignKey("ContentId");
 
                     b.HasOne("Core.Entities.User", "UserAdded")
                         .WithMany()
-                        .HasForeignKey("UserAddedId")
-                        .OnDelete(DeleteBehavior.Cascade)
-                        .IsRequired();
+                        .HasForeignKey("UserAddedId");
 
                     b.Navigation("Content");
 
diff --git a/Backend/Backend/Program.cs b/Backend/Backend/Program.cs
index 89b3219..4380358 100644
--- a/Backend/Backend/Program.cs
+++ b/Backend/Backend/Program.cs
@@ -113,6 +113,8 @@ using (var scope = app.Services.CreateScope())
     // In development we seed dummy data
     if (app.Environment.IsDevelopment())
     {
+        //context.Database.EnsureDeleted();
+        //context.Database.EnsureCreated();
         context.Database.Migrate();
         Seeder.SeedDevelopment(context);
     }
diff --git a/Backend/Core/Entities/AnnotationTag.cs b/Backend/Core/Entities/AnnotationTag.cs
index 9241c44..798b090 100644
--- a/Backend/Core/Entities/AnnotationTag.cs
+++ b/Backend/Core/Entities/AnnotationTag.cs
@@ -17,7 +17,7 @@ namespace Core.Entities
         /** Nullable for optional */
         public SubTag? SubTag { get; set; }
 
-        public int Instance { get; set; }
+        public Guid Instance { get; set; }
         public string Note { get; set; }
         public int Position { get; set; }
         public int Length { get; set; }
diff --git a/Backend/Core/Seeding/Seeder.cs b/Backend/Core/Seeding/Seeder.cs
index 3e1a7df..7fe2795 100644
--- a/Backend/Core/Seeding/Seeder.cs
+++ b/Backend/Core/Seeding/Seeder.cs
@@ -36,7 +36,7 @@ namespace Core.Seeding
             AnnotationTag at = new AnnotationTag()
             {
                 Annotation = annotation,
-                Instance = 1,
+                Instance = Guid.NewGuid(),
                 Length = 10,
                 Position = 0,
                 SubTag = subtag,
diff --git a/Backend/Core/Services/AnnotationService/AnnotationServiceEF.cs b/Backend/Core/Services/AnnotationService/AnnotationServiceEF.cs
index 3cde9ae..e71f215 100644
--- a/Backend/Core/Services/AnnotationService/AnnotationServiceEF.cs
+++ b/Backend/Core/Services/AnnotationService/AnnotationServiceEF.cs
@@ -184,6 +184,10 @@ namespace Core.Services.AnnotationService
             HtmlSanitizer sanitizer = new HtmlSanitizer();
             sanitizer.AllowedAttributes.Clear();
             sanitizer.AllowedAttributes.Add(idAttributeName);
+            if (sanitizer.AllowedTags.Contains("script"))
+            {
+                sanitizer.AllowedTags.Remove("script");
+            }
 
             docToRender = sanitizer.Sanitize(docToRender);
 
@@ -195,5 +199,49 @@ namespace Core.Services.AnnotationService
         {
             return text.Contains("<!DOCTYPE html>");
         }
+
+        public void AddAnnotationInstance(Guid annotationId, Guid userId, ERole userRole, AnnotationInstanceAddRequest request)
+        {
+            var annotation = context.Annotations
+               .Where(a => a.Id == annotationId)
+               .Include(a => a.User)
+               .Include(a => a.Document).ThenInclude(d => d.Content)
+               .First();
+
+            if (userRole < ERole.ADMINISTRATOR)
+            {
+                if (annotation.User.Id != userId)
+                {
+                    throw new UnauthorizedAccessException($"User {userId} does not have assigned annotation {annotationId}");
+                }
+            }
+
+            AnnotationTag annotationTag = new()
+            {
+                Annotation = annotation,
+                Instance = request.InstanceId == null ? Guid.NewGuid() : request.InstanceId.Value,
+                Length = request.Length,
+                Position = request.Position,
+                Note = ""
+            };
+
+            if (request.Type == ETagType.TAG)
+            {
+                annotationTag.Tag = context.Tags.Where(t => t.Id == request.Id).Single();
+                annotationTag.SubTag = null;
+            }
+            else if (request.Type == ETagType.SUBTAG)
+            {
+                var subTag = context.SubTags.Where(st => st.Id == request.Id).Include(st => st.Tag).Single();
+                annotationTag.SubTag = subTag;
+                annotationTag.Tag = subTag.Tag;
+            }
+            else
+            {
+                throw new ArgumentException($"Unknown tag type {request.Type}");
+            }
+
+            context.SaveChanges();
+        }
     }
 }
diff --git a/Backend/Core/Services/AnnotationService/IAnnotationService.cs b/Backend/Core/Services/AnnotationService/IAnnotationService.cs
index afb584d..5946e9c 100644
--- a/Backend/Core/Services/AnnotationService/IAnnotationService.cs
+++ b/Backend/Core/Services/AnnotationService/IAnnotationService.cs
@@ -13,5 +13,7 @@ namespace Core.Services.AnnotationService
         public void CreateDocumentAnnotations(AnnotationsAddRequest request, Guid userId);
         public AnnotationListResponse GetUserAnnotations(Guid userId);
         public AnnotationInfo GetAnnotation(Guid annotationId, Guid userId, ERole userRole);
+        public void AddAnnotationInstance(Guid annotationId, Guid userId, ERole userRole, AnnotationInstanceAddRequest request);
+
     }
 }
diff --git a/Backend/Models/Annotations/AnnotationInstanceAddRequest.cs b/Backend/Models/Annotations/AnnotationInstanceAddRequest.cs
new file mode 100644
index 0000000..a56ff8f
--- /dev/null
+++ b/Backend/Models/Annotations/AnnotationInstanceAddRequest.cs
@@ -0,0 +1,20 @@
+using Models.Enums;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Models.Annotations
+{
+    public class AnnotationInstanceAddRequest
+    {
+        public int Position { get; set; }
+        public int Length { get; set; }
+        public ETagType Type { get; set; }
+        public Guid Id { get; set; }
+        
+        // If se to null, the instance is a new one 
+        public Guid? InstanceId { get; set; }
+    }
+}
diff --git a/Backend/Models/Enums/ETagType.cs b/Backend/Models/Enums/ETagType.cs
new file mode 100644
index 0000000..18686de
--- /dev/null
+++ b/Backend/Models/Enums/ETagType.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Models.Enums
+{
+    public enum ETagType
+    {
+        TAG,
+        SUBTAG
+    }
+}
diff --git a/Backend/Models/Tags/TagInstanceInfo.cs b/Backend/Models/Tags/TagInstanceInfo.cs
index 204a690..3c5cbcc 100644
--- a/Backend/Models/Tags/TagInstanceInfo.cs
+++ b/Backend/Models/Tags/TagInstanceInfo.cs
@@ -15,7 +15,7 @@ namespace Models.Tags
         public string? SubTagName { get; set; }
         public Guid? SubTagId { get; set; }
 
-        public int Instance { get; set; }
+        public Guid Instance { get; set; }
         public int Position { get; set; }
         public int Length { get; set; }
 
-- 
GitLab


From d08c4fb0cdf4588a7bcf1773ff5f8401bb8c8d8d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Vojt=C4=9Bch=20Barti=C4=8Dka?= <aitakaitov@gmail.com>
Date: Sun, 17 Apr 2022 14:45:46 +0200
Subject: [PATCH 03/12] Fixed error in AnnotationController

---
 Backend/Backend/Controllers/AnnotationController.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Backend/Backend/Controllers/AnnotationController.cs b/Backend/Backend/Controllers/AnnotationController.cs
index ccfe949..a187467 100644
--- a/Backend/Backend/Controllers/AnnotationController.cs
+++ b/Backend/Backend/Controllers/AnnotationController.cs
@@ -77,7 +77,7 @@ namespace RestAPI.Controllers
         [HttpPost("/annotation/{annotationId}")]
         [ProducesResponseType((int)HttpStatusCode.OK)]
         [ProducesResponseType((int)HttpStatusCode.Forbidden)]
-        public ActionResult PostAnnotation([FromServices] ClientInfo clientInfo, [FromQuery] Guid annotationId, [FromBody] AnnotationInstanceAddRequest request)
+        public ActionResult PostAnnotation([FromServices] ClientInfo clientInfo, Guid annotationId, [FromBody] AnnotationInstanceAddRequest request)
         {
             if (clientInfo.LoggedUser == null)
             {
-- 
GitLab


From abf94f214dc29530a3d07cba63e5673ff6094a4e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Luka=CC=81s=CC=8C=20Vlc=CC=8Cek?= <vlcek@net-inout.cz>
Date: Sun, 17 Apr 2022 15:23:41 +0200
Subject: [PATCH 04/12] Backend - saving data fixed

---
 Backend/Core/Services/AnnotationService/AnnotationServiceEF.cs | 1 +
 1 file changed, 1 insertion(+)

diff --git a/Backend/Core/Services/AnnotationService/AnnotationServiceEF.cs b/Backend/Core/Services/AnnotationService/AnnotationServiceEF.cs
index e71f215..c97e009 100644
--- a/Backend/Core/Services/AnnotationService/AnnotationServiceEF.cs
+++ b/Backend/Core/Services/AnnotationService/AnnotationServiceEF.cs
@@ -241,6 +241,7 @@ namespace Core.Services.AnnotationService
                 throw new ArgumentException($"Unknown tag type {request.Type}");
             }
 
+            context.AnnotationTags.Add(annotationTag);
             context.SaveChanges();
         }
     }
-- 
GitLab


From 4bc995912c81ebe8ddf01a440fb51bb09d4f816e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Luka=CC=81s=CC=8C=20Vlc=CC=8Cek?= <vlcek@net-inout.cz>
Date: Sun, 17 Apr 2022 15:24:42 +0200
Subject: [PATCH 05/12] Adding tag/subtag to selected text

---
 webapp/api/api.ts                             | 125 +++++++++++++++++-
 .../components/annotation/AnnotationItem.tsx  |   7 +-
 .../annotation/DocumentAnnotationView.tsx     |  60 ---------
 webapp/components/types/tag.tsx               |  10 +-
 webapp/contexts/AnnotationContext.tsx         |  69 ++++++++--
 webapp/contexts/TagCategoryContext.tsx        |  14 +-
 webapp/pages/annotation/[id].tsx              |  28 ++--
 webapp/utils/selectionUtils.ts                |  88 ++++++++++++
 8 files changed, 315 insertions(+), 86 deletions(-)
 create mode 100644 webapp/utils/selectionUtils.ts

diff --git a/webapp/api/api.ts b/webapp/api/api.ts
index 3478280..b26d5f3 100644
--- a/webapp/api/api.ts
+++ b/webapp/api/api.ts
@@ -76,6 +76,43 @@ export interface AnnotationInfo {
      */
     'tagInstances'?: Array<TagInstanceInfo> | null;
 }
+/**
+ * 
+ * @export
+ * @interface AnnotationInstanceAddRequest
+ */
+export interface AnnotationInstanceAddRequest {
+    /**
+     * 
+     * @type {number}
+     * @memberof AnnotationInstanceAddRequest
+     */
+    'position'?: number;
+    /**
+     * 
+     * @type {number}
+     * @memberof AnnotationInstanceAddRequest
+     */
+    'length'?: number;
+    /**
+     * 
+     * @type {ETagType}
+     * @memberof AnnotationInstanceAddRequest
+     */
+    'type'?: ETagType;
+    /**
+     * 
+     * @type {string}
+     * @memberof AnnotationInstanceAddRequest
+     */
+    'id'?: string;
+    /**
+     * 
+     * @type {string}
+     * @memberof AnnotationInstanceAddRequest
+     */
+    'instanceId'?: string | null;
+}
 /**
  * 
  * @export
@@ -321,6 +358,20 @@ export const EState = {
 export type EState = typeof EState[keyof typeof EState];
 
 
+/**
+ * 
+ * @export
+ * @enum {string}
+ */
+
+export const ETagType = {
+    Tag: 'TAG',
+    Subtag: 'SUBTAG'
+} as const;
+
+export type ETagType = typeof ETagType[keyof typeof ETagType];
+
+
 /**
  * 
  * @export
@@ -553,10 +604,10 @@ export interface TagInstanceInfo {
     'subTagId'?: string | null;
     /**
      * 
-     * @type {number}
+     * @type {string}
      * @memberof TagInstanceInfo
      */
-    'instance'?: number;
+    'instance'?: string;
     /**
      * 
      * @type {number}
@@ -710,6 +761,43 @@ export const AnnotationApiAxiosParamCreator = function (configuration?: Configur
                 options: localVarRequestOptions,
             };
         },
+        /**
+         * 
+         * @param {string} annotationId 
+         * @param {AnnotationInstanceAddRequest} [annotationInstanceAddRequest] 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        annotationAnnotationIdPost: async (annotationId: string, annotationInstanceAddRequest?: AnnotationInstanceAddRequest, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            // verify required parameter 'annotationId' is not null or undefined
+            assertParamExists('annotationAnnotationIdPost', 'annotationId', annotationId)
+            const localVarPath = `/annotation/{annotationId}`
+                .replace(`{${"annotationId"}}`, encodeURIComponent(String(annotationId)));
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+
+    
+            localVarHeaderParameter['Content-Type'] = 'application/json';
+
+            setSearchParams(localVarUrlObj, localVarQueryParameter);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+            localVarRequestOptions.data = serializeDataIfNeeded(annotationInstanceAddRequest, localVarRequestOptions, configuration)
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
         /**
          * 
          * @param {AnnotationsAddRequest} [annotationsAddRequest] 
@@ -763,6 +851,17 @@ export const AnnotationApiFp = function(configuration?: Configuration) {
             const localVarAxiosArgs = await localVarAxiosParamCreator.annotationAnnotationIdGet(annotationId, options);
             return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
         },
+        /**
+         * 
+         * @param {string} annotationId 
+         * @param {AnnotationInstanceAddRequest} [annotationInstanceAddRequest] 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async annotationAnnotationIdPost(annotationId: string, annotationInstanceAddRequest?: AnnotationInstanceAddRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.annotationAnnotationIdPost(annotationId, annotationInstanceAddRequest, options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
         /**
          * 
          * @param {AnnotationsAddRequest} [annotationsAddRequest] 
@@ -792,6 +891,16 @@ export const AnnotationApiFactory = function (configuration?: Configuration, bas
         annotationAnnotationIdGet(annotationId: string, options?: any): AxiosPromise<AnnotationInfo> {
             return localVarFp.annotationAnnotationIdGet(annotationId, options).then((request) => request(axios, basePath));
         },
+        /**
+         * 
+         * @param {string} annotationId 
+         * @param {AnnotationInstanceAddRequest} [annotationInstanceAddRequest] 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        annotationAnnotationIdPost(annotationId: string, annotationInstanceAddRequest?: AnnotationInstanceAddRequest, options?: any): AxiosPromise<void> {
+            return localVarFp.annotationAnnotationIdPost(annotationId, annotationInstanceAddRequest, options).then((request) => request(axios, basePath));
+        },
         /**
          * 
          * @param {AnnotationsAddRequest} [annotationsAddRequest] 
@@ -822,6 +931,18 @@ export class AnnotationApi extends BaseAPI {
         return AnnotationApiFp(this.configuration).annotationAnnotationIdGet(annotationId, options).then((request) => request(this.axios, this.basePath));
     }
 
+    /**
+     * 
+     * @param {string} annotationId 
+     * @param {AnnotationInstanceAddRequest} [annotationInstanceAddRequest] 
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof AnnotationApi
+     */
+    public annotationAnnotationIdPost(annotationId: string, annotationInstanceAddRequest?: AnnotationInstanceAddRequest, options?: AxiosRequestConfig) {
+        return AnnotationApiFp(this.configuration).annotationAnnotationIdPost(annotationId, annotationInstanceAddRequest, options).then((request) => request(this.axios, this.basePath));
+    }
+
     /**
      * 
      * @param {AnnotationsAddRequest} [annotationsAddRequest] 
diff --git a/webapp/components/annotation/AnnotationItem.tsx b/webapp/components/annotation/AnnotationItem.tsx
index 7dfef34..d67a4bc 100644
--- a/webapp/components/annotation/AnnotationItem.tsx
+++ b/webapp/components/annotation/AnnotationItem.tsx
@@ -19,6 +19,8 @@ import { TagInstanceInfo } from '../../api';
  * @returns The item that represents an annotation.
  */
 export function AnnotationItem(props: { tag: Tag }) {
+    const { markSelectedText } = useContext(AnnotationContext);
+
     /**
      * Should properties of this annotation be visible?
      */
@@ -88,7 +90,10 @@ export function AnnotationItem(props: { tag: Tag }) {
                 <Col sm="auto" className="d-flex align-items-center">
                     <TagOutlined />
                 </Col>
-                <Col className="d-flex align-items-center">{props.tag.name}</Col>
+                <Col className="d-flex align-items-center">
+                    {props.tag.tagName}
+                    {props.tag.subtagName ? ' (' + props.tag.subtagName + ')' : ''}
+                </Col>
                 <Col sm="auto">
                     <Button
                         type="text"
diff --git a/webapp/components/annotation/DocumentAnnotationView.tsx b/webapp/components/annotation/DocumentAnnotationView.tsx
index 8c2090e..f55fe05 100644
--- a/webapp/components/annotation/DocumentAnnotationView.tsx
+++ b/webapp/components/annotation/DocumentAnnotationView.tsx
@@ -15,66 +15,6 @@ export function DocumentAnnotationView() {
 
     return (
         <div>
-            <Button
-                onClick={() => {
-                    const selection = window.getSelection();
-                    if (!selection) {
-                        return;
-                    }
-                    if (!annotation?.tagStartPositions || !annotation.tagLengths) {
-                        console.log('start or lengths not found');
-                        return;
-                    }
-
-                    let startTag = selection.anchorNode;
-                    let endTag = selection.focusNode;
-
-                    if (!startTag || !endTag) {
-                        console.log('Selection not found');
-                        return;
-                    }
-
-                    if (startTag.nodeName.includes('#')) {
-                        startTag = startTag.parentNode;
-                    }
-                    if (endTag.nodeName.includes('#')) {
-                        endTag = endTag.parentNode;
-                    }
-                    if (!startTag || !endTag) {
-                        console.log('Selection element not found');
-                        return;
-                    }
-
-                    if (!(startTag instanceof Element)) {
-                        console.log('StartTag is not instance of Element');
-                        return;
-                    }
-                    if (!(endTag instanceof Element)) {
-                        console.log('EndTag is not instance of Element');
-                        return;
-                    }
-
-                    const startElement = startTag as Element;
-                    const endElement = endTag as Element;
-
-                    const startId: number =
-                        Number(startElement.getAttribute('aswi-tag-id')) ?? -1;
-                    const endId: number =
-                        Number(endElement.getAttribute('aswi-tag-id')) ?? -1;
-
-                    const startPosition =
-                        annotation.tagStartPositions[startId] +
-                        annotation.tagLengths[startId] +
-                        selection.anchorOffset;
-                    const endPosition =
-                        annotation.tagStartPositions[endId] +
-                        annotation.tagLengths[endId] +
-                        selection.focusOffset -
-                        1;
-                }}
-            >
-                Test
-            </Button>
             <div
                 dangerouslySetInnerHTML={{ __html: annotation.documentToRender ?? '' }}
             />
diff --git a/webapp/components/types/tag.tsx b/webapp/components/types/tag.tsx
index 62c23b8..d0a04aa 100644
--- a/webapp/components/types/tag.tsx
+++ b/webapp/components/types/tag.tsx
@@ -1,11 +1,17 @@
-import { TagInstanceInfo } from '../../api';
+import { SubTagInfo, TagInfo, TagInstanceInfo } from '../../api';
 
 /**
  * Special tag used in annotation panel.
  */
 export type Tag = {
-    name: string;
+    tagName: string;
+    subtagName: string | null;
+
     category: string;
     visible: boolean;
     occurrences: TagInstanceInfo[];
+
+    tagId: string;
+    subtagId: string | null;
+    instanceId: string;
 };
diff --git a/webapp/contexts/AnnotationContext.tsx b/webapp/contexts/AnnotationContext.tsx
index 74f28e4..502f1b6 100644
--- a/webapp/contexts/AnnotationContext.tsx
+++ b/webapp/contexts/AnnotationContext.tsx
@@ -1,7 +1,8 @@
 import React, { createContext, useEffect, useState } from 'react';
-import { AnnotationInfo, TagInstanceInfo } from '../api';
+import { AnnotationInfo, ETagType, SubTagInfo, TagInfo, TagInstanceInfo } from '../api';
 import { Tag } from '../components/types/tag';
 import { annotationController } from '../controllers';
+import { GetSelectionInfo } from '../utils/selectionUtils';
 
 /**
  * Interface of an annotation context provider.
@@ -53,6 +54,12 @@ interface IAnnotationContextProvider {
     annotation: AnnotationInfo | null;
     mappedTags: Tag[] | null;
     refreshAnnotation: () => void;
+
+    markSelectedText: (
+        tagId: string,
+        subtagId: string | null,
+        instanceID: string | null
+    ) => void;
 }
 
 /**
@@ -119,6 +126,10 @@ export const AnnotationContext = createContext<IAnnotationContextProvider>({
     refreshAnnotation: () => {
         return;
     },
+
+    markSelectedText: () => {
+        return;
+    },
 });
 
 /**
@@ -139,12 +150,49 @@ const AnnotationProvider = (props: {
 
     const [mappedTags, setMappedTags] = useState<Tag[] | null>(null);
 
+    async function markSelectedText(
+        tagId: string,
+        subtagId: string | null,
+        instanceId: string | null
+    ) {
+        if (!annotation) {
+            console.log('annotation not found');
+            return;
+        }
+
+        const selectionInfo = GetSelectionInfo(annotation);
+        if (!selectionInfo) {
+            console.log(
+                'not able to continue, selection processing not completed successfully'
+            );
+            return;
+        }
+
+        const id = subtagId ?? tagId;
+        const type: ETagType = subtagId == null ? ETagType.Tag : ETagType.Subtag;
+
+        const res = await annotationController.annotationAnnotationIdPost(
+            props.annotationId,
+            {
+                id: id,
+                instanceId,
+                type,
+                position: selectionInfo.startPositionOriginalDocument,
+                length: selectionInfo.selectionLengthOriginalDocument,
+            }
+        );
+        console.log('res');
+        console.log(res);
+
+        await refreshAnnotation();
+    }
+
     /**
      * Default implementation of addOccurrence method.
      * @param tag The tag with new occurrence.
      */
-    const addOccurrence = (tag: Tag) => {
-        //TODO: Implement method (should use objects from server API)
+    const addOccurrence = async (tag: Tag) => {
+        await markSelectedText(tag.tagId, tag.subtagId ?? null, tag.instanceId);
     };
 
     /**
@@ -182,17 +230,21 @@ const AnnotationProvider = (props: {
     };
 
     const remapAnnotations = (data: AnnotationInfo) => {
-        let map = new Map<number, Tag>();
+        let map = new Map<string, Tag>();
         data.tagInstances?.forEach((tagInstance) => {
-            if (map.has(tagInstance.instance ?? 0)) {
-                let tag = map.get(tagInstance.instance ?? 0);
+            if (map.has(tagInstance.instance ?? '-')) {
+                let tag = map.get(tagInstance.instance ?? '-');
                 tag!.occurrences = [...tag!.occurrences, tagInstance];
             } else {
-                map.set(tagInstance.position ?? 0, {
-                    name: tagInstance.tagName ?? '',
+                map.set(tagInstance.instance ?? '-', {
+                    tagName: tagInstance.tagName ?? '',
+                    subtagName: tagInstance.subTagName ?? null,
                     category: tagInstance.tagCategoryName ?? '',
                     visible: true,
                     occurrences: [tagInstance],
+                    tagId: tagInstance.tagId ?? '',
+                    instanceId: tagInstance.instance ?? '',
+                    subtagId: tagInstance.subTagId ?? null,
                 });
             }
         });
@@ -229,6 +281,7 @@ const AnnotationProvider = (props: {
                 refreshAnnotation,
                 annotation,
                 mappedTags,
+                markSelectedText,
             }}
         >
             {props.children}
diff --git a/webapp/contexts/TagCategoryContext.tsx b/webapp/contexts/TagCategoryContext.tsx
index 315149b..f2b4ab3 100644
--- a/webapp/contexts/TagCategoryContext.tsx
+++ b/webapp/contexts/TagCategoryContext.tsx
@@ -1,6 +1,9 @@
-import React, { createContext, useEffect, useState } from 'react';
+import React, { createContext, useContext, useEffect, useState } from 'react';
 import { SubTagInfo, TagCategoryInfo, TagInfo } from '../api';
 import { tagController } from '../controllers';
+import { LoggedUserContext } from './LoggedUserContext';
+import Annotation from '../pages/annotation/[id]';
+import { AnnotationContext } from './AnnotationContext';
 
 /**
  * Interface of a tag context provider.
@@ -78,6 +81,8 @@ export const TagCategoryContext = createContext<ITagCategoryContextProvider>({
  * @returns Prepared html of the provider.
  */
 const TagCategoryProvider = (props: { children: React.ReactNode }) => {
+    const { markSelectedText } = useContext(AnnotationContext);
+
     /**
      * Tags managed by the context.
      */
@@ -99,6 +104,13 @@ const TagCategoryProvider = (props: { children: React.ReactNode }) => {
     const selectTag = (tag: TagInfo, subTag: SubTagInfo | null) => {
         setSelectedTag(tag);
         setSelectedSubTag(subTag);
+
+        if (!tag.id) {
+            console.log('invalid selection');
+            return;
+        }
+
+        markSelectedText(tag.id, subTag?.id ?? null, null);
     };
 
     /**
diff --git a/webapp/pages/annotation/[id].tsx b/webapp/pages/annotation/[id].tsx
index dec1dfe..80afdcf 100644
--- a/webapp/pages/annotation/[id].tsx
+++ b/webapp/pages/annotation/[id].tsx
@@ -5,6 +5,8 @@ import { MainLayout } from '../../layouts/MainLayout';
 import 'antd/dist/antd.css';
 import styles from '/styles/Annotation.module.scss';
 import AnnotationProvider from '../../contexts/AnnotationContext';
+import TagCategoryProvider from '../../contexts/TagCategoryContext';
+
 import { useRouter } from 'next/router';
 
 /**
@@ -27,19 +29,21 @@ function Annotation() {
 
     return (
         <AnnotationProvider annotationId={annotationId}>
-            <MainLayout>
-                <div className={styles.layoutWrapper}>
-                    <div className={styles.tags}>
-                        <TagPanel />
-                    </div>
-                    <div className={styles.document}>
-                        <DocumentAnnotationView />
-                    </div>
-                    <div className={styles.annotations}>
-                        <AnnotationPanel />
+            <TagCategoryProvider>
+                <MainLayout>
+                    <div className={styles.layoutWrapper}>
+                        <div className={styles.tags}>
+                            <TagPanel />
+                        </div>
+                        <div className={styles.document}>
+                            <DocumentAnnotationView />
+                        </div>
+                        <div className={styles.annotations}>
+                            <AnnotationPanel />
+                        </div>
                     </div>
-                </div>
-            </MainLayout>
+                </MainLayout>
+            </TagCategoryProvider>
         </AnnotationProvider>
     );
 }
diff --git a/webapp/utils/selectionUtils.ts b/webapp/utils/selectionUtils.ts
new file mode 100644
index 0000000..8f7e990
--- /dev/null
+++ b/webapp/utils/selectionUtils.ts
@@ -0,0 +1,88 @@
+import { AnnotationInfo } from '../api';
+
+export interface SelectionInfo {
+    startElementId: number;
+    endElementId: number;
+    startPositionOriginalDocument: number;
+    endPositionOriginalDocument: number;
+    selectionLengthOriginalDocument: number;
+}
+export function GetSelectionInfo(annotation: AnnotationInfo): SelectionInfo | null {
+    const selection = window.getSelection();
+    if (!selection) {
+        return null;
+    }
+    if (!annotation?.tagStartPositions || !annotation.tagLengths) {
+        console.log('start or lengths not found');
+        return null;
+    }
+
+    let startTag = selection.anchorNode;
+    let endTag = selection.focusNode;
+
+    if (!startTag || !endTag) {
+        console.log('Selection not found');
+        return null;
+    }
+
+    if (startTag.nodeName.includes('#')) {
+        startTag = startTag.parentNode;
+    }
+    if (endTag.nodeName.includes('#')) {
+        endTag = endTag.parentNode;
+    }
+    if (!startTag || !endTag) {
+        console.log('Selection element not found');
+        return null;
+    }
+
+    if (!(startTag instanceof Element)) {
+        console.log('StartTag is not instance of Element');
+        return null;
+    }
+    if (!(endTag instanceof Element)) {
+        console.log('EndTag is not instance of Element');
+        return null;
+    }
+
+    let startElement = startTag as Element;
+    let endElement = endTag as Element;
+
+    let startId: number = Number(startElement.getAttribute('aswi-tag-id')) ?? -1;
+    let endId: number = Number(endElement.getAttribute('aswi-tag-id')) ?? -1;
+
+    let startPosition =
+        annotation.tagStartPositions[startId] +
+        annotation.tagLengths[startId] +
+        selection.anchorOffset;
+    let endPosition =
+        annotation.tagStartPositions[endId] +
+        annotation.tagLengths[endId] +
+        selection.focusOffset -
+        1;
+
+    // need to switch start and end elements (selection was in the opposite way then expected)
+    if (startPosition > endPosition) {
+        let temp = startPosition;
+        startPosition = endPosition;
+        endPosition = temp;
+
+        temp = startId;
+        startId = endId;
+        endId = temp;
+
+        const tempElement = startElement;
+        startElement = endElement;
+        endElement = tempElement;
+    }
+
+    const length = endPosition - startPosition + 1;
+
+    return {
+        endElementId: endId,
+        startElementId: startId,
+        startPositionOriginalDocument: startPosition,
+        endPositionOriginalDocument: endPosition,
+        selectionLengthOriginalDocument: length,
+    };
+}
-- 
GitLab


From 0f8d6304152d20511d43d066a93a1e8b29537e52 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Vojt=C4=9Bch=20Barti=C4=8Dka?= <aitakaitov@gmail.com>
Date: Sun, 17 Apr 2022 15:41:37 +0200
Subject: [PATCH 06/12] Endpoint for annotation instance deletion - untested

---
 .../Controllers/AnnotationController.cs       | 29 ++++++++++++++
 .../AnnotationService/AnnotationServiceEF.cs  | 38 +++++++++++++++++++
 .../AnnotationService/IAnnotationService.cs   |  1 +
 Backend/Models/Tags/TagInstanceInfo.cs        |  2 +
 4 files changed, 70 insertions(+)

diff --git a/Backend/Backend/Controllers/AnnotationController.cs b/Backend/Backend/Controllers/AnnotationController.cs
index a187467..201468b 100644
--- a/Backend/Backend/Controllers/AnnotationController.cs
+++ b/Backend/Backend/Controllers/AnnotationController.cs
@@ -102,5 +102,34 @@ namespace RestAPI.Controllers
             }
 
         }
+
+        [HttpDelete("/annotation/{annotationId}/{tagInstanceId}")]
+        [ProducesResponseType((int)HttpStatusCode.OK)]
+        [ProducesResponseType((int)HttpStatusCode.Forbidden)]
+        public ActionResult DeleteAnnotationInstance([FromServices] ClientInfo clientInfo, Guid annotationId, Guid tagInstanceId)
+        {
+            if (clientInfo.LoggedUser == null)
+            {
+                logger.Warning("ClientInfo has null LoggerUser in [Authorized] controller /annotations");
+                return Problem();
+            }
+
+            // Take care of - non-admin user requesting not-assigned annotation
+            // non-existent annotation
+            try
+            {
+                annotationService.DeleteAnnotationInstance(annotationId, tagInstanceId, clientInfo.LoggedUser.Id, clientInfo.LoggedUser.Role);
+                return Ok();
+            }
+            catch (InvalidOperationException e)
+            {
+                throw new BadRequestException("Could not find specified annotation");
+            }
+            catch (UnauthorizedAccessException)
+            {
+                return Forbid();
+            }
+
+        }
     }
 }
diff --git a/Backend/Core/Services/AnnotationService/AnnotationServiceEF.cs b/Backend/Core/Services/AnnotationService/AnnotationServiceEF.cs
index c97e009..e3d929a 100644
--- a/Backend/Core/Services/AnnotationService/AnnotationServiceEF.cs
+++ b/Backend/Core/Services/AnnotationService/AnnotationServiceEF.cs
@@ -244,5 +244,43 @@ namespace Core.Services.AnnotationService
             context.AnnotationTags.Add(annotationTag);
             context.SaveChanges();
         }
+
+        public void DeleteAnnotationInstance(Guid annotationId, Guid tagInstanceId, Guid loggedUserId, ERole userRole)
+        {
+            Annotation annotation = null;
+            try
+            {
+                annotation = context.Annotations
+                   .Where(a => a.Id == annotationId)
+                   .Include(a => a.User)
+                   .Include(a => a.Document).ThenInclude(d => d.Content)
+                   .First();
+
+            }
+            catch (Exception ex)
+            {
+                throw new InvalidOperationException("Could not find annotation");
+            }
+
+
+            if (userRole < ERole.ADMINISTRATOR)
+            {
+                if (annotation.User.Id != loggedUserId)
+                {
+                    throw new UnauthorizedAccessException($"User {loggedUserId} does not have assigned annotation {annotationId}");
+                }
+            }
+
+            if (!context.AnnotationTags.Any(at => at.Id == tagInstanceId))
+            {
+                throw new InvalidOperationException("Could not find tag instance");
+            }
+
+            context.AnnotationTags
+                .Where(at => at.Id == tagInstanceId).ToList()
+                .ForEach(a => context.AnnotationTags.Remove(a));
+            
+            context.SaveChanges();
+        }
     }
 }
diff --git a/Backend/Core/Services/AnnotationService/IAnnotationService.cs b/Backend/Core/Services/AnnotationService/IAnnotationService.cs
index 5946e9c..07dddb1 100644
--- a/Backend/Core/Services/AnnotationService/IAnnotationService.cs
+++ b/Backend/Core/Services/AnnotationService/IAnnotationService.cs
@@ -14,6 +14,7 @@ namespace Core.Services.AnnotationService
         public AnnotationListResponse GetUserAnnotations(Guid userId);
         public AnnotationInfo GetAnnotation(Guid annotationId, Guid userId, ERole userRole);
         public void AddAnnotationInstance(Guid annotationId, Guid userId, ERole userRole, AnnotationInstanceAddRequest request);
+        public void DeleteAnnotationInstance(Guid annotationId, Guid tagInstanceId, Guid loggedUserId, ERole userRole);
 
     }
 }
diff --git a/Backend/Models/Tags/TagInstanceInfo.cs b/Backend/Models/Tags/TagInstanceInfo.cs
index 3c5cbcc..cbbc4e6 100644
--- a/Backend/Models/Tags/TagInstanceInfo.cs
+++ b/Backend/Models/Tags/TagInstanceInfo.cs
@@ -8,6 +8,8 @@ namespace Models.Tags
 {
     public class TagInstanceInfo
     {
+        /** For database */
+        public Guid Id { get; set; }
         public string TagName { get; set; }
         public Guid TagId { get; set; }
         public string TagCategoryName { get; set; }
-- 
GitLab


From 2ebf32d0c9142b907dc53a55a3de62ecf8ed8675 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Vojt=C4=9Bch=20Barti=C4=8Dka?= <aitakaitov@gmail.com>
Date: Sun, 17 Apr 2022 15:49:20 +0200
Subject: [PATCH 07/12] Changed Id to OccurenceId in TagInstanceInfo

---
 Backend/Core/MapperProfiles/TagProfileEF.cs | 3 ++-
 Backend/Models/Tags/TagInstanceInfo.cs      | 2 +-
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/Backend/Core/MapperProfiles/TagProfileEF.cs b/Backend/Core/MapperProfiles/TagProfileEF.cs
index dcc4e0e..4095ddd 100644
--- a/Backend/Core/MapperProfiles/TagProfileEF.cs
+++ b/Backend/Core/MapperProfiles/TagProfileEF.cs
@@ -16,7 +16,8 @@ namespace Core.MapperProfiles
             CreateMap<TagCategory, TagCategoryInfo>();
             CreateMap<Tag, TagInfo>();
             CreateMap<SubTag, SubTagInfo>();
-            CreateMap<AnnotationTag, TagInstanceInfo>();
+            CreateMap<AnnotationTag, TagInstanceInfo>()
+                .ForMember(ti => ti.OccurenceId, opt => opt.MapFrom(at => at.Id));
         }
     }
 }
diff --git a/Backend/Models/Tags/TagInstanceInfo.cs b/Backend/Models/Tags/TagInstanceInfo.cs
index cbbc4e6..788893d 100644
--- a/Backend/Models/Tags/TagInstanceInfo.cs
+++ b/Backend/Models/Tags/TagInstanceInfo.cs
@@ -9,7 +9,7 @@ namespace Models.Tags
     public class TagInstanceInfo
     {
         /** For database */
-        public Guid Id { get; set; }
+        public Guid OccurenceId { get; set; }
         public string TagName { get; set; }
         public Guid TagId { get; set; }
         public string TagCategoryName { get; set; }
-- 
GitLab


From 77667f3fd5f7248264977a3aed8e40f26f88f546 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Vojt=C4=9Bch=20Barti=C4=8Dka?= <aitakaitov@gmail.com>
Date: Sun, 17 Apr 2022 15:51:28 +0200
Subject: [PATCH 08/12] Minor rename

---
 Backend/Backend/Controllers/AnnotationController.cs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/Backend/Backend/Controllers/AnnotationController.cs b/Backend/Backend/Controllers/AnnotationController.cs
index 201468b..d4b9728 100644
--- a/Backend/Backend/Controllers/AnnotationController.cs
+++ b/Backend/Backend/Controllers/AnnotationController.cs
@@ -106,7 +106,7 @@ namespace RestAPI.Controllers
         [HttpDelete("/annotation/{annotationId}/{tagInstanceId}")]
         [ProducesResponseType((int)HttpStatusCode.OK)]
         [ProducesResponseType((int)HttpStatusCode.Forbidden)]
-        public ActionResult DeleteAnnotationInstance([FromServices] ClientInfo clientInfo, Guid annotationId, Guid tagInstanceId)
+        public ActionResult DeleteAnnotationInstance([FromServices] ClientInfo clientInfo, Guid annotationId, Guid occurenceId)
         {
             if (clientInfo.LoggedUser == null)
             {
@@ -118,7 +118,7 @@ namespace RestAPI.Controllers
             // non-existent annotation
             try
             {
-                annotationService.DeleteAnnotationInstance(annotationId, tagInstanceId, clientInfo.LoggedUser.Id, clientInfo.LoggedUser.Role);
+                annotationService.DeleteAnnotationInstance(annotationId, occurenceId, clientInfo.LoggedUser.Id, clientInfo.LoggedUser.Role);
                 return Ok();
             }
             catch (InvalidOperationException e)
-- 
GitLab


From 8316c10270b5cd14b98b769770c2c5d65a4da46c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Vojt=C4=9Bch=20Barti=C4=8Dka?= <aitakaitov@gmail.com>
Date: Sun, 17 Apr 2022 15:52:40 +0200
Subject: [PATCH 09/12] Last fix

---
 Backend/Backend/Controllers/AnnotationController.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Backend/Backend/Controllers/AnnotationController.cs b/Backend/Backend/Controllers/AnnotationController.cs
index d4b9728..e6e1f98 100644
--- a/Backend/Backend/Controllers/AnnotationController.cs
+++ b/Backend/Backend/Controllers/AnnotationController.cs
@@ -103,7 +103,7 @@ namespace RestAPI.Controllers
 
         }
 
-        [HttpDelete("/annotation/{annotationId}/{tagInstanceId}")]
+        [HttpDelete("/annotation/{annotationId}/{occurenceId}")]
         [ProducesResponseType((int)HttpStatusCode.OK)]
         [ProducesResponseType((int)HttpStatusCode.Forbidden)]
         public ActionResult DeleteAnnotationInstance([FromServices] ClientInfo clientInfo, Guid annotationId, Guid occurenceId)
-- 
GitLab


From 3f66b62447a126f02af249db079b144a2313d9f3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Luka=CC=81s=CC=8C=20Vlc=CC=8Cek?= <vlcek@net-inout.cz>
Date: Sun, 17 Apr 2022 15:47:59 +0200
Subject: [PATCH 10/12] Comments added to selection utils

---
 webapp/utils/selectionUtils.ts | 15 +++++++++++----
 1 file changed, 11 insertions(+), 4 deletions(-)

diff --git a/webapp/utils/selectionUtils.ts b/webapp/utils/selectionUtils.ts
index 8f7e990..ce0737a 100644
--- a/webapp/utils/selectionUtils.ts
+++ b/webapp/utils/selectionUtils.ts
@@ -7,18 +7,22 @@ export interface SelectionInfo {
     endPositionOriginalDocument: number;
     selectionLengthOriginalDocument: number;
 }
+const idAttributeName = 'aswi-tag-id';
+
 export function GetSelectionInfo(annotation: AnnotationInfo): SelectionInfo | null {
     const selection = window.getSelection();
     if (!selection) {
         return null;
     }
+
     if (!annotation?.tagStartPositions || !annotation.tagLengths) {
+        // invalid data provided from API
         console.log('start or lengths not found');
         return null;
     }
 
-    let startTag = selection.anchorNode;
-    let endTag = selection.focusNode;
+    let startTag = selection.anchorNode; // anchor node = node where selection started
+    let endTag = selection.focusNode; // focus node = node where selection ended
 
     if (!startTag || !endTag) {
         console.log('Selection not found');
@@ -26,11 +30,14 @@ export function GetSelectionInfo(annotation: AnnotationInfo): SelectionInfo | nu
     }
 
     if (startTag.nodeName.includes('#')) {
+        // it is not an element (usually #text)
         startTag = startTag.parentNode;
     }
     if (endTag.nodeName.includes('#')) {
+        // it is not an element (usually #text)
         endTag = endTag.parentNode;
     }
+
     if (!startTag || !endTag) {
         console.log('Selection element not found');
         return null;
@@ -48,8 +55,8 @@ export function GetSelectionInfo(annotation: AnnotationInfo): SelectionInfo | nu
     let startElement = startTag as Element;
     let endElement = endTag as Element;
 
-    let startId: number = Number(startElement.getAttribute('aswi-tag-id')) ?? -1;
-    let endId: number = Number(endElement.getAttribute('aswi-tag-id')) ?? -1;
+    let startId: number = Number(startElement.getAttribute(idAttributeName)) ?? -1;
+    let endId: number = Number(endElement.getAttribute(idAttributeName)) ?? -1;
 
     let startPosition =
         annotation.tagStartPositions[startId] +
-- 
GitLab


From 9fdb7d556f0aeb823465803942d00d212085ce70 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Luka=CC=81s=CC=8C=20Vlc=CC=8Cek?= <vlcek@net-inout.cz>
Date: Sun, 17 Apr 2022 15:56:45 +0200
Subject: [PATCH 11/12] Deleting occurences

---
 webapp/api/api.ts                     | 76 +++++++++++++++++++++++++++
 webapp/contexts/AnnotationContext.tsx | 17 ++++--
 2 files changed, 89 insertions(+), 4 deletions(-)

diff --git a/webapp/api/api.ts b/webapp/api/api.ts
index b26d5f3..89c8e56 100644
--- a/webapp/api/api.ts
+++ b/webapp/api/api.ts
@@ -566,6 +566,12 @@ export interface TagInfo {
  * @interface TagInstanceInfo
  */
 export interface TagInstanceInfo {
+    /**
+     * 
+     * @type {string}
+     * @memberof TagInstanceInfo
+     */
+    'occurenceId'?: string;
     /**
      * 
      * @type {string}
@@ -752,6 +758,43 @@ export const AnnotationApiAxiosParamCreator = function (configuration?: Configur
 
 
     
+            setSearchParams(localVarUrlObj, localVarQueryParameter);
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+
+            return {
+                url: toPathString(localVarUrlObj),
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         * 
+         * @param {string} annotationId 
+         * @param {string} occurenceId 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        annotationAnnotationIdOccurenceIdDelete: async (annotationId: string, occurenceId: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            // verify required parameter 'annotationId' is not null or undefined
+            assertParamExists('annotationAnnotationIdOccurenceIdDelete', 'annotationId', annotationId)
+            // verify required parameter 'occurenceId' is not null or undefined
+            assertParamExists('annotationAnnotationIdOccurenceIdDelete', 'occurenceId', occurenceId)
+            const localVarPath = `/annotation/{annotationId}/{occurenceId}`
+                .replace(`{${"annotationId"}}`, encodeURIComponent(String(annotationId)))
+                .replace(`{${"occurenceId"}}`, encodeURIComponent(String(occurenceId)));
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+
+            const localVarRequestOptions = { method: 'DELETE', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+
+    
             setSearchParams(localVarUrlObj, localVarQueryParameter);
             let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
             localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
@@ -851,6 +894,17 @@ export const AnnotationApiFp = function(configuration?: Configuration) {
             const localVarAxiosArgs = await localVarAxiosParamCreator.annotationAnnotationIdGet(annotationId, options);
             return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
         },
+        /**
+         * 
+         * @param {string} annotationId 
+         * @param {string} occurenceId 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async annotationAnnotationIdOccurenceIdDelete(annotationId: string, occurenceId: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
+            const localVarAxiosArgs = await localVarAxiosParamCreator.annotationAnnotationIdOccurenceIdDelete(annotationId, occurenceId, options);
+            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
+        },
         /**
          * 
          * @param {string} annotationId 
@@ -891,6 +945,16 @@ export const AnnotationApiFactory = function (configuration?: Configuration, bas
         annotationAnnotationIdGet(annotationId: string, options?: any): AxiosPromise<AnnotationInfo> {
             return localVarFp.annotationAnnotationIdGet(annotationId, options).then((request) => request(axios, basePath));
         },
+        /**
+         * 
+         * @param {string} annotationId 
+         * @param {string} occurenceId 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        annotationAnnotationIdOccurenceIdDelete(annotationId: string, occurenceId: string, options?: any): AxiosPromise<void> {
+            return localVarFp.annotationAnnotationIdOccurenceIdDelete(annotationId, occurenceId, options).then((request) => request(axios, basePath));
+        },
         /**
          * 
          * @param {string} annotationId 
@@ -931,6 +995,18 @@ export class AnnotationApi extends BaseAPI {
         return AnnotationApiFp(this.configuration).annotationAnnotationIdGet(annotationId, options).then((request) => request(this.axios, this.basePath));
     }
 
+    /**
+     * 
+     * @param {string} annotationId 
+     * @param {string} occurenceId 
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof AnnotationApi
+     */
+    public annotationAnnotationIdOccurenceIdDelete(annotationId: string, occurenceId: string, options?: AxiosRequestConfig) {
+        return AnnotationApiFp(this.configuration).annotationAnnotationIdOccurenceIdDelete(annotationId, occurenceId, options).then((request) => request(this.axios, this.basePath));
+    }
+
     /**
      * 
      * @param {string} annotationId 
diff --git a/webapp/contexts/AnnotationContext.tsx b/webapp/contexts/AnnotationContext.tsx
index 502f1b6..b3be87f 100644
--- a/webapp/contexts/AnnotationContext.tsx
+++ b/webapp/contexts/AnnotationContext.tsx
@@ -181,8 +181,6 @@ const AnnotationProvider = (props: {
                 length: selectionInfo.selectionLengthOriginalDocument,
             }
         );
-        console.log('res');
-        console.log(res);
 
         await refreshAnnotation();
     }
@@ -207,8 +205,19 @@ const AnnotationProvider = (props: {
      * Deletes an occurrence of an annotation.
      * @param occurrence Occurrence that should be deleted.
      */
-    const deleteOccurrence = (occurrence: TagInstanceInfo) => {
-        //TODO: Implement method (should use objects from server API)
+    const deleteOccurrence = async (occurrence: TagInstanceInfo) => {
+        if (!occurrence.occurenceId) {
+            console.log('invalid occurrence');
+            return;
+        }
+
+        const deleteRes =
+            await annotationController.annotationAnnotationIdOccurenceIdDelete(
+                props.annotationId,
+                occurrence.occurenceId
+            );
+
+        await refreshAnnotation();
     };
 
     /**
-- 
GitLab


From 51f70e003068d8db4ce5a952ec9fa3329e39de19 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Luka=CC=81s=CC=8C=20Vlc=CC=8Cek?= <vlcek@net-inout.cz>
Date: Sun, 17 Apr 2022 15:56:49 +0200
Subject: [PATCH 12/12] Build fixed

---
 webapp/pages/classes/index.tsx             | 2 +-
 webapp/pages/documents/admin/index.tsx     | 2 +-
 webapp/pages/documents/annotator/index.tsx | 2 +-
 webapp/pages/export/index.tsx              | 2 +-
 webapp/pages/login/index.tsx               | 2 +-
 webapp/pages/tags/index.tsx                | 2 +-
 webapp/pages/users/index.tsx               | 2 +-
 7 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/webapp/pages/classes/index.tsx b/webapp/pages/classes/index.tsx
index 8465291..2ef901f 100644
--- a/webapp/pages/classes/index.tsx
+++ b/webapp/pages/classes/index.tsx
@@ -7,7 +7,7 @@ import { Typography } from 'antd';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { faBookmark } from '@fortawesome/free-solid-svg-icons';
 import { LoggedUserContext } from '../../contexts/LoggedUserContext';
-import { MainLayout } from '../../layouts/mainLayout';
+import { MainLayout } from '../../layouts/MainLayout';
 
 function ClassesPage() {
     const redirecting = useUnauthRedirect('/login');
diff --git a/webapp/pages/documents/admin/index.tsx b/webapp/pages/documents/admin/index.tsx
index ac575ab..73547ac 100644
--- a/webapp/pages/documents/admin/index.tsx
+++ b/webapp/pages/documents/admin/index.tsx
@@ -7,7 +7,7 @@ import { Button, Typography } from 'antd';
 import { faFileLines } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { LoggedUserContext } from '../../../contexts/LoggedUserContext';
-import { MainLayout } from '../../../layouts/mainLayout';
+import { MainLayout } from '../../../layouts/MainLayout';
 import { documentController } from '../../../controllers';
 
 function AdminDocumentPage() {
diff --git a/webapp/pages/documents/annotator/index.tsx b/webapp/pages/documents/annotator/index.tsx
index 469b76a..5b30256 100644
--- a/webapp/pages/documents/annotator/index.tsx
+++ b/webapp/pages/documents/annotator/index.tsx
@@ -8,7 +8,7 @@ import UserNavBar from '../../../components/navigation/UserNavBar';
 import { faFileLines } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { LoggedUserContext } from '../../../contexts/LoggedUserContext';
-import { MainLayout } from '../../../layouts/mainLayout';
+import { MainLayout } from '../../../layouts/MainLayout';
 
 function UserDocumentPage() {
     const redirecting = useUnauthRedirect('/login');
diff --git a/webapp/pages/export/index.tsx b/webapp/pages/export/index.tsx
index 1f533b6..6ce97f7 100644
--- a/webapp/pages/export/index.tsx
+++ b/webapp/pages/export/index.tsx
@@ -7,7 +7,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { faFileExport } from '@fortawesome/free-solid-svg-icons';
 import { Typography } from 'antd';
 import { LoggedUserContext } from '../../contexts/LoggedUserContext';
-import { MainLayout } from '../../layouts/mainLayout';
+import { MainLayout } from '../../layouts/MainLayout';
 
 function ExportPage() {
     const redirecting = useUnauthRedirect('/login');
diff --git a/webapp/pages/login/index.tsx b/webapp/pages/login/index.tsx
index 7d492e9..d115be9 100644
--- a/webapp/pages/login/index.tsx
+++ b/webapp/pages/login/index.tsx
@@ -2,7 +2,7 @@ import 'antd/dist/antd.css';
 import { Form, Input, Button } from 'antd';
 import { UserOutlined, LockOutlined } from '@ant-design/icons';
 import { LoggedUserContext } from '../../contexts/LoggedUserContext';
-import { LoginLayout } from '../../layouts/loginLayout';
+import { LoginLayout } from '../../layouts/LoginLayout';
 import { useContext } from 'react';
 import { useRouter } from 'next/router';
 import { ShowToast } from '../../utils/alerts';
diff --git a/webapp/pages/tags/index.tsx b/webapp/pages/tags/index.tsx
index c304f1f..b9a5f7c 100644
--- a/webapp/pages/tags/index.tsx
+++ b/webapp/pages/tags/index.tsx
@@ -7,7 +7,7 @@ import { Typography } from 'antd';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { faTags } from '@fortawesome/free-solid-svg-icons';
 import { LoggedUserContext } from '../../contexts/LoggedUserContext';
-import { MainLayout } from '../../layouts/mainLayout';
+import { MainLayout } from '../../layouts/MainLayout';
 
 function TagsPage() {
     const redirecting = useUnauthRedirect('/login');
diff --git a/webapp/pages/users/index.tsx b/webapp/pages/users/index.tsx
index 2190587..765f8bc 100644
--- a/webapp/pages/users/index.tsx
+++ b/webapp/pages/users/index.tsx
@@ -7,7 +7,7 @@ import { Typography } from 'antd';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { faUsers } from '@fortawesome/free-solid-svg-icons';
 import { LoggedUserContext } from '../../contexts/LoggedUserContext';
-import { MainLayout } from '../../layouts/mainLayout';
+import { MainLayout } from '../../layouts/MainLayout';
 
 function UsersPage() {
     const redirecting = useUnauthRedirect('/login');
-- 
GitLab