Repository: LuteOrg/lute-v3 (language: python)

Issue:
Auto-convert pending parent tag to real tag on Save
(edited from the original issue)

**Description**

I try to add a parent term.  I could add it successfully when the parent term was defined.  ~~When the parent term wasn't defined previously, it is not automatically created.   Thus I could not add it as the parent term.~~

When the parent term doesn't exist, it's not clear that you have to hit Return to convert it to a tag, which is then saved.  What should happen: any pending text in the "parents" text box should be auto-converted to a tag when the form is saved.

**To Reproduce**

Steps to reproduce the behavior, e.g.:

1. open a book for reading
2. click on a term to open the term window, ex "entitled"
3. fill the parents field, ex "entitle"
4. click save button.  The term window disappear with a message "entitled updated".
5. click on the term "entitled" to open the term window again, and find the parents field still blank.

**Screenshots**
![image](https://github.com/jzohrab/lute-v3/assets/7263899/2bdb0eda-a804-44b3-b8d2-51641568e8c2)

**Extra software info, if not already included in the Description:**

- windows 10
- chrome
- installation: python pip
- Version:  3.2.4



Discussion / hints:
Hi @bill-hkc - thanks for taking the time to write the issue.

You actually need to hit the "Return" key after entering the new text to convert the parent text into a tag.  If you do that, Lute should create the new parent when the tag is saved.  e.g. Before return:

> <img width="220" alt="image" src="https://github.com/jzohrab/lute-v3/assets/1637133/500902d7-00e1-49e1-b57d-12439f4d1507">

After return:

> <img width="120" alt="image" src="https://github.com/jzohrab/lute-v3/assets/1637133/937c9d15-6b75-431e-9074-a7307aeae75b">

Then when you hit save (or control-Enter), things get created correctly.

I think this is still a good issue, some others have been caught by this as well.  I can't auto-create the tag once focus is lost for other reasons given by other users, but I think that converting any "pending" parent tag into a real tag just before Save would be a good idea.  So I'll take the liberty of editing the issue title to something more suitable.  Thanks!
Dev notes:

- the parent tags are managed by tagify (https://github.com/yairEO/tagify).  Per the repo readme, tagify has a "addTagOnBlur" property, but we **can't** use that for Lute as sometimes external dictionaries pull focus mid-parent-tag typing, which causes problems
- should add some kind of handler to the Javascript save to convert any pending parent input text to a fully-fledged tag.  I'm not sure exactly what that would entail, perhaps doing a "sendkeys" or something to simulate a user "enter" in the tag textbox control.

Fix patch:
diff --git a/lute/templates/term/_form.html b/lute/templates/term/_form.html
index ad443833..4227d96f 100644
--- a/lute/templates/term/_form.html
+++ b/lute/templates/term/_form.html
@@ -18,7 +18,8 @@
 
 
 <div id="term-form-container">
-  <form id="term-form" name="term_form" method="POST">
+  <form id="term-form" name="term_form" method="POST"
+        onsubmit="return convert_pending_parent_tags(event);">
     {{ form.hidden_tag() }}
     <div id="term">
       <div id="languageSel"
@@ -68,7 +69,7 @@
           {% if term.id %}
           <button id="delete" type="button" class="btn" onclick="deleteTerm()">Delete</button>
           {% endif %}
-          <button id="submit" type="submit" title="Shortcut: Control+Enter" class="btn btn-primary">Save</button>
+          <button id="btnsubmit" type="submit" title="Shortcut: Control+Enter" class="btn btn-primary">Save</button>
       </div>
     </div>
   </form>
@@ -305,7 +306,7 @@
     $(document).keydown(function(event) {
       console.log(event);
       if (event.ctrlKey && event.key === "Enter") {
-        $("#submit").click();
+        $("#btnsubmit").click();
       }
     });
 
@@ -366,6 +367,51 @@
   });  // end $(document).ready
 
 
+  // =============================
+  // Submit.
+
+  // Users may enter in a parent term without actually hitting return
+  // in the tag input box, resulting in nothing getting posted.  This
+  // is annoying, so convert any pending text into a full tag before
+  // submitting.
+  function convert_pending_parent_tags(event) {
+    const pending_tag = $('span.tagify__input').text().trim();
+    if (pending_tag == '')
+      return true;
+    // console.log(`adding: ${pending_tag}`);
+
+    // Simply calling "addTags" is not enough, as there appears to be
+    // some kind of async processing and timing in that method.  We
+    // have to listen to the 'add' event, and only submit once that is
+    // called.
+    parents_tagify.on('add', onFinalTagAdded);
+
+    // Add the pending tag, and suppress the actual submit.
+    parents_tagify.addTags([pending_tag], true, false);
+    event.preventDefault();
+    return false;
+  }
+
+
+  // If some extra text has been added, wait for processing to be
+  // done, and then submit the form.
+  function onFinalTagAdded(e) {
+    // console.log('Tag added:', e.detail.data.value);
+
+    // We still have to wait for data to be fully processed :-(
+    const DELAY = 150;  // ms
+    setTimeout(function() {
+      const form = $('#term-form');
+      form[0].submit();
+    }, DELAY);
+
+    parents_tagify.off('add', onFinalTagAdded);  // clean up listener.
+  }
+
+
+  // =============================
+  // Delete.
+
   function deleteTerm() {
     const msg = "Are you sure you want to delete this term?\n\n" +
           "This action cannot be undone, and if this term has children, they will be orphaned.";


Test patch:
diff --git a/tests/acceptance/conftest.py b/tests/acceptance/conftest.py
index eb0fbc23..e458edc4 100644
--- a/tests/acceptance/conftest.py
+++ b/tests/acceptance/conftest.py
@@ -239,12 +239,14 @@ def given_book_from_file(luteclient, lang, title, filename):
     thisdir = os.path.dirname(os.path.realpath(__file__))
     fullpath = os.path.join(thisdir, "sample_files", filename)
     luteclient.make_book_from_file(title, fullpath, lang)
+    _sleep(1)  # Hack!
 
 
 @given(parsers.parse("a {lang} book from url {url}"))
 def given_book_from_url(luteclient, lang, url):
     "Book is made from url in dev_api."
     luteclient.make_book_from_url(url, lang)
+    _sleep(1)  # Hack!
 
 
 @given(parsers.parse('the book table loads "{title}"'))
diff --git a/tests/acceptance/lute_test_client.py b/tests/acceptance/lute_test_client.py
index 1adc2b2b..0d1e6bed 100644
--- a/tests/acceptance/lute_test_client.py
+++ b/tests/acceptance/lute_test_client.py
@@ -209,7 +209,7 @@ def make_term(self, lang, updates):
         updates["language_id"] = self.language_ids[lang]
         b = self.browser
         self._fill_term_form(b, updates)
-        b.find_by_css("#submit").first.click()
+        b.find_by_css("#btnsubmit").first.click()
 
     def get_term_table_content(self):
         "Get term table content."
@@ -332,7 +332,7 @@ def click_word_fill_form(self, word, updates=None):
             time.sleep(0.4)  # Hack, test failing.
             self._fill_term_form(iframe, updates)
             time.sleep(0.4)  # Hack, test failing.
-            iframe.find_by_css("#submit").first.click()
+            iframe.find_by_css("#btnsubmit").first.click()
             time.sleep(0.4)  # Hack, test failing.
 
             # Only refresh the reading frame if everything was ok.