git-tfs-id: [http://tfs.userrendszerhaz.hu:8080/tfs/DefaultCollection]$/MediaCube...
authorVásáry Dániel <daniel.vasary@userrendszerhaz.hu>
Wed, 29 Jan 2020 13:22:08 +0000 (13:22 +0000)
committerVásáry Dániel <daniel.vasary@userrendszerhaz.hu>
Wed, 29 Jan 2020 13:22:08 +0000 (13:22 +0000)
42 files changed:
client/DxPlay/PlayerGraph.cs
client/MXFFileParser/MXFFile.cs
client/Maestro/Configuration/-UJ-configuration-tqc-check.json
client/Maestro/Configuration/editor.json
client/Maestro/MaestroForm.Metadata.cs
client/Maestro/MaestroForm.Target.cs
client/Maestro/Properties/AssemblyInfo.cs
client/Maestro/RestoreMedia.cs
client/MaestroShared/Commons/PatternNameMaker.cs
client/MaestroShared/Configuration/ConfigurationInfo.cs
client/MaestroShared/Properties/AssemblyInfo.cs
client/MaestroShared/Targets/TargetProcessorParameter.cs
client/MaestroShared/Targets/UNCTargetProcessor.cs
server/-configuration/log4j2.xml
server/-modules/MediaCube.iml [new file with mode: 0644]
server/-product/user.jobengine.product.iml [new file with mode: 0644]
server/hu.user.mediacube.executors.tests/META-INF/MANIFEST.MF
server/hu.user.mediacube.executors.tests/src/hu/user/mediacube/executors/tests/HSMMigrateStepTest.java
server/hu.user.mediacube.executors.tests/src/hu/user/mediacube/executors/tests/MediaBaseTest.java [new file with mode: 0644]
server/hu.user.mediacube.indexer/indexer (1).iml [new file with mode: 0644]
server/hu.user.mediacube.indexer/indexer (2).iml [new file with mode: 0644]
server/hu.user.mediacube.indexer/indexer.iml [new file with mode: 0644]
server/test/.classpath [new file with mode: 0644]
server/test/.project [new file with mode: 0644]
server/test/.settings/org.eclipse.jdt.core.prefs [new file with mode: 0644]
server/test/src/Echo.java [new file with mode: 0644]
server/user.commons.log4j2/user.commons.log4j2.iml [new file with mode: 0644]
server/user.jobengine.executors/src/user/jobengine/server/steps/HSMMigrateStep.java
server/user.jobengine.executors/user.jobengine.executors.iml [new file with mode: 0644]
server/user.jobengine.osgi.commons/user.jobengine.osgi.commons.iml [new file with mode: 0644]
server/user.jobengine.osgi.db/user.jobengine.osgi.db.iml [new file with mode: 0644]
server/user.jobengine.osgi.server/META-INF/MANIFEST.MF
server/user.jobengine.osgi.server/css/tagify.css [new file with mode: 0644]
server/user.jobengine.osgi.server/js/tagify.js [new file with mode: 0644]
server/user.jobengine.osgi.server/js/tagify.min.js [new file with mode: 0644]
server/user.jobengine.osgi.server/js/tagify/tagify.polyfills.min.js [new file with mode: 0644]
server/user.jobengine.osgi.server/pages/searchitems.zul
server/user.jobengine.osgi.server/test/user/jobengine/server/scheduler/CronExpressionTest.java
server/user.jobengine.osgi.server/user.jobengine.osgi.server.iml [new file with mode: 0644]
server/user.jobengine.osgi.services/user.jobengine.osgi.services.iml [new file with mode: 0644]
server/user.mediacube.metadata/user.mediacube.metadata.iml [new file with mode: 0644]
server/user.tsm.client/user.tsm.client.iml [new file with mode: 0644]

index 3cfa8619b1098878e648b489a7ae22be21cca46e..88ed3a46241362e2591990a8d8ced901ed8f1dbe 100644 (file)
@@ -40,8 +40,8 @@ namespace DxPlay {
                     EnableDeinterlace(videoDecoder);\r
                     IBaseFilter sampleGrabber = AddSampleGrabber(graphBuilder, videoDecoder);\r
                     SampleGrabber = (ISampleGrabber)sampleGrabber;\r
-                    //IBaseFilter videoRenderer = AddRenderer(graphBuilder, sampleGrabber, handle);\r
-                    IBaseFilter videoRenderer = AddSimpleRenderer(graphBuilder, sampleGrabber, handle);\r
+                    IBaseFilter videoRenderer = AddRenderer(graphBuilder, sampleGrabber, handle);\r
+                    //IBaseFilter videoRenderer = AddSimpleRenderer(graphBuilder, sampleGrabber, handle);\r
                     FilterGraphTools.RenderPin(graphBuilder, sampleGrabber, "Output");\r
                     ConfigureSimpleRenderer(handle);\r
 \r
index 0e98de2f618b6a94e7fb5f8575ac9ffa641040c8..f01a562ae4e4a0bbe240820e5435103a6a978cf2 100644 (file)
@@ -91,6 +91,11 @@ namespace Myriadbits.MXF {
                     break;\r
             }\r
         }\r
+        //TODO XML kiolvasás és parse\r
+        public string InspectMetadata() {\r
+            ParsePartial();\r
+            return null;\r
+        }\r
 \r
         /// <summary>\r
         /// Fully Parse an MXF file \r
index 31499b3d69d06e841e501e9858e40ed83f648706..549dfcf6a260ccaa400cbf7c3ca5c40635971557 100644 (file)
@@ -1,6 +1,6 @@
 {\r
   "title": "TQC check",\r
-  "active": true,\r
+  "active": false,\r
   "startInTray": false,\r
   "enableCustomMetadataId": true,\r
   "player": {\r
index 2024a0e457d54bab800c296899f1762b3aeeb451..0e802bf1ded7d464632d5c554be5cb851924589c 100644 (file)
                "$type": "UNCSource",\r
                "filter": "avi,wav,mxf,mts",\r
                "local": {\r
-                       "address": "file://c:/remote/promise/FINISHED_SHOWS",\r
+                       "address": "file://c:/_video/",\r
                        "timeout": 1000\r
                },\r
-    "remote": {\r
-      "address": "ftp://10.11.1.100/Promise/FINISHED_SHOWS",\r
-      "userName": "editor1",\r
-      "password": "mBsAKn0RRr+lErAWAu+oMD/3CRxlBLNvm3UB84SKl5KBVYD5+wIANFL0eszfbAUtzYKqdN/dEB/6ItBNz9D6C4/hppcYrg0+73+xFW9KYEwd2KfgHaH5uslbA/8IyI/U",\r
-      "timeout": 1000\r
-    }\r
+               "remote": {\r
+                       "address": "ftp://10.11.1.100/Promise/",\r
+                       "userName": "editor1",\r
+                       "password": "mBsAKn0RRr+lErAWAu+oMD/3CRxlBLNvm3UB84SKl5KBVYD5+wIANFL0eszfbAUtzYKqdN/dEB/6ItBNz9D6C4/hppcYrg0+73+xFW9KYEwd2KfgHaH5uslbA/8IyI/U",\r
+                       "timeout": 1000\r
+               }\r
        },\r
        "metadatas": [\r
                {\r
                        "label": "Adáskész",\r
                        "processor": "FXPTargetProcessor",\r
                        "outputFormat": "%ID%",\r
+                       "reference": [ "Mentés" ],\r
                        "saveSegments": true,\r
+                       "subFolderFormat": "%SOURCEFOLDERNAME%",\r
                        "tag": "Adáskész",\r
-      "remote": {\r
-        "address": "ftp://10.11.1.100/Promise/PROGRAM/TEST",\r
-        "userName": "editor1",\r
-        "password": "mBsAKn0RRr+lErAWAu+oMD/3CRxlBLNvm3UB84SKl5KBVYD5+wIANFL0eszfbAUtzYKqdN/dEB/6ItBNz9D6C4/hppcYrg0+73+xFW9KYEwd2KfgHaH5uslbA/8IyI/U",\r
-        "timeout": 1000\r
-      }\r
+                       "remote": {\r
+                               "address": "ftp://10.11.1.100/Promise/PROGRAM/TEST",\r
+                               "userName": "editor1",\r
+                               "password": "mBsAKn0RRr+lErAWAu+oMD/3CRxlBLNvm3UB84SKl5KBVYD5+wIANFL0eszfbAUtzYKqdN/dEB/6ItBNz9D6C4/hppcYrg0+73+xFW9KYEwd2KfgHaH5uslbA/8IyI/U",\r
+                               "timeout": 1000\r
+                       }\r
                },\r
-    {\r
-      "label": "Archiválás",\r
-      "processor": "FXPTargetProcessor",\r
-      "outputFormat": "%ID%",\r
-      "saveArchiveMetadata": true,\r
-      "tag": "Archiválás",\r
-      "remote": {\r
-        "address": "ftp://10.11.1.100/Promise/ARCHIVE/TEST",\r
-        "userName": "editor1",\r
-        "password": "mBsAKn0RRr+lErAWAu+oMD/3CRxlBLNvm3UB84SKl5KBVYD5+wIANFL0eszfbAUtzYKqdN/dEB/6ItBNz9D6C4/hppcYrg0+73+xFW9KYEwd2KfgHaH5uslbA/8IyI/U",\r
-        "timeout": 1000\r
-      }\r
-    }\r
+               {\r
+                       "label": "Archiválás",\r
+                       "processor": "FXPTargetProcessor",\r
+                       "outputFormat": "%ID%",\r
+                       "saveArchiveMetadata": true,\r
+                       "tag": "Archiválás",\r
+                       "remote": {\r
+                               "address": "ftp://10.11.1.100/Promise/ARCHIVE/TEST",\r
+                               "userName": "editor1",\r
+                               "password": "mBsAKn0RRr+lErAWAu+oMD/3CRxlBLNvm3UB84SKl5KBVYD5+wIANFL0eszfbAUtzYKqdN/dEB/6ItBNz9D6C4/hppcYrg0+73+xFW9KYEwd2KfgHaH5uslbA/8IyI/U",\r
+                               "timeout": 1000\r
+                       }\r
+               },\r
+                       {\r
+                               "label": "Mentés",\r
+                               "readOnly":  true,\r
+                               "processor": "UNCTargetProcessor",\r
+                               "outputFormat": "%SOURCENAME%",\r
+                               "tag": "Mentés",\r
+                               "subFolderFormat": "Transfered",\r
+                               "moveToFolder": true\r
+                       }\r
 \r
   ]\r
 }\r
index e3b0c4a38083e169c08adc1c52c317e4e73035f4..7a7babaa80d3cfafa3313186b848c71dc4ef109f 100644 (file)
@@ -342,10 +342,10 @@ namespace Maestro {
             IEnumerable<Story> octopusResult = null;\r
             switch (metadataType) {\r
                 case MetadataType.OctopusPlaceHolder:\r
-                    octopusResult = api.GetStoriesByPlaceHolderID(id);\r
+                    octopusResult = api?.GetStoriesByPlaceHolderID(id);\r
                     break;\r
                 case MetadataType.OctopusStory:\r
-                    octopusResult = api.GetStoriesByParentStoryID(id);\r
+                    octopusResult = api?.GetStoriesByParentStoryID(id);\r
                     break;\r
             }\r
             List<Story> stories = octopusResult?.ToList();\r
index f4d301c280d24f3f7be62dadf2a91c7d652e9f6a..8332eb8d5511c6b201fa407e13bfab9985370ade 100644 (file)
@@ -41,6 +41,7 @@ namespace Maestro {
                 Dock = DockStyle.Top,\r
                 Tag = target\r
             };\r
+            checkBox.Enabled = !target.ReadOnly;\r
             formTooltip.SetToolTip(checkBox, target.Remote?.Address?.ToString());\r
             checkBox.CheckStateChanged += (s, e) => OnChecked(checkBox, target);\r
             panelActions.Controls.Add(checkBox);\r
@@ -192,8 +193,10 @@ namespace Maestro {
             //szegmens informaciook hozzaadasa\r
             if (ArchiveMetadata != null && HasNoneEmptySegments()) {\r
                 var segments = "Szegmensek:" + Environment.NewLine;\r
-                MovieSegments.ToList().ForEach(s => { segments += $"{s.TCIn.ToString()} - {s.TCOut.ToString()}" + Environment.NewLine; });\r
-                ArchiveMetadata.mediaDescription += Environment.NewLine + segments;\r
+                if (MovieSegments != null && MovieSegments.Count > 0) { \r
+                    MovieSegments.ToList().ForEach(s => { segments += $"{s.TCIn.ToString()} - {s.TCOut.ToString()}" + Environment.NewLine; });\r
+                    ArchiveMetadata.mediaDescription += Environment.NewLine + segments;\r
+                }\r
             }\r
 \r
             return true;\r
@@ -203,6 +206,9 @@ namespace Maestro {
             FileSystemSource source = bindingSource.DataSource as FileSystemSource;\r
 \r
             TargetProcessorParameter result = new TargetProcessorParameter();\r
+            if (sourceItem is FileSourceItem) {\r
+                result.SourcePath = ((FileSourceItem)sourceItem).FileInfo.DirectoryName;\r
+            }\r
             result.SourcePathOverride = (source == null || source.Path.Equals(Configuration.Source.Local.Address.LocalPath)) ? null : source.Path;\r
             result.MediaCubeApi = mediaCubeApi;\r
             result.TrafficApi = trafficIDSelector.trafficAPI;\r
@@ -228,32 +234,46 @@ namespace Maestro {
                 result.ArchiveMetadata.duration = sourceItem.Frames;\r
                 result.ArchiveMetadata.userName = result.UserName;\r
                 if (SelectedMetadata.Kind == MetadataType.MediaCube) {\r
-                    result.ArchiveMetadata.itemHouseId = PatternNameMaker.Get(result.ArchiveMetadata.itemHouseId, result.ID, result.InputFileName, null, null, result.MetadataText);\r
-                    result.ArchiveMetadata.itemTitle = PatternNameMaker.Get(result.ArchiveMetadata.itemTitle, result.ID, result.InputFileName, null, null, result.MetadataText);\r
-                    result.ArchiveMetadata.mediaHouseId = PatternNameMaker.Get(result.ArchiveMetadata.mediaHouseId, result.ID, result.InputFileName, null, null, result.MetadataText);\r
-                    result.ArchiveMetadata.mediaTitle = PatternNameMaker.Get(result.ArchiveMetadata.mediaTitle, result.ID, result.InputFileName, null, null, result.MetadataText);\r
+                    result.ArchiveMetadata.itemHouseId = PatternNameMaker.Get(result.ArchiveMetadata.itemHouseId, result.ID, null, result.InputFileName, null, null, result.MetadataText);\r
+                    result.ArchiveMetadata.itemTitle = PatternNameMaker.Get(result.ArchiveMetadata.itemTitle, result.ID, null, result.InputFileName, null, null, result.MetadataText);\r
+                    result.ArchiveMetadata.mediaHouseId = PatternNameMaker.Get(result.ArchiveMetadata.mediaHouseId, result.ID, null, result.InputFileName, null, null, result.MetadataText);\r
+                    result.ArchiveMetadata.mediaTitle = PatternNameMaker.Get(result.ArchiveMetadata.mediaTitle, result.ID, null, result.InputFileName, null, null, result.MetadataText);\r
                 }\r
             }\r
             return result;\r
         }\r
 \r
-        private List<CheckBox> HandleCheckBoxReferences(string[] reference, bool check) {\r
+        private bool HasCheckedReferer(CheckBox checkBox) {\r
+            var controlls = panelActions.Controls;\r
+            foreach (Control actual in controlls) {\r
+                if (actual is CheckBox actualCheckbox) {\r
+                    Target target = (Target)actualCheckbox.Tag;\r
+                    if (target.Reference!= null && target.Reference.Contains(checkBox.Text) && actualCheckbox.Checked)\r
+                        return true;\r
+                }\r
+            }\r
+            return false;\r
+        }\r
+\r
+        private void HandleCheckBoxReferences(string[] reference, bool check) {\r
             if (reference == null || reference.Length == 0)\r
-                return null;\r
+                return;\r
             var controlls = panelActions.Controls;\r
-            List<CheckBox> result = null;\r
             foreach (Control actual in controlls) {\r
                 if (actual is CheckBox actualCheckbox && reference.Contains(actualCheckbox.Text)) {\r
                     if (check) {\r
                         actualCheckbox.Checked = check;\r
-                        if (result == null)\r
-                            result = new List<CheckBox>();\r
-                        result.Add(actualCheckbox);\r
+                    } else {\r
+                        //minden parent unchecked?\r
+                        if (!HasCheckedReferer(actualCheckbox))\r
+                            actualCheckbox.Checked = check;\r
                     }\r
-                    actualCheckbox.Enabled = !check;\r
+\r
+                    Target target = (Target)actualCheckbox.Tag;\r
+                    if (!target.ReadOnly)\r
+                        actualCheckbox.Enabled = !check;\r
                 }\r
             }\r
-            return result;\r
         }\r
 \r
         private ISourceItem GetSourceItemFromBindingSource(string actual) {\r
@@ -267,8 +287,11 @@ namespace Maestro {
         private void ChangeProcessButtonsState(bool enabled) {\r
             if (panelActions.Controls == null)\r
                 return;\r
-            foreach (Control c in panelActions.Controls)\r
-                c.Enabled = enabled;\r
+            foreach (Control c in panelActions.Controls) {\r
+                Target target = (Target)c.Tag;\r
+                if (!target.ReadOnly)\r
+                    c.Enabled = enabled;\r
+            }\r
         }\r
 \r
         private void UpdateProcessorButtonsEnabled() {\r
index c29e19c2e0285b7d0b4424ec9de9ac181c7d3e4d..0d15fbc5e084fa8502fbd6b31c678af644df4866 100644 (file)
@@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
 // You can specify all the values or you can default the Build and Revision Numbers\r
 // by using the '*' as shown below:\r
 // [assembly: AssemblyVersion("1.0.*")]\r
-[assembly: AssemblyVersion("2.1.0.0")]\r
-[assembly: AssemblyFileVersion("2.1.0.0")]\r
+[assembly: AssemblyVersion("2.1.0.4")]\r
+[assembly: AssemblyFileVersion("2.1.0.4")]\r
index 08ac6228038ca9355d833b236bfd46f91d1977cc..45591277054148ed07aa92cbfb27d8c725006e49 100644 (file)
@@ -45,7 +45,7 @@ namespace Maestro {
                 }));\r
             });\r
             api = new MediaCubeWSApi(mediaCubeMetadata.WSServer, messageBus);\r
-            string pattern = PatternNameMaker.Get(mediaCubeMetadata.RestoreNamePattern, null, null, null, null, null, null);\r
+            string pattern = PatternNameMaker.Get(mediaCubeMetadata.RestoreNamePattern, null, null, null, null, null, null, null);\r
             JObject data = new JObject {\r
                 { "targetPath", mediaCubeMetadata.ServerRestoreFolder },\r
                 { "targetNamePattern", pattern },\r
index edd42ee90d747a31092d4c1077ee41f537a3e925..16e268a344538bd3e6dcc1b4841297b00d6f5fb3 100644 (file)
@@ -19,6 +19,7 @@ namespace MaestroShared.Commons {
         private const string PATTERN_IDROOT = "%IDROOT%";\r
         private const string PATTERN_GUID = "%GUID%";\r
         private const string PATTERN_SOURCENAME = "%SOURCENAME%";\r
+        private const string PATTERN_SOURCEFOLDERNAME = "%SOURCEFOLDERNAME%";\r
         private const string PATTERN_SOURCESTARTID = "%SOURCESTARTID%";\r
         private const string PATTERN_TIMESTAMP = "%TIMESTAMP%";\r
         private const string PATTERN_DATESTAMP = "%DATESTAMP%";\r
@@ -45,7 +46,7 @@ namespace MaestroShared.Commons {
             return result;\r
         }\r
 \r
-        static public string Get(string pattern, string id, string inputName, string outputName, string userName, string text, DateTime? storedDateTime = null, string json = null, string itemTitle = null, string mediaTitle = null, string format = null) {\r
+        static public string Get(string pattern, string id, string sourcePath, string inputName, string outputName, string userName, string text, DateTime? storedDateTime = null, string json = null, string itemTitle = null, string mediaTitle = null, string format = null) {\r
             if (pattern == null)\r
                 return null;\r
             string idRoot = id != null && id.Contains(UNDERSCORE) ? id.Split(UNDERSCORE[0])[0] : id;\r
@@ -72,6 +73,10 @@ namespace MaestroShared.Commons {
                     result = result.Replace(PATTERN_SOURCESTARTID, sourceStartID);\r
             }\r
 \r
+            if (!String.IsNullOrEmpty(sourcePath)) {\r
+                result = result.Replace(PATTERN_SOURCEFOLDERNAME, Normalize(Path.GetFileName(sourcePath)));\r
+            }\r
+\r
             if (!String.IsNullOrEmpty(outputName))\r
                 result = result.Replace(PATTERN_TARGETNAME, outputName);\r
 \r
index 825a4c8c6a598471adff3d8d849c38be66767bdb..800f58b449c855ad4e628cc508075f6d1faa5e84 100644 (file)
@@ -95,6 +95,7 @@ namespace MaestroShared.Configuration {
     }\r
 \r
     public class Target {\r
+        public bool ReadOnly { get; set; }\r
         public string Label { get; set; }\r
         public string Processor { get; set; }\r
         public string OutputFormat { get; set; }\r
@@ -125,6 +126,8 @@ namespace MaestroShared.Configuration {
         public string PopupMessage { get; set; }\r
         public string SourceNexioAgency { get; set; }\r
         public int SourceNexioKillDateDays { get; set; }\r
+        public bool MoveToFolder { get; set; }\r
+\r
     }\r
 \r
     public class Connection {\r
index f38916389a3b3d3f2f610e17768cb257504ed02e..5936c1a59e9edc85553fa107ffdf761bcd278ad1 100644 (file)
@@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
 // You can specify all the values or you can default the Build and Revision Numbers\r
 // by using the '*' as shown below:\r
 // [assembly: AssemblyVersion("1.0.*")]\r
-[assembly: AssemblyVersion("2.0.9.8")]\r
-[assembly: AssemblyFileVersion("2.0.9.8")]\r
+[assembly: AssemblyVersion("2.1.0.1")]\r
+[assembly: AssemblyFileVersion("2.1.0.1")]\r
index 932d3e059cbaafa49cc818e1264060de48297d9f..c772f970bbd853ef44348d033b07028930b43cd6 100644 (file)
@@ -8,6 +8,7 @@ using System.Collections.Generic;
 \r
 namespace MaestroShared.Target {\r
     public class TargetProcessorParameter {\r
+        public string SourcePath { get; set; }\r
         public string SourcePathOverride { get; set; }\r
         public Source SourceConfig { get; set; }\r
         public Configuration.Target TargetConfig { get; set; }\r
index b150fc17afb0ab0ed773151d32f29bf3155f9c25..39896b0d830aedf799bb2802295526e2c83f3916 100644 (file)
@@ -40,6 +40,7 @@ namespace MaestroShared.Targets {
         private const string STAR = "*";\r
         protected FileInfo inputFile;\r
         protected string workingDir;\r
+        private string sourcePath;\r
 \r
         public WorkflowAction workFlowAction { get; set; }\r
 \r
@@ -47,10 +48,15 @@ namespace MaestroShared.Targets {
             logger.Trace(Strings.ENTRY);\r
             base.Initialize(parent, parameters);\r
             InputName = parameters.InputFileName;\r
-            if (!String.IsNullOrEmpty(parameters.SourcePathOverride))\r
+            sourcePath = parameters.SourcePath;\r
+            if (!string.IsNullOrEmpty(parameters.SourcePathOverride))\r
                 Input = Path.Combine(parameters.SourcePathOverride, parameters.InputFileName);\r
-            else\r
-                Input = Path.Combine(parameters.SourceConfig.Local.Address.LocalPath, parameters.InputFileName);\r
+            else { \r
+                if (parameters.SourcePath == null)\r
+                   Input = Path.Combine(parameters.SourceConfig.Local.Address.LocalPath, parameters.InputFileName);\r
+                else\r
+                   Input = Path.Combine(parameters.SourcePath, parameters.InputFileName);\r
+            }\r
             inputFile = new FileInfo(Input);\r
             ID = parameters.ID;\r
             workFlowAction = new WorkflowAction() {\r
@@ -73,8 +79,9 @@ namespace MaestroShared.Targets {
             if (Parameters.TargetConfig.SaveSegments && Parameters.MovieSegments != null) {\r
                 string fileName = Parameters?.TrafficApi?.SaveSegments(Parameters.VariantID, Parameters.MetadataKind, Parameters.MovieSegments, Parameters.SelectedSegments);\r
                 if (true.Equals(Parameters?.TrafficMetadata?.MultiSegmentEnabled)) {\r
-                    //a Traffic adja a nevet\r
-                    OutputName = fileName ?? throw new Exception("A fájlnév nem lehet üres.");\r
+\r
+                    OutputName = fileName + inputFile.Extension;\r
+\r
                 }\r
             }\r
         }\r
@@ -84,7 +91,7 @@ namespace MaestroShared.Targets {
             bool result = false;\r
             try {\r
                 BeforeExecute();\r
-                workingDir = DetermineWorkingDirectory(Parameters.TargetConfig.Remote);\r
+                workingDir = DetermineWorkingDirectory(Parameters.TargetConfig);\r
                 EnsureDirectoryExistence(workingDir);\r
 \r
                 //multiszegmens mukodes eseten a filenevet a traffic generalja\r
@@ -103,7 +110,7 @@ namespace MaestroShared.Targets {
                     UploadFile();\r
                     ValidateTransfer();\r
                     //logger.Info("Spend (s):" + (DateTime.Now - started).TotalSeconds);\r
-                    if (Parameters.TargetConfig.DeleteAfterCopy)\r
+                    if (Parameters.TargetConfig.DeleteAfterCopy || Parameters.TargetConfig.MoveToFolder)\r
                         DeleteAfterCopy();\r
                     ExecuteCompleted();\r
 \r
@@ -217,6 +224,7 @@ namespace MaestroShared.Targets {
             }\r
             if (Parameters.TargetConfig.SendEmailOnSuccess && !String.IsNullOrEmpty(Parameters.TargetConfig.SuccessEmailRecipient) && !String.IsNullOrEmpty(Parameters.TargetConfig.SuccessEmailPattern))\r
                 SendEmail(Parameters.TargetConfig.SuccessEmailRecipient, Parameters.TargetConfig.SuccessEmailSubject, Parameters.TargetConfig.SuccessEmailPattern);\r
+\r
             logger.Trace(Strings.EXIT);\r
         }\r
 \r
@@ -319,9 +327,8 @@ namespace MaestroShared.Targets {
             return path;\r
         }\r
 \r
-        protected string DetermineWorkingDirectory(Connection connection) {\r
-            logger.Trace(Strings.ENTRY);\r
-            string result = Slash(connection.Address.LocalPath);\r
+        private string ApplyDynamicOutput(string outputPath) {\r
+            string result = outputPath;\r
             if (String.IsNullOrEmpty(Parameters.TargetConfig.SubFolderFormat))\r
                 return result;\r
             string subFolderName = Slash(GetDynamicText(Parameters.TargetConfig.SubFolderFormat));\r
@@ -340,7 +347,19 @@ namespace MaestroShared.Targets {
                 else\r
                     result = Slash(Path.Combine(result, searchResult));\r
             }\r
+            return result;\r
+        }\r
 \r
+        protected string DetermineWorkingDirectory(Configuration.Target target) {\r
+            logger.Trace(Strings.ENTRY);\r
+            string result = null;\r
+            Connection connection = target.Remote;\r
+            if (target.MoveToFolder) {\r
+                result = sourcePath;\r
+            } else {\r
+                result = Slash(connection.Address.LocalPath);\r
+            }\r
+            result = ApplyDynamicOutput(result);\r
             logger.Trace(Strings.EXIT);\r
             return result;\r
         }\r
@@ -422,7 +441,7 @@ namespace MaestroShared.Targets {
         }\r
 \r
         protected string GetDynamicText(string pattern) {\r
-            return PatternNameMaker.Get(pattern, ID, InputName, Output, Parameters.UserName, Parameters.MetadataText, Parameters.CreateDate, Parameters.ArchiveMetadata?.ToString(), Parameters.ArchiveMetadata?.itemTitle, Parameters.ArchiveMetadata?.mediaTitle, Parameters.ArchiveMetadata?.format);\r
+            return PatternNameMaker.Get(pattern, ID, sourcePath, InputName, Output, Parameters.UserName, Parameters.MetadataText, Parameters.CreateDate, Parameters.ArchiveMetadata?.ToString(), Parameters.ArchiveMetadata?.itemTitle, Parameters.ArchiveMetadata?.mediaTitle, Parameters.ArchiveMetadata?.format);\r
         }\r
 \r
         private string CreateOutputFileName() {\r
index 1be0d20ae4db151234852e25609bba356556c7fa..dbc3d3bc4b26c75f5735dfa14dce7bbecda3a1ef 100644 (file)
@@ -55,7 +55,7 @@
                        <AppenderRef ref="MarkeredConsole" />\r
 <!--                   <AppenderRef ref="RollingFile" /> -->\r
                        <AppenderRef ref="MarkeredMail" />\r
-                       <AppenderRef ref="MarkeredDB2" />\r
+<!--                   <AppenderRef ref="MarkeredDB2" /> -->\r
                </Root>\r
                <Logger name="org.zkoss" level="trace" additivity="false" />\r
                <Logger name="org.quartz" level="ERROR" additivity="false" />\r
diff --git a/server/-modules/MediaCube.iml b/server/-modules/MediaCube.iml
new file mode 100644 (file)
index 0000000..36ec37c
--- /dev/null
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">\r
+  <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">\r
+    <output url="file://$MODULE_DIR$/target/classes" />\r
+    <output-test url="file://$MODULE_DIR$/target/test-classes" />\r
+    <content url="file://$MODULE_DIR$">\r
+      <excludeFolder url="file://$MODULE_DIR$/target" />\r
+    </content>\r
+    <orderEntry type="inheritedJdk" />\r
+    <orderEntry type="sourceFolder" forTests="false" />\r
+  </component>\r
+</module>
\ No newline at end of file
diff --git a/server/-product/user.jobengine.product.iml b/server/-product/user.jobengine.product.iml
new file mode 100644 (file)
index 0000000..36ec37c
--- /dev/null
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">\r
+  <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">\r
+    <output url="file://$MODULE_DIR$/target/classes" />\r
+    <output-test url="file://$MODULE_DIR$/target/test-classes" />\r
+    <content url="file://$MODULE_DIR$">\r
+      <excludeFolder url="file://$MODULE_DIR$/target" />\r
+    </content>\r
+    <orderEntry type="inheritedJdk" />\r
+    <orderEntry type="sourceFolder" forTests="false" />\r
+  </component>\r
+</module>
\ No newline at end of file
index 2fa649d6fc2dc29a53c9d747753ad722a78b9358..bba0565e3b7ed8134f8eab8c4e1940d24a73dc67 100644 (file)
@@ -6,4 +6,5 @@ Bundle-Version: 1.0.0.qualifier
 Fragment-Host: user.jobengine.executors;bundle-version="1.0.0"
 Bundle-RequiredExecutionEnvironment: JavaSE-1.8
 Import-Package: org.apache.commons.io.filefilter;version="2.2.0",
+ org.apache.commons.io.output;version="2.2.0",
  org.junit
index 44c216d58b185da8296b9aa0d72670a65bc3c5d1..6c083e0c8fe24ac1e822602bfcbda1e0d2028897 100644 (file)
@@ -75,7 +75,7 @@ public class HSMMigrateStepTest {
        public void testResumableCopy() throws Exception {\r
                HSMMigrateStep sut = new HSMMigrateStep();\r
                Paths.get("c:/_video/03c.mp4").toFile().delete();\r
-               sut.copyChunk(Paths.get("c:/_video/1.txt"), Paths.get("c:/_video/2.txt"), 5);\r
+               //sut.copyChunk(Paths.get("c:/_video/1.txt"), Paths.get("c:/_video/2.txt"), 5);\r
                sut.resumeableCopy(Paths.get("c:/_video/1.txt"), Paths.get("c:/_video/2.txt"));\r
        }\r
 }\r
diff --git a/server/hu.user.mediacube.executors.tests/src/hu/user/mediacube/executors/tests/MediaBaseTest.java b/server/hu.user.mediacube.executors.tests/src/hu/user/mediacube/executors/tests/MediaBaseTest.java
new file mode 100644 (file)
index 0000000..5d5debb
--- /dev/null
@@ -0,0 +1,53 @@
+package hu.user.mediacube.executors.tests;\r
+\r
+import java.util.List;\r
+\r
+import org.apache.commons.io.FilenameUtils;\r
+import org.apache.commons.io.output.ByteArrayOutputStream;\r
+import org.apache.commons.net.ftp.FTPClient;\r
+import org.junit.Test;\r
+\r
+import user.commons.RemoteFile;\r
+import user.commons.StoreUri;\r
+import user.commons.remotestore.FtpDirectoryLister;\r
+import user.commons.remotestore.IDirectoryLister;\r
+import user.commons.remotestore.RemoteStoreProtocol;\r
+\r
+public class MediaBaseTest {\r
+\r
+       @Test\r
+       public void listMediaBase() throws Exception {\r
+               StoreUri nexioUri = new StoreUri();\r
+               nexioUri.setProtocol(RemoteStoreProtocol.FTP);\r
+               nexioUri.setUri("10.10.1.55");\r
+               nexioUri.setPortNumber(2098);\r
+               nexioUri.setUserName("ftp");\r
+               nexioUri.setPassword("ftp");\r
+               try {\r
+                       FTPClient ftp = ((FtpDirectoryLister) nexioUri.getLister()).connect();\r
+                       IDirectoryLister lister = nexioUri.getLister();\r
+                       List<RemoteFile> list = lister.list();\r
+                       for (RemoteFile rf : list) {\r
+                               if (rf.getIsFolder())\r
+                                       continue;\r
+                               String baseName = FilenameUtils.getBaseName(rf.getName());\r
+\r
+                               try (ByteArrayOutputStream output = new ByteArrayOutputStream()) {\r
+                                       ftp.retrieveFile(baseName + ".xml", output);\r
+                                       byte[] targetArray = output.toByteArray();\r
+                                       System.out.println(rf.getName() + " " + targetArray.length);\r
+                                       //Thread.sleep(100);\r
+                               } catch (Exception ie) {\r
+                                       System.err.println(ie.getMessage());\r
+                               }\r
+                       }\r
+\r
+               } catch (Exception e) {\r
+                       System.err.println(e.getMessage());\r
+               } finally {\r
+                       nexioUri.cleanUp();\r
+               }\r
+\r
+       }\r
+\r
+}\r
diff --git a/server/hu.user.mediacube.indexer/indexer (1).iml b/server/hu.user.mediacube.indexer/indexer (1).iml
new file mode 100644 (file)
index 0000000..a643bf9
--- /dev/null
@@ -0,0 +1,136 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<module type="JAVA_MODULE" version="4">\r
+  <component name="MavenCustomPomFilePath">\r
+    <option name="mavenPomFileUrl" value="file://$MODULE_DIR$/dependency-reduced-pom.xml" />\r
+  </component>\r
+  <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">\r
+    <output url="file://$MODULE_DIR$/target/classes" />\r
+    <output-test url="file://$MODULE_DIR$/target/test-classes" />\r
+    <content url="file://$MODULE_DIR$/src/main/java">\r
+      <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />\r
+    </content>\r
+    <content url="file://$MODULE_DIR$/src/main/resources">\r
+      <sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />\r
+    </content>\r
+    <content url="file://$MODULE_DIR$/src/test/java">\r
+      <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />\r
+    </content>\r
+    <content url="file://$MODULE_DIR$/src/test/resources">\r
+      <sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />\r
+    </content>\r
+    <orderEntry type="inheritedJdk" />\r
+    <orderEntry type="sourceFolder" forTests="false" />\r
+    <orderEntry type="library" name="Maven: org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.0" level="project" />\r
+    <orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter:2.1.6.RELEASE" level="project" />\r
+    <orderEntry type="library" name="Maven: org.springframework.boot:spring-boot:2.1.6.RELEASE" level="project" />\r
+    <orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-autoconfigure:2.1.6.RELEASE" level="project" />\r
+    <orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-logging:2.1.6.RELEASE" level="project" />\r
+    <orderEntry type="library" name="Maven: ch.qos.logback:logback-classic:1.2.3" level="project" />\r
+    <orderEntry type="library" name="Maven: ch.qos.logback:logback-core:1.2.3" level="project" />\r
+    <orderEntry type="library" name="Maven: org.apache.logging.log4j:log4j-to-slf4j:2.11.2" level="project" />\r
+    <orderEntry type="library" name="Maven: org.apache.logging.log4j:log4j-api:2.11.2" level="project" />\r
+    <orderEntry type="library" name="Maven: org.slf4j:jul-to-slf4j:1.7.26" level="project" />\r
+    <orderEntry type="library" name="Maven: javax.annotation:javax.annotation-api:1.3.2" level="project" />\r
+    <orderEntry type="library" scope="RUNTIME" name="Maven: org.yaml:snakeyaml:1.23" level="project" />\r
+    <orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-jdbc:2.1.6.RELEASE" level="project" />\r
+    <orderEntry type="library" name="Maven: com.zaxxer:HikariCP:3.2.0" level="project" />\r
+    <orderEntry type="library" name="Maven: org.springframework:spring-jdbc:5.1.8.RELEASE" level="project" />\r
+    <orderEntry type="library" name="Maven: org.springframework:spring-tx:5.1.8.RELEASE" level="project" />\r
+    <orderEntry type="library" name="Maven: org.mybatis.spring.boot:mybatis-spring-boot-autoconfigure:2.1.0" level="project" />\r
+    <orderEntry type="library" name="Maven: org.mybatis:mybatis-spring:2.0.2" level="project" />\r
+    <orderEntry type="library" name="Maven: org.mybatis:mybatis:3.5.2" level="project" />\r
+    <orderEntry type="library" name="Maven: com.ibm:db2jcc4:4.19.26" level="project" />\r
+    <orderEntry type="library" name="Maven: org.apache.lucene:lucene-core:8.2.0" level="project" />\r
+    <orderEntry type="library" name="Maven: org.apache.lucene:lucene-highlighter:8.2.0" level="project" />\r
+    <orderEntry type="library" name="Maven: org.apache.lucene:lucene-memory:8.2.0" level="project" />\r
+    <orderEntry type="library" name="Maven: org.apache.lucene:lucene-queries:8.2.0" level="project" />\r
+    <orderEntry type="library" name="Maven: org.apache.lucene:lucene-misc:8.2.0" level="project" />\r
+    <orderEntry type="library" name="Maven: org.apache.lucene:lucene-analyzers:3.6.2" level="project" />\r
+    <orderEntry type="library" name="Maven: org.apache.lucene:lucene-queryparser:8.2.0" level="project" />\r
+    <orderEntry type="library" name="Maven: org.apache.lucene:lucene-sandbox:8.2.0" level="project" />\r
+    <orderEntry type="library" name="Maven: org.apache.solr:solr-solrj:8.2.0" level="project" />\r
+    <orderEntry type="library" name="Maven: commons-io:commons-io:2.5" level="project" />\r
+    <orderEntry type="library" name="Maven: org.apache.commons:commons-math3:3.6.1" level="project" />\r
+    <orderEntry type="library" name="Maven: org.apache.httpcomponents:httpclient:4.5.9" level="project" />\r
+    <orderEntry type="library" name="Maven: org.apache.httpcomponents:httpcore:4.4.11" level="project" />\r
+    <orderEntry type="library" name="Maven: org.apache.httpcomponents:httpmime:4.5.9" level="project" />\r
+    <orderEntry type="library" name="Maven: org.apache.zookeeper:zookeeper:3.5.5" level="project" />\r
+    <orderEntry type="library" name="Maven: org.apache.zookeeper:zookeeper-jute:3.5.5" level="project" />\r
+    <orderEntry type="library" name="Maven: org.codehaus.woodstox:stax2-api:3.1.4" level="project" />\r
+    <orderEntry type="library" name="Maven: org.codehaus.woodstox:woodstox-core-asl:4.4.1" level="project" />\r
+    <orderEntry type="library" name="Maven: org.eclipse.jetty:jetty-alpn-client:9.4.19.v20190610" level="project" />\r
+    <orderEntry type="library" name="Maven: org.eclipse.jetty:jetty-alpn-java-client:9.4.19.v20190610" level="project" />\r
+    <orderEntry type="library" name="Maven: org.eclipse.jetty:jetty-client:9.4.19.v20190610" level="project" />\r
+    <orderEntry type="library" name="Maven: org.eclipse.jetty:jetty-http:9.4.19.v20190610" level="project" />\r
+    <orderEntry type="library" name="Maven: org.eclipse.jetty:jetty-io:9.4.19.v20190610" level="project" />\r
+    <orderEntry type="library" name="Maven: org.eclipse.jetty:jetty-util:9.4.19.v20190610" level="project" />\r
+    <orderEntry type="library" name="Maven: org.eclipse.jetty.http2:http2-client:9.4.19.v20190610" level="project" />\r
+    <orderEntry type="library" name="Maven: org.eclipse.jetty.http2:http2-common:9.4.19.v20190610" level="project" />\r
+    <orderEntry type="library" name="Maven: org.eclipse.jetty.http2:http2-hpack:9.4.19.v20190610" level="project" />\r
+    <orderEntry type="library" name="Maven: org.eclipse.jetty.http2:http2-http-client-transport:9.4.19.v20190610" level="project" />\r
+    <orderEntry type="library" name="Maven: org.slf4j:jcl-over-slf4j:1.7.26" level="project" />\r
+    <orderEntry type="library" name="Maven: org.slf4j:slf4j-api:1.7.26" level="project" />\r
+    <orderEntry type="library" name="Maven: org.springframework:spring-web:5.1.8.RELEASE" level="project" />\r
+    <orderEntry type="library" name="Maven: org.springframework:spring-beans:5.1.8.RELEASE" level="project" />\r
+    <orderEntry type="library" name="Maven: org.springframework:spring-core:5.1.8.RELEASE" level="project" />\r
+    <orderEntry type="library" name="Maven: org.springframework:spring-jcl:5.1.8.RELEASE" level="project" />\r
+    <orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-undertow:2.1.6.RELEASE" level="project" />\r
+    <orderEntry type="library" name="Maven: io.undertow:undertow-core:2.0.21.Final" level="project" />\r
+    <orderEntry type="library" name="Maven: org.jboss.logging:jboss-logging:3.3.2.Final" level="project" />\r
+    <orderEntry type="library" name="Maven: org.jboss.xnio:xnio-api:3.3.8.Final" level="project" />\r
+    <orderEntry type="library" scope="RUNTIME" name="Maven: org.jboss.xnio:xnio-nio:3.3.8.Final" level="project" />\r
+    <orderEntry type="library" name="Maven: io.undertow:undertow-servlet:2.0.21.Final" level="project" />\r
+    <orderEntry type="library" name="Maven: org.jboss.spec.javax.annotation:jboss-annotations-api_1.2_spec:1.0.2.Final" level="project" />\r
+    <orderEntry type="library" name="Maven: io.undertow:undertow-websockets-jsr:2.0.21.Final" level="project" />\r
+    <orderEntry type="library" name="Maven: org.jboss.spec.javax.websocket:jboss-websocket-api_1.1_spec:1.1.4.Final" level="project" />\r
+    <orderEntry type="library" name="Maven: org.glassfish:javax.el:3.0.0" level="project" />\r
+    <orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-web:2.1.6.RELEASE" level="project" />\r
+    <orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-json:2.1.6.RELEASE" level="project" />\r
+    <orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-databind:2.9.9" level="project" />\r
+    <orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-annotations:2.9.0" level="project" />\r
+    <orderEntry type="library" name="Maven: com.fasterxml.jackson.core:jackson-core:2.9.9" level="project" />\r
+    <orderEntry type="library" name="Maven: com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.9.9" level="project" />\r
+    <orderEntry type="library" name="Maven: com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.9" level="project" />\r
+    <orderEntry type="library" name="Maven: com.fasterxml.jackson.module:jackson-module-parameter-names:2.9.9" level="project" />\r
+    <orderEntry type="library" name="Maven: org.hibernate.validator:hibernate-validator:6.0.17.Final" level="project" />\r
+    <orderEntry type="library" name="Maven: javax.validation:validation-api:2.0.1.Final" level="project" />\r
+    <orderEntry type="library" name="Maven: com.fasterxml:classmate:1.4.0" level="project" />\r
+    <orderEntry type="library" name="Maven: org.springframework:spring-webmvc:5.1.8.RELEASE" level="project" />\r
+    <orderEntry type="library" name="Maven: org.springframework:spring-aop:5.1.8.RELEASE" level="project" />\r
+    <orderEntry type="library" name="Maven: org.springframework:spring-context:5.1.8.RELEASE" level="project" />\r
+    <orderEntry type="library" name="Maven: org.springframework:spring-expression:5.1.8.RELEASE" level="project" />\r
+    <orderEntry type="library" name="Maven: org.springframework.boot:spring-boot-starter-thymeleaf:2.1.6.RELEASE" level="project" />\r
+    <orderEntry type="library" name="Maven: org.thymeleaf:thymeleaf-spring5:3.0.11.RELEASE" level="project" />\r
+    <orderEntry type="library" name="Maven: org.thymeleaf:thymeleaf:3.0.11.RELEASE" level="project" />\r
+    <orderEntry type="library" name="Maven: org.attoparser:attoparser:2.0.5.RELEASE" level="project" />\r
+    <orderEntry type="library" name="Maven: org.unbescape:unbescape:1.1.6.RELEASE" level="project" />\r
+    <orderEntry type="library" name="Maven: org.thymeleaf.extras:thymeleaf-extras-java8time:3.0.4.RELEASE" level="project" />\r
+    <orderEntry type="library" name="Maven: javax.servlet:javax.servlet-api:4.0.0" level="project" />\r
+    <orderEntry type="library" scope="TEST" name="Maven: junit:junit:4.12" level="project" />\r
+    <orderEntry type="library" scope="TEST" name="Maven: org.hamcrest:hamcrest-core:1.3" level="project" />\r
+    <orderEntry type="library" scope="TEST" name="Maven: org.springframework:spring-test:5.1.8.RELEASE" level="project" />\r
+    <orderEntry type="library" scope="TEST" name="Maven: org.springframework.boot:spring-boot-starter-test:2.1.8.RELEASE" level="project" />\r
+    <orderEntry type="library" scope="TEST" name="Maven: org.springframework.boot:spring-boot-test:2.1.6.RELEASE" level="project" />\r
+    <orderEntry type="library" scope="TEST" name="Maven: org.springframework.boot:spring-boot-test-autoconfigure:2.1.6.RELEASE" level="project" />\r
+    <orderEntry type="library" scope="TEST" name="Maven: com.jayway.jsonpath:json-path:2.4.0" level="project" />\r
+    <orderEntry type="library" scope="TEST" name="Maven: net.minidev:json-smart:2.3" level="project" />\r
+    <orderEntry type="library" scope="TEST" name="Maven: net.minidev:accessors-smart:1.2" level="project" />\r
+    <orderEntry type="library" scope="TEST" name="Maven: org.ow2.asm:asm:5.0.4" level="project" />\r
+    <orderEntry type="library" scope="TEST" name="Maven: org.assertj:assertj-core:3.11.1" level="project" />\r
+    <orderEntry type="library" scope="TEST" name="Maven: org.mockito:mockito-core:2.23.4" level="project" />\r
+    <orderEntry type="library" scope="TEST" name="Maven: net.bytebuddy:byte-buddy:1.9.13" level="project" />\r
+    <orderEntry type="library" scope="TEST" name="Maven: net.bytebuddy:byte-buddy-agent:1.9.13" level="project" />\r
+    <orderEntry type="library" scope="TEST" name="Maven: org.objenesis:objenesis:2.6" level="project" />\r
+    <orderEntry type="library" scope="TEST" name="Maven: org.hamcrest:hamcrest-library:1.3" level="project" />\r
+    <orderEntry type="library" scope="TEST" name="Maven: org.skyscreamer:jsonassert:1.5.0" level="project" />\r
+    <orderEntry type="library" scope="TEST" name="Maven: com.vaadin.external.google:android-json:0.0.20131108.vaadin1" level="project" />\r
+    <orderEntry type="library" scope="TEST" name="Maven: org.xmlunit:xmlunit-core:2.6.2" level="project" />\r
+    <orderEntry type="library" scope="TEST" name="Maven: javax.xml.bind:jaxb-api:2.3.1" level="project" />\r
+    <orderEntry type="library" scope="TEST" name="Maven: javax.activation:javax.activation-api:1.2.0" level="project" />\r
+    <orderEntry type="library" name="Maven: org.jboss.resteasy:resteasy-jackson-provider:3.0.11.Final" level="project" />\r
+    <orderEntry type="library" name="Maven: org.codehaus.jackson:jackson-core-asl:1.9.12" level="project" />\r
+    <orderEntry type="library" name="Maven: org.codehaus.jackson:jackson-mapper-asl:1.9.12" level="project" />\r
+    <orderEntry type="library" name="Maven: org.codehaus.jackson:jackson-jaxrs:1.9.12" level="project" />\r
+    <orderEntry type="library" name="Maven: org.codehaus.jackson:jackson-xc:1.9.12" level="project" />\r
+  </component>\r
+</module>
\ No newline at end of file
diff --git a/server/hu.user.mediacube.indexer/indexer (2).iml b/server/hu.user.mediacube.indexer/indexer (2).iml
new file mode 100644 (file)
index 0000000..7531122
--- /dev/null
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<module type="JAVA_MODULE" version="4">\r
+  <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">\r
+    <output url="file://$MODULE_DIR$/target/classes" />\r
+    <output-test url="file://$MODULE_DIR$/target/test-classes" />\r
+    <content url="file://$MODULE_DIR$">\r
+      <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />\r
+      <sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />\r
+      <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />\r
+      <sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />\r
+      <excludeFolder url="file://$MODULE_DIR$/target" />\r
+    </content>\r
+    <orderEntry type="inheritedJdk" />\r
+    <orderEntry type="sourceFolder" forTests="false" />\r
+  </component>\r
+</module>
\ No newline at end of file
diff --git a/server/hu.user.mediacube.indexer/indexer.iml b/server/hu.user.mediacube.indexer/indexer.iml
new file mode 100644 (file)
index 0000000..3b8b11c
--- /dev/null
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">\r
+  <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">\r
+    <output url="file://$MODULE_DIR$/target/classes" />\r
+    <output-test url="file://$MODULE_DIR$/target/test-classes" />\r
+    <content url="file://$MODULE_DIR$">\r
+      <sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />\r
+      <sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />\r
+      <sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />\r
+      <sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />\r
+      <excludeFolder url="file://$MODULE_DIR$/target" />\r
+    </content>\r
+    <orderEntry type="inheritedJdk" />\r
+    <orderEntry type="sourceFolder" forTests="false" />\r
+  </component>\r
+</module>
\ No newline at end of file
diff --git a/server/test/.classpath b/server/test/.classpath
new file mode 100644 (file)
index 0000000..63b7e89
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<classpath>\r
+       <classpathentry kind="src" path="src"/>\r
+       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>\r
+       <classpathentry kind="output" path="bin"/>\r
+</classpath>\r
diff --git a/server/test/.project b/server/test/.project
new file mode 100644 (file)
index 0000000..c8a61fe
--- /dev/null
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<projectDescription>\r
+       <name>test</name>\r
+       <comment></comment>\r
+       <projects>\r
+       </projects>\r
+       <buildSpec>\r
+               <buildCommand>\r
+                       <name>org.eclipse.jdt.core.javabuilder</name>\r
+                       <arguments>\r
+                       </arguments>\r
+               </buildCommand>\r
+       </buildSpec>\r
+       <natures>\r
+               <nature>org.eclipse.jdt.core.javanature</nature>\r
+       </natures>\r
+</projectDescription>\r
diff --git a/server/test/.settings/org.eclipse.jdt.core.prefs b/server/test/.settings/org.eclipse.jdt.core.prefs
new file mode 100644 (file)
index 0000000..bb35fa0
--- /dev/null
@@ -0,0 +1,11 @@
+eclipse.preferences.version=1\r
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled\r
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8\r
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve\r
+org.eclipse.jdt.core.compiler.compliance=1.8\r
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate\r
+org.eclipse.jdt.core.compiler.debug.localVariable=generate\r
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate\r
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error\r
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error\r
+org.eclipse.jdt.core.compiler.source=1.8\r
diff --git a/server/test/src/Echo.java b/server/test/src/Echo.java
new file mode 100644 (file)
index 0000000..2d12d02
--- /dev/null
@@ -0,0 +1,11 @@
+\r
+import java.io.IOException;\r
+\r
+public class Echo {\r
+\r
+       public static void main(String[] args) throws IOException {\r
+               System.out.println("Hello boot. Press enter!");\r
+               System.in.read();\r
+       }\r
+\r
+}\r
diff --git a/server/user.commons.log4j2/user.commons.log4j2.iml b/server/user.commons.log4j2/user.commons.log4j2.iml
new file mode 100644 (file)
index 0000000..ebab1e5
--- /dev/null
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">\r
+  <component name="FacetManager">\r
+    <facet type="Osmorc" name="OSGi">\r
+      <configuration manifestGenerationMode="Manually" manifestLocation="META-INF/MANIFEST.MF" jarfileLocation="user.commons.log4j2.jar" outputPathType="CompilerOutputPath" bndFileLocation="" bundlorFileLocation="" bundleActivator="" bundleSymbolicName="" bundleVersion="1.0.0" ignoreFilePattern="" useProjectDefaultManifestFileLocation="false" alwaysRebuildBundleJAR="false" doNotSynchronizeWithMaven="false">\r
+        <additionalProperties />\r
+        <additionalJARContents />\r
+      </configuration>\r
+    </facet>\r
+  </component>\r
+  <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">\r
+    <output url="file://$MODULE_DIR$/target/classes" />\r
+    <output-test url="file://$MODULE_DIR$/target/test-classes" />\r
+    <content url="file://$MODULE_DIR$">\r
+      <excludeFolder url="file://$MODULE_DIR$/target" />\r
+    </content>\r
+    <orderEntry type="inheritedJdk" />\r
+    <orderEntry type="sourceFolder" forTests="false" />\r
+  </component>\r
+</module>
\ No newline at end of file
index ed6fc097e5967f9ce8e2fead871e3c60dea71240..6c8f3cf526bdc01d9dc082785d3feee1b5b22451 100644 (file)
@@ -66,33 +66,6 @@ public class HSMMigrateStep extends JobStep {
                volumeHistory.drop();\r
        }\r
 \r
-       public void copyChunk(Path source, Path target, long chunk) throws IOException {\r
-\r
-               File sourceFile = source.toFile();\r
-               File targetFile = target.toFile();\r
-\r
-               try (InputStream in = new BufferedInputStream(new FileInputStream(sourceFile));\r
-                               OutputStream out = new BufferedOutputStream(new FileOutputStream(targetFile))) {\r
-\r
-                       byte[] buffer = new byte[1];\r
-                       long copied = 0;\r
-                       int lengthRead;\r
-                       while ((lengthRead = in.read(buffer)) > 0) {\r
-                               out.write(buffer, 0, lengthRead);\r
-                               out.flush();\r
-\r
-                               copied += lengthRead;\r
-                               if (copied > chunk) {\r
-                                       out.close();\r
-                                       in.close();\r
-                                       return;\r
-                               }\r
-\r
-                       }\r
-               }\r
-\r
-       }\r
-\r
        private BasicDBObject createMetadata(String volumeName, String fileName) throws Exception {\r
 \r
                Path filePath = Paths.get(fileName);\r
@@ -297,6 +270,8 @@ public class HSMMigrateStep extends JobStep {
                                        repeat = 0;\r
                                        successCopy = true;\r
                                } catch (Exception e) {\r
+                                       if (Files.exists(targetFilePath) && targetFilePath.toFile().length() == 0)\r
+                                               Files.delete(targetFilePath);\r
                                        //logger.warn(marker, "Hiba a másolás során: {} ({})", sourceFilePath, e.getMessage());\r
                                        repeat--;\r
                                }\r
@@ -363,7 +338,7 @@ public class HSMMigrateStep extends JobStep {
                try (InputStream in = new BufferedInputStream(new FileInputStream(sourceFile));\r
                                OutputStream out = new BufferedOutputStream(new FileOutputStream(targetFile, targetExists))) {\r
 \r
-                       byte[] buffer = new byte[256 * 1024 * 4 * 100];\r
+                       byte[] buffer = new byte[128 * 1024];\r
                        int lengthRead;\r
 \r
                        if (targetExists)\r
diff --git a/server/user.jobengine.executors/user.jobengine.executors.iml b/server/user.jobengine.executors/user.jobengine.executors.iml
new file mode 100644 (file)
index 0000000..4aa9e80
--- /dev/null
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">\r
+  <component name="FacetManager">\r
+    <facet type="Osmorc" name="OSGi">\r
+      <configuration manifestGenerationMode="Manually" manifestLocation="META-INF/MANIFEST.MF" jarfileLocation="user.jobengine.executors.jar" outputPathType="CompilerOutputPath" bndFileLocation="" bundlorFileLocation="" bundleActivator="" bundleSymbolicName="" bundleVersion="1.0.0" ignoreFilePattern="" useProjectDefaultManifestFileLocation="false" alwaysRebuildBundleJAR="false" doNotSynchronizeWithMaven="false">\r
+        <additionalProperties />\r
+        <additionalJARContents />\r
+      </configuration>\r
+    </facet>\r
+  </component>\r
+  <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">\r
+    <output url="file://$MODULE_DIR$/target/classes" />\r
+    <output-test url="file://$MODULE_DIR$/target/test-classes" />\r
+    <content url="file://$MODULE_DIR$">\r
+      <excludeFolder url="file://$MODULE_DIR$/target" />\r
+    </content>\r
+    <orderEntry type="inheritedJdk" />\r
+    <orderEntry type="sourceFolder" forTests="false" />\r
+  </component>\r
+</module>
\ No newline at end of file
diff --git a/server/user.jobengine.osgi.commons/user.jobengine.osgi.commons.iml b/server/user.jobengine.osgi.commons/user.jobengine.osgi.commons.iml
new file mode 100644 (file)
index 0000000..282fc1a
--- /dev/null
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">\r
+  <component name="FacetManager">\r
+    <facet type="Osmorc" name="OSGi">\r
+      <configuration manifestGenerationMode="Manually" manifestLocation="META-INF/MANIFEST.MF" jarfileLocation="user.jobengine.osgi.commons.jar" outputPathType="CompilerOutputPath" bndFileLocation="" bundlorFileLocation="" bundleActivator="" bundleSymbolicName="" bundleVersion="1.0.0" ignoreFilePattern="" useProjectDefaultManifestFileLocation="false" alwaysRebuildBundleJAR="false" doNotSynchronizeWithMaven="false">\r
+        <additionalProperties />\r
+        <additionalJARContents />\r
+      </configuration>\r
+    </facet>\r
+  </component>\r
+  <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">\r
+    <output url="file://$MODULE_DIR$/target/classes" />\r
+    <output-test url="file://$MODULE_DIR$/target/test-classes" />\r
+    <content url="file://$MODULE_DIR$">\r
+      <excludeFolder url="file://$MODULE_DIR$/target" />\r
+    </content>\r
+    <orderEntry type="inheritedJdk" />\r
+    <orderEntry type="sourceFolder" forTests="false" />\r
+  </component>\r
+</module>
\ No newline at end of file
diff --git a/server/user.jobengine.osgi.db/user.jobengine.osgi.db.iml b/server/user.jobengine.osgi.db/user.jobengine.osgi.db.iml
new file mode 100644 (file)
index 0000000..cf7fe4b
--- /dev/null
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">\r
+  <component name="FacetManager">\r
+    <facet type="Osmorc" name="OSGi">\r
+      <configuration manifestGenerationMode="Manually" manifestLocation="META-INF/MANIFEST.MF" jarfileLocation="user.jobengine.osgi.db.jar" outputPathType="CompilerOutputPath" bndFileLocation="" bundlorFileLocation="" bundleActivator="" bundleSymbolicName="" bundleVersion="1.0.0" ignoreFilePattern="" useProjectDefaultManifestFileLocation="false" alwaysRebuildBundleJAR="false" doNotSynchronizeWithMaven="false">\r
+        <additionalProperties />\r
+        <additionalJARContents />\r
+      </configuration>\r
+    </facet>\r
+  </component>\r
+  <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">\r
+    <output url="file://$MODULE_DIR$/target/classes" />\r
+    <output-test url="file://$MODULE_DIR$/target/test-classes" />\r
+    <content url="file://$MODULE_DIR$">\r
+      <excludeFolder url="file://$MODULE_DIR$/target" />\r
+    </content>\r
+    <orderEntry type="inheritedJdk" />\r
+    <orderEntry type="sourceFolder" forTests="false" />\r
+  </component>\r
+</module>
\ No newline at end of file
index c0fcecad272cfae77eed1d083c4307a41f62f1f1..b51a2ed56f16cda375bb5bc57ee87bda99e13b72 100644 (file)
@@ -6,6 +6,7 @@ Bundle-Version: 1.0.0
 Service-Component: OSGI-INF/component.xml, OSGI-INF/componentBinder.xml
 Import-Package: javax.servlet;version="3.1.0",
  javax.servlet.http;version="3.1.0",
+ org.apache.commons.io.output;version="2.2.0",
  org.apache.logging.log4j;version="2.8.2",
  org.apache.logging.log4j.message;version="2.8.2",
  org.eclipse.core.runtime.adaptor,
diff --git a/server/user.jobengine.osgi.server/css/tagify.css b/server/user.jobengine.osgi.server/css/tagify.css
new file mode 100644 (file)
index 0000000..b4f589b
--- /dev/null
@@ -0,0 +1 @@
+:root{--tagify-dd-color-primary:rgb(53,149,246);--tagify-dd-bg-color:white}.tagify{--tags-border-color:#DDD;--tag-bg:#E5E5E5;--tag-hover:#D3E2E2;--tag-text-color:black;--tag-text-color--edit:black;--tag-pad:0.3em 0.5em;--tag-inset-shadow-size:1.1em;--tag-invalid-color:#D39494;--tag-invalid-bg:rgba(211, 148, 148, 0.5);--tag-remove-bg:rgba(211, 148, 148, 0.3);--tag-remove-btn-bg:none;--tag-remove-btn-bg--hover:#c77777;--tag--min-width:1ch;--tag--max-width:auto;--tag-hide-transition:.3s;--loader-size:.8em;display:flex;align-items:flex-start;flex-wrap:wrap;border:1px solid #ddd;border:1px solid var(--tags-border-color);padding:0;line-height:1.1;cursor:text;outline:0;position:relative;transition:.1s}@keyframes tags--bump{30%{transform:scale(1.2)}}@keyframes rotateLoader{to{transform:rotate(1turn)}}.tagify:hover{border-color:#ccc}.tagify.tagify--focus{transition:0s;border-color:#3595f6}.tagify[readonly]{cursor:default}.tagify[readonly]>.tagify__input{visibility:hidden;width:0;margin:5px 0}.tagify[readonly] .tagify__tag__removeBtn{display:none}.tagify[readonly] .tagify__tag>div{padding:.3em .5em;padding:var(--tag-pad)}.tagify[readonly] .tagify__tag>div::before{background:linear-gradient(45deg,var(--tag-bg) 25%,transparent 25%,transparent 50%,var(--tag-bg) 50%,var(--tag-bg) 75%,transparent 75%,transparent) 0/5px 5px;box-shadow:none;filter:brightness(.95)}.tagify--loading .tagify__input::before{content:none}.tagify--loading .tagify__input::after{content:'';vertical-align:middle;margin:-2px 0 -2px .5em;opacity:1;width:.7em;height:.7em;width:var(--loader-size);height:var(--loader-size);border:3px solid;border-color:#eee #bbb #888 transparent;border-radius:50%;animation:rotateLoader .4s infinite linear}.tagify--loading .tagify__input:empty::after{margin-left:0}.tagify+input,.tagify+textarea{display:none!important}.tagify__tag{display:inline-flex;align-items:center;margin:5px 0 5px 5px;position:relative;z-index:1;outline:0;cursor:default;transition:.13s ease-out}.tagify__tag>div{vertical-align:top;box-sizing:border-box;max-width:100%;padding:.3em .5em;padding:var(--tag-pad);color:#000;color:var(--tag-text-color);line-height:inherit;border-radius:3px;-webkit-user-select:none;user-select:none;transition:.13s ease-out}.tagify__tag>div>*{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:inline-block;vertical-align:top;min-width:var(--tag--min-width);max-width:var(--tag--max-width);transition:.8s ease,.1s color}.tagify__tag>div>[contenteditable]{outline:0;-webkit-user-select:text;user-select:text;cursor:text;margin:-2px;padding:2px;max-width:350px}.tagify__tag>div::before{content:'';position:absolute;border-radius:inherit;left:0;top:0;right:0;bottom:0;z-index:-1;pointer-events:none;transition:120ms ease;animation:tags--bump .3s ease-out 1;box-shadow:0 0 0 1.1em #e5e5e5 inset;box-shadow:0 0 0 calc(var(--tag-inset-shadow-size)) var(--tag-bg) inset}.tagify__tag:hover:not([readonly]) div::before{top:-2px;right:-2px;bottom:-2px;left:-2px;box-shadow:0 0 0 1.1em #d3e2e2 inset;box-shadow:0 0 0 var(--tag-inset-shadow-size) var(--tag-hover) inset}.tagify__tag.tagify--noAnim{animation:none}.tagify__tag.tagify--hide{width:0!important;padding-left:0;padding-right:0;margin-left:0;margin-right:0;opacity:0;transform:scale(0);transition:.3s;transition:var(--tag-hide-transition);pointer-events:none}.tagify__tag.tagify--mark div::before{animation:none}.tagify__tag.tagify--notAllowed div>span{opacity:.5}.tagify__tag.tagify--notAllowed div::before{box-shadow:0 0 0 1.1em rgba(211,148,148,.5) inset!important;box-shadow:0 0 0 var(--tag-inset-shadow-size) var(--tag-invalid-bg) inset!important;transition:.2s}.tagify__tag[readonly] .tagify__tag__removeBtn{display:none}.tagify__tag[readonly]>div::before{background:linear-gradient(45deg,var(--tag-bg) 25%,transparent 25%,transparent 50%,var(--tag-bg) 50%,var(--tag-bg) 75%,transparent 75%,transparent) 0/5px 5px;box-shadow:none;filter:brightness(.95)}.tagify__tag--editable>div{color:#000;color:var(--tag-text-color--edit)}.tagify__tag--editable>div::before{box-shadow:0 0 0 2px #d3e2e2 inset!important;box-shadow:0 0 0 2px var(--tag-hover) inset!important}.tagify__tag--editable.tagify--invalid>div::before{box-shadow:0 0 0 2px #d39494 inset!important;box-shadow:0 0 0 2px var(--tag-invalid-color) inset!important}.tagify__tag__removeBtn{order:5;display:inline-flex;align-items:center;justify-content:center;border-radius:50px;cursor:pointer;font:14px Serif;background:0 0;background:var(--tag-remove-btn-bg);color:#000;color:var(--tag-text-color);width:14px;height:14px;margin-right:4.66667px;margin-left:-4.66667px;transition:.2s ease-out}.tagify__tag__removeBtn::after{content:"\00D7"}.tagify__tag__removeBtn:hover{color:#fff;background:#c77777;background:var(--tag-remove-btn-bg--hover)}.tagify__tag__removeBtn:hover+div>span{opacity:.5}.tagify__tag__removeBtn:hover+div::before{box-shadow:0 0 0 1.1em rgba(211,148,148,.3) inset!important;box-shadow:0 0 0 var(--tag-inset-shadow-size) var(--tag-remove-bg) inset!important;transition:.2s}.tagify:not(.tagify--mix) .tagify__input br{display:none}.tagify:not(.tagify--mix) .tagify__input *{display:inline;white-space:nowrap}.tagify__input{display:block;min-width:110px;margin:5px;padding:.3em .5em;padding:var(--tag-pad,.3em .5em);line-height:inherit;position:relative;white-space:pre-line}.tagify__input::before{display:inline-block;width:0}.tagify__input:empty::before{transition:.2s ease-out;opacity:.5;transform:none;width:auto}.tagify__input:focus{outline:0}.tagify__input:focus::before{transition:.2s ease-out;opacity:0;transform:translatex(6px)}@supports (-moz-appearance:none){.tagify__input:focus::before{display:none}}.tagify__input:focus:empty::before{transition:.2s ease-out;opacity:.3;transform:none}@supports (-moz-appearance:none){.tagify__input:focus:empty::before{display:inline-block}}.tagify__input::before{content:attr(data-placeholder);line-height:1.8;position:absolute;top:0;z-index:1;color:#000;white-space:nowrap;pointer-events:none;opacity:0}.tagify--mix .tagify__input::before{position:static;line-height:inherit}@supports (-moz-appearance:none){.tagify__input::before{line-height:inherit;position:relative}}.tagify__input::after{content:attr(data-suggest);display:inline-block;white-space:pre;color:#000;opacity:.3;pointer-events:none;max-width:100px}.tagify__input .tagify__tag{margin:0}.tagify__input .tagify__tag>div{padding-top:0;padding-bottom:0}.tagify--mix{line-height:1.7}.tagify--mix .tagify__input{padding:5px;margin:0;width:100%;height:100%;line-height:inherit}.tagify--mix .tagify__input::after{content:none}.tagify--select::after{content:'>';opacity:.5;position:absolute;top:50%;right:0;bottom:0;font:16px monospace;line-height:8px;height:8px;pointer-events:none;transform:translate(-150%,-50%) scaleX(1.2) rotate(90deg);transition:.2s ease-in-out}.tagify--select[aria-expanded=true]::after{transform:translate(-150%,-50%) rotate(270deg) scaleY(1.2)}.tagify--select .tagify__tag{position:absolute;top:0;right:1.8em;bottom:0}.tagify--select .tagify__tag div{display:none}.tagify--select .tagify__input{width:100%}.tagify--invalid{--tags-border-color:#D39494}.tagify__dropdown{position:absolute;z-index:9999;transform:translateY(1px);overflow:hidden}.tagify__dropdown[placement=top]{margin-top:0;transform:translateY(-2px)}.tagify__dropdown[placement=top] .tagify__dropdown__wrapper{border-top-width:1px;border-bottom-width:0}.tagify__dropdown--text{box-shadow:0 0 0 3px rgba(var(--tagify-dd-color-primary),.1);font-size:.9em}.tagify__dropdown--text .tagify__dropdown__wrapper{border-width:1px}.tagify__dropdown__wrapper{max-height:300px;overflow:hidden;background:#fff;background:var(--tagify-dd-bg-color);border:1px solid #3595f6;border-color:var(--tagify-dd-color-primary);border-top-width:0;box-shadow:0 2px 4px -2px rgba(0,0,0,.2);transition:.25s cubic-bezier(0,1,.5,1)}.tagify__dropdown__wrapper:hover{overflow:auto}.tagify__dropdown--initial .tagify__dropdown__wrapper{max-height:20px;transform:translateY(-1em)}.tagify__dropdown--initial[placement=top] .tagify__dropdown__wrapper{transform:translateY(2em)}.tagify__dropdown__item{box-sizing:inherit;padding:.3em .5em;margin:1px;cursor:pointer;border-radius:2px;position:relative;outline:0}.tagify__dropdown__item--active{background:#3595f6;background:var(--tagify-dd-color-primary);color:#fff}.tagify__dropdown__item:active{filter:brightness(105%)}
\ No newline at end of file
diff --git a/server/user.jobengine.osgi.server/js/tagify.js b/server/user.jobengine.osgi.server/js/tagify.js
new file mode 100644 (file)
index 0000000..04ab478
--- /dev/null
@@ -0,0 +1,1961 @@
+/**
+ * Tagify (v 3.2.6)- tags input component
+ * By Yair Even-Or
+ * Don't sell this code. (c)
+ * https://github.com/yairEO/tagify
+ */
+;(function(root, factory) {
+  if (typeof define === 'function' && define.amd) {
+    define([], factory);
+  } else if (typeof exports === 'object') {
+    module.exports = factory();
+  } else {
+    root.Tagify = factory();
+  }
+}(this, function() {
+"use strict";
+
+function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); }
+
+function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance"); }
+
+function _iterableToArray(iter) { if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); }
+
+function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } }
+
+function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
+
+function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
+
+function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
+
+/**
+ * @constructor
+ * @param {Object} input    DOM element
+ * @param {Object} settings settings object
+ */
+function Tagify(input, settings) {
+  // protection
+  if (!input) {
+    console.warn('Tagify: ', 'invalid input element ', input);
+    return this;
+  }
+
+  this.applySettings(input, settings || {});
+  this.state = {
+    editing: {},
+    actions: {},
+    // UI actions for state-locking
+    dropdown: {}
+  };
+  this.value = []; // tags' data
+  // events' callbacks references will be stores here, so events could be unbinded
+
+  this.listeners = {};
+  this.DOM = {}; // Store all relevant DOM elements in an Object
+
+  this.extend(this, new this.EventDispatcher(this));
+  this.build(input);
+  this.getCSSVars();
+  this.loadOriginalValues();
+  this.events.customBinding.call(this);
+  this.events.binding.call(this);
+  input.autofocus && this.DOM.input.focus();
+}
+
+Tagify.prototype = {
+  isIE: window.document.documentMode,
+  // https://developer.mozilla.org/en-US/docs/Web/API/Document/compatMode#Browser_compatibility
+  TEXTS: {
+    empty: "empty",
+    exceed: "number of tags exceeded",
+    pattern: "pattern mismatch",
+    duplicate: "already exists",
+    notAllowed: "not allowed"
+  },
+  DEFAULTS: {
+    delimiters: ",",
+    // [RegEx] split tags by any of these delimiters ("null" to cancel) Example: ",| |."
+    pattern: null,
+    // RegEx pattern to validate input by. Ex: /[1-9]/
+    maxTags: Infinity,
+    // Maximum number of tags
+    callbacks: {},
+    // Exposed callbacks object to be triggered on certain events
+    addTagOnBlur: true,
+    // Flag - automatically adds the text which was inputed as a tag when blur event happens
+    duplicates: false,
+    // Flag - allow tuplicate tags
+    whitelist: [],
+    // Array of tags to suggest as the user types (can be used along with "enforceWhitelist" setting)
+    blacklist: [],
+    // A list of non-allowed tags
+    enforceWhitelist: false,
+    // Flag - Only allow tags allowed in whitelist
+    keepInvalidTags: false,
+    // Flag - if true, do not remove tags which did not pass validation
+    mixTagsAllowedAfter: /,|\.|\:|\s/,
+    // RegEx - Define conditions in which mix-tags content is allowing a tag to be added after
+    mixTagsInterpolator: ['[[', ']]'],
+    // Interpolation for mix mode. Everything between this will becmoe a tag
+    backspace: true,
+    // false / true / "edit"
+    skipInvalid: false,
+    // If `true`, do not add invalid, temporary, tags before automatically removing them
+    editTags: 2,
+    // 1 or 2 clicks to edit a tag. false/null for not allowing editing
+    transformTag: function transformTag() {},
+    // Takes a tag input string as argument and returns a transformed value
+    autoComplete: {
+      enabled: true,
+      // Tries to suggest the input's value while typing (match from whitelist) by adding the rest of term as grayed-out text
+      rightKey: false // If `true`, when Right key is pressed, use the suggested value to create a tag, else just auto-completes the input. in mixed-mode this is set to "true"
+
+    },
+    dropdown: {
+      classname: '',
+      enabled: 2,
+      // minimum input characters needs to be typed for the dropdown to show
+      maxItems: 10,
+      searchKeys: [],
+      fuzzySearch: true,
+      highlightFirst: false,
+      // highlights first-matched item in the list
+      closeOnSelect: true,
+      // closes the dropdown after selecting an item, if `enabled:0` (which means always show dropdown)
+      position: 'all' // 'manual' / 'text' / 'all'
+
+    }
+  },
+  // Using ARIA & role attributes
+  // https://www.w3.org/TR/wai-aria-practices/examples/combobox/aria1.1pattern/listbox-combo.html
+  templates: {
+    wrapper: function wrapper(input, settings) {
+      return "<tags class=\"tagify ".concat(settings.mode ? "tagify--" + settings.mode : "", " ").concat(input.className, "\"\n                        ").concat(settings.readonly ? 'readonly aria-readonly="true"' : 'aria-haspopup="listbox" aria-expanded="false"', "\n                        role=\"tagslist\"\n                        tabIndex=\"-1\">\n                <span contenteditable data-placeholder=\"").concat(settings.placeholder || '&#8203;', "\" aria-placeholder=\"").concat(settings.placeholder || '', "\"\n                    class=\"tagify__input\"\n                    role=\"textbox\"\n                    aria-controls=\"dropdown\"\n                    aria-autocomplete=\"both\"\n                    aria-multiline=\"").concat(settings.mode == 'mix' ? true : false, "\"></span>\n            </tags>");
+    },
+    tag: function tag(value, tagData) {
+      return "<tag title='".concat(tagData.title || value, "'\n                        contenteditable='false'\n                        spellcheck='false'\n                        tabIndex=\"-1\"\n                        class='tagify__tag ").concat(tagData["class"] ? tagData["class"] : "", "'\n                        ").concat(this.getAttributes(tagData), ">\n                <x title='' class='tagify__tag__removeBtn' role='button' aria-label='remove tag'></x>\n                <div>\n                    <span class='tagify__tag-text'>").concat(value, "</span>\n                </div>\n            </tag>");
+    },
+    dropdownItem: function dropdownItem(item) {
+      var mapValueTo = this.settings.dropdown.mapValueTo,
+          value = (mapValueTo ? typeof mapValueTo == 'function' ? mapValueTo(item) : item[mapValueTo] : item.value) || item.value,
+          sanitizedValue = (value || item).replace(/`|'/g, "&#39;");
+      return "<div ".concat(this.getAttributes(item), "\n                        class='tagify__dropdown__item ").concat(item["class"] ? item["class"] : "", "'\n                        tabindex=\"0\"\n                        role=\"option\"\n                        aria-labelledby=\"dropdown-label\">").concat(sanitizedValue, "</div>");
+    }
+  },
+  customEventsList: ['add', 'remove', 'invalid', 'input', 'click', 'keydown', 'focus', 'blur', 'edit:input', 'edit:updated', 'edit:start', 'edit:keydown', 'dropdown:show', 'dropdown:hide', 'dropdown:select'],
+  applySettings: function applySettings(input, settings) {
+    var _this2 = this;
+
+    this.DEFAULTS.templates = this.templates;
+    this.settings = this.extend({}, this.DEFAULTS, settings);
+    this.settings.readonly = input.hasAttribute('readonly'); // if "readonly" do not include an "input" element inside the Tags component
+
+    this.settings.placeholder = input.getAttribute('placeholder') || this.settings.placeholder || "";
+    if (this.isIE) this.settings.autoComplete = false; // IE goes crazy if this isn't false
+
+    ["whitelist", "blacklist"].forEach(function (name) {
+      var attrVal = input.getAttribute('data-' + name);
+
+      if (attrVal) {
+        attrVal = attrVal.split(_this2.settings.delimiters);
+        if (attrVal instanceof Array) _this2.settings[name] = attrVal;
+      }
+    }); // backward-compatibility for old version of "autoComplete" setting:
+
+    if ("autoComplete" in settings && !this.isObject(settings.autoComplete)) {
+      this.settings.autoComplete = this.DEFAULTS.autoComplete;
+      this.settings.autoComplete.enabled = settings.autoComplete;
+    }
+
+    if (input.pattern) try {
+      this.settings.pattern = new RegExp(input.pattern);
+    } catch (e) {} // Convert the "delimiters" setting into a REGEX object
+
+    if (this.settings.delimiters) {
+      try {
+        this.settings.delimiters = new RegExp(this.settings.delimiters, "g");
+      } catch (e) {}
+    } // make sure the dropdown will be shown on "focus" and not only after typing something (in "select" mode)
+
+
+    if (this.settings.mode == 'select') this.settings.dropdown.enabled = 0;
+    if (this.settings.mode == 'mix') this.settings.autoComplete.rightKey = true;
+  },
+
+  /**
+   * Creates a string of HTML element attributes
+   * @param {Object} data [Tag data]
+   */
+  getAttributes: function getAttributes(data) {
+    // only items which are objects have properties which can be used as attributes
+    if (Object.prototype.toString.call(data) != "[object Object]") return '';
+    var keys = Object.keys(data),
+        s = "",
+        propName,
+        i;
+
+    for (i = keys.length; i--;) {
+      propName = keys[i];
+      if (propName != 'class' && data.hasOwnProperty(propName) && data[propName]) s += " " + propName + (data[propName] ? "=\"".concat(data[propName], "\"") : "");
+    }
+
+    return s;
+  },
+
+  /**
+   * utility method
+   * https://stackoverflow.com/a/35385518/104380
+   * @param  {String} s [HTML string]
+   * @return {Object}   [DOM node]
+   */
+  parseHTML: function parseHTML(s) {
+    var parser = new DOMParser(),
+        node = parser.parseFromString(s.trim(), "text/html");
+    return node.body.firstElementChild;
+  },
+
+  /**
+   * utility method
+   * https://stackoverflow.com/a/25396011/104380
+   */
+  escapeHTML: function escapeHTML(s) {
+    var text = document.createTextNode(s),
+        p = document.createElement('p');
+    p.appendChild(text);
+    return p.innerHTML;
+  },
+
+  /**
+   * Get the caret position relative to the viewport
+   * https://stackoverflow.com/q/58985076/104380
+   *
+   * @returns {object} left, top distance in pixels
+   */
+  getCaretGlobalPosition: function getCaretGlobalPosition() {
+    var sel = document.getSelection();
+
+    if (sel.rangeCount) {
+      var r = sel.getRangeAt(0);
+      var node = r.startContainer;
+      var offset = r.startOffset;
+      var rect, r2;
+
+      if (offset > 0) {
+        r2 = document.createRange();
+        r2.setStart(node, offset - 1);
+        r2.setEnd(node, offset);
+        rect = r2.getBoundingClientRect();
+        return {
+          left: rect.right,
+          top: rect.top,
+          bottom: rect.bottom
+        };
+      }
+    }
+
+    return {
+      left: -9999,
+      top: -9999
+    };
+  },
+
+  /**
+   * Get specific CSS variables which are relevant to this script and parse them as needed.
+   * The result is saved on the instance in "this.CSSVars"
+   */
+  getCSSVars: function getCSSVars() {
+    var compStyle = getComputedStyle(this.DOM.scope, null);
+
+    var getProp = function getProp(name) {
+      return compStyle.getPropertyValue('--' + name);
+    };
+
+    function seprateUnitFromValue(a) {
+      if (!a) return {};
+      a = a.trim().split(' ')[0];
+      var unit = a.split(/\d+/g).filter(function (n) {
+        return n;
+      }).pop().trim(),
+          value = +a.split(unit).filter(function (n) {
+        return n;
+      })[0].trim();
+      return {
+        value: value,
+        unit: unit
+      };
+    }
+
+    this.CSSVars = {
+      tagHideTransition: function (_ref) {
+        var value = _ref.value,
+            unit = _ref.unit;
+        return unit == 's' ? value * 1000 : value;
+      }(seprateUnitFromValue(getProp('tag-hide-transition')))
+    };
+  },
+
+  /**
+   * builds the HTML of this component
+   * @param  {Object} input [DOM element which would be "transformed" into "Tags"]
+   */
+  build: function build(input) {
+    var DOM = this.DOM,
+        template = this.settings.templates.wrapper(input, this.settings);
+    DOM.originalInput = input;
+    DOM.scope = this.parseHTML(template);
+    DOM.input = DOM.scope.querySelector('[contenteditable]');
+    input.parentNode.insertBefore(DOM.scope, input);
+
+    if (this.settings.dropdown.enabled >= 0) {
+      this.dropdown.init.call(this);
+    }
+  },
+
+  /**
+   * revert any changes made by this component
+   */
+  destroy: function destroy() {
+    this.DOM.scope.parentNode.removeChild(this.DOM.scope);
+    this.dropdown.hide.call(this, true);
+  },
+
+  /**
+   * if the original input had any values, add them as tags
+   */
+  loadOriginalValues: function loadOriginalValues(value) {
+    value = value || this.DOM.originalInput.value; // if the original input already had any value (tags)
+
+    if (!value) return;
+    this.removeAllTags();
+    if (this.settings.mode == 'mix') this.parseMixTags(value.trim());else {
+      try {
+        if (typeof JSON.parse(value) !== 'string') value = JSON.parse(value);
+      } catch (err) {}
+
+      this.addTags(value).forEach(function (tag) {
+        return tag && tag.classList.add('tagify--noAnim');
+      });
+    }
+  },
+
+  /**
+   * Checks if an argument is a javascript Object
+   */
+  isObject: function isObject(obj) {
+    var type = Object.prototype.toString.call(obj).split(' ')[1].slice(0, -1);
+    return obj === Object(obj) && type != 'Array' && type != 'Function' && type != 'RegExp' && type != 'HTMLUnknownElement';
+  },
+
+  /**
+   * merge objects into a single new one
+   * TEST: extend({}, {a:{foo:1}, b:[]}, {a:{bar:2}, b:[1], c:()=>{}})
+   */
+  extend: function extend(o, o1, o2) {
+    var that = this;
+    if (!(o instanceof Object)) o = {};
+    copy(o, o1);
+    if (o2) copy(o, o2);
+
+    function copy(a, b) {
+      // copy o2 to o
+      for (var key in b) {
+        if (b.hasOwnProperty(key)) {
+          if (that.isObject(b[key])) {
+            if (!that.isObject(a[key])) a[key] = Object.assign({}, b[key]);else copy(a[key], b[key]);
+          } else a[key] = b[key];
+        }
+      }
+    }
+
+    return o;
+  },
+  cloneEvent: function cloneEvent(e) {
+    var clonedEvent = {};
+
+    for (var v in e) {
+      clonedEvent[v] = e[v];
+    }
+
+    return clonedEvent;
+  },
+
+  /**
+   * A constructor for exposing events to the outside
+   */
+  EventDispatcher: function EventDispatcher(instance) {
+    // Create a DOM EventTarget object
+    var target = document.createTextNode('');
+
+    function addRemove(op, events, cb) {
+      if (cb) events.split(/\s+/g).forEach(function (name) {
+        return target[op + 'EventListener'].call(target, name, cb);
+      });
+    } // Pass EventTarget interface calls to DOM EventTarget object
+
+
+    this.off = function (events, cb) {
+      addRemove('remove', events, cb);
+      return this;
+    };
+
+    this.on = function (events, cb) {
+      if (cb && typeof cb == 'function') addRemove('add', events, cb);
+      return this;
+    };
+
+    this.trigger = function (eventName, data) {
+      var e;
+      if (!eventName) return;
+
+      if (instance.settings.isJQueryPlugin) {
+        if (eventName == 'remove') eventName = 'removeTag'; // issue #222
+
+        jQuery(instance.DOM.originalInput).triggerHandler(eventName, [data]);
+      } else {
+        try {
+          e = new CustomEvent(eventName, {
+            "detail": this.extend({}, data, {
+              tagify: this
+            })
+          });
+        } catch (err) {
+          console.warn(err);
+        }
+
+        target.dispatchEvent(e);
+      }
+    };
+  },
+
+  /**
+   * Toogle loading state on/off
+   * @param {Boolean} isLoading
+   */
+  loading: function loading(isLoading) {
+    // IE11 doesn't support toggle with second parameter
+    this.DOM.scope.classList[isLoading ? "add" : "remove"]('tagify--loading');
+    return this;
+  },
+  toggleFocusClass: function toggleFocusClass(force) {
+    this.DOM.scope.classList.toggle('tagify--focus', !!force);
+  },
+
+  /**
+   * DOM events listeners binding
+   */
+  events: {
+    // bind custom events which were passed in the settings
+    customBinding: function customBinding() {
+      var _this3 = this;
+
+      this.customEventsList.forEach(function (name) {
+        _this3.on(name, _this3.settings.callbacks[name]);
+      });
+    },
+    binding: function binding() {
+      var bindUnbind = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
+
+      var _CB = this.events.callbacks,
+          _CBR,
+          action = bindUnbind ? 'addEventListener' : 'removeEventListener'; // do not allow the main events to be bound more than once
+
+
+      if (this.state.mainEvents && bindUnbind) return; // set the binding state of the main events, so they will not be bound more than once
+
+      this.state.mainEvents = bindUnbind;
+
+      if (bindUnbind && !this.listeners.main) {
+        // this event should never be unbinded:
+        // IE cannot register "input" events on contenteditable elements, so the "keydown" should be used instead..
+        this.DOM.input.addEventListener(this.isIE ? "keydown" : "input", _CB[this.isIE ? "onInputIE" : "onInput"].bind(this));
+        if (this.settings.isJQueryPlugin) jQuery(this.DOM.originalInput).on('tagify.removeAllTags', this.removeAllTags.bind(this));
+      } // setup callback references so events could be removed later
+
+
+      _CBR = this.listeners.main = this.listeners.main || {
+        focus: ['input', _CB.onFocusBlur.bind(this)],
+        blur: ['input', _CB.onFocusBlur.bind(this)],
+        keydown: ['input', _CB.onKeydown.bind(this)],
+        click: ['scope', _CB.onClickScope.bind(this)],
+        dblclick: ['scope', _CB.onDoubleClickScope.bind(this)]
+      };
+
+      for (var eventName in _CBR) {
+        // make sure the focus/blur event is always regesitered (and never more than once)
+        if (eventName == 'blur' && !bindUnbind) return;
+
+        this.DOM[_CBR[eventName][0]][action](eventName, _CBR[eventName][1]);
+      }
+    },
+
+    /**
+     * DOM events callbacks
+     */
+    callbacks: {
+      onFocusBlur: function onFocusBlur(e) {
+        var text = e.target ? e.target.textContent.trim() : '',
+            // a string
+        _s = this.settings,
+            type = e.type; // goes into this scenario only on input "blur" and a tag was clicked
+
+        if (e.relatedTarget && e.relatedTarget.classList.contains('tagify__tag') && this.DOM.scope.contains(e.relatedTarget)) return;
+
+        if (type == 'blur' && e.relatedTarget === this.DOM.scope) {
+          this.dropdown.hide.call(this);
+          this.DOM.input.focus();
+          return;
+        }
+
+        if (this.state.actions.selectOption && (_s.dropdown.enabled || !_s.dropdown.closeOnSelect)) return;
+        this.state.hasFocus = type == "focus" ? +new Date() : false;
+        this.toggleFocusClass(this.state.hasFocus);
+        this.setRangeAtStartEnd(false);
+
+        if (_s.mode == 'mix') {
+          if (e.type == "blur") this.dropdown.hide.call(this);
+          return;
+        }
+
+        if (type == "focus") {
+          this.trigger("focus", {
+            relatedTarget: e.relatedTarget
+          }); //  e.target.classList.remove('placeholder');
+
+          if (_s.dropdown.enabled === 0 && _s.mode != "select") {
+            this.dropdown.show.call(this);
+          }
+
+          return;
+        } else if (type == "blur") {
+          this.trigger("blur", {
+            relatedTarget: e.relatedTarget
+          });
+          this.loading(false); // do not add a tag if "selectOption" action was just fired (this means a tag was just added from the dropdown)
+
+          text && !this.state.actions.selectOption && _s.addTagOnBlur && this.addTags(text, true);
+        }
+
+        this.DOM.input.removeAttribute('style');
+        this.dropdown.hide.call(this);
+      },
+      onKeydown: function onKeydown(e) {
+        var _this4 = this;
+
+        var s = e.target.textContent.trim(),
+            tags;
+        this.trigger("keydown", {
+          originalEvent: this.cloneEvent(e)
+        });
+
+        if (this.settings.mode == 'mix') {
+          switch (e.key) {
+            case 'Left':
+            case 'ArrowLeft':
+              {
+                // when left arrow was pressed, raise a flag so when the dropdown is shown, right-arrow will be ignored
+                // because it seems likely the user wishes to use the arrows to move the caret
+                this.state.actions.ArrowLeft = true;
+                break;
+              }
+
+            case 'Delete':
+            case 'Backspace':
+              {
+                var selection = document.getSelection(),
+                    isFF = !!navigator.userAgent.match(/firefox/i);
+                if (isFF && selection && selection.anchorOffset == 0) this.removeTag(selection.anchorNode.previousSibling);
+                var values = []; // find out which tag(s) were deleted and update "this.value" accordingly
+
+                tags = this.DOM.input.children; // a minimum delay is needed before the node actually gets ditached from the document (don't know why),
+                // to know exactly which tag was deleted. This is the easiest way of knowing besides using MutationObserver
+
+                setTimeout(function () {
+                  // iterate over the list of tags still in the document and then filter only those from the "this.value" collection
+                  [].forEach.call(tags, function (tagElm) {
+                    return values.push(tagElm.getAttribute('value'));
+                  });
+                  _this4.value = _this4.value.filter(function (d) {
+                    return values.indexOf(d.value) != -1;
+                  });
+                });
+                break;
+              }
+            // currently commented to allow new lines in mixed-mode
+            // case 'Enter' :
+            //     e.preventDefault(); // solves Chrome bug - http://stackoverflow.com/a/20398191/104380
+          }
+
+          return true;
+        }
+
+        switch (e.key) {
+          case 'Backspace':
+            if (s == "" || s.charCodeAt(0) == 8203) {
+              // 8203: ZERO WIDTH SPACE unicode
+              if (this.settings.backspace === true) this.removeTag();else if (this.settings.backspace == 'edit') setTimeout(this.editTag.bind(this), 0); // timeout reason: when edited tag gets focused and the caret is placed at the end, the last character gets deletec (because of backspace)
+            }
+
+            break;
+
+          case 'Esc':
+          case 'Escape':
+            if (this.state.dropdown.visible) return;
+            e.target.blur();
+            break;
+
+          case 'Down':
+          case 'ArrowDown':
+            // if( this.settings.mode == 'select' ) // issue #333
+            if (!this.state.dropdown.visible) this.dropdown.show.call(this);
+            break;
+
+          case 'ArrowRight':
+            {
+              var tagData = this.state.inputSuggestion || this.state.ddItemData;
+
+              if (tagData && this.settings.autoComplete.rightKey) {
+                this.addTags([tagData], true);
+                return;
+              }
+
+              break;
+            }
+
+          case 'Tab':
+            {
+              if (!s) return true;
+            }
+
+          case 'Enter':
+            e.preventDefault(); // solves Chrome bug - http://stackoverflow.com/a/20398191/104380
+            // because the main "keydown" event is bound before the dropdown events, this will fire first and will not *yet*
+            // know if an option was just selected from the dropdown menu. If an option was selected,
+            // the dropdown events should handle adding the tag
+
+            setTimeout(function () {
+              if (_this4.state.actions.selectOption) return;
+
+              _this4.addTags(s, true);
+            });
+        }
+      },
+      onInput: function onInput(e) {
+        var value = this.settings.mode == 'mix' ? this.DOM.input.textContent : this.input.normalize.call(this),
+            showSuggestions = value.length >= this.settings.dropdown.enabled,
+            data = {
+          value: value,
+          inputElm: this.DOM.input
+        };
+        if (this.settings.mode == 'mix') return this.events.callbacks.onMixTagsInput.call(this, e);
+
+        if (!value) {
+          this.input.set.call(this, '');
+          return;
+        }
+
+        if (this.input.value == value) return; // for IE; since IE doesn't have an "input" event so "keyDown" is used instead
+
+        data.isValid = this.validateTag(value);
+        this.trigger('input', data); // "input" event must be triggered at this point, before the dropdown is shown
+        // save the value on the input's State object
+
+        this.input.set.call(this, value, false); // update the input with the normalized value and run validations
+        // this.setRangeAtStartEnd(); // fix caret position
+
+        if (value.search(this.settings.delimiters) != -1) {
+          if (this.addTags(value)) {
+            this.input.set.call(this); // clear the input field's value
+          }
+        } else if (this.settings.dropdown.enabled >= 0) {
+          this.dropdown[showSuggestions ? "show" : "hide"].call(this, value);
+        }
+      },
+      onMixTagsInput: function onMixTagsInput(e) {
+        var _this5 = this;
+
+        var sel,
+            range,
+            split,
+            tag,
+            showSuggestions,
+            _s = this.settings;
+        if (this.hasMaxTags()) return true;
+
+        if (window.getSelection) {
+          sel = window.getSelection();
+
+          if (sel.rangeCount > 0) {
+            range = sel.getRangeAt(0).cloneRange();
+            range.collapse(true);
+            range.setStart(window.getSelection().focusNode, 0);
+            split = range.toString().split(_s.mixTagsAllowedAfter); // ["foo", "bar", "@a"]
+
+            tag = split[split.length - 1].match(_s.pattern);
+
+            if (tag) {
+              this.state.actions.ArrowLeft = false; // start fresh, assuming the user did not (yet) used any arrow to move the caret
+
+              this.state.tag = {
+                prefix: tag[0],
+                value: tag.input.split(tag[0])[1]
+              };
+              showSuggestions = this.state.tag.value.length >= _s.dropdown.enabled;
+            }
+          }
+        }
+
+        this.update(); // wait until the "this.value" has been updated (see "onKeydown" method for "mix-mode")
+        // the dropdown must be shown only after this event has been driggered, so an implementer could
+        // dynamically change the whitelist.
+
+        setTimeout(function () {
+          _this5.trigger("input", _this5.extend({}, _this5.state.tag, {
+            textContent: _this5.DOM.input.textContent
+          }));
+
+          if (_this5.state.tag) _this5.dropdown[showSuggestions ? "show" : "hide"].call(_this5, _this5.state.tag.value);
+        }, 10);
+      },
+      onInputIE: function onInputIE(e) {
+        var _this = this; // for the "e.target.textContent" to be changed, the browser requires a small delay
+
+
+        setTimeout(function () {
+          _this.events.callbacks.onInput.call(_this, e);
+        });
+      },
+      onClickScope: function onClickScope(e) {
+        var tagElm = e.target.closest('.tagify__tag'),
+            _s = this.settings,
+            timeDiffFocus = +new Date() - this.state.hasFocus,
+            tagElmIdx;
+
+        if (e.target == this.DOM.scope) {
+          // if( !this.state.hasFocus )
+          //   this.dropdown.hide.call(this)
+          this.DOM.input.focus();
+          return;
+        } else if (e.target.classList.contains("tagify__tag__removeBtn")) {
+          this.removeTag(e.target.parentNode);
+          return;
+        } else if (tagElm) {
+          tagElmIdx = this.getNodeIndex(tagElm);
+          this.trigger("click", {
+            tag: tagElm,
+            index: tagElmIdx,
+            data: this.value[tagElmIdx],
+            originalEvent: this.cloneEvent(e)
+          });
+          if (this.settings.editTags == 1) this.events.callbacks.onDoubleClickScope.call(this, e);
+          return;
+        } // when clicking on the input itself
+        else if (e.target == this.DOM.input && timeDiffFocus > 500) {
+            if (this.state.dropdown.visible) this.dropdown.hide.call(this);else if (_s.dropdown.enabled === 0 && _s.mode != 'mix') this.dropdown.show.call(this);
+            return;
+          }
+
+        if (_s.mode == 'select') !this.state.dropdown.visible && this.dropdown.show.call(this);
+      },
+      onEditTagInput: function onEditTagInput(editableElm, e) {
+        var tagElm = editableElm.closest('tag'),
+            tagElmIdx = this.getNodeIndex(tagElm),
+            value = this.input.normalize.call(this, editableElm),
+            isValid = value.toLowerCase() == editableElm.originalValue.toLowerCase() || this.validateTag(value);
+        tagElm.classList.toggle('tagify--invalid', isValid !== true);
+        tagElm.isValid = isValid; // show dropdown if typed text is equal or more than the "enabled" dropdown setting
+
+        if (value.length >= this.settings.dropdown.enabled) {
+          this.state.editing.value = value;
+          this.dropdown.show.call(this, value);
+        }
+
+        this.trigger("edit:input", {
+          tag: tagElm,
+          index: tagElmIdx,
+          data: this.extend({}, this.value[tagElmIdx], {
+            newValue: value
+          }),
+          originalEvent: this.cloneEvent(e)
+        });
+      },
+      onEditTagBlur: function onEditTagBlur(editableElm) {
+        if (!this.state.hasFocus) this.toggleFocusClass();
+        if (!this.DOM.scope.contains(editableElm)) return;
+
+        var tagElm = editableElm.closest('.tagify__tag'),
+            tagElmIdx = this.getNodeIndex(tagElm),
+            currentValue = this.input.normalize.call(this, editableElm),
+            value = currentValue || editableElm.originalValue,
+            hasChanged = value != editableElm.originalValue,
+            isValid = tagElm.isValid,
+            tagData = _objectSpread({}, this.value[tagElmIdx], {
+          value: value
+        }); //  this.DOM.input.focus()
+
+
+        if (!currentValue) {
+          this.removeTag(tagElm);
+          return;
+        }
+
+        if (hasChanged) {
+          this.settings.transformTag.call(this, tagData); // re-validate after tag transformation
+
+          isValid = this.validateTag(tagData.value);
+        } else {
+          this.onEditTagDone(tagElm);
+          return;
+        }
+
+        if (isValid !== undefined && isValid !== true) return;
+        this.onEditTagDone(tagElm, tagData);
+      },
+      onEditTagkeydown: function onEditTagkeydown(e) {
+        this.trigger("edit:keydown", {
+          originalEvent: this.cloneEvent(e)
+        });
+
+        switch (e.key) {
+          case 'Esc':
+          case 'Escape':
+            e.target.textContent = e.target.originalValue;
+
+          case 'Enter':
+          case 'Tab':
+            e.preventDefault();
+            e.target.blur();
+        }
+      },
+      onDoubleClickScope: function onDoubleClickScope(e) {
+        var tagElm = e.target.closest('tag'),
+            _s = this.settings,
+            isEditingTag,
+            isReadyOnlyTag;
+        if (!tagElm) return;
+        isEditingTag = tagElm.classList.contains('tagify__tag--editable'), isReadyOnlyTag = tagElm.hasAttribute('readonly');
+        if (_s.mode != 'select' && !_s.readonly && !isEditingTag && !isReadyOnlyTag && this.settings.editTags) this.editTag(tagElm);
+        this.toggleFocusClass(true);
+      }
+    }
+  },
+
+  /**
+   * @param {Node} tagElm the tag element to edit. if nothing specified, use last last
+   */
+  editTag: function editTag() {
+    var _this6 = this;
+
+    var tagElm = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.getLastTag();
+
+    var editableElm = tagElm.querySelector('.tagify__tag-text'),
+        tagIdx = this.getNodeIndex(tagElm),
+        tagData = this.value[tagIdx],
+        _CB = this.events.callbacks,
+        that = this,
+        delayed_onEditTagBlur = function delayed_onEditTagBlur() {
+      setTimeout(_CB.onEditTagBlur.bind(that), 0, editableElm);
+    };
+
+    if (!editableElm) {
+      console.warn('Cannot find element in Tag template: ', '.tagify__tag-text');
+      return;
+    }
+
+    if ("editable" in tagData && !tagData.editable) return;
+    tagElm.classList.add('tagify__tag--editable');
+    editableElm.originalValue = editableElm.textContent;
+    editableElm.setAttribute('contenteditable', true);
+    editableElm.addEventListener('blur', delayed_onEditTagBlur);
+    editableElm.addEventListener('input', _CB.onEditTagInput.bind(this, editableElm));
+    editableElm.addEventListener('keydown', function (e) {
+      return _CB.onEditTagkeydown.call(_this6, e);
+    });
+    editableElm.focus();
+    this.setRangeAtStartEnd(false, editableElm);
+    this.state.editing = {
+      scope: tagElm,
+      input: tagElm.querySelector("[contenteditable]")
+    };
+    this.trigger("edit:start", {
+      tag: tagElm,
+      index: tagIdx,
+      data: tagData
+    });
+    return this;
+  },
+  onEditTagDone: function onEditTagDone(tagElm, tagData) {
+    var eventData = {
+      tag: tagElm,
+      index: this.getNodeIndex(tagElm),
+      data: tagData
+    };
+    this.trigger("edit:beforeUpdate", eventData);
+    this.replaceTag(tagElm, tagData);
+    this.trigger("edit:updated", eventData);
+  },
+
+  /**
+   * Exit a tag's edit-mode.
+   * if "tagData" exists, replace the tag element with new data and update Tagify value
+   */
+  replaceTag: function replaceTag(tagElm, tagData) {
+    var _this7 = this;
+
+    var editableElm = tagElm.querySelector('.tagify__tag-text'),
+        clone = editableElm.cloneNode(true),
+        tagElmIdx = this.getNodeIndex(tagElm);
+    if (this.state.editing.locked) return; // when editing a tag and selecting a dropdown suggested item, the state should be "locked"
+    // so "onEditTagBlur" won't run and change the tag also *after* it was just changed.
+
+    this.state.editing = {
+      locked: true
+    };
+    setTimeout(function () {
+      return delete _this7.state.editing.locked;
+    }, 500); // update DOM nodes
+
+    clone.removeAttribute('contenteditable');
+    tagElm.classList.remove('tagify__tag--editable'); // guarantee to remove all events which were added by the "editTag" method
+
+    editableElm.parentNode.replaceChild(clone, editableElm); // continue only if there was a reason for it
+
+    if (tagData) {
+      clone.innerHTML = tagData.value;
+      clone.title = tagData.value; // update data
+
+      this.value[tagElmIdx] = tagData;
+      this.update();
+    }
+  },
+
+  /** https://stackoverflow.com/a/59156872/104380
+   * @param {Boolean} start indicating where to place it (start or end of the node)
+   * @param {Object}  node  DOM node to place the caret at
+   */
+  setRangeAtStartEnd: function setRangeAtStartEnd(start, node) {
+    node = node || this.DOM.input;
+    node = node.lastChild || node;
+    var sel = document.getSelection();
+
+    if (sel.rangeCount) {
+      ['Start', 'End'].forEach(function (pos) {
+        return sel.getRangeAt(0)["set" + pos](node, start ? 0 : node.length);
+      });
+    }
+  },
+
+  /**
+   * input bridge for accessing & setting
+   * @type {Object}
+   */
+  input: {
+    value: '',
+    set: function set() {
+      var s = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
+      var updateDOM = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
+      var hideDropdown = this.settings.dropdown.closeOnSelect;
+      this.input.value = s;
+      if (updateDOM) this.DOM.input.innerHTML = s;
+      if (!s && hideDropdown) setTimeout(this.dropdown.hide.bind(this), 20); // setTimeout duration must be HIGER than the dropdown's item "onClick" method's "focus()" event, because the "hide" method re-binds the main events and it will catch the "blur" event and will cause
+
+      this.input.autocomplete.suggest.call(this);
+      this.input.validate.call(this);
+    },
+
+    /**
+     * Marks the tagify's input as "invalid" if the value did not pass "validateTag()"
+     */
+    validate: function validate() {
+      var isValid = !this.input.value || this.validateTag(this.input.value);
+      if (this.settings.mode == 'select') this.DOM.scope.classList.toggle('tagify--invalid', isValid !== true);else this.DOM.input.classList.toggle('tagify__input--invalid', isValid !== true);
+    },
+    // remove any child DOM elements that aren't of type TEXT (like <br>)
+    normalize: function normalize(node) {
+      var clone = node || this.DOM.input,
+          //.cloneNode(true),
+      v = []; // when a text was pasted in FF, the "this.DOM.input" element will have <br> but no newline symbols (\n), and this will
+      // result in tags no being properly created if one wishes to create a separate tag per newline.
+
+      clone.childNodes.forEach(function (n) {
+        return n.nodeType == 3 && v.push(n.nodeValue);
+      });
+      v = v.join("\n");
+
+      try {
+        // "delimiters" might be of a non-regex value, where this will fail ("Tags With Properties" example in demo page):
+        v = v.replace(/(?:\r\n|\r|\n)/g, this.settings.delimiters.source.charAt(0));
+      } catch (err) {}
+
+      v = v.replace(/\s/g, ' ') // replace NBSPs with spaces characters
+      .replace(/^\s+/, ""); // trimLeft
+
+      return v;
+    },
+
+    /**
+     * suggest the rest of the input's value (via CSS "::after" using "content:attr(...)")
+     * @param  {String} s [description]
+     */
+    autocomplete: {
+      suggest: function suggest(data) {
+        if (!this.settings.autoComplete.enabled) return;
+        data = data || {};
+        if (typeof data == 'string') data = {
+          value: data
+        };
+        var suggestedText = data.value || '',
+            suggestionStart = suggestedText.substr(0, this.input.value.length).toLowerCase(),
+            suggestionTrimmed = suggestedText.substring(this.input.value.length);
+
+        if (!suggestedText || !this.input.value || suggestionStart != this.input.value.toLowerCase()) {
+          this.DOM.input.removeAttribute("data-suggest");
+          delete this.state.inputSuggestion;
+        } else {
+          this.DOM.input.setAttribute("data-suggest", suggestionTrimmed);
+          this.state.inputSuggestion = data;
+        }
+      },
+
+      /**
+       * sets the suggested text as the input's value & cleanup the suggestion autocomplete.
+       * @param {String} s [text]
+       */
+      set: function set(s) {
+        var dataSuggest = this.DOM.input.getAttribute('data-suggest'),
+            suggestion = s || (dataSuggest ? this.input.value + dataSuggest : null);
+
+        if (suggestion) {
+          if (this.settings.mode == 'mix') {
+            this.replaceTextWithNode(document.createTextNode(this.state.tag.prefix + suggestion));
+          } else {
+            this.input.set.call(this, suggestion);
+            this.setRangeAtStartEnd();
+          }
+
+          this.input.autocomplete.suggest.call(this);
+          this.dropdown.hide.call(this);
+          return true;
+        }
+
+        return false;
+      }
+    }
+  },
+  getNodeIndex: function getNodeIndex(node) {
+    var index = 0;
+    if (node) while (node = node.previousElementSibling) {
+      index++;
+    }
+    return index;
+  },
+  getTagElms: function getTagElms() {
+    return this.DOM.scope.querySelectorAll('.tagify__tag');
+  },
+  getLastTag: function getLastTag() {
+    var lastTag = this.DOM.scope.querySelectorAll('tag:not(.tagify--hide):not([readonly])');
+    return lastTag[lastTag.length - 1];
+  },
+
+  /**
+   * Searches if any tag with a certain value already exis
+   * @param  {String/Object} v [text value / tag data object]
+   * @return {Boolean}
+   */
+  isTagDuplicate: function isTagDuplicate(v) {
+    var _this8 = this;
+
+    // duplications are irrelevant for this scenario
+    if (this.settings.mode == 'select') return false;
+    return this.value.some(function (item) {
+      return _this8.isObject(v) ? JSON.stringify(item).toLowerCase() === JSON.stringify(v).toLowerCase() : v.trim().toLowerCase() === item.value.toLowerCase();
+    });
+  },
+  getTagIndexByValue: function getTagIndexByValue(value) {
+    var result = [];
+    this.getTagElms().forEach(function (tagElm, i) {
+      if (tagElm.textContent.trim().toLowerCase() == value.toLowerCase()) result.push(i);
+    });
+    return result;
+  },
+  getTagElmByValue: function getTagElmByValue(value) {
+    var tagIdx = this.getTagIndexByValue(value)[0];
+    return this.getTagElms()[tagIdx];
+  },
+
+  /**
+   * Mark a tag element by its value
+   * @param  {String|Number} value  [text value to search for]
+   * @param  {Object}          tagElm [a specific "tag" element to compare to the other tag elements siblings]
+   * @return {boolean}                [found / not found]
+   */
+  markTagByValue: function markTagByValue(value, tagElm) {
+    tagElm = tagElm || this.getTagElmByValue(value); // check AGAIN if "tagElm" is defined
+
+    if (tagElm) {
+      tagElm.classList.add('tagify--mark'); //   setTimeout(() => { tagElm.classList.remove('tagify--mark') }, 100);
+
+      return tagElm;
+    }
+
+    return false;
+  },
+
+  /**
+   * make sure the tag, or words in it, is not in the blacklist
+   */
+  isTagBlacklisted: function isTagBlacklisted(v) {
+    v = v.toLowerCase().trim();
+    return this.settings.blacklist.filter(function (x) {
+      return v == x.toLowerCase();
+    }).length;
+  },
+
+  /**
+   * make sure the tag, or words in it, is not in the blacklist
+   */
+  isTagWhitelisted: function isTagWhitelisted(v) {
+    return this.settings.whitelist.some(function (item) {
+      return typeof v == 'string' ? v.trim().toLowerCase() === (item.value || item).toLowerCase() : JSON.stringify(item).toLowerCase() === JSON.stringify(v).toLowerCase();
+    });
+  },
+
+  /**
+   * validate a tag object BEFORE the actual tag will be created & appeneded
+   * @param  {String} s
+   * @return {Boolean/String}  ["true" if validation has passed, String for a fail]
+   */
+  validateTag: function validateTag(s) {
+    var value = s.trim(),
+        _s = this.settings,
+        result = true; // check for empty value
+
+    if (!value) result = this.TEXTS.empty; // check if pattern should be used and if so, use it to test the value
+    else if (_s.pattern && !_s.pattern.test(value)) result = this.TEXTS.pattern; // if duplicates are not allowed and there is a duplicate
+      else if (!_s.duplicates && this.isTagDuplicate(value)) result = this.TEXTS.duplicate;else if (this.isTagBlacklisted(value) || _s.enforceWhitelist && !this.isTagWhitelisted(value)) result = this.TEXTS.notAllowed;
+    return result;
+  },
+  hasMaxTags: function hasMaxTags() {
+    if (this.value.length >= this.settings.maxTags) return this.TEXTS.exceed;
+    return false;
+  },
+
+  /**
+   * pre-proccess the tagsItems, which can be a complex tagsItems like an Array of Objects or a string comprised of multiple words
+   * so each item should be iterated on and a tag created for.
+   * @return {Array} [Array of Objects]
+   */
+  normalizeTags: function normalizeTags(tagsItems) {
+    var _this$settings = this.settings,
+        whitelist = _this$settings.whitelist,
+        delimiters = _this$settings.delimiters,
+        mode = _this$settings.mode,
+        whitelistWithProps = whitelist ? whitelist[0] instanceof Object : false,
+        isArray = tagsItems instanceof Array,
+        isCollection = isArray && tagsItems[0] instanceof Object && "value" in tagsItems[0],
+        temp = [],
+        mapStringToCollection = function mapStringToCollection(s) {
+      return s.split(delimiters).filter(function (n) {
+        return n;
+      }).map(function (v) {
+        return {
+          value: v.trim()
+        };
+      });
+    }; // no need to continue if "tagsItems" is an Array of Objects
+
+
+    if (isCollection) {
+      var _ref2;
+
+      // iterate the collection items and check for values that can be splitted into multiple tags
+      tagsItems = (_ref2 = []).concat.apply(_ref2, _toConsumableArray(tagsItems.map(function (item) {
+        return mapStringToCollection(item.value).map(function (newItem) {
+          return _objectSpread({}, item, {}, newItem);
+        });
+      })));
+      return tagsItems;
+    }
+
+    if (typeof tagsItems == 'number') tagsItems = tagsItems.toString(); // if the value is a "simple" String, ex: "aaa, bbb, ccc"
+
+    if (typeof tagsItems == 'string') {
+      if (!tagsItems.trim()) return []; // go over each tag and add it (if there were multiple ones)
+
+      tagsItems = mapStringToCollection(tagsItems);
+    } else if (isArray) {
+      var _ref3;
+
+      tagsItems = (_ref3 = []).concat.apply(_ref3, _toConsumableArray(tagsItems.map(function (item) {
+        return mapStringToCollection(item);
+      })));
+    } // search if the tag exists in the whitelist as an Object (has props),
+    // to be able to use its properties
+
+
+    if (whitelistWithProps) {
+      tagsItems.forEach(function (item) {
+        // the "value" prop should preferably be unique
+        var matchObj = whitelist.filter(function (WL_item) {
+          return WL_item.value.toLowerCase() == item.value.toLowerCase();
+        });
+
+        if (matchObj[0]) {
+          temp.push(matchObj[0]); // set the Array (with the found Object) as the new value
+        } else if (mode != 'mix') temp.push(item);
+      });
+      tagsItems = temp;
+    }
+
+    return tagsItems;
+  },
+
+  /**
+   * Used to parse the initial value of a textarea (or input) element and gemerate mixed text w/ tags
+   * https://stackoverflow.com/a/57598892/104380
+   * @param {String} s
+   */
+  parseMixTags: function parseMixTags(s) {
+    var _this9 = this;
+
+    var _this$settings2 = this.settings,
+        mixTagsInterpolator = _this$settings2.mixTagsInterpolator,
+        duplicates = _this$settings2.duplicates,
+        transformTag = _this$settings2.transformTag,
+        enforceWhitelist = _this$settings2.enforceWhitelist;
+    s = s.split(mixTagsInterpolator[0]).map(function (s1, i) {
+      var s2 = s1.split(mixTagsInterpolator[1]),
+          preInterpolated = s2[0],
+          tagData,
+          tagElm;
+
+      try {
+        tagData = JSON.parse(preInterpolated);
+      } catch (err) {
+        tagData = _this9.normalizeTags(preInterpolated)[0]; //{value:preInterpolated}
+      }
+
+      if (s2.length > 1 && (!enforceWhitelist || _this9.isTagWhitelisted(tagData.value)) && !(!duplicates && _this9.isTagDuplicate(tagData))) {
+        transformTag.call(_this9, tagData);
+        tagElm = _this9.createTagElem(tagData);
+        s2[0] = tagElm.outerHTML; //+ "&#8288;"  // put a zero-space at the end so the caret won't jump back to the start (when the last input's child element is a tag)
+
+        _this9.value.push(tagData);
+      } else if (s1) return i ? mixTagsInterpolator[0] + s1 : s1;
+
+      return s2.join('');
+    }).join('');
+    this.DOM.input.innerHTML = s;
+    this.DOM.input.appendChild(document.createTextNode(''));
+    this.update();
+    return s;
+  },
+
+  /**
+   * For mixed-mode: replaces a text starting with a prefix with a wrapper element (tag or something)
+   * First there *has* to be a "this.state.tag" which is a string that was just typed and is staring with a prefix
+   */
+  replaceTextWithNode: function replaceTextWithNode(wrapperElm, tagString) {
+    if (!this.state.tag && !tagString) return;
+    tagString = tagString || this.state.tag.prefix + this.state.tag.value;
+    var idx,
+        replacedNode,
+        selection = window.getSelection(),
+        nodeAtCaret = selection.anchorNode; // ex. replace #ba with the tag "bart" where "|" is where the caret is:
+    // start with: "#ba #ba| #ba"
+    // split the text node at the index of the caret
+
+    nodeAtCaret.splitText(selection.anchorOffset); // "#ba #ba"
+    // get index of last occurence of "#ba"
+
+    idx = nodeAtCaret.nodeValue.lastIndexOf(tagString);
+    replacedNode = nodeAtCaret.splitText(idx); // clean up the tag's string and put tag element instead
+
+    replacedNode.nodeValue = replacedNode.nodeValue.replace(tagString, '');
+    nodeAtCaret.parentNode.insertBefore(wrapperElm, replacedNode);
+    this.DOM.input.normalize();
+    return replacedNode;
+  },
+
+  /**
+   * For selecting a single option (not used for multiple tags)
+   * @param {Object} tagElm   Tag DOM node
+   * @param {Object} tagData  Tag data
+   */
+  selectTag: function selectTag(tagElm, tagData) {
+    this.input.set.call(this, tagData.value, true);
+    setTimeout(this.setRangeAtStartEnd.bind(this));
+    if (this.getLastTag()) this.replaceTag(this.getLastTag(), tagData);else this.appendTag(tagElm);
+    this.value[0] = tagData;
+    this.trigger('add', {
+      tag: tagElm,
+      data: tagData
+    });
+    this.update();
+    return [tagElm];
+  },
+
+  /**
+   * add an empty "tag" element in an editable state
+   */
+  addEmptyTag: function addEmptyTag() {
+    var tagData = {
+      value: ""
+    },
+        tagElm = this.createTagElem(tagData); // add the tag to the component's DOM
+
+    this.appendTag(tagElm);
+    this.value.push(tagData);
+    this.update();
+    this.editTag(tagElm);
+  },
+
+  /**
+   * add a "tag" element to the "tags" component
+   * @param {String/Array} tagsItems   [A string (single or multiple values with a delimiter), or an Array of Objects or just Array of Strings]
+   * @param {Boolean}      clearInput  [flag if the input's value should be cleared after adding tags]
+   * @param {Boolean}      skipInvalid [do not add, mark & remove invalid tags]
+   * @return {Array} Array of DOM elements (tags)
+   */
+  addTags: function addTags(tagsItems, clearInput) {
+    var _this10 = this;
+
+    var skipInvalid = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : this.settings.skipInvalid;
+    var tagElems = [],
+        tagElm,
+        _s = this.settings;
+
+    if (!tagsItems || tagsItems.length == 0) {
+      // is mode is "select" clean all tags
+      if (_s.mode == 'select') this.removeAllTags();
+      return tagElems;
+    } // converts Array/String/Object to an Array of Objects
+
+
+    tagsItems = this.normalizeTags(tagsItems); // if in edit-mode, do not continue but instead replace the tag's text
+
+    if (this.state.editing.scope) {
+      return this.onEditTagDone(this.state.editing.scope, tagsItems[0]);
+    }
+
+    if (_s.mode == 'mix') {
+      _s.transformTag.call(this, tagsItems[0]);
+
+      tagElm = this.createTagElem(tagsItems[0]); // insert the new tag to the END if "addTags" was called from outside
+
+      if (!this.replaceTextWithNode(tagElm)) {
+        this.DOM.input.appendChild(tagElm);
+      } // fixes a firefox bug where if the last child of the input is a tag and not a text, the input cannot get focus (by Tab key)
+
+
+      this.DOM.input.appendChild(document.createTextNode(''));
+      tagsItems[0].prefix = tagsItems[0].prefix || this.state.tag ? this.state.tag.prefix : (_s.pattern.source || _s.pattern)[0];
+      this.value.push(tagsItems[0]);
+      this.update();
+      this.state.tag = null;
+      this.trigger('add', this.extend({}, {
+        tag: tagElm
+      }, {
+        data: tagsItems[0]
+      })); // fixes a firefox bug where if the last child of the input is a tag and not a text, the input cannot get focus (by Tab key)
+
+      this.DOM.input.appendChild(document.createTextNode(''));
+      return tagElm;
+    }
+
+    if (_s.mode == 'select') clearInput = false;
+    this.DOM.input.removeAttribute('style');
+    tagsItems.forEach(function (tagData) {
+      var tagValidation,
+          tagElm,
+          tagElmParams = {}; // shallow-clone tagData so later modifications will not apply to the source
+
+      tagData = Object.assign({}, tagData);
+
+      _s.transformTag.call(_this10, tagData); ///////////////// ( validation )//////////////////////
+
+
+      tagValidation = _this10.hasMaxTags() || _this10.validateTag(tagData.value);
+
+      if (tagValidation !== true) {
+        if (skipInvalid) return;
+        tagElmParams["aria-invalid"] = true;
+        tagElmParams["class"] = (tagData["class"] || '') + ' tagify--notAllowed';
+        tagElmParams.title = tagValidation;
+
+        _this10.markTagByValue(tagData.value);
+      } /////////////////////////////////////////////////////
+      // add accessibility attributes
+
+
+      tagElmParams.role = "tag";
+      if (tagData.readonly) tagElmParams["aria-readonly"] = true; // Create tag HTML element
+
+      tagElm = _this10.createTagElem(_this10.extend({}, tagData, tagElmParams));
+      tagElems.push(tagElm); // mode-select overrides
+
+      if (_s.mode == 'select') {
+        return _this10.selectTag(tagElm, tagData);
+      } // add the tag to the component's DOM
+
+
+      _this10.appendTag(tagElm);
+
+      if (tagValidation === true) {
+        // update state
+        _this10.value.push(tagData);
+
+        _this10.update();
+
+        _this10.trigger('add', {
+          tag: tagElm,
+          index: _this10.value.length - 1,
+          data: tagData
+        });
+      } else {
+        _this10.trigger("invalid", {
+          data: tagData,
+          index: _this10.value.length,
+          tag: tagElm,
+          message: tagValidation
+        });
+
+        if (!_s.keepInvalidTags) // remove invalid tags (if "keepInvalidTags" is set to "false")
+          setTimeout(function () {
+            return _this10.removeTag(tagElm, true);
+          }, 1000);
+      }
+
+      _this10.dropdown.position.call(_this10); // reposition the dropdown because the just-added tag might cause a new-line
+
+    });
+
+    if (tagsItems.length && clearInput) {
+      this.input.set.call(this);
+    }
+
+    this.dropdown.refilter.call(this);
+    return tagElems;
+  },
+
+  /**
+   * appened (validated) tag to the component's DOM scope
+   */
+  appendTag: function appendTag(tagElm) {
+    var insertBeforeNode = this.DOM.scope.lastElementChild;
+    if (insertBeforeNode === this.DOM.input) this.DOM.scope.insertBefore(tagElm, insertBeforeNode);else this.DOM.scope.appendChild(tagElm);
+  },
+
+  /**
+   * Removed new lines and irrelevant spaces which might affect layout, and are better gone
+   * @param {string} s [HTML string]
+   */
+  minify: function minify(s) {
+    return s ? s.replace(/\>[\r\n ]+\</g, "><").replace(/(<.*?>)|\s+/g, function (m, $1) {
+      return $1 ? $1 : ' ';
+    }) // https://stackoverflow.com/a/44841484/104380
+    : "";
+  },
+
+  /**
+   * creates a DOM tag element and injects it into the component (this.DOM.scope)
+   * @param  {Object}  tagData [text value & properties for the created tag]
+   * @return {Object} [DOM element]
+   */
+  createTagElem: function createTagElem(tagData) {
+    var tagElm,
+        v = this.escapeHTML(tagData.value),
+        template = this.settings.templates.tag.call(this, v, tagData);
+    if (this.settings.readonly) tagData.readonly = true;
+    template = this.minify(template);
+    tagElm = this.parseHTML(template);
+    return tagElm;
+  },
+
+  /**
+   * Removes a tag
+   * @param  {Object|String}  tagElm          [DOM element or a String value. if undefined or null, remove last added tag]
+   * @param  {Boolean}        silent          [A flag, which when turned on, does not removes any value and does not update the original input value but simply removes the tag from tagify]
+   * @param  {Number}         tranDuration    [Transition duration in MS]
+   */
+  removeTag: function removeTag(tagElm, silent, tranDuration) {
+    tagElm = tagElm || this.getLastTag();
+    tranDuration = tranDuration || this.CSSVars.tagHideTransition;
+    if (typeof tagElm == 'string') tagElm = this.getTagElmByValue(tagElm);
+    if (!(tagElm instanceof HTMLElement)) return;
+    var tagData,
+        that = this,
+        tagIdx = this.getNodeIndex(tagElm); // this.getTagIndexByValue(tagElm.textContent)
+
+    if (this.settings.mode == 'select') {
+      tranDuration = 0;
+      this.input.set.call(this);
+    }
+
+    if (tagElm.classList.contains('tagify--notAllowed')) silent = true;
+
+    function removeNode() {
+      if (!tagElm.parentNode) return;
+      tagElm.parentNode.removeChild(tagElm);
+
+      if (!silent) {
+        tagData = that.value.splice(tagIdx, 1)[0]; // remove the tag from the data object
+
+        that.update(); // update the original input with the current value
+
+        that.trigger('remove', {
+          tag: tagElm,
+          index: tagIdx,
+          data: tagData
+        });
+        that.dropdown.refilter.call(that);
+        that.dropdown.position.call(that);
+      } else if (that.settings.keepInvalidTags) that.trigger('remove', {
+        tag: tagElm,
+        index: tagIdx
+      });
+    }
+
+    function animation() {
+      tagElm.style.width = parseFloat(window.getComputedStyle(tagElm).width) + 'px';
+      document.body.clientTop; // force repaint for the width to take affect before the "hide" class below
+
+      tagElm.classList.add('tagify--hide'); // manual timeout (hack, since transitionend cannot be used because of hover)
+
+      setTimeout(removeNode, tranDuration);
+    }
+
+    if (tranDuration && tranDuration > 10) animation();else removeNode();
+  },
+  removeAllTags: function removeAllTags() {
+    this.value = [];
+    this.update();
+    Array.prototype.slice.call(this.getTagElms()).forEach(function (elm) {
+      return elm.parentNode.removeChild(elm);
+    });
+    this.dropdown.position.call(this);
+    if (this.settings.mode == 'select') this.input.set.call(this);
+  },
+  preUpdate: function preUpdate() {
+    this.DOM.scope.classList.toggle('tagify--hasMaxTags', this.value.length >= this.settings.maxTags);
+    this.DOM.scope.classList.toggle('tagify--noTags', !this.value.length);
+  },
+
+  /**
+   * update the origianl (hidden) input field's value
+   * see - https://stackoverflow.com/q/50957841/104380
+   */
+  update: function update() {
+    this.preUpdate();
+    this.DOM.originalInput.value = this.settings.mode == 'mix' ? this.getMixedTagsAsString() : this.value.length ? JSON.stringify(this.value) : "";
+  },
+  getMixedTagsAsString: function getMixedTagsAsString() {
+    var _this11 = this;
+
+    var result = "",
+        i = 0,
+        _interpolator = this.settings.mixTagsInterpolator;
+    this.DOM.input.childNodes.forEach(function (node) {
+      if (node.nodeType == 1 && node.classList.contains("tagify__tag")) result += _interpolator[0] + JSON.stringify(_this11.value[i++]) + _interpolator[1];else result += node.textContent;
+    });
+    return result;
+  },
+
+  /**
+   * Meassures an element's height, which might yet have been added DOM
+   * https://stackoverflow.com/q/5944038/104380
+   * @param {DOM} node
+   */
+  getNodeHeight: function getNodeHeight(node) {
+    var height,
+        clone = node.cloneNode(true);
+    clone.style.cssText = "position:fixed; top:-9999px; opacity:0";
+    document.body.appendChild(clone);
+    height = clone.clientHeight;
+    clone.parentNode.removeChild(clone);
+    return height;
+  },
+
+  /**
+   * Dropdown controller
+   * @type {Object}
+   */
+  dropdown: {
+    init: function init() {
+      this.DOM.dropdown = this.dropdown.build.call(this);
+      this.DOM.dropdown.content = this.DOM.dropdown.querySelector('.tagify__dropdown__wrapper');
+    },
+    build: function build() {
+      var _this$settings$dropdo = this.settings.dropdown,
+          position = _this$settings$dropdo.position,
+          classname = _this$settings$dropdo.classname,
+          _className = "".concat(position == 'manual' ? "" : "tagify__dropdown tagify__dropdown--".concat(position), " ").concat(classname).trim(),
+          elm = this.parseHTML("<div class=\"".concat(_className, "\" role=\"listbox\" aria-labelledby=\"dropdown\">\n                        <div class=\"tagify__dropdown__wrapper\"></div>\n                    </div>"));
+
+      return elm;
+    },
+    show: function show(value) {
+      var _this12 = this;
+
+      var listHTML,
+          _s = this.settings,
+          firstListItem,
+          firstListItemValue,
+          ddHeight,
+          isManual = _s.dropdown.position == 'manual';
+      if (!_s.whitelist || !_s.whitelist.length || _s.dropdown.enable === false) return; // if no value was supplied, show all the "whitelist" items in the dropdown
+      // @type [Array] listItems
+      // TODO: add a Setting to control items' sort order for "listItems"
+
+      this.suggestedListItems = this.dropdown.filterListItems.call(this, value); // hide suggestions list if no suggestions were matched
+
+      if (this.suggestedListItems.length) {
+        firstListItem = this.suggestedListItems[0];
+        firstListItemValue = firstListItem.value || firstListItem;
+
+        if (_s.autoComplete) {
+          // only fill the sugegstion if the value of the first list item STARTS with the input value (regardless of "fuzzysearch" setting)
+          if (firstListItemValue.indexOf(value) == 0) this.input.autocomplete.suggest.call(this, firstListItem);
+        }
+      } else {
+        this.input.autocomplete.suggest.call(this);
+        this.dropdown.hide.call(this);
+        return;
+      }
+
+      listHTML = this.dropdown.createListHTML.call(this, this.suggestedListItems);
+      this.DOM.dropdown.content.innerHTML = this.minify(listHTML); // if "enforceWhitelist" is "true", highlight the first suggested item
+
+      if (_s.enforceWhitelist && !isManual || _s.dropdown.highlightFirst) this.dropdown.highlightOption.call(this, this.DOM.dropdown.content.children[0]);
+      this.DOM.scope.setAttribute("aria-expanded", true);
+      this.trigger("dropdown:show", this.DOM.dropdown); // set the dropdown visible state to be the same as the searched value.
+      // MUST be set *before* position() is called
+
+      this.state.dropdown.visible = value || true;
+      this.dropdown.position.call(this); // if the dropdown has yet to be appended to the document,
+      // append the dropdown to the body element & handle events
+
+      if (!document.body.contains(this.DOM.dropdown)) {
+        if (!isManual) {
+          this.events.binding.call(this, false); // unbind the main events
+          // let the element render in the DOM first to accurately measure it
+          // this.DOM.dropdown.style.cssText = "left:-9999px; top:-9999px;";
+
+          ddHeight = this.getNodeHeight(this.DOM.dropdown);
+          this.DOM.dropdown.classList.add('tagify__dropdown--initial');
+          this.dropdown.position.call(this, ddHeight);
+          document.body.appendChild(this.DOM.dropdown);
+          setTimeout(function () {
+            return _this12.DOM.dropdown.classList.remove('tagify__dropdown--initial');
+          });
+        } // timeout is needed for when pressing arrow down to show the dropdown,
+        // so the key event won't get registered in the dropdown events listeners
+
+
+        setTimeout(this.dropdown.events.binding.bind(this));
+      }
+    },
+    hide: function hide(force) {
+      var _this$DOM = this.DOM,
+          scope = _this$DOM.scope,
+          dropdown = _this$DOM.dropdown,
+          isManual = this.settings.dropdown.position == 'manual' && !force;
+      if (!dropdown || !document.body.contains(dropdown) || isManual) return;
+      window.removeEventListener('resize', this.dropdown.position);
+      this.dropdown.events.binding.call(this, false); // unbind all events
+      // must delay because if the dropdown is open, and the input (scope) is clicked,
+      // the dropdown should be now closed, and the next click should re-open it,
+      // and without this timeout, clicking to close will re-open immediately
+
+      setTimeout(this.events.binding.bind(this), 250); // re-bind main events
+
+      scope.setAttribute("aria-expanded", false);
+      dropdown.parentNode.removeChild(dropdown);
+      this.state.dropdown.visible = false;
+      this.state.ddItemData = null;
+      this.state.ddItemElm = null;
+      this.trigger("dropdown:hide", dropdown);
+    },
+
+    /**
+     * fill data into the suggestions list (mainly used to update the list when removing tags, so they will be re-added to the list. not efficient)
+     */
+    refilter: function refilter() {
+      this.suggestedListItems = this.dropdown.filterListItems.call(this, '');
+      var listHTML = this.dropdown.createListHTML.call(this, this.suggestedListItems);
+      this.DOM.dropdown.content.innerHTML = this.minify(listHTML);
+    },
+    position: function position(ddHeight) {
+      var isBelowViewport,
+          rect,
+          top,
+          bottom,
+          left,
+          width,
+          ddElm = this.DOM.dropdown;
+      if (!this.state.dropdown.visible) return;
+
+      if (this.settings.dropdown.position == 'text') {
+        rect = this.getCaretGlobalPosition();
+        bottom = rect.bottom;
+        top = rect.top;
+        left = rect.left;
+        width = 'auto';
+      } else {
+        rect = this.DOM.scope.getBoundingClientRect();
+        top = rect.top;
+        bottom = rect.bottom - 1;
+        left = rect.left;
+        width = rect.width + "px";
+      }
+
+      top = Math.floor(top);
+      bottom = Math.ceil(bottom);
+      isBelowViewport = document.documentElement.clientHeight - bottom < (ddHeight || ddElm.clientHeight); // flip vertically if there is no space for the dropdown below the input
+
+      ddElm.style.cssText = "left:" + (left + window.pageXOffset) + "px; width:" + width + ";" + (isBelowViewport ? "bottom:" + (document.documentElement.clientHeight - top - window.pageYOffset - 2) + "px;" : "top: " + (bottom + window.pageYOffset) + "px");
+      ddElm.setAttribute('placement', isBelowViewport ? "top" : "bottom");
+    },
+    events: {
+      /**
+       * Events should only be binded when the dropdown is rendered and removed when isn't
+       * @param  {Boolean} bindUnbind [optional. true when wanting to unbind all the events]
+       */
+      binding: function binding() {
+        var bindUnbind = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
+
+        // references to the ".bind()" methods must be saved so they could be unbinded later
+        var _CB = this.dropdown.events.callbacks,
+            _CBR = this.listeners.dropdown = this.listeners.dropdown || {
+          position: this.dropdown.position.bind(this),
+          onKeyDown: _CB.onKeyDown.bind(this),
+          onMouseOver: _CB.onMouseOver.bind(this),
+          onMouseLeave: _CB.onMouseLeave.bind(this),
+          onClick: _CB.onClick.bind(this)
+        },
+            action = bindUnbind ? 'addEventListener' : 'removeEventListener';
+
+        if (this.settings.dropdown.position != 'manual') {
+          window[action]('resize', _CBR.position);
+          window[action]('keydown', _CBR.onKeyDown);
+        } //  window[action]('mousedown', _CBR.onClick);
+
+
+        this.DOM.dropdown[action]('mouseover', _CBR.onMouseOver);
+        this.DOM.dropdown[action]('mouseleave', _CBR.onMouseLeave);
+        this.DOM.dropdown[action]('mousedown', _CBR.onClick); // add back the main "click" event because it is needed for removing/clicking already-existing tags, even if dropdown is shown
+
+        this.DOM[this.listeners.main.click[0]][action]('click', this.listeners.main.click[1]);
+      },
+      callbacks: {
+        onKeyDown: function onKeyDown(e) {
+          // get the "active" element, and if there was none (yet) active, use first child
+          var activeListElm = this.DOM.dropdown.querySelector("[class$='--active']"),
+              selectedElm = activeListElm;
+
+          switch (e.key) {
+            case 'ArrowDown':
+            case 'ArrowUp':
+            case 'Down': // >IE11
+
+            case 'Up':
+              {
+                // >IE11
+                e.preventDefault();
+                var dropdownItems;
+                if (selectedElm) selectedElm = selectedElm[(e.key == 'ArrowUp' || e.key == 'Up' ? "previous" : "next") + "ElementSibling"]; // if no element was found, loop
+
+                if (!selectedElm) {
+                  dropdownItems = this.DOM.dropdown.content.children;
+                  selectedElm = dropdownItems[e.key == 'ArrowUp' || e.key == 'Up' ? dropdownItems.length - 1 : 0];
+                }
+
+                this.dropdown.highlightOption.call(this, selectedElm, true);
+                break;
+              }
+
+            case 'Escape':
+            case 'Esc':
+              // IE11
+              this.dropdown.hide.call(this);
+              break;
+
+            case 'ArrowRight':
+              if (this.state.actions.ArrowLeft) return;
+
+            case 'Tab':
+              {
+                e.preventDefault(); // in mix-mode, treat arrowRight like Enter key, so a tag will be created
+
+                if (this.settings.mode != 'mix' && !this.settings.autoComplete.rightKey) {
+                  try {
+                    var value = selectedElm ? selectedElm.textContent : this.suggestedListItems[0].value;
+                    this.input.autocomplete.set.call(this, value);
+                  } catch (err) {}
+
+                  return false;
+                }
+              }
+
+            case 'Enter':
+              {
+                e.preventDefault();
+                this.dropdown.selectOption.call(this, activeListElm);
+                break;
+              }
+
+            case 'Backspace':
+              {
+                if (this.settings.mode == 'mix' || this.state.editing.scope) return;
+
+                var _value = this.input.value.trim();
+
+                if (_value == "" || _value.charCodeAt(0) == 8203) {
+                  if (this.settings.backspace === true) this.removeTag();else if (this.settings.backspace == 'edit') setTimeout(this.editTag.bind(this), 0);
+                }
+              }
+          }
+        },
+        onMouseOver: function onMouseOver(e) {
+          var ddItem = e.target.closest('.tagify__dropdown__item'); // event delegation check
+
+          ddItem && this.dropdown.highlightOption.call(this, ddItem);
+        },
+        onMouseLeave: function onMouseLeave(e) {
+          // de-highlight any previously highlighted option
+          this.dropdown.highlightOption.call(this);
+        },
+        onClick: function onClick(e) {
+          if (e.button != 0 || e.target == this.DOM.dropdown) return; // allow only mouse left-clicks
+
+          var listItemElm = e.target.closest(".tagify__dropdown__item");
+          this.dropdown.selectOption.call(this, listItemElm);
+        }
+      }
+    },
+
+    /**
+     * mark the currently active suggestion option
+     * @param {Object}  elm            option DOM node
+     * @param {Boolean} adjustScroll   when navigation with keyboard arrows (up/down), aut-scroll to always show the highlighted element
+     */
+    highlightOption: function highlightOption(elm, adjustScroll) {
+      var className = "tagify__dropdown__item--active",
+          itemData; // focus casues a bug in Firefox with the placeholder been shown on the input element
+      // if( this.settings.dropdown.position != 'manual' )
+      //     elm.focus();
+
+      if (this.state.ddItemElm) {
+        this.state.ddItemElm.classList.remove(className);
+        this.state.ddItemElm.removeAttribute("aria-selected");
+      }
+
+      if (!elm) {
+        this.state.ddItemData = null;
+        this.state.ddItemElm = null;
+        this.input.autocomplete.suggest.call(this);
+        return;
+      }
+
+      itemData = this.suggestedListItems[this.getNodeIndex(elm)];
+      this.state.ddItemData = itemData;
+      this.state.ddItemElm = elm; // this.DOM.dropdown.querySelectorAll("[class$='--active']").forEach(activeElm => activeElm.classList.remove(className));
+
+      elm.classList.add(className);
+      elm.setAttribute("aria-selected", true);
+      if (adjustScroll) elm.parentNode.scrollTop = elm.clientHeight + elm.offsetTop - elm.parentNode.clientHeight; // Try to autocomplete the typed value with the currently highlighted dropdown item
+
+      if (this.settings.autoComplete) {
+        this.input.autocomplete.suggest.call(this, itemData);
+        if (this.settings.dropdown.position != 'manual') this.dropdown.position.call(this); // suggestions might alter the height of the tagify wrapper because of unkown suggested term length that could drop to the next line
+      }
+    },
+
+    /**
+     * Create a tag from the currently active suggestion option
+     * @param {Object} elm  DOM node to select
+     */
+    selectOption: function selectOption(elm) {
+      var _this13 = this;
+
+      if (!elm) return; // temporary set the "actions" state to indicate to the main "blur" event it shouldn't run
+
+      this.state.actions.selectOption = true;
+      setTimeout(function () {
+        return _this13.state.actions.selectOption = false;
+      }, 50);
+      var hideDropdown = this.settings.dropdown.closeOnSelect,
+          value = this.suggestedListItems[this.getNodeIndex(elm)] || this.input.value;
+      this.trigger("dropdown:select", value);
+      this.addTags([value], true); // Tagify instances should re-focus to the input element once an option was selected, to allow continuous typing
+
+      setTimeout(function () {
+        _this13.DOM.input.focus();
+
+        _this13.toggleFocusClass(true);
+      });
+
+      if (hideDropdown) {
+        this.dropdown.hide.call(this); // setTimeout(() => this.events.callbacks.onFocusBlur.call(this, {type:"blur"}), 60)
+      }
+    },
+
+    /**
+     * returns an HTML string of the suggestions' list items
+     * @param {string} value string to filter the whitelist by
+     * @return {Array} list of filtered whitelist items according to the settings provided and current value
+     */
+    filterListItems: function filterListItems(value) {
+      var _this14 = this;
+
+      var _s = this.settings,
+          list = [],
+          whitelist = _s.whitelist,
+          suggestionsCount = _s.dropdown.maxItems || Infinity,
+          searchKeys = _s.dropdown.searchKeys.concat(["searchBy", "value"]),
+          whitelistItem,
+          valueIsInWhitelist,
+          whitelistItemValueIndex,
+          searchBy,
+          isDuplicate,
+          i = 0;
+
+      if (!value) {
+        return (_s.duplicates ? whitelist : whitelist.filter(function (item) {
+          return !_this14.isTagDuplicate(item.value || item);
+        }) // don't include tags which have already been added.
+        ).slice(0, suggestionsCount); // respect "maxItems" dropdown setting
+      }
+
+      for (; i < whitelist.length; i++) {
+        whitelistItem = whitelist[i] instanceof Object ? whitelist[i] : {
+          value: whitelist[i]
+        }; //normalize value as an Object
+
+        searchBy = searchKeys.reduce(function (values, k) {
+          return values + " " + (whitelistItem[k] || "");
+        }, "").toLowerCase();
+        whitelistItemValueIndex = searchBy.indexOf(value.toLowerCase());
+        valueIsInWhitelist = _s.dropdown.fuzzySearch ? whitelistItemValueIndex >= 0 : whitelistItemValueIndex == 0;
+        isDuplicate = !_s.duplicates && this.isTagDuplicate(whitelistItem.value); // match for the value within each "whitelist" item
+
+        if (valueIsInWhitelist && !isDuplicate && suggestionsCount--) list.push(whitelistItem);
+        if (suggestionsCount == 0) break;
+      }
+
+      return list;
+    },
+
+    /**
+     * Creates the dropdown items' HTML
+     * @param  {Array} list  [Array of Objects]
+     * @return {String}
+     */
+    createListHTML: function createListHTML(optionsArr) {
+      var template = this.settings.templates.dropdownItem.bind(this);
+      return this.minify(optionsArr.map(template).join(""));
+    }
+  }
+};
+return Tagify;
+}));
diff --git a/server/user.jobengine.osgi.server/js/tagify.min.js b/server/user.jobengine.osgi.server/js/tagify.min.js
new file mode 100644 (file)
index 0000000..24c19a9
--- /dev/null
@@ -0,0 +1,7 @@
+/**
+ * Tagify (v 3.2.6)- tags input component
+ * By Yair Even-Or
+ * Don't sell this code. (c)
+ * https://github.com/yairEO/tagify
+ */
+!function(t,e){"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?module.exports=e():t.Tagify=e()}(this,function(){"use strict";function u(t){return function(t){if(Array.isArray(t)){for(var e=0,i=new Array(t.length);e<t.length;e++)i[e]=t[e];return i}}(t)||function(t){if(Symbol.iterator in Object(t)||"[object Arguments]"===Object.prototype.toString.call(t))return Array.from(t)}(t)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance")}()}function s(e,t){var i=Object.keys(e);if(Object.getOwnPropertySymbols){var s=Object.getOwnPropertySymbols(e);t&&(s=s.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),i.push.apply(i,s)}return i}function g(e){for(var t=1;t<arguments.length;t++){var i=null!=arguments[t]?arguments[t]:{};t%2?s(i,!0).forEach(function(t){n(e,t,i[t])}):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(i)):s(i).forEach(function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(i,t))})}return e}function n(t,e,i){return e in t?Object.defineProperty(t,e,{value:i,enumerable:!0,configurable:!0,writable:!0}):t[e]=i,t}function t(t,e){if(!t)return console.warn("Tagify: ","invalid input element ",t),this;this.applySettings(t,e||{}),this.state={editing:{},actions:{},dropdown:{}},this.value=[],this.listeners={},this.DOM={},this.extend(this,new this.EventDispatcher(this)),this.build(t),this.getCSSVars(),this.loadOriginalValues(),this.events.customBinding.call(this),this.events.binding.call(this),t.autofocus&&this.DOM.input.focus()}return t.prototype={isIE:window.document.documentMode,TEXTS:{empty:"empty",exceed:"number of tags exceeded",pattern:"pattern mismatch",duplicate:"already exists",notAllowed:"not allowed"},DEFAULTS:{delimiters:",",pattern:null,maxTags:1/0,callbacks:{},addTagOnBlur:!0,duplicates:!1,whitelist:[],blacklist:[],enforceWhitelist:!1,keepInvalidTags:!1,mixTagsAllowedAfter:/,|\.|\:|\s/,mixTagsInterpolator:["[[","]]"],backspace:!0,skipInvalid:!1,editTags:2,transformTag:function(){},autoComplete:{enabled:!0,rightKey:!1},dropdown:{classname:"",enabled:2,maxItems:10,searchKeys:[],fuzzySearch:!0,highlightFirst:!1,closeOnSelect:!0,position:"all"}},templates:{wrapper:function(t,e){return'<tags class="tagify '.concat(e.mode?"tagify--"+e.mode:""," ").concat(t.className,'"\n                        ').concat(e.readonly?'readonly aria-readonly="true"':'aria-haspopup="listbox" aria-expanded="false"','\n                        role="tagslist"\n                        tabIndex="-1">\n                <span contenteditable data-placeholder="').concat(e.placeholder||"&#8203;",'" aria-placeholder="').concat(e.placeholder||"",'"\n                    class="tagify__input"\n                    role="textbox"\n                    aria-controls="dropdown"\n                    aria-autocomplete="both"\n                    aria-multiline="').concat("mix"==e.mode,'"></span>\n            </tags>')},tag:function(t,e){return"<tag title='".concat(e.title||t,"'\n                        contenteditable='false'\n                        spellcheck='false'\n                        tabIndex=\"-1\"\n                        class='tagify__tag ").concat(e.class?e.class:"","'\n                        ").concat(this.getAttributes(e),">\n                <x title='' class='tagify__tag__removeBtn' role='button' aria-label='remove tag'></x>\n                <div>\n                    <span class='tagify__tag-text'>").concat(t,"</span>\n                </div>\n            </tag>")},dropdownItem:function(t){var e=this.settings.dropdown.mapValueTo,i=((e?"function"==typeof e?e(t):t[e]:t.value)||t.value||t).replace(/`|'/g,"&#39;");return"<div ".concat(this.getAttributes(t),"\n                        class='tagify__dropdown__item ").concat(t.class?t.class:"",'\'\n                        tabindex="0"\n                        role="option"\n                        aria-labelledby="dropdown-label">').concat(i,"</div>")}},customEventsList:["add","remove","invalid","input","click","keydown","focus","blur","edit:input","edit:updated","edit:start","edit:keydown","dropdown:show","dropdown:hide","dropdown:select"],applySettings:function(i,t){var s=this;if(this.DEFAULTS.templates=this.templates,this.settings=this.extend({},this.DEFAULTS,t),this.settings.readonly=i.hasAttribute("readonly"),this.settings.placeholder=i.getAttribute("placeholder")||this.settings.placeholder||"",this.isIE&&(this.settings.autoComplete=!1),["whitelist","blacklist"].forEach(function(t){var e=i.getAttribute("data-"+t);e&&(e=e.split(s.settings.delimiters))instanceof Array&&(s.settings[t]=e)}),"autoComplete"in t&&!this.isObject(t.autoComplete)&&(this.settings.autoComplete=this.DEFAULTS.autoComplete,this.settings.autoComplete.enabled=t.autoComplete),i.pattern)try{this.settings.pattern=new RegExp(i.pattern)}catch(t){}if(this.settings.delimiters)try{this.settings.delimiters=new RegExp(this.settings.delimiters,"g")}catch(t){}"select"==this.settings.mode&&(this.settings.dropdown.enabled=0),"mix"==this.settings.mode&&(this.settings.autoComplete.rightKey=!0)},getAttributes:function(t){if("[object Object]"!=Object.prototype.toString.call(t))return"";var e,i,s=Object.keys(t),n="";for(i=s.length;i--;)"class"!=(e=s[i])&&t.hasOwnProperty(e)&&t[e]&&(n+=" "+e+(t[e]?'="'.concat(t[e],'"'):""));return n},parseHTML:function(t){return(new DOMParser).parseFromString(t.trim(),"text/html").body.firstElementChild},escapeHTML:function(t){var e=document.createTextNode(t),i=document.createElement("p");return i.appendChild(e),i.innerHTML},getCaretGlobalPosition:function(){var t=document.getSelection();if(t.rangeCount){var e,i,s=t.getRangeAt(0),n=s.startContainer,a=s.startOffset;if(0<a)return(i=document.createRange()).setStart(n,a-1),i.setEnd(n,a),{left:(e=i.getBoundingClientRect()).right,top:e.top,bottom:e.bottom}}return{left:-9999,top:-9999}},getCSSVars:function(){var t,e,i,s=getComputedStyle(this.DOM.scope,null);this.CSSVars={tagHideTransition:(t=function(t){if(!t)return{};var e=(t=t.trim().split(" ")[0]).split(/\d+/g).filter(function(t){return t}).pop().trim();return{value:+t.split(e).filter(function(t){return t})[0].trim(),unit:e}}((i="tag-hide-transition",s.getPropertyValue("--"+i))),e=t.value,"s"==t.unit?1e3*e:e)}},build:function(t){var e=this.DOM,i=this.settings.templates.wrapper(t,this.settings);e.originalInput=t,e.scope=this.parseHTML(i),e.input=e.scope.querySelector("[contenteditable]"),t.parentNode.insertBefore(e.scope,t),0<=this.settings.dropdown.enabled&&this.dropdown.init.call(this)},destroy:function(){this.DOM.scope.parentNode.removeChild(this.DOM.scope),this.dropdown.hide.call(this,!0)},loadOriginalValues:function(t){if(t=t||this.DOM.originalInput.value)if(this.removeAllTags(),"mix"==this.settings.mode)this.parseMixTags(t.trim());else{try{"string"!=typeof JSON.parse(t)&&(t=JSON.parse(t))}catch(t){}this.addTags(t).forEach(function(t){return t&&t.classList.add("tagify--noAnim")})}},isObject:function(t){var e=Object.prototype.toString.call(t).split(" ")[1].slice(0,-1);return t===Object(t)&&"Array"!=e&&"Function"!=e&&"RegExp"!=e&&"HTMLUnknownElement"!=e},extend:function(t,e,i){var s=this;function n(t,e){for(var i in e)e.hasOwnProperty(i)&&(s.isObject(e[i])?s.isObject(t[i])?n(t[i],e[i]):t[i]=Object.assign({},e[i]):t[i]=e[i])}return t instanceof Object||(t={}),n(t,e),i&&n(t,i),t},cloneEvent:function(t){var e={};for(var i in t)e[i]=t[i];return e},EventDispatcher:function(s){var n=document.createTextNode("");function i(e,t,i){i&&t.split(/\s+/g).forEach(function(t){return n[e+"EventListener"].call(n,t,i)})}this.off=function(t,e){return i("remove",t,e),this},this.on=function(t,e){return e&&"function"==typeof e&&i("add",t,e),this},this.trigger=function(t,e){var i;if(t)if(s.settings.isJQueryPlugin)"remove"==t&&(t="removeTag"),jQuery(s.DOM.originalInput).triggerHandler(t,[e]);else{try{i=new CustomEvent(t,{detail:this.extend({},e,{tagify:this})})}catch(t){console.warn(t)}n.dispatchEvent(i)}}},loading:function(t){return this.DOM.scope.classList[t?"add":"remove"]("tagify--loading"),this},toggleFocusClass:function(t){this.DOM.scope.classList.toggle("tagify--focus",!!t)},events:{customBinding:function(){var e=this;this.customEventsList.forEach(function(t){e.on(t,e.settings.callbacks[t])})},binding:function(t){var e,i=!(0<arguments.length&&void 0!==t)||t,s=this.events.callbacks,n=i?"addEventListener":"removeEventListener";if(!this.state.mainEvents||!i)for(var a in(this.state.mainEvents=i)&&!this.listeners.main&&(this.DOM.input.addEventListener(this.isIE?"keydown":"input",s[this.isIE?"onInputIE":"onInput"].bind(this)),this.settings.isJQueryPlugin&&jQuery(this.DOM.originalInput).on("tagify.removeAllTags",this.removeAllTags.bind(this))),e=this.listeners.main=this.listeners.main||{focus:["input",s.onFocusBlur.bind(this)],blur:["input",s.onFocusBlur.bind(this)],keydown:["input",s.onKeydown.bind(this)],click:["scope",s.onClickScope.bind(this)],dblclick:["scope",s.onDoubleClickScope.bind(this)]}){if("blur"==a&&!i)return;this.DOM[e[a][0]][n](a,e[a][1])}},callbacks:{onFocusBlur:function(t){var e=t.target?t.target.textContent.trim():"",i=this.settings,s=t.type;if(!(t.relatedTarget&&t.relatedTarget.classList.contains("tagify__tag")&&this.DOM.scope.contains(t.relatedTarget))){if("blur"==s&&t.relatedTarget===this.DOM.scope)return this.dropdown.hide.call(this),void this.DOM.input.focus();if(!this.state.actions.selectOption||!i.dropdown.enabled&&i.dropdown.closeOnSelect)if(this.state.hasFocus="focus"==s&&+new Date,this.toggleFocusClass(this.state.hasFocus),this.setRangeAtStartEnd(!1),"mix"!=i.mode){if("focus"==s)return this.trigger("focus",{relatedTarget:t.relatedTarget}),void(0===i.dropdown.enabled&&"select"!=i.mode&&this.dropdown.show.call(this));"blur"==s&&(this.trigger("blur",{relatedTarget:t.relatedTarget}),this.loading(!1),e&&!this.state.actions.selectOption&&i.addTagOnBlur&&this.addTags(e,!0)),this.DOM.input.removeAttribute("style"),this.dropdown.hide.call(this)}else"blur"==t.type&&this.dropdown.hide.call(this)}},onKeydown:function(t){var e,i=this,s=t.target.textContent.trim();if(this.trigger("keydown",{originalEvent:this.cloneEvent(t)}),"mix"==this.settings.mode){switch(t.key){case"Left":case"ArrowLeft":this.state.actions.ArrowLeft=!0;break;case"Delete":case"Backspace":var n=document.getSelection();!!navigator.userAgent.match(/firefox/i)&&n&&0==n.anchorOffset&&this.removeTag(n.anchorNode.previousSibling);var a=[];e=this.DOM.input.children,setTimeout(function(){[].forEach.call(e,function(t){return a.push(t.getAttribute("value"))}),i.value=i.value.filter(function(t){return-1!=a.indexOf(t.value)})})}return!0}switch(t.key){case"Backspace":""!=s&&8203!=s.charCodeAt(0)||(!0===this.settings.backspace?this.removeTag():"edit"==this.settings.backspace&&setTimeout(this.editTag.bind(this),0));break;case"Esc":case"Escape":if(this.state.dropdown.visible)return;t.target.blur();break;case"Down":case"ArrowDown":this.state.dropdown.visible||this.dropdown.show.call(this);break;case"ArrowRight":var o=this.state.inputSuggestion||this.state.ddItemData;if(o&&this.settings.autoComplete.rightKey)return void this.addTags([o],!0);break;case"Tab":if(!s)return!0;case"Enter":t.preventDefault(),setTimeout(function(){i.state.actions.selectOption||i.addTags(s,!0)})}},onInput:function(t){var e="mix"==this.settings.mode?this.DOM.input.textContent:this.input.normalize.call(this),i=e.length>=this.settings.dropdown.enabled,s={value:e,inputElm:this.DOM.input};if("mix"==this.settings.mode)return this.events.callbacks.onMixTagsInput.call(this,t);e?this.input.value!=e&&(s.isValid=this.validateTag(e),this.trigger("input",s),this.input.set.call(this,e,!1),-1!=e.search(this.settings.delimiters)?this.addTags(e)&&this.input.set.call(this):0<=this.settings.dropdown.enabled&&this.dropdown[i?"show":"hide"].call(this,e)):this.input.set.call(this,"")},onMixTagsInput:function(){var t,e,i,s,n,a=this,o=this.settings;if(this.hasMaxTags())return!0;window.getSelection&&0<(t=window.getSelection()).rangeCount&&((e=t.getRangeAt(0).cloneRange()).collapse(!0),e.setStart(window.getSelection().focusNode,0),(s=(i=e.toString().split(o.mixTagsAllowedAfter))[i.length-1].match(o.pattern))&&(this.state.actions.ArrowLeft=!1,this.state.tag={prefix:s[0],value:s.input.split(s[0])[1]},n=this.state.tag.value.length>=o.dropdown.enabled)),this.update(),setTimeout(function(){a.trigger("input",a.extend({},a.state.tag,{textContent:a.DOM.input.textContent})),a.state.tag&&a.dropdown[n?"show":"hide"].call(a,a.state.tag.value)},10)},onInputIE:function(t){var e=this;setTimeout(function(){e.events.callbacks.onInput.call(e,t)})},onClickScope:function(t){var e,i=t.target.closest(".tagify__tag"),s=this.settings,n=new Date-this.state.hasFocus;if(t.target!=this.DOM.scope){if(!t.target.classList.contains("tagify__tag__removeBtn"))return i?(e=this.getNodeIndex(i),this.trigger("click",{tag:i,index:e,data:this.value[e],originalEvent:this.cloneEvent(t)}),void(1==this.settings.editTags&&this.events.callbacks.onDoubleClickScope.call(this,t))):void(t.target==this.DOM.input&&500<n?this.state.dropdown.visible?this.dropdown.hide.call(this):0===s.dropdown.enabled&&"mix"!=s.mode&&this.dropdown.show.call(this):"select"==s.mode&&(this.state.dropdown.visible||this.dropdown.show.call(this)));this.removeTag(t.target.parentNode)}else this.DOM.input.focus()},onEditTagInput:function(t,e){var i=t.closest("tag"),s=this.getNodeIndex(i),n=this.input.normalize.call(this,t),a=n.toLowerCase()==t.originalValue.toLowerCase()||this.validateTag(n);i.classList.toggle("tagify--invalid",!0!==a),i.isValid=a,n.length>=this.settings.dropdown.enabled&&(this.state.editing.value=n,this.dropdown.show.call(this,n)),this.trigger("edit:input",{tag:i,index:s,data:this.extend({},this.value[s],{newValue:n}),originalEvent:this.cloneEvent(e)})},onEditTagBlur:function(t){if(this.state.hasFocus||this.toggleFocusClass(),this.DOM.scope.contains(t)){var e=t.closest(".tagify__tag"),i=this.getNodeIndex(e),s=this.input.normalize.call(this,t),n=s||t.originalValue,a=n!=t.originalValue,o=e.isValid,r=g({},this.value[i],{value:n});s?a?(this.settings.transformTag.call(this,r),void 0!==(o=this.validateTag(r.value))&&!0!==o||this.onEditTagDone(e,r)):this.onEditTagDone(e):this.removeTag(e)}},onEditTagkeydown:function(t){switch(this.trigger("edit:keydown",{originalEvent:this.cloneEvent(t)}),t.key){case"Esc":case"Escape":t.target.textContent=t.target.originalValue;case"Enter":case"Tab":t.preventDefault(),t.target.blur()}},onDoubleClickScope:function(t){var e,i,s=t.target.closest("tag"),n=this.settings;s&&(e=s.classList.contains("tagify__tag--editable"),i=s.hasAttribute("readonly"),"select"==n.mode||n.readonly||e||i||!this.settings.editTags||this.editTag(s),this.toggleFocusClass(!0))}}},editTag:function(t){var e=this,i=0<arguments.length&&void 0!==t?t:this.getLastTag(),s=i.querySelector(".tagify__tag-text"),n=this.getNodeIndex(i),a=this.value[n],o=this.events.callbacks,r=this;if(s){if(!("editable"in a)||a.editable)return i.classList.add("tagify__tag--editable"),s.originalValue=s.textContent,s.setAttribute("contenteditable",!0),s.addEventListener("blur",function(){setTimeout(o.onEditTagBlur.bind(r),0,s)}),s.addEventListener("input",o.onEditTagInput.bind(this,s)),s.addEventListener("keydown",function(t){return o.onEditTagkeydown.call(e,t)}),s.focus(),this.setRangeAtStartEnd(!1,s),this.state.editing={scope:i,input:i.querySelector("[contenteditable]")},this.trigger("edit:start",{tag:i,index:n,data:a}),this}else console.warn("Cannot find element in Tag template: ",".tagify__tag-text")},onEditTagDone:function(t,e){var i={tag:t,index:this.getNodeIndex(t),data:e};this.trigger("edit:beforeUpdate",i),this.replaceTag(t,e),this.trigger("edit:updated",i)},replaceTag:function(t,e){var i=this,s=t.querySelector(".tagify__tag-text"),n=s.cloneNode(!0),a=this.getNodeIndex(t);this.state.editing.locked||(this.state.editing={locked:!0},setTimeout(function(){return delete i.state.editing.locked},500),n.removeAttribute("contenteditable"),t.classList.remove("tagify__tag--editable"),s.parentNode.replaceChild(n,s),e&&(n.innerHTML=e.value,n.title=e.value,this.value[a]=e,this.update()))},setRangeAtStartEnd:function(e,i){i=(i=i||this.DOM.input).lastChild||i;var s=document.getSelection();s.rangeCount&&["Start","End"].forEach(function(t){return s.getRangeAt(0)["set"+t](i,e?0:i.length)})},input:{value:"",set:function(t,e){var i=0<arguments.length&&void 0!==t?t:"",s=!(1<arguments.length&&void 0!==e)||e,n=this.settings.dropdown.closeOnSelect;this.input.value=i,s&&(this.DOM.input.innerHTML=i),!i&&n&&setTimeout(this.dropdown.hide.bind(this),20),this.input.autocomplete.suggest.call(this),this.input.validate.call(this)},validate:function(){var t=!this.input.value||this.validateTag(this.input.value);"select"==this.settings.mode?this.DOM.scope.classList.toggle("tagify--invalid",!0!==t):this.DOM.input.classList.toggle("tagify__input--invalid",!0!==t)},normalize:function(t){var e=t||this.DOM.input,i=[];e.childNodes.forEach(function(t){return 3==t.nodeType&&i.push(t.nodeValue)}),i=i.join("\n");try{i=i.replace(/(?:\r\n|\r|\n)/g,this.settings.delimiters.source.charAt(0))}catch(t){}return i=i.replace(/\s/g," ").replace(/^\s+/,"")},autocomplete:{suggest:function(t){if(this.settings.autoComplete.enabled){"string"==typeof(t=t||{})&&(t={value:t});var e=t.value||"",i=e.substr(0,this.input.value.length).toLowerCase(),s=e.substring(this.input.value.length);e&&this.input.value&&i==this.input.value.toLowerCase()?(this.DOM.input.setAttribute("data-suggest",s),this.state.inputSuggestion=t):(this.DOM.input.removeAttribute("data-suggest"),delete this.state.inputSuggestion)}},set:function(t){var e=this.DOM.input.getAttribute("data-suggest"),i=t||(e?this.input.value+e:null);return!!i&&("mix"==this.settings.mode?this.replaceTextWithNode(document.createTextNode(this.state.tag.prefix+i)):(this.input.set.call(this,i),this.setRangeAtStartEnd()),this.input.autocomplete.suggest.call(this),this.dropdown.hide.call(this),!0)}}},getNodeIndex:function(t){var e=0;if(t)for(;t=t.previousElementSibling;)e++;return e},getTagElms:function(){return this.DOM.scope.querySelectorAll(".tagify__tag")},getLastTag:function(){var t=this.DOM.scope.querySelectorAll("tag:not(.tagify--hide):not([readonly])");return t[t.length-1]},isTagDuplicate:function(e){var i=this;return"select"!=this.settings.mode&&this.value.some(function(t){return i.isObject(e)?JSON.stringify(t).toLowerCase()===JSON.stringify(e).toLowerCase():e.trim().toLowerCase()===t.value.toLowerCase()})},getTagIndexByValue:function(i){var s=[];return this.getTagElms().forEach(function(t,e){t.textContent.trim().toLowerCase()==i.toLowerCase()&&s.push(e)}),s},getTagElmByValue:function(t){var e=this.getTagIndexByValue(t)[0];return this.getTagElms()[e]},markTagByValue:function(t,e){return!!(e=e||this.getTagElmByValue(t))&&(e.classList.add("tagify--mark"),e)},isTagBlacklisted:function(e){return e=e.toLowerCase().trim(),this.settings.blacklist.filter(function(t){return e==t.toLowerCase()}).length},isTagWhitelisted:function(e){return this.settings.whitelist.some(function(t){return"string"==typeof e?e.trim().toLowerCase()===(t.value||t).toLowerCase():JSON.stringify(t).toLowerCase()===JSON.stringify(e).toLowerCase()})},validateTag:function(t){var e=t.trim(),i=this.settings,s=!0;return e?i.pattern&&!i.pattern.test(e)?s=this.TEXTS.pattern:!i.duplicates&&this.isTagDuplicate(e)?s=this.TEXTS.duplicate:(this.isTagBlacklisted(e)||i.enforceWhitelist&&!this.isTagWhitelisted(e))&&(s=this.TEXTS.notAllowed):s=this.TEXTS.empty,s},hasMaxTags:function(){return this.value.length>=this.settings.maxTags&&this.TEXTS.exceed},normalizeTags:function(t){function i(t){return t.split(a).filter(function(t){return t}).map(function(t){return{value:t.trim()}})}var e,s=this.settings,n=s.whitelist,a=s.delimiters,o=s.mode,r=!!n&&n[0]instanceof Object,l=t instanceof Array,d=l&&t[0]instanceof Object&&"value"in t[0],c=[];if(d)return t=(e=[]).concat.apply(e,u(t.map(function(e){return i(e.value).map(function(t){return g({},e,{},t)})})));if("number"==typeof t&&(t=t.toString()),"string"==typeof t){if(!t.trim())return[];t=i(t)}else if(l){var h;t=(h=[]).concat.apply(h,u(t.map(function(t){return i(t)})))}return r&&(t.forEach(function(e){var t=n.filter(function(t){return t.value.toLowerCase()==e.value.toLowerCase()});t[0]?c.push(t[0]):"mix"!=o&&c.push(e)}),t=c),t},parseMixTags:function(t){var o=this,e=this.settings,r=e.mixTagsInterpolator,l=e.duplicates,d=e.transformTag,c=e.enforceWhitelist;return t=t.split(r[0]).map(function(t,e){var i,s,n=t.split(r[1]),a=n[0];try{i=JSON.parse(a)}catch(t){i=o.normalizeTags(a)[0]}if(!(1<n.length)||c&&!o.isTagWhitelisted(i.value)||!l&&o.isTagDuplicate(i)){if(t)return e?r[0]+t:t}else d.call(o,i),s=o.createTagElem(i),n[0]=s.outerHTML,o.value.push(i);return n.join("")}).join(""),this.DOM.input.innerHTML=t,this.DOM.input.appendChild(document.createTextNode("")),this.update(),t},replaceTextWithNode:function(t,e){if(this.state.tag||e){e=e||this.state.tag.prefix+this.state.tag.value;var i,s,n=window.getSelection(),a=n.anchorNode;return a.splitText(n.anchorOffset),i=a.nodeValue.lastIndexOf(e),(s=a.splitText(i)).nodeValue=s.nodeValue.replace(e,""),a.parentNode.insertBefore(t,s),this.DOM.input.normalize(),s}},selectTag:function(t,e){return this.input.set.call(this,e.value,!0),setTimeout(this.setRangeAtStartEnd.bind(this)),this.getLastTag()?this.replaceTag(this.getLastTag(),e):this.appendTag(t),this.value[0]=e,this.trigger("add",{tag:t,data:e}),this.update(),[t]},addEmptyTag:function(){var t={value:""},e=this.createTagElem(t);this.appendTag(e),this.value.push(t),this.update(),this.editTag(e)},addTags:function(t,e,i){var s,n=this,a=2<arguments.length&&void 0!==i?i:this.settings.skipInvalid,o=[],r=this.settings;return t&&0!=t.length?(t=this.normalizeTags(t),this.state.editing.scope?this.onEditTagDone(this.state.editing.scope,t[0]):"mix"==r.mode?(r.transformTag.call(this,t[0]),s=this.createTagElem(t[0]),this.replaceTextWithNode(s)||this.DOM.input.appendChild(s),this.DOM.input.appendChild(document.createTextNode("")),t[0].prefix=t[0].prefix||this.state.tag?this.state.tag.prefix:(r.pattern.source||r.pattern)[0],this.value.push(t[0]),this.update(),this.state.tag=null,this.trigger("add",this.extend({},{tag:s},{data:t[0]})),this.DOM.input.appendChild(document.createTextNode("")),s):("select"==r.mode&&(e=!1),this.DOM.input.removeAttribute("style"),t.forEach(function(t){var e,i,s={};if(t=Object.assign({},t),r.transformTag.call(n,t),!0!==(e=n.hasMaxTags()||n.validateTag(t.value))){if(a)return;s["aria-invalid"]=!0,s.class=(t.class||"")+" tagify--notAllowed",s.title=e,n.markTagByValue(t.value)}if(s.role="tag",t.readonly&&(s["aria-readonly"]=!0),i=n.createTagElem(n.extend({},t,s)),o.push(i),"select"==r.mode)return n.selectTag(i,t);n.appendTag(i),!0===e?(n.value.push(t),n.update(),n.trigger("add",{tag:i,index:n.value.length-1,data:t})):(n.trigger("invalid",{data:t,index:n.value.length,tag:i,message:e}),r.keepInvalidTags||setTimeout(function(){return n.removeTag(i,!0)},1e3)),n.dropdown.position.call(n)}),t.length&&e&&this.input.set.call(this),this.dropdown.refilter.call(this),o)):("select"==r.mode&&this.removeAllTags(),o)},appendTag:function(t){var e=this.DOM.scope.lastElementChild;e===this.DOM.input?this.DOM.scope.insertBefore(t,e):this.DOM.scope.appendChild(t)},minify:function(t){return t?t.replace(/\>[\r\n ]+\</g,"><").replace(/(<.*?>)|\s+/g,function(t,e){return e||" "}):""},createTagElem:function(t){var e=this.escapeHTML(t.value),i=this.settings.templates.tag.call(this,e,t);return this.settings.readonly&&(t.readonly=!0),i=this.minify(i),this.parseHTML(i)},removeTag:function(t,e,i){if(t=t||this.getLastTag(),i=i||this.CSSVars.tagHideTransition,"string"==typeof t&&(t=this.getTagElmByValue(t)),t instanceof HTMLElement){var s,n=this,a=this.getNodeIndex(t);"select"==this.settings.mode&&(i=0,this.input.set.call(this)),t.classList.contains("tagify--notAllowed")&&(e=!0),i&&10<i?(t.style.width=parseFloat(window.getComputedStyle(t).width)+"px",document.body.clientTop,t.classList.add("tagify--hide"),setTimeout(o,i)):o()}function o(){t.parentNode&&(t.parentNode.removeChild(t),e?n.settings.keepInvalidTags&&n.trigger("remove",{tag:t,index:a}):(s=n.value.splice(a,1)[0],n.update(),n.trigger("remove",{tag:t,index:a,data:s}),n.dropdown.refilter.call(n),n.dropdown.position.call(n)))}},removeAllTags:function(){this.value=[],this.update(),Array.prototype.slice.call(this.getTagElms()).forEach(function(t){return t.parentNode.removeChild(t)}),this.dropdown.position.call(this),"select"==this.settings.mode&&this.input.set.call(this)},preUpdate:function(){this.DOM.scope.classList.toggle("tagify--hasMaxTags",this.value.length>=this.settings.maxTags),this.DOM.scope.classList.toggle("tagify--noTags",!this.value.length)},update:function(){this.preUpdate(),this.DOM.originalInput.value="mix"==this.settings.mode?this.getMixedTagsAsString():this.value.length?JSON.stringify(this.value):""},getMixedTagsAsString:function(){var e=this,i="",s=0,n=this.settings.mixTagsInterpolator;return this.DOM.input.childNodes.forEach(function(t){1==t.nodeType&&t.classList.contains("tagify__tag")?i+=n[0]+JSON.stringify(e.value[s++])+n[1]:i+=t.textContent}),i},getNodeHeight:function(t){var e,i=t.cloneNode(!0);return i.style.cssText="position:fixed; top:-9999px; opacity:0",document.body.appendChild(i),e=i.clientHeight,i.parentNode.removeChild(i),e},dropdown:{init:function(){this.DOM.dropdown=this.dropdown.build.call(this),this.DOM.dropdown.content=this.DOM.dropdown.querySelector(".tagify__dropdown__wrapper")},build:function(){var t=this.settings.dropdown,e=t.position,i=t.classname,s="".concat("manual"==e?"":"tagify__dropdown tagify__dropdown--".concat(e)," ").concat(i).trim();return this.parseHTML('<div class="'.concat(s,'" role="listbox" aria-labelledby="dropdown">\n                        <div class="tagify__dropdown__wrapper"></div>\n                    </div>'))},show:function(t){var e,i,s,n,a=this,o=this.settings,r="manual"==o.dropdown.position;if(o.whitelist&&o.whitelist.length&&!1!==o.dropdown.enable){if(this.suggestedListItems=this.dropdown.filterListItems.call(this,t),!this.suggestedListItems.length)return this.input.autocomplete.suggest.call(this),void this.dropdown.hide.call(this);s=(i=this.suggestedListItems[0]).value||i,o.autoComplete&&0==s.indexOf(t)&&this.input.autocomplete.suggest.call(this,i),e=this.dropdown.createListHTML.call(this,this.suggestedListItems),this.DOM.dropdown.content.innerHTML=this.minify(e),(o.enforceWhitelist&&!r||o.dropdown.highlightFirst)&&this.dropdown.highlightOption.call(this,this.DOM.dropdown.content.children[0]),this.DOM.scope.setAttribute("aria-expanded",!0),this.trigger("dropdown:show",this.DOM.dropdown),this.state.dropdown.visible=t||!0,this.dropdown.position.call(this),document.body.contains(this.DOM.dropdown)||(r||(this.events.binding.call(this,!1),n=this.getNodeHeight(this.DOM.dropdown),this.DOM.dropdown.classList.add("tagify__dropdown--initial"),this.dropdown.position.call(this,n),document.body.appendChild(this.DOM.dropdown),setTimeout(function(){return a.DOM.dropdown.classList.remove("tagify__dropdown--initial")})),setTimeout(this.dropdown.events.binding.bind(this)))}},hide:function(t){var e=this.DOM,i=e.scope,s=e.dropdown,n="manual"==this.settings.dropdown.position&&!t;s&&document.body.contains(s)&&!n&&(window.removeEventListener("resize",this.dropdown.position),this.dropdown.events.binding.call(this,!1),setTimeout(this.events.binding.bind(this),250),i.setAttribute("aria-expanded",!1),s.parentNode.removeChild(s),this.state.dropdown.visible=!1,this.state.ddItemData=null,this.state.ddItemElm=null,this.trigger("dropdown:hide",s))},refilter:function(){this.suggestedListItems=this.dropdown.filterListItems.call(this,"");var t=this.dropdown.createListHTML.call(this,this.suggestedListItems);this.DOM.dropdown.content.innerHTML=this.minify(t)},position:function(t){var e,i,s,n,a,o,r=this.DOM.dropdown;this.state.dropdown.visible&&(o="text"==this.settings.dropdown.position?(n=(i=this.getCaretGlobalPosition()).bottom,s=i.top,a=i.left,"auto"):(s=(i=this.DOM.scope.getBoundingClientRect()).top,n=i.bottom-1,a=i.left,i.width+"px"),s=Math.floor(s),n=Math.ceil(n),e=document.documentElement.clientHeight-n<(t||r.clientHeight),r.style.cssText="left:"+(a+window.pageXOffset)+"px; width:"+o+";"+(e?"bottom:"+(document.documentElement.clientHeight-s-window.pageYOffset-2)+"px;":"top: "+(n+window.pageYOffset)+"px"),r.setAttribute("placement",e?"top":"bottom"))},events:{binding:function(t){var e=!(0<arguments.length&&void 0!==t)||t,i=this.dropdown.events.callbacks,s=this.listeners.dropdown=this.listeners.dropdown||{position:this.dropdown.position.bind(this),onKeyDown:i.onKeyDown.bind(this),onMouseOver:i.onMouseOver.bind(this),onMouseLeave:i.onMouseLeave.bind(this),onClick:i.onClick.bind(this)},n=e?"addEventListener":"removeEventListener";"manual"!=this.settings.dropdown.position&&(window[n]("resize",s.position),window[n]("keydown",s.onKeyDown)),this.DOM.dropdown[n]("mouseover",s.onMouseOver),this.DOM.dropdown[n]("mouseleave",s.onMouseLeave),this.DOM.dropdown[n]("mousedown",s.onClick),this.DOM[this.listeners.main.click[0]][n]("click",this.listeners.main.click[1])},callbacks:{onKeyDown:function(t){var e=this.DOM.dropdown.querySelector("[class$='--active']"),i=e;switch(t.key){case"ArrowDown":case"ArrowUp":case"Down":case"Up":var s;t.preventDefault(),i=(i=i&&i[("ArrowUp"==t.key||"Up"==t.key?"previous":"next")+"ElementSibling"])||(s=this.DOM.dropdown.content.children)["ArrowUp"==t.key||"Up"==t.key?s.length-1:0],this.dropdown.highlightOption.call(this,i,!0);break;case"Escape":case"Esc":this.dropdown.hide.call(this);break;case"ArrowRight":if(this.state.actions.ArrowLeft)return;case"Tab":if(t.preventDefault(),"mix"!=this.settings.mode&&!this.settings.autoComplete.rightKey){try{var n=i?i.textContent:this.suggestedListItems[0].value;this.input.autocomplete.set.call(this,n)}catch(t){}return!1}case"Enter":t.preventDefault(),this.dropdown.selectOption.call(this,e);break;case"Backspace":if("mix"==this.settings.mode||this.state.editing.scope)return;var a=this.input.value.trim();""!=a&&8203!=a.charCodeAt(0)||(!0===this.settings.backspace?this.removeTag():"edit"==this.settings.backspace&&setTimeout(this.editTag.bind(this),0))}},onMouseOver:function(t){var e=t.target.closest(".tagify__dropdown__item");e&&this.dropdown.highlightOption.call(this,e)},onMouseLeave:function(){this.dropdown.highlightOption.call(this)},onClick:function(t){if(0==t.button&&t.target!=this.DOM.dropdown){var e=t.target.closest(".tagify__dropdown__item");this.dropdown.selectOption.call(this,e)}}}},highlightOption:function(t,e){var i,s="tagify__dropdown__item--active";if(this.state.ddItemElm&&(this.state.ddItemElm.classList.remove(s),this.state.ddItemElm.removeAttribute("aria-selected")),!t)return this.state.ddItemData=null,this.state.ddItemElm=null,void this.input.autocomplete.suggest.call(this);i=this.suggestedListItems[this.getNodeIndex(t)],this.state.ddItemData=i,(this.state.ddItemElm=t).classList.add(s),t.setAttribute("aria-selected",!0),e&&(t.parentNode.scrollTop=t.clientHeight+t.offsetTop-t.parentNode.clientHeight),this.settings.autoComplete&&(this.input.autocomplete.suggest.call(this,i),"manual"!=this.settings.dropdown.position&&this.dropdown.position.call(this))},selectOption:function(t){var e=this;if(t){this.state.actions.selectOption=!0,setTimeout(function(){return e.state.actions.selectOption=!1},50);var i=this.settings.dropdown.closeOnSelect,s=this.suggestedListItems[this.getNodeIndex(t)]||this.input.value;this.trigger("dropdown:select",s),this.addTags([s],!0),setTimeout(function(){e.DOM.input.focus(),e.toggleFocusClass(!0)}),i&&this.dropdown.hide.call(this)}},filterListItems:function(t){var i,e,s,n,a=this,o=this.settings,r=[],l=o.whitelist,d=o.dropdown.maxItems||1/0,c=o.dropdown.searchKeys.concat(["searchBy","value"]),h=0;if(!t)return(o.duplicates?l:l.filter(function(t){return!a.isTagDuplicate(t.value||t)})).slice(0,d);for(;h<l.length&&(i=l[h]instanceof Object?l[h]:{value:l[h]},s=c.reduce(function(t,e){return t+" "+(i[e]||"")},"").toLowerCase().indexOf(t.toLowerCase()),e=o.dropdown.fuzzySearch?0<=s:0==s,n=!o.duplicates&&this.isTagDuplicate(i.value),e&&!n&&d--&&r.push(i),0!=d);h++);return r},createListHTML:function(t){var e=this.settings.templates.dropdownItem.bind(this);return this.minify(t.map(e).join(""))}}},t});
\ No newline at end of file
diff --git a/server/user.jobengine.osgi.server/js/tagify/tagify.polyfills.min.js b/server/user.jobengine.osgi.server/js/tagify/tagify.polyfills.min.js
new file mode 100644 (file)
index 0000000..e4c75cc
--- /dev/null
@@ -0,0 +1 @@
+!function(){if(String.prototype.includes||(String.prototype.includes=function(t,e){"use strict";return"number"!=typeof e&&(e=0),!(e+t.length>this.length)&&-1!==this.indexOf(t,e)}),"function"==typeof window.CustomEvent)return;function t(t,e){e=e||{bubbles:!1,cancelable:!1,detail:void 0};var n=document.createEvent("CustomEvent");return n.initCustomEvent(t,e.bubbles,e.cancelable,e.detail),n}t.prototype=window.Event.prototype,window.CustomEvent=t,"function"!=typeof Object.assign&&Object.defineProperty(Object,"assign",{value:function(t){"use strict";if(null==t)throw new TypeError("Cannot convert undefined or null to object");for(var e=Object(t),n=1;n<arguments.length;n++){var o=arguments[n];if(null!=o)for(var r in o)Object.prototype.hasOwnProperty.call(o,r)&&(e[r]=o[r])}return e},writable:!0,configurable:!0}),window.NodeList&&!NodeList.prototype.forEach&&(NodeList.prototype.forEach=Array.prototype.forEach),Array.prototype.findIndex||Object.defineProperty(Array.prototype,"findIndex",{value:function(t){if(null==this)throw new TypeError('"this" is null or not defined');var e=Object(this),n=e.length>>>0;if("function"!=typeof t)throw new TypeError("predicate must be a function");for(var o=arguments[1],r=0;r<n;){var i=e[r];if(t.call(o,i,r,e))return r;r++}return-1},configurable:!0,writable:!0}),Element.prototype.matches||(Element.prototype.matches=Element.prototype.msMatchesSelector||Element.prototype.webkitMatchesSelector),Element.prototype.closest||(Element.prototype.closest=function(t){var e=this;if(!document.documentElement.contains(e))return null;do{if(e.matches(t))return e;e=e.parentElement||e.parentNode}while(null!==e&&1===e.nodeType);return null}),document.execCommand("AutoUrlDetect",!1,!1)}();
\ No newline at end of file
index 8d2335687a1c27c58d28a7ac8f1d0465463e047a..f0ec19cc8fcdabc4f366aef37ba546c6ade5619a 100644 (file)
@@ -4,6 +4,8 @@
 \r
 <zk xmlns:w="http://www.zkoss.org/2005/zk/client" xmlns:h="xhtml" xmlns:n="native" xmlns:c="client">\r
        <style src="/css/video-js.css" />\r
+       <style src="/css/tagify.css" /> \r
+       <h:script src="/js/tagify.min.js"  />\r
        <h:script src="/js/videojs-ie8.min.js"  />\r
        <h:script src="/js/video.js"  />\r
        <script type="text/javascript">\r
                        console.log("pushTcOut", current);\r
                        zk.Widget.$('$videoListener').fire('onTcOut', {meta: current}, {toServer:true});\r
                }\r
+/*             \r
+                       */\r
+var input = document.querySelector('textarea[name=tags2]'),\r
+    tagify = new Tagify(input, {\r
+        enforceWhitelist : true,\r
+        whitelist        : ["The Shawshank Redemption", "The Godfather", "The Godfather: Part II", "The Dark Knight", "12 Angry Men", "Schindler's List", "Pulp Fiction", "The Lord of the Rings: The Return of the King", "The Good, the Bad and the Ugly", "Fight Club", "The Lord of the Rings: The Fellowship of the Ring", "Star Wars: Episode V - The Empire Strikes Back", "Forrest Gump", "Inception", "The Lord of the Rings: The Two Towers", "One Flew Over the Cuckoo's Nest", "Goodfellas", "The Matrix", "Seven Samurai", "Star Wars: Episode IV - A New Hope", "City of God", "Se7en", "The Silence of the Lambs", "It's a Wonderful Life", "The Usual Suspects", "Life Is Beautiful", "Léon: The Professional", "Spirited Away", "Saving Private Ryan", "La La Land", "Once Upon a Time in the West", "American History X", "Interstellar", "Casablanca", "Psycho", "City Lights", "The Green Mile", "Raiders of the Lost Ark", "The Intouchables", "Modern Times", "Rear Window", "The Pianist", "The Departed", "Terminator 2: Judgment Day", "Back to the Future", "Whiplash", "Gladiator", "Memento", "Apocalypse Now", "The Prestige", "The Lion King", "Alien", "Dr. Strangelove or: How I Learned to Stop Worrying and Love the Bomb", "Sunset Boulevard", "The Great Dictator", "Cinema Paradiso", "The Lives of Others", "Paths of Glory", "Grave of the Fireflies", "Django Unchained", "The Shining", "WALL·E", "American Beauty", "The Dark Knight Rises", "Princess Mononoke", "Aliens", "Oldboy", "Once Upon a Time in America", "Citizen Kane", "Das Boot", "Witness for the Prosecution", "North by Northwest", "Vertigo", "Star Wars: Episode VI - Return of the Jedi", "Reservoir Dogs", "M", "Braveheart", "Amélie", "Requiem for a Dream", "A Clockwork Orange", "Taxi Driver", "Lawrence of Arabia", "Like Stars on Earth", "Double Indemnity", "To Kill a Mockingbird", "Eternal Sunshine of the Spotless Mind", "Toy Story 3", "Amadeus", "My Father and My Son", "Full Metal Jacket", "The Sting", "2001: A Space Odyssey", "Singin' in the Rain", "Bicycle Thieves", "Toy Story", "Dangal", "The Kid", "Inglourious Basterds", "Snatch", "Monty Python and the Holy Grail", "Hacksaw Ridge", "3 Idiots", "L.A. Confidential", "For a Few Dollars More", "Scarface", "Rashomon", "The Apartment", "The Hunt", "Good Will Hunting", "Indiana Jones and the Last Crusade", "A Separation", "Metropolis", "Yojimbo", "All About Eve", "Batman Begins", "Up", "Some Like It Hot", "The Treasure of the Sierra Madre", "Unforgiven", "Downfall", "Raging Bull", "The Third Man", "Die Hard", "Children of Heaven", "The Great Escape", "Heat", "Chinatown", "Inside Out", "Pan's Labyrinth", "Ikiru", "My Neighbor Totoro", "On the Waterfront", "Room", "Ran", "The Gold Rush", "The Secret in Their Eyes", "The Bridge on the River Kwai", "Blade Runner", "Mr. Smith Goes to Washington", "The Seventh Seal", "Howl's Moving Castle", "Lock, Stock and Two Smoking Barrels", "Judgment at Nuremberg", "Casino", "The Bandit", "Incendies", "A Beautiful Mind", "A Wednesday", "The General", "The Elephant Man", "Wild Strawberries", "Arrival", "V for Vendetta", "Warrior", "The Wolf of Wall Street", "Manchester by the Sea", "Sunrise", "The Passion of Joan of Arc", "Gran Torino", "Rang De Basanti", "Trainspotting", "Dial M for Murder", "The Big Lebowski", "The Deer Hunter", "Tokyo Story", "Gone with the Wind", "Fargo", "Finding Nemo", "The Sixth Sense", "The Thing", "Hera Pheri", "Cool Hand Luke", "Andaz Apna Apna", "Rebecca", "No Country for Old Men", "How to Train Your Dragon", "Munna Bhai M.B.B.S.", "Sholay", "Kill Bill: Vol. 1", "Into the Wild", "Mary and Max", "Gone Girl", "There Will Be Blood", "Come and See", "It Happened One Night", "Life of Brian", "Rush", "Hotel Rwanda", "Platoon", "Shutter Island", "Network", "The Wages of Fear", "Stand by Me", "Wild Tales", "In the Name of the Father", "Spotlight", "Star Wars: The Force Awakens", "The Nights of Cabiria", "The 400 Blows", "Butch Cassidy and the Sundance Kid", "Mad Max: Fury Road", "The Maltese Falcon", "12 Years a Slave", "Ben-Hur", "The Grand Budapest Hotel", "Persona", "Million Dollar Baby", "Amores Perros", "Jurassic Park", "The Princess Bride", "Hachi: A Dog's Tale", "Memories of Murder", "Stalker", "Nausicaä of the Valley of the Wind", "Drishyam", "The Truman Show", "The Grapes of Wrath", "Before Sunrise", "Touch of Evil", "Annie Hall", "The Message", "Rocky", "Gandhi", "Harry Potter and the Deathly Hallows: Part 2", "The Bourne Ultimatum", "Diabolique", "Donnie Darko", "Monsters, Inc.", "Prisoners", "8½", "The Terminator", "The Wizard of Oz", "Catch Me If You Can", "Groundhog Day", "Twelve Monkeys", "Zootopia", "La Haine", "Barry Lyndon", "Jaws", "The Best Years of Our Lives", "Infernal Affairs", "Udaan", "The Battle of Algiers", "Strangers on a Train", "Dog Day Afternoon", "Sin City", "Kind Hearts and Coronets", "Gangs of Wasseypur", "The Help"],\r
+        callbacks        : {\r
+            add    : console.log,  // callback when adding a tag\r
+            remove : console.log   // callback when removing a tag\r
+        }\r
+    });\r
        </h:script>\r
 \r
        <!-- csak igy jo a list sebessege -->\r
                                        </north>\r
                                        <center border="none">\r
                                                <div height="100%">\r
+                                                       <h:textarea name='tags2' placeholder='Movie names'>\r
+                                                           The Matrix, Pulp Fiction, Mad Max\r
+                                                       </h:textarea>                                           \r
                                                        <toolbar width="100%" style="min-height:40px; font-size:8px">\r
                                                                <div style="max-width:450px; margin:auto;" >\r
                                                                        <div sclass="menuButtons">\r
                                                                        </div>\r
                                                                </div>\r
                                                        </toolbar>\r
+                                                       \r
                                                        <div id="mediaDetails" width="100%" vflex="true" style="padding: 4px; overflow:auto; background-color: white">\r
                                                                <div>\r
                                                                        <label style="font-size:8pt; color: gray" value="Gyűjtő azonosító" />\r
index ced165eb943bc35e92c52cd6af450519b336cb1f..c6b2dbaff11b7ec853d1d0a02c53dc611192303d 100644 (file)
@@ -10,9 +10,9 @@ public class CronExpressionTest {
        @Test
        public void test1() throws Exception {
 
-               CronExpression ce = new CronExpression("0/30 0 15-8 * * ?");
+               CronExpression ce = new CronExpression("0 0 */1 * * ?");
                Date nextTime = new Date();
-               for (int i = 0; i < 1000; i++) {
+               for (int i = 0; i < 10; i++) {
                        nextTime = ce.getNextValidTimeAfter(nextTime);
                        System.out.println(nextTime);
                }
diff --git a/server/user.jobengine.osgi.server/user.jobengine.osgi.server.iml b/server/user.jobengine.osgi.server/user.jobengine.osgi.server.iml
new file mode 100644 (file)
index 0000000..5c1ed95
--- /dev/null
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">\r
+  <component name="FacetManager">\r
+    <facet type="Osmorc" name="OSGi">\r
+      <configuration manifestGenerationMode="Manually" manifestLocation="META-INF/MANIFEST.MF" jarfileLocation="user.jobengine.osgi.server.jar" outputPathType="CompilerOutputPath" bndFileLocation="" bundlorFileLocation="" bundleActivator="" bundleSymbolicName="" bundleVersion="1.0.0" ignoreFilePattern="" useProjectDefaultManifestFileLocation="false" alwaysRebuildBundleJAR="false" doNotSynchronizeWithMaven="false">\r
+        <additionalProperties />\r
+        <additionalJARContents />\r
+      </configuration>\r
+    </facet>\r
+  </component>\r
+  <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">\r
+    <output url="file://$MODULE_DIR$/target/classes" />\r
+    <output-test url="file://$MODULE_DIR$/target/test-classes" />\r
+    <content url="file://$MODULE_DIR$">\r
+      <excludeFolder url="file://$MODULE_DIR$/target" />\r
+    </content>\r
+    <orderEntry type="inheritedJdk" />\r
+    <orderEntry type="sourceFolder" forTests="false" />\r
+  </component>\r
+</module>
\ No newline at end of file
diff --git a/server/user.jobengine.osgi.services/user.jobengine.osgi.services.iml b/server/user.jobengine.osgi.services/user.jobengine.osgi.services.iml
new file mode 100644 (file)
index 0000000..6f54920
--- /dev/null
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">\r
+  <component name="FacetManager">\r
+    <facet type="Osmorc" name="OSGi">\r
+      <configuration manifestGenerationMode="Manually" manifestLocation="META-INF/MANIFEST.MF" jarfileLocation="user.jobengine.osgi.services.jar" outputPathType="CompilerOutputPath" bndFileLocation="" bundlorFileLocation="" bundleActivator="" bundleSymbolicName="" bundleVersion="1.0.0" ignoreFilePattern="" useProjectDefaultManifestFileLocation="false" alwaysRebuildBundleJAR="false" doNotSynchronizeWithMaven="false">\r
+        <additionalProperties />\r
+        <additionalJARContents />\r
+      </configuration>\r
+    </facet>\r
+  </component>\r
+  <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">\r
+    <output url="file://$MODULE_DIR$/target/classes" />\r
+    <output-test url="file://$MODULE_DIR$/target/test-classes" />\r
+    <content url="file://$MODULE_DIR$">\r
+      <excludeFolder url="file://$MODULE_DIR$/target" />\r
+    </content>\r
+    <orderEntry type="inheritedJdk" />\r
+    <orderEntry type="sourceFolder" forTests="false" />\r
+  </component>\r
+</module>
\ No newline at end of file
diff --git a/server/user.mediacube.metadata/user.mediacube.metadata.iml b/server/user.mediacube.metadata/user.mediacube.metadata.iml
new file mode 100644 (file)
index 0000000..9547ab5
--- /dev/null
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">\r
+  <component name="FacetManager">\r
+    <facet type="Osmorc" name="OSGi">\r
+      <configuration manifestGenerationMode="Manually" manifestLocation="META-INF/MANIFEST.MF" jarfileLocation="user.mediacube.metadata.jar" outputPathType="CompilerOutputPath" bndFileLocation="" bundlorFileLocation="" bundleActivator="" bundleSymbolicName="" bundleVersion="1.0.0" ignoreFilePattern="" useProjectDefaultManifestFileLocation="false" alwaysRebuildBundleJAR="false" doNotSynchronizeWithMaven="false">\r
+        <additionalProperties />\r
+        <additionalJARContents />\r
+      </configuration>\r
+    </facet>\r
+  </component>\r
+  <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">\r
+    <output url="file://$MODULE_DIR$/target/classes" />\r
+    <output-test url="file://$MODULE_DIR$/target/test-classes" />\r
+    <content url="file://$MODULE_DIR$">\r
+      <excludeFolder url="file://$MODULE_DIR$/target" />\r
+    </content>\r
+    <orderEntry type="inheritedJdk" />\r
+    <orderEntry type="sourceFolder" forTests="false" />\r
+  </component>\r
+</module>
\ No newline at end of file
diff --git a/server/user.tsm.client/user.tsm.client.iml b/server/user.tsm.client/user.tsm.client.iml
new file mode 100644 (file)
index 0000000..6cd8b0a
--- /dev/null
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">\r
+  <component name="FacetManager">\r
+    <facet type="Osmorc" name="OSGi">\r
+      <configuration manifestGenerationMode="Manually" manifestLocation="META-INF/MANIFEST.MF" jarfileLocation="user.tsm.client.jar" outputPathType="CompilerOutputPath" bndFileLocation="" bundlorFileLocation="" bundleActivator="" bundleSymbolicName="" bundleVersion="1.0.0" ignoreFilePattern="" useProjectDefaultManifestFileLocation="false" alwaysRebuildBundleJAR="false" doNotSynchronizeWithMaven="false">\r
+        <additionalProperties />\r
+        <additionalJARContents />\r
+      </configuration>\r
+    </facet>\r
+  </component>\r
+  <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">\r
+    <output url="file://$MODULE_DIR$/target/classes" />\r
+    <output-test url="file://$MODULE_DIR$/target/test-classes" />\r
+    <content url="file://$MODULE_DIR$">\r
+      <excludeFolder url="file://$MODULE_DIR$/target" />\r
+    </content>\r
+    <orderEntry type="inheritedJdk" />\r
+    <orderEntry type="sourceFolder" forTests="false" />\r
+  </component>\r
+</module>
\ No newline at end of file