ES6: Port Tagger to ES6

Change-Id: I67d35ec3cf24f62d3c23a0384488a1a59fe888bb
diff --git a/Changes b/Changes
index cf7dd84..70299f7 100644
--- a/Changes
+++ b/Changes
@@ -1,4 +1,5 @@
-        - Migration of pageInfo to ES6 (hebasta)
+        - Migration pageInfo to ES6 (hebasta)
+        - Migration tagger to ES6 (hebasta)
 
 0.66 2026-09-02
         - Fix link to NEGRA corpus (diewald)
diff --git a/dev/js/src-lib/palette.js b/dev/js/src-lib/palette.js
new file mode 100644
index 0000000..140a5a8
--- /dev/null
+++ b/dev/js/src-lib/palette.js
@@ -0,0 +1,80 @@
+import KorAP from './util/util.js';
+  /*
+   * Created with http://tools.medialab.sciences-po.fr/iwanthue/
+   * and H: 0-360, C: 0.4-2.1, L: 0.7-1.5, 64 colors
+   */
+  const palette = [
+    '9C8AC7',
+    '6DED31',
+    'DF7F23',
+    '5AECD1',
+    'F54597',
+    'D4D275',
+    'E6A8A1',
+    '67E682',
+    '719A93',
+    'E67AE6',
+    'E8F03F',
+    'F45042',
+    '6D9C25',
+    'D0C3D9',
+    'E37076',
+    '509A58',
+    'BE9962',
+    'D88AB6',
+    '71ABCE',
+    '62DCEB',
+    'D4B73C',
+    'A0E5B0',
+    '5BBB26',
+    'A7E230',
+    '7E8EEA',
+    'D1856B',
+    '59E9A9',
+    'F24A61',
+    'A6D455',
+    'AEE38B',
+    '4DA385',
+    '999C46',
+    'AFDDDE',
+    'BA85E0',
+    'B68C31',
+    '8AA46C',
+    'D9874C',
+    'A18EA8',
+    'E56BAC',
+    'ECA72F',
+    '3AE152',
+    '5B9EDF',
+    'E9672F',
+    '43B046',
+    'E96D59',
+    'D6B3E6',
+    'E6BB6A',
+    '92C5A9',
+    'BE7C85',
+    '9C9B1E',
+    'CCD896',
+    '84E861',
+    'DA7093',
+    'DDEA6F',
+    'BCC92E',
+    '59C9BD',
+    '52A8B0',
+    '51BC7C',
+    '7EB051',
+    'F0D036',
+    'D970C5',
+    'E95F7D',
+    'EF9798',
+    'ED5490'
+  ];
+
+  KorAP.Palette = {
+    getC : function (index) {
+      return '#' + palette[index % 64];
+    }
+  };
+
+
+export default KorAP.Palette;
diff --git a/dev/js/src-lib/tagger/demo/index.html b/dev/js/src-lib/tagger/demo/index.html
new file mode 100644
index 0000000..544ceb6
--- /dev/null
+++ b/dev/js/src-lib/tagger/demo/index.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Tagger demo</title>
+    <meta charset="utf-8" />
+      <link type="text/css" rel="stylesheet" href="/css/kalamar-latest.css" />
+   <style type="text/css" rel="stylesheet">
+.info {
+  background-color: #ddd;
+  color: black;
+  padding: 1em;
+  font-family: mono;
+}
+body {
+  margin: 0;
+  background-color: #ddd;
+}
+span.close::after {
+  content: 'x'
+}
+
+    </style>
+  </head>
+  <body>
+    <main>
+      <h1>Tagger demo</h1>
+  <script type="module" src="./taggerdemo.js"></script>
+      <div class="tagger" id="tagger"></div>
+    </main>
+  </body>
+</html>
+
diff --git a/dev/js/src-lib/tagger/demo/taggerdemo.js b/dev/js/src-lib/tagger/demo/taggerdemo.js
new file mode 100644
index 0000000..d509905
--- /dev/null
+++ b/dev/js/src-lib/tagger/demo/taggerdemo.js
@@ -0,0 +1,4 @@
+import Tagger from '../tagger.js';
+
+const gTagger = new Tagger(document.getElementById('tagger'));
+
diff --git a/dev/js/src-lib/tagger/package.json b/dev/js/src-lib/tagger/package.json
new file mode 100644
index 0000000..4eb4bdc
--- /dev/null
+++ b/dev/js/src-lib/tagger/package.json
@@ -0,0 +1,11 @@
+{
+  "name": "Kalamar-tagger",
+  "version": "0.0.1",
+  "description": "Tagger for Kalmar",
+  "main": "tagger.js",
+  "scripts": {
+    "dev": "vite --open /demo/index.html"
+  },
+  "author": "",
+  "license": "ISC"
+}
diff --git a/dev/js/src-lib/tagger/tagger.js b/dev/js/src-lib/tagger/tagger.js
new file mode 100644
index 0000000..6f34bdc
--- /dev/null
+++ b/dev/js/src-lib/tagger/tagger.js
@@ -0,0 +1,38 @@
+import p from '../palette.js';
+import '../util/util.js';
+
+class Tagger {
+   
+  constructor(elem) {
+      this._nr = 0;
+      this._elem = elem;
+      elem.addE('div');
+
+      const newCat = elem.addE('input');
+      newCat.setAttribute('type', 'text');
+      newCat.addEventListener('keypress', function (e) {
+        const key = e.key || e.keyCode || e.which;
+        if (key === 'Enter' || key === 13) {
+          this.addTag(newCat.value);
+          newCat.value = '';
+        };
+      }.bind(this));
+
+      this._cat = [];
+    }
+
+
+
+    addTag(name) {
+      this._cat.push(name);
+      this._nr++;
+
+      const cat = document.createElement('span');
+      cat.addT(name);
+      cat.addE('span').setAttribute('class','close');
+      cat.style.backgroundColor = p.getC(this._nr);
+      this._elem.firstChild.appendChild(cat);
+    }
+  }
+
+export default Tagger;
diff --git a/dev/js/src-lib/tagger/test/taggerSpec.js b/dev/js/src-lib/tagger/test/taggerSpec.js
new file mode 100644
index 0000000..bf025e0
--- /dev/null
+++ b/dev/js/src-lib/tagger/test/taggerSpec.js
@@ -0,0 +1,144 @@
+import { describe, it, expect, beforeEach, afterEach } from 'vitest';
+import Tagger from '../tagger.js';
+import '../../palette.js';
+
+describe('Tagger', () => {
+  let container;
+  let tagger;
+
+  beforeEach(() => {
+    // Create a container element for the tagger
+    container = document.createElement('div');
+    document.body.appendChild(container);
+    
+    // Initialize the tagger
+    tagger = new Tagger(container);
+  });
+
+  afterEach(() => {
+    // Clean up
+    document.body.removeChild(container);
+  });
+
+  it('should initialize with correct default values', () => {
+    expect(tagger._nr).toBe(0);
+    expect(tagger._elem).toBe(container);
+    expect(Array.isArray(tagger._cat)).toBe(true);
+    expect(tagger._cat.length).toBe(0);
+  });
+
+  it('should create a div element on initialization', () => {
+    expect(container.children.length).toBeGreaterThan(0);
+    expect(container.firstChild.tagName).toBe('DIV');
+  });
+
+  it('should create an input element on initialization', () => {
+    const input = container.querySelector('input');
+    expect(input).not.toBeNull();
+    expect(input.getAttribute('type')).toBe('text');
+  });
+
+  it('should add a tag with addTag method', () => {
+    const tagName = 'TestTag';
+    tagger.addTag(tagName);
+
+    expect(tagger._cat[0]).toBe(tagName);
+    expect(tagger._nr).toBe(1);
+  });
+
+  it('should increment counter for each tag added', () => {
+    tagger.addTag('Tag1');
+    tagger.addTag('Tag2');
+    tagger.addTag('Tag3');
+
+    expect(tagger._nr).toBe(3);
+    expect(tagger._cat.length).toBe(3);
+  });
+
+  it('should create span elements for added tags', () => {
+    tagger.addTag('TestTag');
+
+    const tagElement = container.firstChild.querySelector('span');
+    expect(tagElement).not.toBeNull();
+    expect(tagElement.textContent).toContain('TestTag');
+  });
+
+  it('should add a close button to each tag', () => {
+    tagger.addTag('TestTag');
+
+    const tagElement = container.firstChild.querySelector('span');
+    const closeButton = tagElement.querySelector('span.close');
+    expect(closeButton).not.toBeNull();
+  });
+
+  it('should assign colors to tags based on palette', () => {
+    tagger.addTag('ColoredTag');
+
+    const tagElement = container.firstChild.querySelector('span');
+    const backgroundColor = tagElement.style.backgroundColor;
+
+    expect(backgroundColor).toBeTruthy();
+  });
+
+  it('should trigger addTag on Enter key press', () => {
+    const input = container.querySelector('input');
+    const tagName = 'KeyPressTag';
+    input.value = tagName;
+
+    const event = new KeyboardEvent('keypress', {
+      keyCode: 13,
+      key: 'Enter',
+      bubbles: true
+    });
+
+    input.dispatchEvent(event);
+
+    expect(tagger._cat[0]).toBe(tagName);
+    expect(input.value).toBe('');
+    expect(tagger._nr).toBe(1);
+  });
+
+  it('should not trigger addTag on non-Enter keys', () => {
+    const input = container.querySelector('input');
+    input.value = 'ShouldNotBeAdded';
+
+    const event = new KeyboardEvent('keypress', {
+      keyCode: 65,
+      key: 'a',
+      bubbles: true
+    });
+
+    input.dispatchEvent(event);
+
+    expect(tagger._cat.length).toBe(0);
+    expect(tagger._nr).toBe(0);
+    expect(input.value).toBe('ShouldNotBeAdded');
+  });
+
+  it('should handle multiple tags with different names', () => {
+    tagger.addTag('JavaScript');
+    tagger.addTag('Testing');
+    tagger.addTag('Vitest');
+
+    expect(tagger._nr).toBe(3);
+    expect(tagger._cat.length).toBe(3);
+    expect(tagger._cat[0]).toBe('JavaScript');
+    expect(tagger._cat[1]).toBe('Testing');
+    expect(tagger._cat[2]).toBe('Vitest');
+  });
+
+  it('should allow empty tag names', () => {
+    tagger.addTag('');
+
+    expect(tagger._cat[0]).toBe('');
+    expect(tagger._nr).toBe(1);
+  });
+
+  it('should handle special characters in tag names', () => {
+    const specialTag = '<script>alert("test")</script>';
+    tagger.addTag(specialTag);
+
+    expect(tagger._cat[0]).toBe(specialTag);
+    expect(tagger._nr).toBe(1);
+  });
+});
diff --git a/dev/js/src-lib/tagger/vite.config.js b/dev/js/src-lib/tagger/vite.config.js
new file mode 100644
index 0000000..1edc2f0
--- /dev/null
+++ b/dev/js/src-lib/tagger/vite.config.js
@@ -0,0 +1,29 @@
+import { defineConfig } from 'vite';
+import path from 'path';
+
+const scssDir = path.resolve(__dirname, '../../../scss'); // Path to your SCSS directory
+const fontDir = path.resolve(__dirname, '../../../font');
+
+export default defineConfig({
+
+  server: {
+    fs: {
+      allow: [
+        scssDir,
+        fontDir,
+        __dirname
+      ]
+    },
+    port: 3000
+  },
+
+ 
+  css: {
+    preprocessorOptions: {
+      scss: {
+        loadPaths: [scssDir]
+      }
+    }
+  }
+});
+