From b3bd8c3e8d367975980043e772f7cd78b7f96bc6 Mon Sep 17 00:00:00 2001
From: ArthurHoaro <arthur@hoa.ro>
Date: Thu, 22 Oct 2020 16:21:03 +0200
Subject: Feature: support any tag separator

So it allows to have multiple words tags.

Breaking change: commas ',' are no longer a default separator.

Fixes #594
---
 assets/default/js/base.js        | 28 ++++++++++++++++------------
 assets/default/scss/shaarli.scss | 10 ++++++++++
 2 files changed, 26 insertions(+), 12 deletions(-)

(limited to 'assets')

diff --git a/assets/default/js/base.js b/assets/default/js/base.js
index 66badfb2..e7bf4909 100644
--- a/assets/default/js/base.js
+++ b/assets/default/js/base.js
@@ -42,19 +42,20 @@ function refreshToken(basePath, callback) {
   xhr.send();
 }
 
-function createAwesompleteInstance(element, tags = []) {
+function createAwesompleteInstance(element, separator, tags = []) {
   const awesome = new Awesomplete(Awesomplete.$(element));
-  // Tags are separated by a space
-  awesome.filter = (text, input) => Awesomplete.FILTER_CONTAINS(text, input.match(/[^ ]*$/)[0]);
+
+  // Tags are separated by separator
+  awesome.filter = (text, input) => Awesomplete.FILTER_CONTAINS(text, input.match(new RegExp(`[^${separator}]*$`))[0]);
   // Insert new selected tag in the input
   awesome.replace = (text) => {
-    const before = awesome.input.value.match(/^.+ \s*|/)[0];
-    awesome.input.value = `${before}${text} `;
+    const before = awesome.input.value.match(new RegExp(`^.+${separator}+|`))[0];
+    awesome.input.value = `${before}${text}${separator}`;
   };
   // Highlight found items
-  awesome.item = (text, input) => Awesomplete.ITEM(text, input.match(/[^ ]*$/)[0]);
+  awesome.item = (text, input) => Awesomplete.ITEM(text, input.match(new RegExp(`[^${separator}]*$`))[0]);
   // Don't display already selected items
-  const reg = /(\w+) /g;
+  const reg = new RegExp(`/(\w+)${separator}/g`);
   let match;
   awesome.data = (item, input) => {
     while ((match = reg.exec(input))) {
@@ -78,13 +79,14 @@ function createAwesompleteInstance(element, tags = []) {
  * @param selector  CSS selector
  * @param tags      Array of tags
  * @param instances List of existing awesomplete instances
+ * @param separator Tags separator character
  */
-function updateAwesompleteList(selector, tags, instances) {
+function updateAwesompleteList(selector, tags, instances, separator) {
   if (instances.length === 0) {
     // First load: create Awesomplete instances
     const elements = document.querySelectorAll(selector);
     [...elements].forEach((element) => {
-      instances.push(createAwesompleteInstance(element, tags));
+      instances.push(createAwesompleteInstance(element, separator, tags));
     });
   } else {
     // Update awesomplete tag list
@@ -214,6 +216,8 @@ function init(description) {
 
 (() => {
   const basePath = document.querySelector('input[name="js_base_path"]').value;
+  const tagsSeparatorElement = document.querySelector('input[name="tags_separator"]');
+  const tagsSeparator = tagsSeparatorElement ? tagsSeparatorElement.value || '\s' : '\s';
 
   /**
    * Handle responsive menu.
@@ -575,7 +579,7 @@ function init(description) {
 
           // Refresh awesomplete values
           existingTags = existingTags.map((tag) => (tag === fromtag ? totag : tag));
-          awesomepletes = updateAwesompleteList('.rename-tag-input', existingTags, awesomepletes);
+          awesomepletes = updateAwesompleteList('.rename-tag-input', existingTags, awesomepletes, tagsSeparator);
         }
       };
       xhr.send(`renametag=1&fromtag=${fromtagUrl}&totag=${encodeURIComponent(totag)}&token=${refreshedToken}`);
@@ -615,14 +619,14 @@ function init(description) {
         refreshToken(basePath);
 
         existingTags = existingTags.filter((tagItem) => tagItem !== tag);
-        awesomepletes = updateAwesompleteList('.rename-tag-input', existingTags, awesomepletes);
+        awesomepletes = updateAwesompleteList('.rename-tag-input', existingTags, awesomepletes, tagsSeparator);
       }
     });
   });
 
   const autocompleteFields = document.querySelectorAll('input[data-multiple]');
   [...autocompleteFields].forEach((autocompleteField) => {
-    awesomepletes.push(createAwesompleteInstance(autocompleteField));
+    awesomepletes.push(createAwesompleteInstance(autocompleteField, tagsSeparator));
   });
 
   const exportForm = document.querySelector('#exportform');
diff --git a/assets/default/scss/shaarli.scss b/assets/default/scss/shaarli.scss
index a7f091e9..ed774a9d 100644
--- a/assets/default/scss/shaarli.scss
+++ b/assets/default/scss/shaarli.scss
@@ -139,6 +139,16 @@ body,
   }
 }
 
+.page-form,
+.pure-alert {
+  code {
+    display: inline-block;
+    padding: 0 2px;
+    color: $dark-grey;
+    background-color: var(--background-color);
+  }
+}
+
 // Make pure-extras alert closable.
 .pure-alert-closable {
   .fa-times {
-- 
cgit v1.2.3


From df9aac5b6406bf192f2e4e0987e25d0de88480df Mon Sep 17 00:00:00 2001
From: ArthurHoaro <arthur@hoa.ro>
Date: Thu, 5 Nov 2020 18:16:52 +0100
Subject: Tags separator: vintage theme compatibility

---
 assets/default/js/base.js |  2 +-
 assets/vintage/js/base.js | 45 ++++++++++++++++++++++++++-------------------
 2 files changed, 27 insertions(+), 20 deletions(-)

(limited to 'assets')

diff --git a/assets/default/js/base.js b/assets/default/js/base.js
index e7bf4909..37069d69 100644
--- a/assets/default/js/base.js
+++ b/assets/default/js/base.js
@@ -217,7 +217,7 @@ function init(description) {
 (() => {
   const basePath = document.querySelector('input[name="js_base_path"]').value;
   const tagsSeparatorElement = document.querySelector('input[name="tags_separator"]');
-  const tagsSeparator = tagsSeparatorElement ? tagsSeparatorElement.value || '\s' : '\s';
+  const tagsSeparator = tagsSeparatorElement ? tagsSeparatorElement.value || ' ' : ' ';
 
   /**
    * Handle responsive menu.
diff --git a/assets/vintage/js/base.js b/assets/vintage/js/base.js
index 66830b59..15b664ce 100644
--- a/assets/vintage/js/base.js
+++ b/assets/vintage/js/base.js
@@ -2,29 +2,36 @@ import Awesomplete from 'awesomplete';
 import 'awesomplete/awesomplete.css';
 
 (() => {
-  const awp = Awesomplete.$;
   const autocompleteFields = document.querySelectorAll('input[data-multiple]');
+  const tagsSeparatorElement = document.querySelector('input[name="tags_separator"]');
+  const tagsSeparator = tagsSeparatorElement ? tagsSeparatorElement.value || " " : " ";
+
   [...autocompleteFields].forEach((autocompleteField) => {
-    const awesomplete = new Awesomplete(awp(autocompleteField));
-    awesomplete.filter = (text, input) => Awesomplete.FILTER_CONTAINS(text, input.match(/[^ ]*$/)[0]);
-    awesomplete.replace = (text) => {
-      const before = awesomplete.input.value.match(/^.+ \s*|/)[0];
-      awesomplete.input.value = `${before}${text} `;
-    };
-    awesomplete.minChars = 1;
+    const awesome = new Awesomplete(Awesomplete.$(autocompleteField));
 
-    autocompleteField.addEventListener('input', () => {
-      const proposedTags = autocompleteField.getAttribute('data-list').replace(/,/g, '').split(' ');
-      const reg = /(\w+) /g;
-      let match;
-      while ((match = reg.exec(autocompleteField.value)) !== null) {
-        const id = proposedTags.indexOf(match[1]);
-        if (id !== -1) {
-          proposedTags.splice(id, 1);
+    // Tags are separated by separator
+    awesome.filter = (text, input) => Awesomplete.FILTER_CONTAINS(
+      text,
+      input.match(new RegExp(`[^${tagsSeparator}]*$`))[0])
+    ;
+    // Insert new selected tag in the input
+    awesome.replace = (text) => {
+      const before = awesome.input.value.match(new RegExp(`^.+${tagsSeparator}+|`))[0];
+      awesome.input.value = `${before}${text}${tagsSeparator}`;
+    };
+    // Highlight found items
+    awesome.item = (text, input) => Awesomplete.ITEM(text, input.match(new RegExp(`[^${tagsSeparator}]*$`))[0]);
+    // Don't display already selected items
+    const reg = new RegExp(`/(\w+)${tagsSeparator}/g`);
+    let match;
+    awesome.data = (item, input) => {
+      while ((match = reg.exec(input))) {
+        if (item === match[1]) {
+          return '';
         }
       }
-
-      awesomplete.list = proposedTags;
-    });
+      return item;
+    };
+    awesome.minChars = 1;
   });
 })();
-- 
cgit v1.2.3


From 8a1ce1da15fdbae99b24700b06f2008c7a657603 Mon Sep 17 00:00:00 2001
From: ArthurHoaro <arthur@hoa.ro>
Date: Thu, 5 Nov 2020 19:08:38 +0100
Subject: ESLint

---
 assets/default/js/base.js |  3 ++-
 assets/vintage/js/base.js | 10 ++++++----
 2 files changed, 8 insertions(+), 5 deletions(-)

(limited to 'assets')

diff --git a/assets/default/js/base.js b/assets/default/js/base.js
index 37069d69..dd532bb7 100644
--- a/assets/default/js/base.js
+++ b/assets/default/js/base.js
@@ -55,7 +55,8 @@ function createAwesompleteInstance(element, separator, tags = []) {
   // Highlight found items
   awesome.item = (text, input) => Awesomplete.ITEM(text, input.match(new RegExp(`[^${separator}]*$`))[0]);
   // Don't display already selected items
-  const reg = new RegExp(`/(\w+)${separator}/g`);
+  // WARNING: pseudo classes does not seem to work with string litterals...
+  const reg = new RegExp(`([^${separator}]+)${separator}`, 'g');
   let match;
   awesome.data = (item, input) => {
     while ((match = reg.exec(input))) {
diff --git a/assets/vintage/js/base.js b/assets/vintage/js/base.js
index 15b664ce..55f1c37d 100644
--- a/assets/vintage/js/base.js
+++ b/assets/vintage/js/base.js
@@ -4,7 +4,7 @@ import 'awesomplete/awesomplete.css';
 (() => {
   const autocompleteFields = document.querySelectorAll('input[data-multiple]');
   const tagsSeparatorElement = document.querySelector('input[name="tags_separator"]');
-  const tagsSeparator = tagsSeparatorElement ? tagsSeparatorElement.value || " " : " ";
+  const tagsSeparator = tagsSeparatorElement ? tagsSeparatorElement.value || ' ' : ' ';
 
   [...autocompleteFields].forEach((autocompleteField) => {
     const awesome = new Awesomplete(Awesomplete.$(autocompleteField));
@@ -12,8 +12,8 @@ import 'awesomplete/awesomplete.css';
     // Tags are separated by separator
     awesome.filter = (text, input) => Awesomplete.FILTER_CONTAINS(
       text,
-      input.match(new RegExp(`[^${tagsSeparator}]*$`))[0])
-    ;
+      input.match(new RegExp(`[^${tagsSeparator}]*$`))[0],
+    );
     // Insert new selected tag in the input
     awesome.replace = (text) => {
       const before = awesome.input.value.match(new RegExp(`^.+${tagsSeparator}+|`))[0];
@@ -21,8 +21,10 @@ import 'awesomplete/awesomplete.css';
     };
     // Highlight found items
     awesome.item = (text, input) => Awesomplete.ITEM(text, input.match(new RegExp(`[^${tagsSeparator}]*$`))[0]);
+
     // Don't display already selected items
-    const reg = new RegExp(`/(\w+)${tagsSeparator}/g`);
+    // WARNING: pseudo classes does not seem to work with string litterals...
+    const reg = new RegExp(`([^${tagsSeparator}]+)${tagsSeparator}`, 'g');
     let match;
     awesome.data = (item, input) => {
       while ((match = reg.exec(input))) {
-- 
cgit v1.2.3