From a22dddcf9bd7d7c87bbc2a0fe49fb3c41c8af43c Mon Sep 17 00:00:00 2001 From: Aga Date: Mon, 26 Jun 2023 14:20:30 +0100 Subject: [PATCH 01/26] start with copy to click widget --- cms/sass/components/_form.scss | 2 +- portality/forms/application_forms.py | 6 +++++- portality/static/js/formulaic.js | 4 +++- portality/templates/application_form/_field.html | 3 +++ 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/cms/sass/components/_form.scss b/cms/sass/components/_form.scss index b203ff5988..fd97423f9f 100644 --- a/cms/sass/components/_form.scss +++ b/cms/sass/components/_form.scss @@ -82,7 +82,7 @@ border-left: 1px solid $sanguine; } -.form__long-help { +.form__long-help, .form__click-to-copy { cursor: pointer; &:hover { diff --git a/portality/forms/application_forms.py b/portality/forms/application_forms.py index c290533678..3b491fe065 100644 --- a/portality/forms/application_forms.py +++ b/portality/forms/application_forms.py @@ -108,7 +108,10 @@ class FieldDefinitions: "validate" : [], "disabled": True } - } + }, + "widgets": [ + "click_to_copy", # ~~^-> ClickToCopy:FormWidget~~ + ], } # ~~->$ OAStatementURL:FormField~~ @@ -2922,6 +2925,7 @@ def wtforms(field, settings): JAVASCRIPT_FUNCTIONS = { "clickable_url": "formulaic.widgets.newClickableUrl", # ~~-> ClickableURL:FormWidget~~ + "click_to_copy": "formulaic.widgets.newClickToCopy", # ~~-> ClickToCopy:FormWidget~~ "clickable_owner": "formulaic.widgets.newClickableOwner", # ~~-> ClickableOwner:FormWidget~~ "select": "formulaic.widgets.newSelect", # ~~-> SelectBox:FormWidget~~ "taglist": "formulaic.widgets.newTagList", # ~~-> TagList:FormWidget~~ diff --git a/portality/static/js/formulaic.js b/portality/static/js/formulaic.js index 8c2b826075..8b89703f83 100644 --- a/portality/static/js/formulaic.js +++ b/portality/static/js/formulaic.js @@ -1195,7 +1195,9 @@ var formulaic = { this.init(); }, - + newClickToCopy : function(params) { + return + }, newClickableOwner : function(params) { return edges.instantiate(formulaic.widgets.ClickableOwner, params) }, diff --git a/portality/templates/application_form/_field.html b/portality/templates/application_form/_field.html index 8ebbaa9fe9..8896befec5 100644 --- a/portality/templates/application_form/_field.html +++ b/portality/templates/application_form/_field.html @@ -8,6 +8,9 @@ {% if f.help("long_help") %} More help {% endif %} + {% if f.has_widget("click_to_copy") %} + Copy value + {% endif %} {% if f.optional %}(Optional){% endif %} {% endset %} From 68719f0184af4d441d26119e7baf3e6370b810ab Mon Sep 17 00:00:00 2001 From: Aga Date: Wed, 28 Jun 2023 11:02:01 +0100 Subject: [PATCH 02/26] add script to find articles with invalid issns --- ...230609_find_articles_with_invalid_issns.py | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 portality/scripts/230609_find_articles_with_invalid_issns.py diff --git a/portality/scripts/230609_find_articles_with_invalid_issns.py b/portality/scripts/230609_find_articles_with_invalid_issns.py new file mode 100644 index 0000000000..c45b8c5d63 --- /dev/null +++ b/portality/scripts/230609_find_articles_with_invalid_issns.py @@ -0,0 +1,66 @@ +from portality import models +from portality.bll.services import article as articlesvc +from portality.bll import exceptions +from portality.core import es_connection +from portality.util import ipt_prefix +import esprit +import csv + +IN_DOAJ = { + "query": { + "bool": { + "must": [ + {"term" : {"admin.in_doaj":True}} + ] + } + } +} + + +if __name__ == "__main__": + + # import argparse + # + # parser = argparse.ArgumentParser() + # parser.add_argument("-o", "--out", help="output file path") + # args = parser.parse_args() + # + # if not args.out: + # print("Please specify an output file path with the -o option") + # parser.print_help() + # exit() + + out = "out.csv" + + with open(out, "w", encoding="utf-8") as f: + writer = csv.writer(f) + writer.writerow(["ID", "PISSN", "EISSN", "Journals found with article's PISSN", "In doaj?", "Journals found with article's EISSN", "In doaj?", "Error"]) + + for a in models.Article.iterate(q=IN_DOAJ, page_size=100, keepalive='5m'): + article = models.Article(_source=a) + bibjson = article.bibjson() + try: + articlesvc.ArticleService._validate_issns(bibjson) + except exceptions.ArticleNotAcceptable as e: + id = article.id + pissn = bibjson.get_identifiers("pissn") + eissn = bibjson.get_identifiers("eissn") + j_p = [j["id"] for j in models.Journal.find_by_issn(pissn)] + j_p_in_doaj = [] + if (j_p): + for j in j_p: + jobj = models.Journal.pull(j) + if (jobj): + j_p_in_doaj.append(jobj.is_in_doaj()) + else: + j_p_in_doaj.append("n/a") + j_e = [j["id"] for j in models.Journal.find_by_issn(eissn)] + j_e_in_doaj = [] + if (j_e): + for j in j_e: + jobj = models.Journal.pull(j) + if (jobj): + j_e_in_doaj.append(jobj.is_in_doaj()) + else: + j_e_in_doaj.append("n/a") + writer.writerow([id, pissn, eissn, j_p, j_p_in_doaj, j_e, j_e_in_doaj, str(e)]) \ No newline at end of file From b7c6cdd9c065b3ecbcb046e7154b2527c31b40fd Mon Sep 17 00:00:00 2001 From: Aga Date: Mon, 17 Jul 2023 11:33:28 +0100 Subject: [PATCH 03/26] Add copy value button to simple fields; copies displayed value --- portality/static/js/formulaic.js | 20 ++++++++++++++++++- .../templates/application_form/_field.html | 2 +- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/portality/static/js/formulaic.js b/portality/static/js/formulaic.js index 91ded843fb..2124afae01 100644 --- a/portality/static/js/formulaic.js +++ b/portality/static/js/formulaic.js @@ -1196,7 +1196,25 @@ var formulaic = { this.init(); }, newClickToCopy : function(params) { - return + return edges.instantiate(formulaic.widgets.ClickToCopy, params) + }, + ClickToCopy : function(params) { + this.fieldDef = params.fieldDef; + this.form = params.formulaic; + this.ns = "formulaic-clicktocopy" + this.init = function() { + var elements = $(".form__click-to-copy"); + edges.on(elements, "click", this, "copy"); + }; + this.copy = function(element) { + var field = $("input[name=" + this.fieldDef.name + "]") + value_def = this.fieldDef.options.filter(item => item.value === field.val()); + value_to_copy = value_def[0]["display"]; + navigator.clipboard.writeText(value_to_copy) + console.log("text copied: " + value_to_copy) + + }; + this.init(); }, newClickableOwner : function(params) { return edges.instantiate(formulaic.widgets.ClickableOwner, params) diff --git a/portality/templates/application_form/_field.html b/portality/templates/application_form/_field.html index 8896befec5..1bca546ef5 100644 --- a/portality/templates/application_form/_field.html +++ b/portality/templates/application_form/_field.html @@ -9,7 +9,7 @@ More help {% endif %} {% if f.has_widget("click_to_copy") %} - Copy value + Copy value {% endif %} {% if f.optional %}(Optional){% endif %} From b6097579e2b0cb584cb6723069a6b8aa90c789c9 Mon Sep 17 00:00:00 2001 From: Aga Date: Mon, 17 Jul 2023 11:43:31 +0100 Subject: [PATCH 04/26] add confirmation with copied value --- portality/static/js/formulaic.js | 4 +++- portality/templates/application_form/_field.html | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/portality/static/js/formulaic.js b/portality/static/js/formulaic.js index 2124afae01..0e0aae3cfe 100644 --- a/portality/static/js/formulaic.js +++ b/portality/static/js/formulaic.js @@ -1212,7 +1212,9 @@ var formulaic = { value_to_copy = value_def[0]["display"]; navigator.clipboard.writeText(value_to_copy) console.log("text copied: " + value_to_copy) - + var confirmation = $("#copy-confirmation--" + this.fieldDef.name); + confirmation.text("Value copied: " + value_to_copy); + confirmation.show().delay(3000).fadeOut(); }; this.init(); }, diff --git a/portality/templates/application_form/_field.html b/portality/templates/application_form/_field.html index 1bca546ef5..d5f007ba6e 100644 --- a/portality/templates/application_form/_field.html +++ b/portality/templates/application_form/_field.html @@ -10,6 +10,7 @@ {% endif %} {% if f.has_widget("click_to_copy") %} Copy value + {% endif %} {% if f.optional %}(Optional){% endif %} From 567879e0d23f9efa15bcde042d55106635cc9250 Mon Sep 17 00:00:00 2001 From: Aga Date: Mon, 17 Jul 2023 13:07:35 +0100 Subject: [PATCH 05/26] add click_to_copy to all fields; doesn't work for multiple fields and doesn't decode country code --- cms/sass/components/_tag.scss | 5 + portality/forms/application_forms.py | 165 +- portality/static/js/formulaic.js | 1557 +++++++++-------- .../templates/application_form/_field.html | 2 +- 4 files changed, 923 insertions(+), 806 deletions(-) diff --git a/cms/sass/components/_tag.scss b/cms/sass/components/_tag.scss index 1f24ebce92..836e55c7ef 100644 --- a/cms/sass/components/_tag.scss +++ b/cms/sass/components/_tag.scss @@ -90,3 +90,8 @@ color: $white; } } + +.tag--confirmation { + background: $dark-green; + color: $white; +} diff --git a/portality/forms/application_forms.py b/portality/forms/application_forms.py index 687cd9f5e3..15e69bb7bf 100644 --- a/portality/forms/application_forms.py +++ b/portality/forms/application_forms.py @@ -141,7 +141,8 @@ class FieldDefinitions: ], "widgets": [ "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ - "clickable_url" # ~~^-> ClickableURL:FormWidget~~ + "clickable_url", # ~~^-> ClickableURL:FormWidget~~ + "click_to_copy" ], "attr": { "type": "url" @@ -179,7 +180,10 @@ class FieldDefinitions: "update_request": { "disabled": True } - } + }, + "widgets": [ + "click_to_copy", # ~~^-> ClickToCopy:FormWidget~~ + ] } # ~~->$ AlternativeTitle:FormField~~ @@ -202,7 +206,10 @@ class FieldDefinitions: "update_request": { "disabled": True } - } + }, + "widgets": [ + "click_to_copy", # ~~^-> ClickToCopy:FormWidget~~ + ] } # ~~->$ JournalURL:FormField~~ @@ -229,7 +236,10 @@ class FieldDefinitions: "journal_url_in_public_doaj" # ~~^-> JournalURLInPublicDOAJ:FormValidator~~ ], } - } + }, + "widgets": [ + "click_to_copy", # ~~^-> ClickToCopy:FormWidget~~ + ] } #~~->$ PISSN:FormField~~ @@ -255,7 +265,8 @@ class FieldDefinitions: "widgets" : [ "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ "full_contents", # ~~^->FullContents:FormWidget~~ - "issn_link" # ~~^->IssnLink:FormWidget~~ + "issn_link", # ~~^->IssnLink:FormWidget~~ + "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ], "contexts": { "public" : { @@ -326,7 +337,8 @@ class FieldDefinitions: "widgets" : [ "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ "full_contents", # ~~^->FullContents:FormWidget~~ - "issn_link" # ~~^->IssnLink:FormWidget~~ + "issn_link", # ~~^->IssnLink:FormWidget~~ + "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ], "contexts": { "public" : { @@ -407,7 +419,8 @@ class FieldDefinitions: "stopWords": STOP_WORDS, "field": "bibjson.keywords" } - } + }, + "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ], "attr": { "class": "input-xlarge" @@ -431,7 +444,8 @@ class FieldDefinitions: ], "widgets": [ {"select": {}}, - "multiple_field" + "multiple_field", + "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ], "help": { "placeholder": "Type or select the language" @@ -452,7 +466,8 @@ class FieldDefinitions: "widgets": [ "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ {"autocomplete": {"type" : "journal", "field": "bibjson.publisher.name.exact"}}, - "full_contents" # ~~^->FullContents:FormWidget~~ + "full_contents", # ~~^->FullContents:FormWidget~~ + "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ], "help": { "placeholder": "Type or select the publisher’s name" @@ -480,7 +495,8 @@ class FieldDefinitions: {"required": {"message": "Enter the country where the publisher carries out its business operations and is registered"}} ], "widgets": [ - {"select": {}} + {"select": {}}, + "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ], "attr": { "class": "input-xlarge" @@ -511,7 +527,8 @@ class FieldDefinitions: "widgets": [ "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ {"autocomplete": {"type" : "journal", "field": "bibjson.institution.name.exact"}}, - "full_contents" # ~~^->FullContents:FormWidget~~ + "full_contents", # ~~^->FullContents:FormWidget~~ + "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ] } @@ -528,7 +545,8 @@ class FieldDefinitions: "placeholder": "Type or select the country" }, "widgets": [ - {"select": {"allow_clear" : True}} + {"select": {"allow_clear" : True}}, + "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ], "attr": { "class": "input-xlarge" @@ -579,6 +597,9 @@ class FieldDefinitions: }, "validate": [ {"required": {"message": "Select at least one type of license"}} + ], + "widgets": [ + "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ] } @@ -599,7 +620,10 @@ class FieldDefinitions: ], "help": { "doaj_criteria": "Content must be licensed" - } + }, + "widgets": [ + "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ + ] } # ~~->$ LicenseTermsURL:FormField~~ @@ -619,7 +643,8 @@ class FieldDefinitions: }, "widgets": [ "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ - "clickable_url" # ~~^-> ClickableURL:FormWidget~~ + "clickable_url", # ~~^-> ClickableURL:FormWidget~~ + "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ] } @@ -641,6 +666,9 @@ class FieldDefinitions: }, "validate": [ {"required": {"message": "Select Yes or No"}} + ], + "widgets": [ + "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ] } @@ -667,7 +695,8 @@ class FieldDefinitions: ], "widgets": [ "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ - "clickable_url" # ~~^-> ClickableURL:FormWidget~~ + "clickable_url", # ~~^-> ClickableURL:FormWidget~~ + "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ] } @@ -692,7 +721,10 @@ class FieldDefinitions: "under any license allowed by the journal " "retain all rights."], "seal_criteria": "The author must retain the copyright" - } + }, + "widgets": [ + "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ + ] } # ~~->$ CopyrightURL:FormField~~ @@ -710,7 +742,8 @@ class FieldDefinitions: ], "widgets": [ "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ - "clickable_url" # ~~^-> ClickableURL:FormWidget~~ + "clickable_url", # ~~^-> ClickableURL:FormWidget~~ + "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ], "contexts": { "public": { @@ -752,6 +785,9 @@ class FieldDefinitions: }, "validate": [ {"required": {"message": "Select at least one type of review process"}} + ], + "widgets": [ + "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ] } @@ -774,6 +810,7 @@ class FieldDefinitions: ], "widgets" : [ "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ + "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ], "asynchronous_warning": [ {"warn_on_value": {"value": "None"}} @@ -796,7 +833,8 @@ class FieldDefinitions: ], "widgets": [ "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ - "clickable_url" # ~~^-> ClickableURL:FormWidget~~ + "clickable_url", # ~~^-> ClickableURL:FormWidget~~ + "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ] } @@ -819,7 +857,10 @@ class FieldDefinitions: "attr": { "min": app.config.get('MINIMAL_OA_START_DATE', 1900), "max": dates.now().year - } + }, + "widgets": [ + "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ + ] } # ~~->$ PlagiarismDetection:FormField~~ @@ -839,6 +880,9 @@ class FieldDefinitions: ], "validate": [ {"required": {"message": "Select Yes or No"}} + ], + "widgets": [ + "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ] } @@ -867,7 +911,8 @@ class FieldDefinitions: ], "widgets": [ "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ - "clickable_url" # ~~^-> ClickableURL:FormWidget~~ + "clickable_url", # ~~^-> ClickableURL:FormWidget~~ + "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ] } @@ -886,7 +931,8 @@ class FieldDefinitions: ], "widgets": [ "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ - "clickable_url" # ~~^-> ClickableURL:FormWidget~~ + "clickable_url", # ~~^-> ClickableURL:FormWidget~~ + "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ] } @@ -905,7 +951,8 @@ class FieldDefinitions: ], "widgets": [ "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ - "clickable_url" # ~~^-> ClickableURL:FormWidget~~ + "clickable_url", # ~~^-> ClickableURL:FormWidget~~ + "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ] } @@ -924,7 +971,8 @@ class FieldDefinitions: ], "widgets": [ "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ - "clickable_url" # ~~^-> ClickableURL:FormWidget~~ + "clickable_url", # ~~^-> ClickableURL:FormWidget~~ + "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ] } @@ -944,7 +992,10 @@ class FieldDefinitions: "attr": { "min": "1", "max": "100" - } + }, + "widgets": [ + "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ + ] } # ~~->$ APC:FormField~~ @@ -965,6 +1016,9 @@ class FieldDefinitions: }, "validate": [ {"required": {"message": "Select Yes or No"}} + ], + "widgets": [ + "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ] } @@ -993,7 +1047,8 @@ class FieldDefinitions: "template": "application_form/_list.html", "entry_template": "application_form/_entry_group_horizontal.html", "widgets": [ - "multiple_field" + "multiple_field", + "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ] } @@ -1009,7 +1064,8 @@ class FieldDefinitions: "placeholder": "Currency" }, "widgets": [ - {"select": {}} + {"select": {}}, + "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ], "attr": { "class": "input-xlarge" @@ -1047,7 +1103,10 @@ class FieldDefinitions: ], "attr": { "min": "1" - } + }, + "widgets":[ + "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ + ] } # ~~->$ APCURL:FormField~~ @@ -1069,7 +1128,8 @@ class FieldDefinitions: ], "widgets": [ "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ - "clickable_url" # ~~^-> ClickableURL:FormWidget~~ + "clickable_url", # ~~^-> ClickableURL:FormWidget~~ + "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ] } @@ -1093,6 +1153,9 @@ class FieldDefinitions: }, "validate": [ {"required": {"message": "Select Yes or No"}} + ], + "widgets": [ + "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ] } @@ -1121,7 +1184,8 @@ class FieldDefinitions: ], "widgets": [ "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ - "clickable_url" # ~~^-> ClickableURL:FormWidget~~ + "clickable_url", # ~~^-> ClickableURL:FormWidget~~ + "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ] } @@ -1142,6 +1206,9 @@ class FieldDefinitions: }, "validate": [ {"required": {"message": "Select Yes or No"}} + ], + "widgets": [ + "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ] } @@ -1169,7 +1236,8 @@ class FieldDefinitions: ], "widgets": [ "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ - "clickable_url" # ~~^-> ClickableURL:FormWidget~~ + "clickable_url", # ~~^-> ClickableURL:FormWidget~~ + "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ] } @@ -1201,6 +1269,9 @@ class FieldDefinitions: }, "validate": [ {"required": {"message": "Select at least one option"}} + ], + "widgets": [ + "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ] } @@ -1230,7 +1301,8 @@ class FieldDefinitions: ], "widgets": [ "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ - "multiple_field" + "multiple_field", + "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ], "attr": { "class": "input-xlarge unstyled-list" @@ -1255,7 +1327,8 @@ class FieldDefinitions: {"warn_on_value": {"value": "None"}} ], "widgets" : [ - "trim_whitespace" # ~~^-> TrimWhitespace:FormWidget~~ + "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ + "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ] } @@ -1302,7 +1375,8 @@ class FieldDefinitions: ], "widgets": [ "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ - "clickable_url" # ~~^-> ClickableURL:FormWidget~~ + "clickable_url", # ~~^-> ClickableURL:FormWidget~~ + "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ] } @@ -1333,6 +1407,9 @@ class FieldDefinitions: ]}, "validate": [ {"required": {"message": "Select at least one option"}} + ], + "widgets": [ + "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ] } @@ -1354,7 +1431,8 @@ class FieldDefinitions: {"warn_on_value": {"value": "None"}} ], "widgets" : [ - "trim_whitespace" # ~~^-> TrimWhitespace:FormWidget~~ + "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ + "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ] } @@ -1380,7 +1458,8 @@ class FieldDefinitions: ], "widgets": [ "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ - "clickable_url" # ~~^-> ClickableURL:FormWidget~~ + "clickable_url", # ~~^-> ClickableURL:FormWidget~~ + "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ], "contexts" : { "public" : { @@ -1440,6 +1519,9 @@ class FieldDefinitions: }, "validate": [ {"required": {"message": "Select at least one option"}} + ], + "widgets": [ + "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ] } @@ -1461,7 +1543,8 @@ class FieldDefinitions: {"warn_on_value": {"value": "None"}} ], "widgets" : [ - "trim_whitespace" # ~~^-> TrimWhitespace:FormWidget~~ + "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ + "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ] } @@ -1490,7 +1573,10 @@ class FieldDefinitions: {"required": {"message": "Select Yes or No"}} ] } - } + }, + "widgets": [ + "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ + ] } # ~~->$ OpenCitations:FormField~~ @@ -1517,7 +1603,10 @@ class FieldDefinitions: {"required": {"message": "Select Yes or No"}} ] } - } + }, + "widgets": [ + "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ + ] } ####################################### diff --git a/portality/static/js/formulaic.js b/portality/static/js/formulaic.js index 0e0aae3cfe..ccff926425 100644 --- a/portality/static/js/formulaic.js +++ b/portality/static/js/formulaic.js @@ -1203,243 +1203,266 @@ var formulaic = { this.form = params.formulaic; this.ns = "formulaic-clicktocopy" this.init = function() { - var elements = $(".form__click-to-copy"); + var elements = $("#click-to-copy--" + this.fieldDef.name); edges.on(elements, "click", this, "copy"); }; this.copy = function(element) { - var field = $("input[name=" + this.fieldDef.name + "]") - value_def = this.fieldDef.options.filter(item => item.value === field.val()); - value_to_copy = value_def[0]["display"]; - navigator.clipboard.writeText(value_to_copy) - console.log("text copied: " + value_to_copy) - var confirmation = $("#copy-confirmation--" + this.fieldDef.name); - confirmation.text("Value copied: " + value_to_copy); - confirmation.show().delay(3000).fadeOut(); - }; - this.init(); - }, - newClickableOwner : function(params) { - return edges.instantiate(formulaic.widgets.ClickableOwner, params) - }, - ClickableOwner : function(params) { - this.fieldDef = params.fieldDef; - this.form = params.formulaic; - - this.ns = "formulaic-clickableowner"; - - this.link = false; - - this.init = function() { - var elements = this.form.controlSelect.input({name: this.fieldDef.name}); - edges.on(elements, "change.ClickableOwner", this, "updateOwner"); - for (var i = 0; i < elements.length; i++) { - this.updateOwner(elements[i]); + // todo: multiple fields! + if (this.fieldDef.input == "radio") { + var field = $("input[name=" + this.fieldDef.name + "]:checked"); + value_def = this.fieldDef.options.filter(item => item.value === field.val()); + value_to_copy = value_def[0]["display"]; } - }; + else if (this.fieldDef.input == "checkbox") { + value_to_copy = "" + $( "input[name=" + this.fieldDef.name + "]:checked" ).each(function() { + value_to_copy = value_to_copy + " " + $(this)[0].value; + }); + console.log(value_to_copy) + } + else if (this.fieldDef.input == "taglist") { + var field = $("#" + this.fieldDef.name); + value_to_copy = field.val() + } + else if (this.fieldDef.input == "select") { + // todo: countries value instead of code! + var field = $("select[name=" + this.fieldDef.name + "]"); + value_to_copy = field.val() + } + else { + var field = $("input[name=" + this.fieldDef.name + "]"); + value_to_copy = field.val() + } + navigator.clipboard.writeText(value_to_copy) + console.log("text copied: " + value_to_copy) + var confirmation = $("#copy-confirmation--" + this.fieldDef.name); + confirmation.text("Value copied: " + value_to_copy); + confirmation.show().delay(3000).fadeOut(); + }; + this.init(); + }, + newClickableOwner : function(params) { + return edges.instantiate(formulaic.widgets.ClickableOwner, params) + }, + ClickableOwner : function(params) { + this.fieldDef = params.fieldDef; + this.form = params.formulaic; + + this.ns = "formulaic-clickableowner"; + + this.link = false; + + this.init = function() { + var elements = this.form.controlSelect.input({name: this.fieldDef.name}); + edges.on(elements, "change.ClickableOwner", this, "updateOwner"); + for (var i = 0; i < elements.length; i++) { + this.updateOwner(elements[i]); + } + }; - this.updateOwner = function(element) { - var that = $(element); - var val = that.val(); + this.updateOwner = function(element) { + var that = $(element); + var val = that.val(); - if (val) { - if (this.link) { - this.link.attr("href", "/account/" + val); - } else { - var classes = edges.css_classes(this.ns, "visit"); - var id = edges.css_id(this.ns, this.fieldDef.name); - that.after('

See this account’s profile

'); + if (val) { + if (this.link) { + this.link.attr("href", "/account/" + val); + } else { + var classes = edges.css_classes(this.ns, "visit"); + var id = edges.css_id(this.ns, this.fieldDef.name); + that.after('

See this account’s profile

'); - var selector = edges.css_id_selector(this.ns, this.fieldDef.name); - this.link = $(selector, this.form.context); + var selector = edges.css_id_selector(this.ns, this.fieldDef.name); + this.link = $(selector, this.form.context); + } + } else if (this.link) { + this.link.remove(); + this.link = false; } - } else if (this.link) { - this.link.remove(); - this.link = false; - } - }; + }; - this.init(); - }, + this.init(); + }, - newTrimWhitespace : function(params) { - return edges.instantiate(formulaic.widgets.TrimWhitespace, params) - }, - TrimWhitespace : function(params) { - this.fieldDef = params.fieldDef; - this.form = params.formulaic; + newTrimWhitespace : function(params) { + return edges.instantiate(formulaic.widgets.TrimWhitespace, params) + }, + TrimWhitespace : function(params) { + this.fieldDef = params.fieldDef; + this.form = params.formulaic; - this.ns = "formulaic-trimwhitespace"; + this.ns = "formulaic-trimwhitespace"; - this.link = false; + this.link = false; - this.init = function () { - var elements = this.form.controlSelect.input({name: this.fieldDef.name}); - edges.on(elements, "focus.TrimWhitespace", this, "trim"); - edges.on(elements, "blur.TrimWhitespace", this, "trim"); + this.init = function () { + var elements = this.form.controlSelect.input({name: this.fieldDef.name}); + edges.on(elements, "focus.TrimWhitespace", this, "trim"); + edges.on(elements, "blur.TrimWhitespace", this, "trim"); - for (var i = 0; i < elements.length; i++) { - this.trim(elements[i]); - } - }; + for (var i = 0; i < elements.length; i++) { + this.trim(elements[i]); + } + }; - this.trim = function(element) { - var that = $(element); - var val = that.val(); - var nv = val.trim(); - if (nv !== val) { - that.val(nv); - } - }; + this.trim = function(element) { + var that = $(element); + var val = that.val(); + var nv = val.trim(); + if (nv !== val) { + that.val(nv); + } + }; - this.init(); - }, + this.init(); + }, - newClickableUrl : function(params) { - return edges.instantiate(formulaic.widgets.ClickableUrl, params) - }, - ClickableUrl : function(params) { - this.fieldDef = params.fieldDef; - this.form = params.formulaic; + newClickableUrl : function(params) { + return edges.instantiate(formulaic.widgets.ClickableUrl, params) + }, + ClickableUrl : function(params) { + this.fieldDef = params.fieldDef; + this.form = params.formulaic; - this.ns = "formulaic-clickableurl"; + this.ns = "formulaic-clickableurl"; - this.link = false; + this.link = false; - this.init = function() { - var elements = this.form.controlSelect.input( - {name: this.fieldDef.name}); - // TODO: should work as-you-type by changing "change" to "keyup" event; doesn't work in edges - //edges.on(elements, "change.ClickableUrl", this, "updateUrl"); - edges.on(elements, "keyup.ClickableUrl", this, "updateUrl"); - - for (var i = 0; i < elements.length; i++) { - this.updateUrl(elements[i]); - } - }; - - this.updateUrl = function(element) { - var that = $(element); - var val = that.val(); - var id = edges.css_id(this.ns, this.fieldDef.name); - - if (val && (val.substring(0,7) === "http://" || val.substring(0,8) === "https://") && val.length > 10) { - if (this.link) { - this.link.text(val); - this.link.attr("href", val); - } else { - var classes = edges.css_classes(this.ns, "visit"); - that.after('

' + val + '

'); + this.init = function() { + var elements = this.form.controlSelect.input( + {name: this.fieldDef.name}); + // TODO: should work as-you-type by changing "change" to "keyup" event; doesn't work in edges + //edges.on(elements, "change.ClickableUrl", this, "updateUrl"); + edges.on(elements, "keyup.ClickableUrl", this, "updateUrl"); - var selector = edges.css_id_selector(this.ns, this.fieldDef.name); - this.link = $(selector, this.form.context); + for (var i = 0; i < elements.length; i++) { + this.updateUrl(elements[i]); } - } else if (this.link) { - this.link.remove(); - this.link = false; - } - }; - - this.init(); - }, + }; - newFullContents : function(params) { - return edges.instantiate(formulaic.widgets.FullContents, params) - }, - FullContents : function(params) { - this.fieldDef = params.fieldDef; - this.form = params.formulaic; - this.args = params.args; + this.updateUrl = function(element) { + var that = $(element); + var val = that.val(); + var id = edges.css_id(this.ns, this.fieldDef.name); - this.ns = "formulaic-fullcontents"; + if (val && (val.substring(0,7) === "http://" || val.substring(0,8) === "https://") && val.length > 10) { + if (this.link) { + this.link.text(val); + this.link.attr("href", val); + } else { + var classes = edges.css_classes(this.ns, "visit"); + that.after('

' + val + '

'); - this.container = false; + var selector = edges.css_id_selector(this.ns, this.fieldDef.name); + this.link = $(selector, this.form.context); + } + } else if (this.link) { + this.link.remove(); + this.link = false; + } + }; - this.init = function() { - var elements = this.form.controlSelect.input({name: this.fieldDef.name}); - edges.on(elements, "keyup.FullContents", this, "updateContents"); + this.init(); + }, - for (var i = 0; i < elements.length; i++) { - this.updateContents(elements[i]); - } - }; + newFullContents : function(params) { + return edges.instantiate(formulaic.widgets.FullContents, params) + }, + FullContents : function(params) { + this.fieldDef = params.fieldDef; + this.form = params.formulaic; + this.args = params.args; - this.updateContents = function(element) { - var that = $(element); - var val = that.val(); + this.ns = "formulaic-fullcontents"; - // if there is a behaviour for when the field is empty and disabled, then check if it is, and if - // it is include the desired alternative text - if (this.args && this.args.empty_disabled) { - if (val === "" && that.prop("disabled")) { - val = this.args.empty_disabled; - } - } + this.container = false; - if (val) { - if (this.container) { - this.container.html('Full contents: ' + edges.escapeHtml(val) + ''); - } else { - var classes = edges.css_classes(this.ns, "contents"); - var id = edges.css_id(this.ns, this.fieldDef.name); - that.after('

Full contents: ' + edges.escapeHtml(val) + '

'); + this.init = function() { + var elements = this.form.controlSelect.input({name: this.fieldDef.name}); + edges.on(elements, "keyup.FullContents", this, "updateContents"); - var selector = edges.css_id_selector(this.ns, this.fieldDef.name); - this.container = $(selector, this.form.context); + for (var i = 0; i < elements.length; i++) { + this.updateContents(elements[i]); } - } else if (this.container) { - this.container.remove(); - this.container = false; - } - }; + }; - this.init(); - }, + this.updateContents = function(element) { + var that = $(element); + var val = that.val(); - newNoteModal : function(params) { - return edges.instantiate(formulaic.widgets.NoteModal, params) - }, - NoteModal : function(params) { - this.fieldDef = params.fieldDef; - this.form = params.formulaic; + // if there is a behaviour for when the field is empty and disabled, then check if it is, and if + // it is include the desired alternative text + if (this.args && this.args.empty_disabled) { + if (val === "" && that.prop("disabled")) { + val = this.args.empty_disabled; + } + } - this.ns = "formulaic-notemodal"; + if (val) { + if (this.container) { + this.container.html('Full contents: ' + edges.escapeHtml(val) + ''); + } else { + var classes = edges.css_classes(this.ns, "contents"); + var id = edges.css_id(this.ns, this.fieldDef.name); + that.after('

Full contents: ' + edges.escapeHtml(val) + '

'); - this.container = false; + var selector = edges.css_id_selector(this.ns, this.fieldDef.name); + this.container = $(selector, this.form.context); + } + } else if (this.container) { + this.container.remove(); + this.container = false; + } + }; - this.init = function() { - var viewClass = edges.css_classes(this.ns, "view"); - var closeClass = edges.css_classes(this.ns, "close"); - let group = $("div[name='" + this.fieldDef["name"] + "__group']") - var textarea = group.find("textarea"); - - let inputs = group.find("input[type=text]") - for (let i = 0; i < inputs.length; i++) { - let jqin = $(inputs[i]); - let iid = jqin.attr("id"); - if (iid.endsWith("_author")) { - let val = jqin.val() - if (val === "") { - jqin.hide(); + this.init(); + }, + + newNoteModal : function(params) { + return edges.instantiate(formulaic.widgets.NoteModal, params) + }, + NoteModal : function(params) { + this.fieldDef = params.fieldDef; + this.form = params.formulaic; + + this.ns = "formulaic-notemodal"; + + this.container = false; + + this.init = function() { + var viewClass = edges.css_classes(this.ns, "view"); + var closeClass = edges.css_classes(this.ns, "close"); + let group = $("div[name='" + this.fieldDef["name"] + "__group']") + var textarea = group.find("textarea"); + + let inputs = group.find("input[type=text]") + for (let i = 0; i < inputs.length; i++) { + let jqin = $(inputs[i]); + let iid = jqin.attr("id"); + if (iid.endsWith("_author")) { + let val = jqin.val() + if (val === "") { + jqin.hide(); + } } } - } - for (var i = 0; i < textarea.length; i++) { - var container = $(textarea[i]); + for (var i = 0; i < textarea.length; i++) { + var container = $(textarea[i]); - let contentHeight = container[0].scrollHeight; - if (contentHeight > 200) { - contentHeight = 200; - } - container.css("height", (contentHeight + 5) + "px"); + let contentHeight = container[0].scrollHeight; + if (contentHeight > 200) { + contentHeight = 200; + } + container.css("height", (contentHeight + 5) + "px"); - var modalId = "modal-" + this.fieldDef["name"] + "-" + i; + var modalId = "modal-" + this.fieldDef["name"] + "-" + i; - var date = $("#" + this.fieldDef["name"] + "-" + i + "-note_date"); - var note = $("#" + this.fieldDef["name"] + "-" + i + "-note"); - var author = $("#" + this.fieldDef["name"] + "-" + i + "-note_author"); + var date = $("#" + this.fieldDef["name"] + "-" + i + "-note_date"); + var note = $("#" + this.fieldDef["name"] + "-" + i + "-note"); + var author = $("#" + this.fieldDef["name"] + "-" + i + "-note_author"); - $(` + $(` `).insertAfter(container); - } - - var viewSelector = edges.css_class_selector(this.ns, "view"); - edges.on(viewSelector, "click", this, "showModal"); - - var closeSelector = edges.css_class_selector(this.ns, "close"); - edges.on(closeSelector, "click", this, "closeModal"); - }; + } - this.showModal = function(element) { - var that = $(element); - var modal = that.siblings(".modal"); - modal.show(); - }; + var viewSelector = edges.css_class_selector(this.ns, "view"); + edges.on(viewSelector, "click", this, "showModal"); - this.closeModal = function(element) { - var that = $(element); - var modal = that.parents(".modal"); - modal.hide(); - }; + var closeSelector = edges.css_class_selector(this.ns, "close"); + edges.on(closeSelector, "click", this, "closeModal"); + }; - this.init(); - }, + this.showModal = function(element) { + var that = $(element); + var modal = that.siblings(".modal"); + modal.show(); + }; - newInfiniteRepeat : function(params) { - return edges.instantiate(formulaic.widgets.InfiniteRepeat, params) - }, - InfiniteRepeat: function(params) { - this.fieldDef = params.fieldDef; - this.args = params.args; + this.closeModal = function(element) { + var that = $(element); + var modal = that.parents(".modal"); + modal.hide(); + }; - this.idRx = /(.+?-)(\d+)(-.+)/; - this.template = ""; - this.container = false; - this.divs = false; + this.init(); + }, + + newInfiniteRepeat : function(params) { + return edges.instantiate(formulaic.widgets.InfiniteRepeat, params) + }, + InfiniteRepeat: function(params) { + this.fieldDef = params.fieldDef; + this.args = params.args; + + this.idRx = /(.+?-)(\d+)(-.+)/; + this.template = ""; + this.container = false; + this.divs = false; + + this.init = function() { + this.divs = $("div[name='" + this.fieldDef["name"] + "__group']"); + for (var i = 0 ; i < this.divs.length; i++) { + var div = $(this.divs[i]); + div.append($('')); + feather.replace(); + } - this.init = function() { - this.divs = $("div[name='" + this.fieldDef["name"] + "__group']"); - for (var i = 0 ; i < this.divs.length; i++) { - var div = $(this.divs[i]); - div.append($('')); - feather.replace(); - } + this.template = $(this.divs[0]).html(); + this.container = $(this.divs[0]).parents(".removable-fields"); - this.template = $(this.divs[0]).html(); - this.container = $(this.divs[0]).parents(".removable-fields"); - - if (this.divs.length === 1) { - let div = $(this.divs[0]); - let inputs = div.find(":input"); - let tripwire = false; - for (var i = 0; i < inputs.length; i++) { - if ($(inputs[i]).val()) { - tripwire = true; - break; + if (this.divs.length === 1) { + let div = $(this.divs[0]); + let inputs = div.find(":input"); + let tripwire = false; + for (var i = 0; i < inputs.length; i++) { + if ($(inputs[i]).val()) { + tripwire = true; + break; + } + } + if (!tripwire) { + // the field is empty + $(this.divs[0]).remove(); + this.divs = []; } } - if (!tripwire) { - // the field is empty - $(this.divs[0]).remove(); - this.divs = []; - } - } - this.addFieldBtn = $("#add_field__" + this.fieldDef["name"]); - this.removeFieldBtns = $('[id^="remove_field__' + this.fieldDef["name"] + '"]'); + this.addFieldBtn = $("#add_field__" + this.fieldDef["name"]); + this.removeFieldBtns = $('[id^="remove_field__' + this.fieldDef["name"] + '"]'); - edges.on(this.addFieldBtn, "click", this, "addField"); - edges.on(this.removeFieldBtns, "click", this, "removeField"); + edges.on(this.addFieldBtn, "click", this, "addField"); + edges.on(this.removeFieldBtns, "click", this, "removeField"); - // show or hide the remove buttons - for (let i = 0; i < this.divs.length; i++) { - let cur_div = $(this.divs[i]); - if (!cur_div.find('textarea').is(':disabled')) { - cur_div.find('[id^="remove_field__"]').show(); + // show or hide the remove buttons + for (let i = 0; i < this.divs.length; i++) { + let cur_div = $(this.divs[i]); + if (!cur_div.find('textarea').is(':disabled')) { + cur_div.find('[id^="remove_field__"]').show(); + } } - } - }; + }; - this.addField = function() { - var currentLargest = -1; - for (var i = 0; i < this.divs.length; i++) { - var div = $(this.divs[i]); - var id = div.find(":input").attr("id"); - var match = id.match(this.idRx); - var thisId = parseInt(match[2]); - if (thisId > currentLargest) { - currentLargest = thisId; + this.addField = function() { + var currentLargest = -1; + for (var i = 0; i < this.divs.length; i++) { + var div = $(this.divs[i]); + var id = div.find(":input").attr("id"); + var match = id.match(this.idRx); + var thisId = parseInt(match[2]); + if (thisId > currentLargest) { + currentLargest = thisId; + } } - } - var newId = currentLargest + 1; - - var frag = '
' + this.template + '
'; - var jqt = $(frag); - var that = this; - jqt.find(":input").each(function() { - var el = $(this); - var id = el.attr("id"); + var newId = currentLargest + 1; - var match = id.match(that.idRx); - if (match) { - var bits = id.split(that.idRx); - var newName = bits[1] + newId + bits[3]; - el.attr("id", newName).attr("name", newName).val(""); - } else { - // could be the remove button - if (id.substring(0, "remove_field".length) === "remove_field") { - el.attr("id", "remove_field__" + that.fieldDef["name"] + "--id_" + newId); - el.show(); + var frag = '
' + this.template + '
'; + var jqt = $(frag); + var that = this; + jqt.find(":input").each(function() { + var el = $(this); + var id = el.attr("id"); + + var match = id.match(that.idRx); + if (match) { + var bits = id.split(that.idRx); + var newName = bits[1] + newId + bits[3]; + el.attr("id", newName).attr("name", newName).val(""); + } else { + // could be the remove button + if (id.substring(0, "remove_field".length) === "remove_field") { + el.attr("id", "remove_field__" + that.fieldDef["name"] + "--id_" + newId); + el.show(); + } + } + }); + if (this.args.enable_on_repeat) { + for (var i = 0; i < this.args.enable_on_repeat.length; i++) { + var enables = jqt.find(that.args.enable_on_repeat[i]); + enables.removeAttr("disabled"); } } - }); - if (this.args.enable_on_repeat) { - for (var i = 0; i < this.args.enable_on_repeat.length; i++) { - var enables = jqt.find(that.args.enable_on_repeat[i]); - enables.removeAttr("disabled"); - } - } - var topPlacement = this.fieldDef.repeatable.add_button_placement === "top"; - if (this.divs.length > 0) { - if (topPlacement) { - $(this.divs[0]).before(jqt); + var topPlacement = this.fieldDef.repeatable.add_button_placement === "top"; + if (this.divs.length > 0) { + if (topPlacement) { + $(this.divs[0]).before(jqt); + } else { + $(this.divs[this.divs.length - 1]).after(jqt); + } } else { - $(this.divs[this.divs.length - 1]).after(jqt); + this.container.append(jqt); } - } else { - this.container.append(jqt); - } - this.divs = $("div[name='" + this.fieldDef["name"] + "__group']"); - this.removeFieldBtns = $('[id^="remove_field__' + this.fieldDef["name"] + '"]'); - edges.on(this.removeFieldBtns, "click", this, "removeField"); - }; + this.divs = $("div[name='" + this.fieldDef["name"] + "__group']"); + this.removeFieldBtns = $('[id^="remove_field__' + this.fieldDef["name"] + '"]'); + edges.on(this.removeFieldBtns, "click", this, "removeField"); + }; - this.removeField = function(element) { - var container = $(element).parents("div[name='" + this.fieldDef["name"] + "__group']"); - container.remove(); - this.divs = $("div[name='" + this.fieldDef["name"] + "__group']"); - }; + this.removeField = function(element) { + var container = $(element).parents("div[name='" + this.fieldDef["name"] + "__group']"); + container.remove(); + this.divs = $("div[name='" + this.fieldDef["name"] + "__group']"); + }; - this.init(); - }, + this.init(); + }, - newMultipleField : function(params) { - return edges.instantiate(formulaic.widgets.MultipleField, params) - }, - MultipleField: function(params) { - this.fieldDef = params.fieldDef; - this.max = this.fieldDef["repeatable"]["initial"] - 1; + newMultipleField : function(params) { + return edges.instantiate(formulaic.widgets.MultipleField, params) + }, + MultipleField: function(params) { + this.fieldDef = params.fieldDef; + this.max = this.fieldDef["repeatable"]["initial"] - 1; - this.init = () => { - if (this.fieldDef["input"] === "group") { - this._setupRepeatingGroup(); - } else { - this._setupRepeatingIndividual(); - } - feather.replace(); - }; + this.init = () => { + if (this.fieldDef["input"] === "group") { + this._setupRepeatingGroup(); + } else { + this._setupRepeatingIndividual(); + } + feather.replace(); + }; - this._setupIndividualSelect2 = function() { - for (var idx = 0; idx < this.fields.length; idx++) { - let f = this.fields[idx]; - let s2_input = $($(f).select2()); - $(f).on("focus", formulaic.widgets._select2_shift_focus); - s2_input.after($('')); - if (idx !== 0) { - s2_input.attr("required", false); - s2_input.attr("data-parsley-validate-if-empty", "true"); - if (!s2_input.val()) { - s2_input.closest('li').hide(); - } else { - this.count++; + this._setupIndividualSelect2 = function() { + for (var idx = 0; idx < this.fields.length; idx++) { + let f = this.fields[idx]; + let s2_input = $($(f).select2()); + $(f).on("focus", formulaic.widgets._select2_shift_focus); + s2_input.after($('')); + if (idx !== 0) { + s2_input.attr("required", false); + s2_input.attr("data-parsley-validate-if-empty", "true"); + if (!s2_input.val()) { + s2_input.closest('li').hide(); + } else { + this.count++; + } } } - } - this.remove_btns = $('[id^="remove_field__' + this.fieldDef["name"] + '"]'); - if (this.count === 0) { - $(this.remove_btns[0]).hide(); - } - - this.addFieldBtn = $("#add_field__" + this.fieldDef["name"]); - this.addFieldBtn.on("click", () => { - $('#s2id_' + this.fieldDef["name"] + '-' + (this.count + 1)).closest('li').show(); - this.count++; - if (this.count > 0) { - $(this.remove_btns[0]).show(); - } - if (this.count >= this.max) { - $(this.addFieldBtn).hide(); + this.remove_btns = $('[id^="remove_field__' + this.fieldDef["name"] + '"]'); + if (this.count === 0) { + $(this.remove_btns[0]).hide(); } - }); - if (this.count >= this.max) { - this.addFieldBtn.hide(); - } - - $(this.remove_btns).each((idx, btn) => { - $(btn).on("click", (event) => { - for (let i = idx; i < this.count; i++) { - let data = $(this.fields[i + 1]).select2('data'); - if (data === null) { - data = {id: i, text: ""}; - } - $(this.fields[i]).select2('data', {id: data.id, text: data.text}); - } - this.count--; - $(this.fields[this.count + 1]).select2('data', {id: this.count + 1, text: ""}); - $('#s2id_' + this.fieldDef["name"] + '-' + (this.count + 1)).closest('li').hide(); - if (this.count === 0) { - $(this.remove_btns[0]).hide(); + this.addFieldBtn = $("#add_field__" + this.fieldDef["name"]); + this.addFieldBtn.on("click", () => { + $('#s2id_' + this.fieldDef["name"] + '-' + (this.count + 1)).closest('li').show(); + this.count++; + if (this.count > 0) { + $(this.remove_btns[0]).show(); } - if (this.count < this.max) { - $(this.addFieldBtn).show(); - } - }) - }) - }; - - this._setupIndividualField = function() { - for (var idx = 0; idx < this.fields.length; idx++) { - let f = this.fields[idx]; - let jqf = $(f); - jqf.after($('')); - if (idx !== 0) { - jqf.attr("required", false); - jqf.attr("data-parsley-validate-if-empty", "true"); - if (!jqf.val()) { - jqf.parent().hide(); - } else { - this.count++; + if (this.count >= this.max) { + $(this.addFieldBtn).hide(); } - } - } - - this.remove_btns = $('[id^="remove_field__' + this.fieldDef["name"] + '-"]'); - if (this.count === 0) { - $(this.remove_btns[0]).hide(); - } + }); - this.addFieldBtn = $("#add_field__" + this.fieldDef["name"]); - this.addFieldBtn.on("click", () => { - let next_input = $('[id="' + this.fieldDef["name"] + '-' + (this.count + 1) +'"]').parent(); - // TODO: why .show() does not work? - $(next_input).show(); - this.count++; - if (this.count > 0) { - $(this.remove_btns[0]).show(); - } if (this.count >= this.max) { - $(this.addFieldBtn).hide(); + this.addFieldBtn.hide(); } - }); - if (this.count >= this.max) { - this.addFieldBtn.hide(); - } + $(this.remove_btns).each((idx, btn) => { + $(btn).on("click", (event) => { + for (let i = idx; i < this.count; i++) { + let data = $(this.fields[i + 1]).select2('data'); + if (data === null) { + data = {id: i, text: ""}; + } + $(this.fields[i]).select2('data', {id: data.id, text: data.text}); + } + this.count--; + $(this.fields[this.count + 1]).select2('data', {id: this.count + 1, text: ""}); + $('#s2id_' + this.fieldDef["name"] + '-' + (this.count + 1)).closest('li').hide(); + if (this.count === 0) { + $(this.remove_btns[0]).hide(); + } + if (this.count < this.max) { + $(this.addFieldBtn).show(); + } + }) + }) + }; - $(this.remove_btns).each((idx, btn) => { - $(btn).on("click", (event) => { - for (let i = idx; i < this.count; i++) { - let data = $(this.fields[i + 1]).val(); - if (data === null) { - data = ""; + this._setupIndividualField = function() { + for (var idx = 0; idx < this.fields.length; idx++) { + let f = this.fields[idx]; + let jqf = $(f); + jqf.after($('')); + if (idx !== 0) { + jqf.attr("required", false); + jqf.attr("data-parsley-validate-if-empty", "true"); + if (!jqf.val()) { + jqf.parent().hide(); + } else { + this.count++; } - $(this.fields[i]).val(data); } + } + + this.remove_btns = $('[id^="remove_field__' + this.fieldDef["name"] + '-"]'); + if (this.count === 0) { + $(this.remove_btns[0]).hide(); + } - this.count--; - $(this.fields[this.count + 1]).val(""); - let last_input = $('[id="' + this.fieldDef["name"] + '-' + (this.count + 1) +'"]').parent(); - $(last_input).hide(); - if (this.count === 0) { - $(this.remove_btns[0]).hide(); + this.addFieldBtn = $("#add_field__" + this.fieldDef["name"]); + this.addFieldBtn.on("click", () => { + let next_input = $('[id="' + this.fieldDef["name"] + '-' + (this.count + 1) +'"]').parent(); + // TODO: why .show() does not work? + $(next_input).show(); + this.count++; + if (this.count > 0) { + $(this.remove_btns[0]).show(); } - if (this.count < this.max) { - $(this.addFieldBtn).show(); + if (this.count >= this.max) { + $(this.addFieldBtn).hide(); } - }) - }); - }; + }); - this._setupRepeatingIndividual = function() { - let tag = this.fieldDef["input"] === "select" ? "select" : "input"; - this.fields = $(tag + '[id^="' + this.fieldDef["name"] + '-"]'); - this.count = 0; + if (this.count >= this.max) { + this.addFieldBtn.hide(); + } - if (tag === "select"){ - this._setupIndividualSelect2(); - } else { - this._setupIndividualField(); - } - }; + $(this.remove_btns).each((idx, btn) => { + $(btn).on("click", (event) => { + for (let i = idx; i < this.count; i++) { + let data = $(this.fields[i + 1]).val(); + if (data === null) { + data = ""; + } + $(this.fields[i]).val(data); + } - this._setupRepeatingGroup = function() { - this.divs = $("div[name='" + this.fieldDef["name"] + "__group']"); - this.count = 0; + this.count--; + $(this.fields[this.count + 1]).val(""); + let last_input = $('[id="' + this.fieldDef["name"] + '-' + (this.count + 1) +'"]').parent(); + $(last_input).hide(); + if (this.count === 0) { + $(this.remove_btns[0]).hide(); + } + if (this.count < this.max) { + $(this.addFieldBtn).show(); + } + }) + }); + }; - for (var idx = 0; idx < this.divs.length; idx++) { - let div = $(this.divs[idx]); - div.append($('')); + this._setupRepeatingIndividual = function() { + let tag = this.fieldDef["input"] === "select" ? "select" : "input"; + this.fields = $(tag + '[id^="' + this.fieldDef["name"] + '-"]'); + this.count = 0; - if (idx !== 0) { - let inputs = div.find(":input"); - var hasVal = false; - for (var j = 0; j < inputs.length; j++) { - $(inputs[j]).attr("required", false) - .attr("data-parsley-required-if", false) - .attr("data-parsley-validate-if-empty", "true"); - if ($(inputs[j]).val()) { - hasVal = true; + if (tag === "select"){ + this._setupIndividualSelect2(); + } else { + this._setupIndividualField(); + } + }; + + this._setupRepeatingGroup = function() { + this.divs = $("div[name='" + this.fieldDef["name"] + "__group']"); + this.count = 0; + + for (var idx = 0; idx < this.divs.length; idx++) { + let div = $(this.divs[idx]); + div.append($('')); + + if (idx !== 0) { + let inputs = div.find(":input"); + var hasVal = false; + for (var j = 0; j < inputs.length; j++) { + $(inputs[j]).attr("required", false) + .attr("data-parsley-required-if", false) + .attr("data-parsley-validate-if-empty", "true"); + if ($(inputs[j]).val()) { + hasVal = true; + } + } + if (!hasVal) { + div.hide(); + } else { + this.count++; } - } - if (!hasVal) { - div.hide(); - } else { - this.count++; } } - } - - this.remove_btns = $('[id^="remove_field__' + this.fieldDef["name"] + '"]'); - if (this.count === 0) { - $(this.remove_btns[0]).hide(); - } - this.addFieldBtn = $("#add_field__" + this.fieldDef["name"]); - this.addFieldBtn.on("click", () => { - $(this.divs[this.count + 1]).show(); - this.count++; - if (this.count > 0) { - $(this.remove_btns[0]).show(); + this.remove_btns = $('[id^="remove_field__' + this.fieldDef["name"] + '"]'); + if (this.count === 0) { + $(this.remove_btns[0]).hide(); } - if (this.count === this.max) { - $(this.addFieldBtn).hide(); + + this.addFieldBtn = $("#add_field__" + this.fieldDef["name"]); + this.addFieldBtn.on("click", () => { + $(this.divs[this.count + 1]).show(); + this.count++; + if (this.count > 0) { + $(this.remove_btns[0]).show(); + } + if (this.count === this.max) { + $(this.addFieldBtn).hide(); + } + }); + + if (this.count >= this.max) { + this.addFieldBtn.hide(); } - }); - if (this.count >= this.max) { - this.addFieldBtn.hide(); - } + $(this.remove_btns).each((idx, btn) => { + $(btn).on("click", () => { + let thisDiv = $(btn).parent(); + let nextDiv = $(thisDiv); + for (let i = idx; i < this.count; i++) { + thisDiv = nextDiv; + nextDiv = nextDiv.next(); + let thisInputs = $(thisDiv).find("select, input[id^='" + this.fieldDef["name"] + "']"); + let nextInputs = $(nextDiv).find("select, input[id^='" + this.fieldDef["name"] + "']"); + for (let j = 0; j < thisInputs.length; j++){ + let thisInput = $(thisInputs[j]); + let nextInput = $(nextInputs[j]); + if (thisInput.is("select")){ + let data = $(nextInput).select2('data'); + if (data === null) { + data = {id: i, text: ""}; + } + $(thisInput).select2('data', {id: data.id, text: data.text}); - $(this.remove_btns).each((idx, btn) => { - $(btn).on("click", () => { - let thisDiv = $(btn).parent(); - let nextDiv = $(thisDiv); - for (let i = idx; i < this.count; i++) { - thisDiv = nextDiv; - nextDiv = nextDiv.next(); - let thisInputs = $(thisDiv).find("select, input[id^='" + this.fieldDef["name"] + "']"); - let nextInputs = $(nextDiv).find("select, input[id^='" + this.fieldDef["name"] + "']"); - for (let j = 0; j < thisInputs.length; j++){ - let thisInput = $(thisInputs[j]); - let nextInput = $(nextInputs[j]); - if (thisInput.is("select")){ - let data = $(nextInput).select2('data'); - if (data === null) { - data = {id: i, text: ""}; } - $(thisInput).select2('data', {id: data.id, text: data.text}); - + else { + $(thisInputs[j]).val($(nextInputs[j]).val()); + } + } + } + this.count--; + $(this.divs[this.count + 1]).find("select, input[id^='" + this.fieldDef["name"] + "']").each((idx, inp) => { + if ($(inp).is("select")){ + $(inp).select2('data', {id: this.count + 1, text: ""}); } else { - $(thisInputs[j]).val($(nextInputs[j]).val()); + $(inp).val(""); } + }); + $(this.divs[this.count + 1]).hide(); + if (this.count === 0) { + $(this.remove_btns[0]).hide(); } - } - this.count--; - $(this.divs[this.count + 1]).find("select, input[id^='" + this.fieldDef["name"] + "']").each((idx, inp) => { - if ($(inp).is("select")){ - $(inp).select2('data', {id: this.count + 1, text: ""}); - } - else { - $(inp).val(""); + if (this.count < this.max) { + $(this.addFieldBtn).show(); } - }); - $(this.divs[this.count + 1]).hide(); - if (this.count === 0) { - $(this.remove_btns[0]).hide(); - } - if (this.count < this.max) { - $(this.addFieldBtn).show(); - } + }) }) - }) - }; + }; - this.init() - }, + this.init() + }, - newSelect : function(params) { - return edges.instantiate(formulaic.widgets.Select, params); - }, - Select : function(params) { - this.fieldDef = params.fieldDef; - this.form = params.formulaic; - this.args = params.args; + newSelect : function(params) { + return edges.instantiate(formulaic.widgets.Select, params); + }, + Select : function(params) { + this.fieldDef = params.fieldDef; + this.form = params.formulaic; + this.args = params.args; - this.ns = "formulaic-select"; - this.elements = false; + this.ns = "formulaic-select"; + this.elements = false; - this.init = function() { - let allow_clear = this.args.allow_clear || false; - this.elements = $("select[name$='" + this.fieldDef.name + "']"); - this.elements.select2({ - allowClear: allow_clear, - newOption: true, - placeholder: "Start typing…" - }); - $(this.elements).on("focus", formulaic.widgets._select2_shift_focus); - }; + this.init = function() { + let allow_clear = this.args.allow_clear || false; + this.elements = $("select[name$='" + this.fieldDef.name + "']"); + this.elements.select2({ + allowClear: allow_clear, + newOption: true, + placeholder: "Start typing…" + }); + $(this.elements).on("focus", formulaic.widgets._select2_shift_focus); + }; - this.init(); - }, + this.init(); + }, + + newTagList : function(params) { + return edges.instantiate(formulaic.widgets.TagList, params); + }, + TagList : function(params) { + this.fieldDef = params.fieldDef; + this.form = params.formulaic; + this.args = params.args; + + this.ns = "formulaic-taglist"; + + this.init = function() { + var stopWords = edges.getParam(this.args.stopWords, []); + + var ajax = { + url: current_scheme + "//" + current_domain + "/autocomplete/journal/" + this.args["field"], + dataType: 'json', + data: function (term, page) { + return { + q: term + }; + }, + results: function (data, page) { + return {results: data["suggestions"].filter((x) => $.inArray(x.text.toLowerCase(), stopWords) === -1)}; + } + }; - newTagList : function(params) { - return edges.instantiate(formulaic.widgets.TagList, params); - }, - TagList : function(params) { - this.fieldDef = params.fieldDef; - this.form = params.formulaic; - this.args = params.args; + var csc = function (term) { + if ($.inArray(term.toLowerCase(), stopWords) !== -1) { + return null; + } + return {id: $.trim(term), text: $.trim(term)}; + }; - this.ns = "formulaic-taglist"; - this.init = function() { - var stopWords = edges.getParam(this.args.stopWords, []); - - var ajax = { - url: current_scheme + "//" + current_domain + "/autocomplete/journal/" + this.args["field"], - dataType: 'json', - data: function (term, page) { - return { - q: term - }; - }, - results: function (data, page) { - return {results: data["suggestions"].filter((x) => $.inArray(x.text.toLowerCase(), stopWords) === -1)}; - } - }; + var initSel = function (element, callback) { + var initial = element.val(); + var entries = initial.split(",").map(x => x.trim()).filter(x => x !== ""); + var data = []; + for (var i = 0; i < entries.length; i++) { + data.push({id: entries[i], text: entries[i]}); + } + callback(data); + }; - var csc = function (term) { - if ($.inArray(term.toLowerCase(), stopWords) !== -1) { - return null; - } - return {id: $.trim(term), text: $.trim(term)}; - }; + // apply the create search choice + let selector = "[name='" + this.fieldDef.name + "']"; + $(selector).select2({ + multiple: true, + minimumInputLength: 1, + ajax: ajax, + createSearchChoice: csc, + initSelection: initSel, + placeholder: "Start typing…", + allowClear: false, + tags: true, + tokenSeparators: [',', ";"], + maximumSelectionSize: this.args["maximumSelectionSize"], + width: 'resolve' + }); + $(selector).on("focus", formulaic.widgets._select2_shift_focus); - var initSel = function (element, callback) { - var initial = element.val(); - var entries = initial.split(",").map(x => x.trim()).filter(x => x !== ""); - var data = []; - for (var i = 0; i < entries.length; i++) { - data.push({id: entries[i], text: entries[i]}); - } - callback(data); }; - // apply the create search choice - let selector = "[name='" + this.fieldDef.name + "']"; - $(selector).select2({ - multiple: true, - minimumInputLength: 1, - ajax: ajax, - createSearchChoice: csc, - initSelection: initSel, - placeholder: "Start typing…", - allowClear: false, - tags: true, - tokenSeparators: [',', ";"], - maximumSelectionSize: this.args["maximumSelectionSize"], - width: 'resolve' - }); - - $(selector).on("focus", formulaic.widgets._select2_shift_focus); - - }; - - this.init(); - }, + this.init(); + }, - newTagEntry : function(params) { - return edges.instantiate(formulaic.widgets.TagEntry, params); - }, - TagEntry : function(params) { - this.fieldDef = params.fieldDef; - this.form = params.formulaic; - this.args = params.args; + newTagEntry : function(params) { + return edges.instantiate(formulaic.widgets.TagEntry, params); + }, + TagEntry : function(params) { + this.fieldDef = params.fieldDef; + this.form = params.formulaic; + this.args = params.args; - this.ns = "formulaic-tagentry"; + this.ns = "formulaic-tagentry"; - this.init = function() { - let selector = "[name='" + this.fieldDef.name + "']"; - $(selector).select2({ - minimumInputLength: 1, - tags: [], - tokenSeparators: [','], - width: 'resolve' - }); - $(selector).on("focus", formulaic.widgets._select2_shift_focus); - }; + this.init = function() { + let selector = "[name='" + this.fieldDef.name + "']"; + $(selector).select2({ + minimumInputLength: 1, + tags: [], + tokenSeparators: [','], + width: 'resolve' + }); + $(selector).on("focus", formulaic.widgets._select2_shift_focus); + }; - this.init(); - }, + this.init(); + }, - newLoadEditors: function(params) { - return edges.instantiate(formulaic.widgets.LoadEditors, params); - }, + newLoadEditors: function(params) { + return edges.instantiate(formulaic.widgets.LoadEditors, params); + }, - LoadEditors: function(params) { - this.fieldDef = params.fieldDef; - this.params = params.args; + LoadEditors: function(params) { + this.fieldDef = params.fieldDef; + this.params = params.args; - this.groupField = false; - this.editorField = false; + this.groupField = false; + this.editorField = false; - this.init = function() { - this.groupField = $("[name='" + this.fieldDef.name + "']"); - this.editorField = $("[name='" + this.params.field + "']"); - edges.on(this.groupField, "change", this, "updateEditors"); - }; + this.init = function() { + this.groupField = $("[name='" + this.fieldDef.name + "']"); + this.editorField = $("[name='" + this.params.field + "']"); + edges.on(this.groupField, "change", this, "updateEditors"); + }; - this.updateEditors = function(element) { - var ed_group_name = $(element).val(); - var ed_query_url = "/admin/dropdown/eg_associates"; + this.updateEditors = function(element) { + var ed_group_name = $(element).val(); + var ed_query_url = "/admin/dropdown/eg_associates"; - // var ed_group_name = $("#s2id_editor_group").find('span').text(); - var that = this; - $.ajax({ - type : "GET", - data : {egn : ed_group_name}, - dataType: "json", - url: ed_query_url, - success: function(resp) - { - // Get the options for the drop-down from our ajax request - var assoc_options = []; - if (resp != null) + // var ed_group_name = $("#s2id_editor_group").find('span').text(); + var that = this; + $.ajax({ + type : "GET", + data : {egn : ed_group_name}, + dataType: "json", + url: ed_query_url, + success: function(resp) { - assoc_options = [["", "No editor assigned"]]; + // Get the options for the drop-down from our ajax request + var assoc_options = []; + if (resp != null) + { + assoc_options = [["", "No editor assigned"]]; - for (var i=0; i").attr("value", assoc_options[j][0]).text(assoc_options[j][1]) - ); + for (var j=0; j < assoc_options.length; j++) { + that.editorField.append( + $("").attr("value", assoc_options[j][0]).text(assoc_options[j][1]) + ); + } } - } - }) - }; - - this.init(); - }, - - newAutocomplete: function(params){ - return edges.instantiate(formulaic.widgets.Autocomplete, params); - }, - - Autocomplete: function(params){ - this.fieldDef = params.fieldDef; - this.params = params.args; - - this.init = function() { - let doc_type = this.params.type || "journal"; - let doc_field = this.params.field; - let mininput = this.params.min_input === undefined ? 3 : this.params.min_input; - let include_input = this.params.include === undefined ? true : this.params.include; - let allow_clear = this.params.allow_clear_input === undefined ? true : this.params.allow_clear_input; - - let ajax = { - url: current_scheme + "//" + current_domain + "/autocomplete/" + doc_type + "/" + doc_field, - dataType: 'json', - data: function (term, page) { - return { - q: term - }; - }, - results: function (data, page) { - return { results: data["suggestions"] }; - } + }) }; - var csc = function(term) {return {"id":term, "text": term};}; + this.init(); + }, + + newAutocomplete: function(params){ + return edges.instantiate(formulaic.widgets.Autocomplete, params); + }, + + Autocomplete: function(params){ + this.fieldDef = params.fieldDef; + this.params = params.args; + + this.init = function() { + let doc_type = this.params.type || "journal"; + let doc_field = this.params.field; + let mininput = this.params.min_input === undefined ? 3 : this.params.min_input; + let include_input = this.params.include === undefined ? true : this.params.include; + let allow_clear = this.params.allow_clear_input === undefined ? true : this.params.allow_clear_input; + + let ajax = { + url: current_scheme + "//" + current_domain + "/autocomplete/" + doc_type + "/" + doc_field, + dataType: 'json', + data: function (term, page) { + return { + q: term + }; + }, + results: function (data, page) { + return { results: data["suggestions"] }; + } + }; - var initSel = function (element, callback) { - var data = {id: element.val(), text: element.val()}; - callback(data); - }; + var csc = function(term) {return {"id":term, "text": term};}; - let selector = "[name='" + this.fieldDef.name + "']"; + var initSel = function (element, callback) { + var data = {id: element.val(), text: element.val()}; + callback(data); + }; - $(selector).on("focus", formulaic.widgets._select2_shift_focus); + let selector = "[name='" + this.fieldDef.name + "']"; - if (include_input) { - // apply the create search choice - $(selector).select2({ - minimumInputLength: mininput, - ajax: ajax, - createSearchChoice: csc, - initSelection : initSel, - allowClear: allow_clear, - width: 'resolve' - }); - } else { - // go without the create search choice option - $(selector).select2({ - minimumInputLength: mininput, - ajax: ajax, - initSelection : initSel, - allowClear: allow_clear, - width: 'resolve' - }); - } + $(selector).on("focus", formulaic.widgets._select2_shift_focus); - $(selector).on("focus", formulaic.widgets._select2_shift_focus); - }; + if (include_input) { + // apply the create search choice + $(selector).select2({ + minimumInputLength: mininput, + ajax: ajax, + createSearchChoice: csc, + initSelection : initSel, + allowClear: allow_clear, + width: 'resolve' + }); + } else { + // go without the create search choice option + $(selector).select2({ + minimumInputLength: mininput, + ajax: ajax, + initSelection : initSel, + allowClear: allow_clear, + width: 'resolve' + }); + } - this.init() - }, - newIssnLink : function(params) { - return edges.instantiate(formulaic.widgets.IssnLink, params) - }, - IssnLink : function(params) { - this.fieldDef = params.fieldDef; - this.form = params.formulaic; - this.issn = params.issn; + $(selector).on("focus", formulaic.widgets._select2_shift_focus); + }; - this.ns = "formulaic-issnlink"; + this.init() + }, + newIssnLink : function(params) { + return edges.instantiate(formulaic.widgets.IssnLink, params) + }, + IssnLink : function(params) { + this.fieldDef = params.fieldDef; + this.form = params.formulaic; + this.issn = params.issn; - this.link = false; - this.url = "https://portal.issn.org/resource/ISSN/"; + this.ns = "formulaic-issnlink"; - this.init = function() { - var elements = this.form.controlSelect.input( - {name: this.fieldDef.name}); - edges.on(elements, "keyup.IssnLink", this, "updateUrl"); + this.link = false; + this.url = "https://portal.issn.org/resource/ISSN/"; - for (var i = 0; i < elements.length; i++) { - this.updateUrl(elements[i]); - } - }; + this.init = function() { + var elements = this.form.controlSelect.input( + {name: this.fieldDef.name}); + edges.on(elements, "keyup.IssnLink", this, "updateUrl"); - this.updateUrl = function(element) { - var that = $(element); - var val = that.val(); - var id = edges.css_id(this.ns, this.fieldDef.name); + for (var i = 0; i < elements.length; i++) { + this.updateUrl(elements[i]); + } + }; - var match = val.match(/[d0-9]{4}-{0,1}[0-9]{3}[0-9xX]{1}/); - var url = this.url + val; + this.updateUrl = function(element) { + var that = $(element); + var val = that.val(); + var id = edges.css_id(this.ns, this.fieldDef.name); - if (val && match) { - if (this.link) { - this.link.text(url); - this.link.attr("href", url); - } else { - var classes = edges.css_classes(this.ns, "visit"); - that.after('

' + url + '

'); + var match = val.match(/[d0-9]{4}-{0,1}[0-9]{3}[0-9xX]{1}/); + var url = this.url + val; - var selector = edges.css_id_selector(this.ns, this.fieldDef.name); - this.link = $(selector, this.form.context); + if (val && match) { + if (this.link) { + this.link.text(url); + this.link.attr("href", url); + } else { + var classes = edges.css_classes(this.ns, "visit"); + that.after('

' + url + '

'); + + var selector = edges.css_id_selector(this.ns, this.fieldDef.name); + this.link = $(selector, this.form.context); + } + } else if (this.link) { + this.link.remove(); + this.link = false; } - } else if (this.link) { - this.link.remove(); - this.link = false; - } - }; + }; - this.init(); - }, - } -}; + this.init(); + }, + } + }; diff --git a/portality/templates/application_form/_field.html b/portality/templates/application_form/_field.html index d5f007ba6e..40be86ce55 100644 --- a/portality/templates/application_form/_field.html +++ b/portality/templates/application_form/_field.html @@ -10,7 +10,7 @@ {% endif %} {% if f.has_widget("click_to_copy") %} Copy value - + {% endif %} {% if f.optional %}(Optional){% endif %} From c451e05fa8e850440dfaed1e449eeb895b3b2ca8 Mon Sep 17 00:00:00 2001 From: Aga Date: Mon, 17 Jul 2023 13:35:18 +0100 Subject: [PATCH 06/26] copy text value of select instead of attr value --- portality/static/js/formulaic.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/portality/static/js/formulaic.js b/portality/static/js/formulaic.js index ccff926425..9d4ccc5659 100644 --- a/portality/static/js/formulaic.js +++ b/portality/static/js/formulaic.js @@ -1226,8 +1226,8 @@ var formulaic = { } else if (this.fieldDef.input == "select") { // todo: countries value instead of code! - var field = $("select[name=" + this.fieldDef.name + "]"); - value_to_copy = field.val() + var field = $("select[name=" + this.fieldDef.name + "] option:selected"); + value_to_copy = field.text() } else { var field = $("input[name=" + this.fieldDef.name + "]"); From 994da75773171ddda2c83568337456a3bd037a44 Mon Sep 17 00:00:00 2001 From: Aga Date: Tue, 18 Jul 2023 12:19:31 +0100 Subject: [PATCH 07/26] Make sure the copy button is displayed in multiple questions Change the appropriate methods of BaseApplication to static Use existing BaseApplication method to copy values of the fields --- portality/static/js/application_form.js | 4 +- portality/static/js/formulaic.js | 43 +++++-------------- .../templates/application_form/_list.html | 4 ++ 3 files changed, 16 insertions(+), 35 deletions(-) diff --git a/portality/static/js/application_form.js b/portality/static/js/application_form.js index 178aef09c4..7046f5ca8b 100644 --- a/portality/static/js/application_form.js +++ b/portality/static/js/application_form.js @@ -117,7 +117,7 @@ doaj.af.BaseApplicationForm = class { }); }; - determineFieldsValue(name) { + static determineFieldsValue(name) { let inputs = this.jq(":input[id='" + name + "']"); if (inputs.length === 0) { inputs = this.jq(":input[id^='" + name + "-']"); @@ -150,7 +150,7 @@ doaj.af.BaseApplicationForm = class { return result; }; - convertValueToText(value){ + static convertValueToText(value){ value = value.filter(v=>(v!=="" && v!==" ")); let result = ""; if (value.length > 0){ diff --git a/portality/static/js/formulaic.js b/portality/static/js/formulaic.js index 9d4ccc5659..39b5718689 100644 --- a/portality/static/js/formulaic.js +++ b/portality/static/js/formulaic.js @@ -1207,39 +1207,16 @@ var formulaic = { edges.on(elements, "click", this, "copy"); }; this.copy = function(element) { - // todo: multiple fields! - if (this.fieldDef.input == "radio") { - var field = $("input[name=" + this.fieldDef.name + "]:checked"); - value_def = this.fieldDef.options.filter(item => item.value === field.val()); - value_to_copy = value_def[0]["display"]; - } - else if (this.fieldDef.input == "checkbox") { - value_to_copy = "" - $( "input[name=" + this.fieldDef.name + "]:checked" ).each(function() { - value_to_copy = value_to_copy + " " + $(this)[0].value; - }); - console.log(value_to_copy) - } - else if (this.fieldDef.input == "taglist") { - var field = $("#" + this.fieldDef.name); - value_to_copy = field.val() - } - else if (this.fieldDef.input == "select") { - // todo: countries value instead of code! - var field = $("select[name=" + this.fieldDef.name + "] option:selected"); - value_to_copy = field.text() - } - else { - var field = $("input[name=" + this.fieldDef.name + "]"); - value_to_copy = field.val() - } - navigator.clipboard.writeText(value_to_copy) - console.log("text copied: " + value_to_copy) - var confirmation = $("#copy-confirmation--" + this.fieldDef.name); - confirmation.text("Value copied: " + value_to_copy); - confirmation.show().delay(3000).fadeOut(); - }; - this.init(); + let value = doaj.af.BaseApplicationForm.determineFieldsValue(this.fieldDef.name) + let value_to_copy = doaj.af.BaseApplicationForm.convertValueToText(value); + navigator.clipboard.writeText(value_to_copy) + console.log("text copied: " + value_to_copy) + var confirmation = $("#copy-confirmation--" + this.fieldDef.name); + confirmation.text("Value copied: " + value_to_copy); + confirmation.show().delay(3000).fadeOut(); + }; + this.init(); + }, newClickableOwner : function(params) { return edges.instantiate(formulaic.widgets.ClickableOwner, params) diff --git a/portality/templates/application_form/_list.html b/portality/templates/application_form/_list.html index bd75ea5795..2f8fc00597 100644 --- a/portality/templates/application_form/_list.html +++ b/portality/templates/application_form/_list.html @@ -7,6 +7,10 @@ {% if f.help("long_help") %} More help {% endif %} + {% if f.has_widget("click_to_copy") %} + Copy value + + {% endif %} {% if f.optional %}(Optional){% endif %} {% if f.get("hint") %}

{{ f.hint | safe }}

{% endif %} From f78b9523602f2b41f67769b49d5ded7aadae01fd Mon Sep 17 00:00:00 2001 From: Aga Date: Tue, 18 Jul 2023 12:21:24 +0100 Subject: [PATCH 08/26] clean up --- portality/static/js/formulaic.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/portality/static/js/formulaic.js b/portality/static/js/formulaic.js index 39b5718689..103855a316 100644 --- a/portality/static/js/formulaic.js +++ b/portality/static/js/formulaic.js @@ -1200,8 +1200,6 @@ var formulaic = { }, ClickToCopy : function(params) { this.fieldDef = params.fieldDef; - this.form = params.formulaic; - this.ns = "formulaic-clicktocopy" this.init = function() { var elements = $("#click-to-copy--" + this.fieldDef.name); edges.on(elements, "click", this, "copy"); @@ -1210,7 +1208,6 @@ var formulaic = { let value = doaj.af.BaseApplicationForm.determineFieldsValue(this.fieldDef.name) let value_to_copy = doaj.af.BaseApplicationForm.convertValueToText(value); navigator.clipboard.writeText(value_to_copy) - console.log("text copied: " + value_to_copy) var confirmation = $("#copy-confirmation--" + this.fieldDef.name); confirmation.text("Value copied: " + value_to_copy); confirmation.show().delay(3000).fadeOut(); From 3ef5f43d810db5c127272bb52e92a8b3804c3513 Mon Sep 17 00:00:00 2001 From: Aga Date: Tue, 18 Jul 2023 13:09:00 +0100 Subject: [PATCH 09/26] remove static from applications_forms js add click to copy only to editor forms and chosen fields --- portality/forms/application_forms.py | 191 ++++++++---------------- portality/static/js/application_form.js | 4 +- portality/static/js/formulaic.js | 5 +- 3 files changed, 66 insertions(+), 134 deletions(-) diff --git a/portality/forms/application_forms.py b/portality/forms/application_forms.py index beb33be64f..c46b30adee 100644 --- a/portality/forms/application_forms.py +++ b/portality/forms/application_forms.py @@ -109,10 +109,7 @@ class FieldDefinitions: "validate" : [], "disabled": True } - }, - "widgets": [ - "click_to_copy", # ~~^-> ClickToCopy:FormWidget~~ - ], + } } # ~~->$ OAStatementURL:FormField~~ @@ -141,8 +138,7 @@ class FieldDefinitions: ], "widgets": [ "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ - "clickable_url", # ~~^-> ClickableURL:FormWidget~~ - "click_to_copy" + "clickable_url" # ~~^-> ClickableURL:FormWidget~~ ], "attr": { "type": "url" @@ -179,11 +175,13 @@ class FieldDefinitions: }, "update_request": { "disabled": True + }, + "admin": { + "widgets": [ + "click_to_copy", # ~~^-> ClickToCopy:FormWidget~~ + ] } - }, - "widgets": [ - "click_to_copy", # ~~^-> ClickToCopy:FormWidget~~ - ] + } } # ~~->$ AlternativeTitle:FormField~~ @@ -205,11 +203,13 @@ class FieldDefinitions: "contexts": { "update_request": { "disabled": True + }, + "admin": { + "widgets": [ + "click_to_copy", # ~~^-> ClickToCopy:FormWidget~~ + ] } - }, - "widgets": [ - "click_to_copy", # ~~^-> ClickToCopy:FormWidget~~ - ] + } } # ~~->$ JournalURL:FormField~~ @@ -236,10 +236,7 @@ class FieldDefinitions: "journal_url_in_public_doaj" # ~~^-> JournalURLInPublicDOAJ:FormValidator~~ ], } - }, - "widgets": [ - "click_to_copy", # ~~^-> ClickToCopy:FormWidget~~ - ] + } } #~~->$ PISSN:FormField~~ @@ -265,8 +262,7 @@ class FieldDefinitions: "widgets" : [ "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ "full_contents", # ~~^->FullContents:FormWidget~~ - "issn_link", # ~~^->IssnLink:FormWidget~~ - "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ + "issn_link" # ~~^->IssnLink:FormWidget~~ ], "contexts": { "public" : { @@ -337,8 +333,7 @@ class FieldDefinitions: "widgets" : [ "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ "full_contents", # ~~^->FullContents:FormWidget~~ - "issn_link", # ~~^->IssnLink:FormWidget~~ - "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ + "issn_link" # ~~^->IssnLink:FormWidget~~ ], "contexts": { "public" : { @@ -419,8 +414,7 @@ class FieldDefinitions: "stopWords": STOP_WORDS, "field": "bibjson.keywords" } - }, - "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ + } ], "attr": { "class": "input-xlarge" @@ -444,8 +438,7 @@ class FieldDefinitions: ], "widgets": [ {"select": {}}, - "multiple_field", - "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ + "multiple_field" ], "help": { "placeholder": "Type or select the language" @@ -466,8 +459,7 @@ class FieldDefinitions: "widgets": [ "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ {"autocomplete": {"type" : "journal", "field": "bibjson.publisher.name.exact"}}, - "full_contents", # ~~^->FullContents:FormWidget~~ - "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ + "full_contents" # ~~^->FullContents:FormWidget~~ ], "help": { "placeholder": "Type or select the publisher’s name" @@ -475,6 +467,11 @@ class FieldDefinitions: "contexts" : { "bulk_edit" : { "validate" : [] + }, + "admin": { + "widgets": [ + "click_to_copy", # ~~^-> ClickToCopy:FormWidget~~ + ] } } } @@ -495,8 +492,7 @@ class FieldDefinitions: {"required": {"message": "Enter the country where the publisher carries out its business operations and is registered"}} ], "widgets": [ - {"select": {}}, - "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ + {"select": {}} ], "attr": { "class": "input-xlarge" @@ -524,11 +520,17 @@ class FieldDefinitions: "a society or other type of institution, enter that here."], "placeholder": "Type or select the society or institution’s name" }, + "contexts" : { + "admin": { + "widgets": [ + "click_to_copy", # ~~^-> ClickToCopy:FormWidget~~ + ] + } + }, "widgets": [ "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ {"autocomplete": {"type" : "journal", "field": "bibjson.institution.name.exact"}}, - "full_contents", # ~~^->FullContents:FormWidget~~ - "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ + "full_contents" # ~~^->FullContents:FormWidget~~ ] } @@ -545,8 +547,7 @@ class FieldDefinitions: "placeholder": "Type or select the country" }, "widgets": [ - {"select": {"allow_clear" : True}}, - "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ + {"select": {"allow_clear" : True}} ], "attr": { "class": "input-xlarge" @@ -597,9 +598,6 @@ class FieldDefinitions: }, "validate": [ {"required": {"message": "Select at least one type of license"}} - ], - "widgets": [ - "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ] } @@ -620,10 +618,7 @@ class FieldDefinitions: ], "help": { "doaj_criteria": "Content must be licensed" - }, - "widgets": [ - "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ - ] + } } # ~~->$ LicenseTermsURL:FormField~~ @@ -643,8 +638,7 @@ class FieldDefinitions: }, "widgets": [ "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ - "clickable_url", # ~~^-> ClickableURL:FormWidget~~ - "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ + "clickable_url" # ~~^-> ClickableURL:FormWidget~~ ] } @@ -666,9 +660,6 @@ class FieldDefinitions: }, "validate": [ {"required": {"message": "Select Yes or No"}} - ], - "widgets": [ - "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ] } @@ -695,8 +686,7 @@ class FieldDefinitions: ], "widgets": [ "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ - "clickable_url", # ~~^-> ClickableURL:FormWidget~~ - "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ + "clickable_url" # ~~^-> ClickableURL:FormWidget~~ ] } @@ -721,10 +711,7 @@ class FieldDefinitions: "under any license allowed by the journal " "retain all rights."], "seal_criteria": "The author must retain the copyright" - }, - "widgets": [ - "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ - ] + } } # ~~->$ CopyrightURL:FormField~~ @@ -742,8 +729,7 @@ class FieldDefinitions: ], "widgets": [ "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ - "clickable_url", # ~~^-> ClickableURL:FormWidget~~ - "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ + "clickable_url" # ~~^-> ClickableURL:FormWidget~~ ], "contexts": { "public": { @@ -785,9 +771,6 @@ class FieldDefinitions: }, "validate": [ {"required": {"message": "Select at least one type of review process"}} - ], - "widgets": [ - "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ] } @@ -809,8 +792,7 @@ class FieldDefinitions: } ], "widgets" : [ - "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ - "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ + "trim_whitespace" # ~~^-> TrimWhitespace:FormWidget~~ ], "asynchronous_warning": [ {"warn_on_value": {"value": "None"}} @@ -833,8 +815,7 @@ class FieldDefinitions: ], "widgets": [ "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ - "clickable_url", # ~~^-> ClickableURL:FormWidget~~ - "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ + "clickable_url" # ~~^-> ClickableURL:FormWidget~~ ] } @@ -857,10 +838,7 @@ class FieldDefinitions: "attr": { "min": app.config.get('MINIMAL_OA_START_DATE', 1900), "max": dates.now().year - }, - "widgets": [ - "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ - ] + } } # ~~->$ PlagiarismDetection:FormField~~ @@ -880,9 +858,6 @@ class FieldDefinitions: ], "validate": [ {"required": {"message": "Select Yes or No"}} - ], - "widgets": [ - "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ] } @@ -911,8 +886,7 @@ class FieldDefinitions: ], "widgets": [ "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ - "clickable_url", # ~~^-> ClickableURL:FormWidget~~ - "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ + "clickable_url" # ~~^-> ClickableURL:FormWidget~~ ] } @@ -931,8 +905,7 @@ class FieldDefinitions: ], "widgets": [ "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ - "clickable_url", # ~~^-> ClickableURL:FormWidget~~ - "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ + "clickable_url" # ~~^-> ClickableURL:FormWidget~~ ] } @@ -951,8 +924,7 @@ class FieldDefinitions: ], "widgets": [ "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ - "clickable_url", # ~~^-> ClickableURL:FormWidget~~ - "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ + "clickable_url" # ~~^-> ClickableURL:FormWidget~~ ] } @@ -971,8 +943,7 @@ class FieldDefinitions: ], "widgets": [ "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ - "clickable_url", # ~~^-> ClickableURL:FormWidget~~ - "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ + "clickable_url" # ~~^-> ClickableURL:FormWidget~~ ] } @@ -992,10 +963,7 @@ class FieldDefinitions: "attr": { "min": "1", "max": "100" - }, - "widgets": [ - "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ - ] + } } # ~~->$ APC:FormField~~ @@ -1016,9 +984,6 @@ class FieldDefinitions: }, "validate": [ {"required": {"message": "Select Yes or No"}} - ], - "widgets": [ - "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ] } @@ -1047,8 +1012,7 @@ class FieldDefinitions: "template": "application_form/_list.html", "entry_template": "application_form/_entry_group_horizontal.html", "widgets": [ - "multiple_field", - "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ + "multiple_field" ] } @@ -1064,8 +1028,7 @@ class FieldDefinitions: "placeholder": "Currency" }, "widgets": [ - {"select": {}}, - "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ + {"select": {}} ], "attr": { "class": "input-xlarge" @@ -1103,10 +1066,7 @@ class FieldDefinitions: ], "attr": { "min": "1" - }, - "widgets":[ - "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ - ] + } } # ~~->$ APCURL:FormField~~ @@ -1128,8 +1088,7 @@ class FieldDefinitions: ], "widgets": [ "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ - "clickable_url", # ~~^-> ClickableURL:FormWidget~~ - "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ + "clickable_url" # ~~^-> ClickableURL:FormWidget~~ ] } @@ -1153,9 +1112,6 @@ class FieldDefinitions: }, "validate": [ {"required": {"message": "Select Yes or No"}} - ], - "widgets": [ - "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ] } @@ -1184,8 +1140,7 @@ class FieldDefinitions: ], "widgets": [ "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ - "clickable_url", # ~~^-> ClickableURL:FormWidget~~ - "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ + "clickable_url" # ~~^-> ClickableURL:FormWidget~~ ] } @@ -1206,9 +1161,6 @@ class FieldDefinitions: }, "validate": [ {"required": {"message": "Select Yes or No"}} - ], - "widgets": [ - "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ] } @@ -1236,8 +1188,7 @@ class FieldDefinitions: ], "widgets": [ "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ - "clickable_url", # ~~^-> ClickableURL:FormWidget~~ - "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ + "clickable_url" # ~~^-> ClickableURL:FormWidget~~ ] } @@ -1269,9 +1220,6 @@ class FieldDefinitions: }, "validate": [ {"required": {"message": "Select at least one option"}} - ], - "widgets": [ - "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ] } @@ -1301,8 +1249,7 @@ class FieldDefinitions: ], "widgets": [ "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ - "multiple_field", - "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ + "multiple_field" ], "attr": { "class": "input-xlarge unstyled-list" @@ -1327,8 +1274,7 @@ class FieldDefinitions: {"warn_on_value": {"value": "None"}} ], "widgets" : [ - "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ - "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ + "trim_whitespace" # ~~^-> TrimWhitespace:FormWidget~~ ] } @@ -1375,8 +1321,7 @@ class FieldDefinitions: ], "widgets": [ "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ - "clickable_url", # ~~^-> ClickableURL:FormWidget~~ - "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ + "clickable_url" # ~~^-> ClickableURL:FormWidget~~ ] } @@ -1407,9 +1352,6 @@ class FieldDefinitions: ]}, "validate": [ {"required": {"message": "Select at least one option"}} - ], - "widgets": [ - "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ] } @@ -1431,8 +1373,7 @@ class FieldDefinitions: {"warn_on_value": {"value": "None"}} ], "widgets" : [ - "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ - "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ + "trim_whitespace" # ~~^-> TrimWhitespace:FormWidget~~ ] } @@ -1518,9 +1459,6 @@ class FieldDefinitions: }, "validate": [ {"required": {"message": "Select at least one option"}} - ], - "widgets": [ - "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ] } @@ -1542,8 +1480,7 @@ class FieldDefinitions: {"warn_on_value": {"value": "None"}} ], "widgets" : [ - "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ - "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ + "trim_whitespace" # ~~^-> TrimWhitespace:FormWidget~~ ] } @@ -1572,10 +1509,7 @@ class FieldDefinitions: {"required": {"message": "Select Yes or No"}} ] } - }, - "widgets": [ - "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ - ] + } } # ~~->$ OpenCitations:FormField~~ @@ -1602,10 +1536,7 @@ class FieldDefinitions: {"required": {"message": "Select Yes or No"}} ] } - }, - "widgets": [ - "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ - ] + } } ####################################### diff --git a/portality/static/js/application_form.js b/portality/static/js/application_form.js index 7046f5ca8b..178aef09c4 100644 --- a/portality/static/js/application_form.js +++ b/portality/static/js/application_form.js @@ -117,7 +117,7 @@ doaj.af.BaseApplicationForm = class { }); }; - static determineFieldsValue(name) { + determineFieldsValue(name) { let inputs = this.jq(":input[id='" + name + "']"); if (inputs.length === 0) { inputs = this.jq(":input[id^='" + name + "-']"); @@ -150,7 +150,7 @@ doaj.af.BaseApplicationForm = class { return result; }; - static convertValueToText(value){ + convertValueToText(value){ value = value.filter(v=>(v!=="" && v!==" ")); let result = ""; if (value.length > 0){ diff --git a/portality/static/js/formulaic.js b/portality/static/js/formulaic.js index 103855a316..02722312db 100644 --- a/portality/static/js/formulaic.js +++ b/portality/static/js/formulaic.js @@ -1205,8 +1205,9 @@ var formulaic = { edges.on(elements, "click", this, "copy"); }; this.copy = function(element) { - let value = doaj.af.BaseApplicationForm.determineFieldsValue(this.fieldDef.name) - let value_to_copy = doaj.af.BaseApplicationForm.convertValueToText(value); + let form = new doaj.af.BaseApplicationForm() + let value = form.determineFieldsValue(this.fieldDef.name) + let value_to_copy = form.convertValueToText(value); navigator.clipboard.writeText(value_to_copy) var confirmation = $("#copy-confirmation--" + this.fieldDef.name); confirmation.text("Value copied: " + value_to_copy); From 695c00ab8440e6c136824e3879992cc04a059d77 Mon Sep 17 00:00:00 2001 From: Aga Date: Tue, 18 Jul 2023 13:26:11 +0100 Subject: [PATCH 10/26] add forms to correct contexts --- portality/forms/application_forms.py | 51 +++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 8 deletions(-) diff --git a/portality/forms/application_forms.py b/portality/forms/application_forms.py index c46b30adee..d1f8a44d20 100644 --- a/portality/forms/application_forms.py +++ b/portality/forms/application_forms.py @@ -167,19 +167,25 @@ class FieldDefinitions: "full_contents" # ~~^->FullContents:FormWidget~~ ], "contexts": { + "admin": { + "widgets": [ + "click_to_copy", # ~~^-> ClickToCopy:FormWidget~~ + ] + }, "editor": { - "disabled": True + "disabled": True, + "widgets": [ + "click_to_copy", # ~~^-> ClickToCopy:FormWidget~~ + ] }, "associate_editor": { - "disabled": True - }, - "update_request": { - "disabled": True - }, - "admin": { + "disabled": True, "widgets": [ "click_to_copy", # ~~^-> ClickToCopy:FormWidget~~ ] + }, + "update_request": { + "disabled": True } } } @@ -208,6 +214,16 @@ class FieldDefinitions: "widgets": [ "click_to_copy", # ~~^-> ClickToCopy:FormWidget~~ ] + }, + "associate_editor": { + "widgets": [ + "click_to_copy", # ~~^-> ClickToCopy:FormWidget~~ + ] + }, + "editor": { + "widgets": [ + "click_to_copy", # ~~^-> ClickToCopy:FormWidget~~ + ] } } } @@ -472,6 +488,16 @@ class FieldDefinitions: "widgets": [ "click_to_copy", # ~~^-> ClickToCopy:FormWidget~~ ] + }, + "associate_editor": { + "widgets": [ + "click_to_copy", # ~~^-> ClickToCopy:FormWidget~~ + ] + }, + "editor": { + "widgets": [ + "click_to_copy", # ~~^-> ClickToCopy:FormWidget~~ + ] } } } @@ -525,6 +551,16 @@ class FieldDefinitions: "widgets": [ "click_to_copy", # ~~^-> ClickToCopy:FormWidget~~ ] + }, + "associate_editor": { + "widgets": [ + "click_to_copy", # ~~^-> ClickToCopy:FormWidget~~ + ] + }, + "editor": { + "widgets": [ + "click_to_copy", # ~~^-> ClickToCopy:FormWidget~~ + ] } }, "widgets": [ @@ -1399,7 +1435,6 @@ class FieldDefinitions: "widgets": [ "trim_whitespace", # ~~^-> TrimWhitespace:FormWidget~~ "clickable_url", # ~~^-> ClickableURL:FormWidget~~ - "click_to_copy" # ~~^-> ClickToCopy:FormWidget~~ ], "contexts" : { "public" : { From af8005cea15e0ad13ed7c6558d76de3393c203c7 Mon Sep 17 00:00:00 2001 From: Aga Date: Tue, 18 Jul 2023 13:31:19 +0100 Subject: [PATCH 11/26] add click_to_copy functional tests --- doajtest/testbook/journal_form/editor_form.yml | 6 ++++++ doajtest/testbook/journal_form/maned_form.yml | 6 ++++++ .../new_application_form/associate_editor_form.yml | 6 ++++++ doajtest/testbook/new_application_form/editor_form.yml | 8 +++++++- doajtest/testbook/new_application_form/maned_form.yml | 6 ++++++ 5 files changed, 31 insertions(+), 1 deletion(-) diff --git a/doajtest/testbook/journal_form/editor_form.yml b/doajtest/testbook/journal_form/editor_form.yml index 16b78e2c77..747bd3f81d 100644 --- a/doajtest/testbook/journal_form/editor_form.yml +++ b/doajtest/testbook/journal_form/editor_form.yml @@ -80,3 +80,9 @@ tests: - step: Attempt to click the "Remove" button results: - You are unable to delete the note + - step: Click "copy" button next to one of the fields (eg. Title) + results: + - Confirmation with fields value is displayed for 3 seconds + - step: Attempt to paste the value (use separate editor) + results: + - Correct value is pasted diff --git a/doajtest/testbook/journal_form/maned_form.yml b/doajtest/testbook/journal_form/maned_form.yml index 0a05f70530..935fe4504f 100644 --- a/doajtest/testbook/journal_form/maned_form.yml +++ b/doajtest/testbook/journal_form/maned_form.yml @@ -120,3 +120,9 @@ tests: - step: Attempt to click the "Remove" button results: - You are unable to delete the note + - step: Click "copy" button next to one of the fields (eg. Title) + results: + - Confirmation with fields value is displayed for 3 seconds + - step: Attempt to paste the value (use separate editor) + results: + - Correct value is pasted diff --git a/doajtest/testbook/new_application_form/associate_editor_form.yml b/doajtest/testbook/new_application_form/associate_editor_form.yml index 366fac92c4..f9f9fd4619 100644 --- a/doajtest/testbook/new_application_form/associate_editor_form.yml +++ b/doajtest/testbook/new_application_form/associate_editor_form.yml @@ -63,3 +63,9 @@ tests: - step: Attempt to click the "Remove" button results: - You are unable to delete the note + - step: Click "copy" button next to one of the fields (eg. Title) + results: + - Confirmation with fields value is displayed for 3 seconds + - step: Attempt to paste the value (use separate editor) + results: + - Correct value is pasted diff --git a/doajtest/testbook/new_application_form/editor_form.yml b/doajtest/testbook/new_application_form/editor_form.yml index cd9b8edf3d..b9db0066f7 100644 --- a/doajtest/testbook/new_application_form/editor_form.yml +++ b/doajtest/testbook/new_application_form/editor_form.yml @@ -64,4 +64,10 @@ tests: - you are unable to edit the note - step: Attempt to click the "Remove" button results: - - You are unable to delete the note \ No newline at end of file + - You are unable to delete the note + - step: Click "copy" button next to one of the fields (eg. Title) + results: + - Confirmation with fields value is displayed for 3 seconds + - step: Attempt to paste the value (use separate editor) + results: + - Correct value is pasted \ No newline at end of file diff --git a/doajtest/testbook/new_application_form/maned_form.yml b/doajtest/testbook/new_application_form/maned_form.yml index 907791691b..98dc0211f6 100644 --- a/doajtest/testbook/new_application_form/maned_form.yml +++ b/doajtest/testbook/new_application_form/maned_form.yml @@ -95,3 +95,9 @@ tests: - step: Attempt to click the "Remove" button results: - You are unable to delete the note + - step: Click "copy" button next to one of the fields (eg. Title) + results: + - Confirmation with fields value is displayed for 3 seconds + - step: Attempt to paste the value (use separate editor) + results: + - Correct value is pasted From 8d4275dd9449fc1b00100fb487a71808ba143b07 Mon Sep 17 00:00:00 2001 From: Aga Date: Tue, 18 Jul 2023 13:40:15 +0100 Subject: [PATCH 12/26] one more functional test --- doajtest/testbook/journal_form/associate_form.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doajtest/testbook/journal_form/associate_form.yml b/doajtest/testbook/journal_form/associate_form.yml index cd333cbc2d..8a9e68b11c 100644 --- a/doajtest/testbook/journal_form/associate_form.yml +++ b/doajtest/testbook/journal_form/associate_form.yml @@ -70,4 +70,10 @@ tests: - step: Attempt to click the "Remove" button results: - You are unable to delete the note + - step: Click "copy" button next to one of the fields (eg. Title) + results: + - Confirmation with fields value is displayed for 3 seconds + - step: Attempt to paste the value (use separate editor) + results: + - Correct value is pasted From 67067bffe39f97fc0f3e5779aff0d2395f73c63b Mon Sep 17 00:00:00 2001 From: Aga Date: Tue, 18 Jul 2023 13:50:27 +0100 Subject: [PATCH 13/26] clean up part 1 --- portality/static/js/formulaic.js | 1521 +++++++++++++++--------------- 1 file changed, 750 insertions(+), 771 deletions(-) diff --git a/portality/static/js/formulaic.js b/portality/static/js/formulaic.js index 02722312db..55935d606d 100644 --- a/portality/static/js/formulaic.js +++ b/portality/static/js/formulaic.js @@ -1195,249 +1195,228 @@ var formulaic = { this.init(); }, - newClickToCopy : function(params) { - return edges.instantiate(formulaic.widgets.ClickToCopy, params) + newClickableOwner : function(params) { + return edges.instantiate(formulaic.widgets.ClickableOwner, params) }, - ClickToCopy : function(params) { + ClickableOwner : function(params) { this.fieldDef = params.fieldDef; + this.form = params.formulaic; + + this.ns = "formulaic-clickableowner"; + + this.link = false; + this.init = function() { - var elements = $("#click-to-copy--" + this.fieldDef.name); - edges.on(elements, "click", this, "copy"); - }; - this.copy = function(element) { - let form = new doaj.af.BaseApplicationForm() - let value = form.determineFieldsValue(this.fieldDef.name) - let value_to_copy = form.convertValueToText(value); - navigator.clipboard.writeText(value_to_copy) - var confirmation = $("#copy-confirmation--" + this.fieldDef.name); - confirmation.text("Value copied: " + value_to_copy); - confirmation.show().delay(3000).fadeOut(); + var elements = this.form.controlSelect.input({name: this.fieldDef.name}); + edges.on(elements, "change.ClickableOwner", this, "updateOwner"); + for (var i = 0; i < elements.length; i++) { + this.updateOwner(elements[i]); + } }; - this.init(); - - }, - newClickableOwner : function(params) { - return edges.instantiate(formulaic.widgets.ClickableOwner, params) - }, - ClickableOwner : function(params) { - this.fieldDef = params.fieldDef; - this.form = params.formulaic; - this.ns = "formulaic-clickableowner"; + this.updateOwner = function(element) { + var that = $(element); + var val = that.val(); - this.link = false; + if (val) { + if (this.link) { + this.link.attr("href", "/account/" + val); + } else { + var classes = edges.css_classes(this.ns, "visit"); + var id = edges.css_id(this.ns, this.fieldDef.name); + that.after('

See this account’s profile

'); - this.init = function() { - var elements = this.form.controlSelect.input({name: this.fieldDef.name}); - edges.on(elements, "change.ClickableOwner", this, "updateOwner"); - for (var i = 0; i < elements.length; i++) { - this.updateOwner(elements[i]); + var selector = edges.css_id_selector(this.ns, this.fieldDef.name); + this.link = $(selector, this.form.context); } - }; - - this.updateOwner = function(element) { - var that = $(element); - var val = that.val(); + } else if (this.link) { + this.link.remove(); + this.link = false; + } + }; - if (val) { - if (this.link) { - this.link.attr("href", "/account/" + val); - } else { - var classes = edges.css_classes(this.ns, "visit"); - var id = edges.css_id(this.ns, this.fieldDef.name); - that.after('

See this account’s profile

'); + this.init(); + }, - var selector = edges.css_id_selector(this.ns, this.fieldDef.name); - this.link = $(selector, this.form.context); - } - } else if (this.link) { - this.link.remove(); - this.link = false; - } - }; + newTrimWhitespace : function(params) { + return edges.instantiate(formulaic.widgets.TrimWhitespace, params) + }, + TrimWhitespace : function(params) { + this.fieldDef = params.fieldDef; + this.form = params.formulaic; - this.init(); - }, + this.ns = "formulaic-trimwhitespace"; - newTrimWhitespace : function(params) { - return edges.instantiate(formulaic.widgets.TrimWhitespace, params) - }, - TrimWhitespace : function(params) { - this.fieldDef = params.fieldDef; - this.form = params.formulaic; + this.link = false; - this.ns = "formulaic-trimwhitespace"; + this.init = function () { + var elements = this.form.controlSelect.input({name: this.fieldDef.name}); + edges.on(elements, "focus.TrimWhitespace", this, "trim"); + edges.on(elements, "blur.TrimWhitespace", this, "trim"); - this.link = false; + for (var i = 0; i < elements.length; i++) { + this.trim(elements[i]); + } + }; - this.init = function () { - var elements = this.form.controlSelect.input({name: this.fieldDef.name}); - edges.on(elements, "focus.TrimWhitespace", this, "trim"); - edges.on(elements, "blur.TrimWhitespace", this, "trim"); + this.trim = function(element) { + var that = $(element); + var val = that.val(); + var nv = val.trim(); + if (nv !== val) { + that.val(nv); + } + }; - for (var i = 0; i < elements.length; i++) { - this.trim(elements[i]); - } - }; + this.init(); + }, - this.trim = function(element) { - var that = $(element); - var val = that.val(); - var nv = val.trim(); - if (nv !== val) { - that.val(nv); - } - }; + newClickableUrl : function(params) { + return edges.instantiate(formulaic.widgets.ClickableUrl, params) + }, + ClickableUrl : function(params) { + this.fieldDef = params.fieldDef; + this.form = params.formulaic; - this.init(); - }, + this.ns = "formulaic-clickableurl"; - newClickableUrl : function(params) { - return edges.instantiate(formulaic.widgets.ClickableUrl, params) - }, - ClickableUrl : function(params) { - this.fieldDef = params.fieldDef; - this.form = params.formulaic; + this.link = false; - this.ns = "formulaic-clickableurl"; + this.init = function() { + var elements = this.form.controlSelect.input( + {name: this.fieldDef.name}); + // TODO: should work as-you-type by changing "change" to "keyup" event; doesn't work in edges + //edges.on(elements, "change.ClickableUrl", this, "updateUrl"); + edges.on(elements, "keyup.ClickableUrl", this, "updateUrl"); + + for (var i = 0; i < elements.length; i++) { + this.updateUrl(elements[i]); + } + }; - this.link = false; + this.updateUrl = function(element) { + var that = $(element); + var val = that.val(); + var id = edges.css_id(this.ns, this.fieldDef.name); - this.init = function() { - var elements = this.form.controlSelect.input( - {name: this.fieldDef.name}); - // TODO: should work as-you-type by changing "change" to "keyup" event; doesn't work in edges - //edges.on(elements, "change.ClickableUrl", this, "updateUrl"); - edges.on(elements, "keyup.ClickableUrl", this, "updateUrl"); + if (val && (val.substring(0,7) === "http://" || val.substring(0,8) === "https://") && val.length > 10) { + if (this.link) { + this.link.text(val); + this.link.attr("href", val); + } else { + var classes = edges.css_classes(this.ns, "visit"); + that.after('

' + val + '

'); - for (var i = 0; i < elements.length; i++) { - this.updateUrl(elements[i]); + var selector = edges.css_id_selector(this.ns, this.fieldDef.name); + this.link = $(selector, this.form.context); } - }; - - this.updateUrl = function(element) { - var that = $(element); - var val = that.val(); - var id = edges.css_id(this.ns, this.fieldDef.name); + } else if (this.link) { + this.link.remove(); + this.link = false; + } + }; - if (val && (val.substring(0,7) === "http://" || val.substring(0,8) === "https://") && val.length > 10) { - if (this.link) { - this.link.text(val); - this.link.attr("href", val); - } else { - var classes = edges.css_classes(this.ns, "visit"); - that.after('

' + val + '

'); + this.init(); + }, - var selector = edges.css_id_selector(this.ns, this.fieldDef.name); - this.link = $(selector, this.form.context); - } - } else if (this.link) { - this.link.remove(); - this.link = false; - } - }; + newFullContents : function(params) { + return edges.instantiate(formulaic.widgets.FullContents, params) + }, + FullContents : function(params) { + this.fieldDef = params.fieldDef; + this.form = params.formulaic; + this.args = params.args; - this.init(); - }, + this.ns = "formulaic-fullcontents"; - newFullContents : function(params) { - return edges.instantiate(formulaic.widgets.FullContents, params) - }, - FullContents : function(params) { - this.fieldDef = params.fieldDef; - this.form = params.formulaic; - this.args = params.args; + this.container = false; - this.ns = "formulaic-fullcontents"; + this.init = function() { + var elements = this.form.controlSelect.input({name: this.fieldDef.name}); + edges.on(elements, "keyup.FullContents", this, "updateContents"); - this.container = false; + for (var i = 0; i < elements.length; i++) { + this.updateContents(elements[i]); + } + }; - this.init = function() { - var elements = this.form.controlSelect.input({name: this.fieldDef.name}); - edges.on(elements, "keyup.FullContents", this, "updateContents"); + this.updateContents = function(element) { + var that = $(element); + var val = that.val(); - for (var i = 0; i < elements.length; i++) { - this.updateContents(elements[i]); + // if there is a behaviour for when the field is empty and disabled, then check if it is, and if + // it is include the desired alternative text + if (this.args && this.args.empty_disabled) { + if (val === "" && that.prop("disabled")) { + val = this.args.empty_disabled; } - }; + } - this.updateContents = function(element) { - var that = $(element); - var val = that.val(); + if (val) { + if (this.container) { + this.container.html('Full contents: ' + edges.escapeHtml(val) + ''); + } else { + var classes = edges.css_classes(this.ns, "contents"); + var id = edges.css_id(this.ns, this.fieldDef.name); + that.after('

Full contents: ' + edges.escapeHtml(val) + '

'); - // if there is a behaviour for when the field is empty and disabled, then check if it is, and if - // it is include the desired alternative text - if (this.args && this.args.empty_disabled) { - if (val === "" && that.prop("disabled")) { - val = this.args.empty_disabled; - } + var selector = edges.css_id_selector(this.ns, this.fieldDef.name); + this.container = $(selector, this.form.context); } + } else if (this.container) { + this.container.remove(); + this.container = false; + } + }; - if (val) { - if (this.container) { - this.container.html('Full contents: ' + edges.escapeHtml(val) + ''); - } else { - var classes = edges.css_classes(this.ns, "contents"); - var id = edges.css_id(this.ns, this.fieldDef.name); - that.after('

Full contents: ' + edges.escapeHtml(val) + '

'); + this.init(); + }, - var selector = edges.css_id_selector(this.ns, this.fieldDef.name); - this.container = $(selector, this.form.context); - } - } else if (this.container) { - this.container.remove(); - this.container = false; - } - }; + newNoteModal : function(params) { + return edges.instantiate(formulaic.widgets.NoteModal, params) + }, + NoteModal : function(params) { + this.fieldDef = params.fieldDef; + this.form = params.formulaic; - this.init(); - }, - - newNoteModal : function(params) { - return edges.instantiate(formulaic.widgets.NoteModal, params) - }, - NoteModal : function(params) { - this.fieldDef = params.fieldDef; - this.form = params.formulaic; - - this.ns = "formulaic-notemodal"; - - this.container = false; - - this.init = function() { - var viewClass = edges.css_classes(this.ns, "view"); - var closeClass = edges.css_classes(this.ns, "close"); - let group = $("div[name='" + this.fieldDef["name"] + "__group']") - var textarea = group.find("textarea"); - - let inputs = group.find("input[type=text]") - for (let i = 0; i < inputs.length; i++) { - let jqin = $(inputs[i]); - let iid = jqin.attr("id"); - if (iid.endsWith("_author")) { - let val = jqin.val() - if (val === "") { - jqin.hide(); - } + this.ns = "formulaic-notemodal"; + + this.container = false; + + this.init = function() { + var viewClass = edges.css_classes(this.ns, "view"); + var closeClass = edges.css_classes(this.ns, "close"); + let group = $("div[name='" + this.fieldDef["name"] + "__group']") + var textarea = group.find("textarea"); + + let inputs = group.find("input[type=text]") + for (let i = 0; i < inputs.length; i++) { + let jqin = $(inputs[i]); + let iid = jqin.attr("id"); + if (iid.endsWith("_author")) { + let val = jqin.val() + if (val === "") { + jqin.hide(); } } + } - for (var i = 0; i < textarea.length; i++) { - var container = $(textarea[i]); + for (var i = 0; i < textarea.length; i++) { + var container = $(textarea[i]); - let contentHeight = container[0].scrollHeight; - if (contentHeight > 200) { - contentHeight = 200; - } - container.css("height", (contentHeight + 5) + "px"); + let contentHeight = container[0].scrollHeight; + if (contentHeight > 200) { + contentHeight = 200; + } + container.css("height", (contentHeight + 5) + "px"); - var modalId = "modal-" + this.fieldDef["name"] + "-" + i; + var modalId = "modal-" + this.fieldDef["name"] + "-" + i; - var date = $("#" + this.fieldDef["name"] + "-" + i + "-note_date"); - var note = $("#" + this.fieldDef["name"] + "-" + i + "-note"); - var author = $("#" + this.fieldDef["name"] + "-" + i + "-note_author"); + var date = $("#" + this.fieldDef["name"] + "-" + i + "-note_date"); + var note = $("#" + this.fieldDef["name"] + "-" + i + "-note"); + var author = $("#" + this.fieldDef["name"] + "-" + i + "-note_author"); - $(` + $(` `).insertAfter(container); - } + } - var viewSelector = edges.css_class_selector(this.ns, "view"); - edges.on(viewSelector, "click", this, "showModal"); + var viewSelector = edges.css_class_selector(this.ns, "view"); + edges.on(viewSelector, "click", this, "showModal"); - var closeSelector = edges.css_class_selector(this.ns, "close"); - edges.on(closeSelector, "click", this, "closeModal"); - }; + var closeSelector = edges.css_class_selector(this.ns, "close"); + edges.on(closeSelector, "click", this, "closeModal"); + }; - this.showModal = function(element) { - var that = $(element); - var modal = that.siblings(".modal"); - modal.show(); - }; + this.showModal = function(element) { + var that = $(element); + var modal = that.siblings(".modal"); + modal.show(); + }; - this.closeModal = function(element) { - var that = $(element); - var modal = that.parents(".modal"); - modal.hide(); - }; + this.closeModal = function(element) { + var that = $(element); + var modal = that.parents(".modal"); + modal.hide(); + }; - this.init(); - }, - - newInfiniteRepeat : function(params) { - return edges.instantiate(formulaic.widgets.InfiniteRepeat, params) - }, - InfiniteRepeat: function(params) { - this.fieldDef = params.fieldDef; - this.args = params.args; - - this.idRx = /(.+?-)(\d+)(-.+)/; - this.template = ""; - this.container = false; - this.divs = false; - - this.init = function() { - this.divs = $("div[name='" + this.fieldDef["name"] + "__group']"); - for (var i = 0 ; i < this.divs.length; i++) { - var div = $(this.divs[i]); - div.append($('')); - feather.replace(); - } + this.init(); + }, - this.template = $(this.divs[0]).html(); - this.container = $(this.divs[0]).parents(".removable-fields"); + newInfiniteRepeat : function(params) { + return edges.instantiate(formulaic.widgets.InfiniteRepeat, params) + }, + InfiniteRepeat: function(params) { + this.fieldDef = params.fieldDef; + this.args = params.args; - if (this.divs.length === 1) { - let div = $(this.divs[0]); - let inputs = div.find(":input"); - let tripwire = false; - for (var i = 0; i < inputs.length; i++) { - if ($(inputs[i]).val()) { - tripwire = true; - break; - } - } - if (!tripwire) { - // the field is empty - $(this.divs[0]).remove(); - this.divs = []; + this.idRx = /(.+?-)(\d+)(-.+)/; + this.template = ""; + this.container = false; + this.divs = false; + + this.init = function() { + this.divs = $("div[name='" + this.fieldDef["name"] + "__group']"); + for (var i = 0 ; i < this.divs.length; i++) { + var div = $(this.divs[i]); + div.append($('')); + feather.replace(); + } + + this.template = $(this.divs[0]).html(); + this.container = $(this.divs[0]).parents(".removable-fields"); + + if (this.divs.length === 1) { + let div = $(this.divs[0]); + let inputs = div.find(":input"); + let tripwire = false; + for (var i = 0; i < inputs.length; i++) { + if ($(inputs[i]).val()) { + tripwire = true; + break; } } + if (!tripwire) { + // the field is empty + $(this.divs[0]).remove(); + this.divs = []; + } + } - this.addFieldBtn = $("#add_field__" + this.fieldDef["name"]); - this.removeFieldBtns = $('[id^="remove_field__' + this.fieldDef["name"] + '"]'); + this.addFieldBtn = $("#add_field__" + this.fieldDef["name"]); + this.removeFieldBtns = $('[id^="remove_field__' + this.fieldDef["name"] + '"]'); - edges.on(this.addFieldBtn, "click", this, "addField"); - edges.on(this.removeFieldBtns, "click", this, "removeField"); + edges.on(this.addFieldBtn, "click", this, "addField"); + edges.on(this.removeFieldBtns, "click", this, "removeField"); - // show or hide the remove buttons - for (let i = 0; i < this.divs.length; i++) { - let cur_div = $(this.divs[i]); - if (!cur_div.find('textarea').is(':disabled')) { - cur_div.find('[id^="remove_field__"]').show(); - } + // show or hide the remove buttons + for (let i = 0; i < this.divs.length; i++) { + let cur_div = $(this.divs[i]); + if (!cur_div.find('textarea').is(':disabled')) { + cur_div.find('[id^="remove_field__"]').show(); } + } - }; + }; - this.addField = function() { - var currentLargest = -1; - for (var i = 0; i < this.divs.length; i++) { - var div = $(this.divs[i]); - var id = div.find(":input").attr("id"); - var match = id.match(this.idRx); - var thisId = parseInt(match[2]); - if (thisId > currentLargest) { - currentLargest = thisId; - } + this.addField = function() { + var currentLargest = -1; + for (var i = 0; i < this.divs.length; i++) { + var div = $(this.divs[i]); + var id = div.find(":input").attr("id"); + var match = id.match(this.idRx); + var thisId = parseInt(match[2]); + if (thisId > currentLargest) { + currentLargest = thisId; } - var newId = currentLargest + 1; + } + var newId = currentLargest + 1; - var frag = '
' + this.template + '
'; - var jqt = $(frag); - var that = this; - jqt.find(":input").each(function() { - var el = $(this); - var id = el.attr("id"); - - var match = id.match(that.idRx); - if (match) { - var bits = id.split(that.idRx); - var newName = bits[1] + newId + bits[3]; - el.attr("id", newName).attr("name", newName).val(""); - } else { - // could be the remove button - if (id.substring(0, "remove_field".length) === "remove_field") { - el.attr("id", "remove_field__" + that.fieldDef["name"] + "--id_" + newId); - el.show(); - } - } - }); - if (this.args.enable_on_repeat) { - for (var i = 0; i < this.args.enable_on_repeat.length; i++) { - var enables = jqt.find(that.args.enable_on_repeat[i]); - enables.removeAttr("disabled"); + var frag = '
' + this.template + '
'; + var jqt = $(frag); + var that = this; + jqt.find(":input").each(function() { + var el = $(this); + var id = el.attr("id"); + + var match = id.match(that.idRx); + if (match) { + var bits = id.split(that.idRx); + var newName = bits[1] + newId + bits[3]; + el.attr("id", newName).attr("name", newName).val(""); + } else { + // could be the remove button + if (id.substring(0, "remove_field".length) === "remove_field") { + el.attr("id", "remove_field__" + that.fieldDef["name"] + "--id_" + newId); + el.show(); } } + }); + if (this.args.enable_on_repeat) { + for (var i = 0; i < this.args.enable_on_repeat.length; i++) { + var enables = jqt.find(that.args.enable_on_repeat[i]); + enables.removeAttr("disabled"); + } + } - var topPlacement = this.fieldDef.repeatable.add_button_placement === "top"; - if (this.divs.length > 0) { - if (topPlacement) { - $(this.divs[0]).before(jqt); - } else { - $(this.divs[this.divs.length - 1]).after(jqt); - } + var topPlacement = this.fieldDef.repeatable.add_button_placement === "top"; + if (this.divs.length > 0) { + if (topPlacement) { + $(this.divs[0]).before(jqt); } else { - this.container.append(jqt); + $(this.divs[this.divs.length - 1]).after(jqt); } + } else { + this.container.append(jqt); + } - this.divs = $("div[name='" + this.fieldDef["name"] + "__group']"); - this.removeFieldBtns = $('[id^="remove_field__' + this.fieldDef["name"] + '"]'); - edges.on(this.removeFieldBtns, "click", this, "removeField"); - }; + this.divs = $("div[name='" + this.fieldDef["name"] + "__group']"); + this.removeFieldBtns = $('[id^="remove_field__' + this.fieldDef["name"] + '"]'); + edges.on(this.removeFieldBtns, "click", this, "removeField"); + }; - this.removeField = function(element) { - var container = $(element).parents("div[name='" + this.fieldDef["name"] + "__group']"); - container.remove(); - this.divs = $("div[name='" + this.fieldDef["name"] + "__group']"); - }; + this.removeField = function(element) { + var container = $(element).parents("div[name='" + this.fieldDef["name"] + "__group']"); + container.remove(); + this.divs = $("div[name='" + this.fieldDef["name"] + "__group']"); + }; - this.init(); - }, + this.init(); + }, - newMultipleField : function(params) { - return edges.instantiate(formulaic.widgets.MultipleField, params) - }, - MultipleField: function(params) { - this.fieldDef = params.fieldDef; - this.max = this.fieldDef["repeatable"]["initial"] - 1; + newMultipleField : function(params) { + return edges.instantiate(formulaic.widgets.MultipleField, params) + }, + MultipleField: function(params) { + this.fieldDef = params.fieldDef; + this.max = this.fieldDef["repeatable"]["initial"] - 1; - this.init = () => { - if (this.fieldDef["input"] === "group") { - this._setupRepeatingGroup(); - } else { - this._setupRepeatingIndividual(); - } - feather.replace(); - }; + this.init = () => { + if (this.fieldDef["input"] === "group") { + this._setupRepeatingGroup(); + } else { + this._setupRepeatingIndividual(); + } + feather.replace(); + }; - this._setupIndividualSelect2 = function() { - for (var idx = 0; idx < this.fields.length; idx++) { - let f = this.fields[idx]; - let s2_input = $($(f).select2()); - $(f).on("focus", formulaic.widgets._select2_shift_focus); - s2_input.after($('')); - if (idx !== 0) { - s2_input.attr("required", false); - s2_input.attr("data-parsley-validate-if-empty", "true"); - if (!s2_input.val()) { - s2_input.closest('li').hide(); - } else { - this.count++; - } + this._setupIndividualSelect2 = function() { + for (var idx = 0; idx < this.fields.length; idx++) { + let f = this.fields[idx]; + let s2_input = $($(f).select2()); + $(f).on("focus", formulaic.widgets._select2_shift_focus); + s2_input.after($('')); + if (idx !== 0) { + s2_input.attr("required", false); + s2_input.attr("data-parsley-validate-if-empty", "true"); + if (!s2_input.val()) { + s2_input.closest('li').hide(); + } else { + this.count++; } } + } - this.remove_btns = $('[id^="remove_field__' + this.fieldDef["name"] + '"]'); - if (this.count === 0) { - $(this.remove_btns[0]).hide(); - } - - this.addFieldBtn = $("#add_field__" + this.fieldDef["name"]); - this.addFieldBtn.on("click", () => { - $('#s2id_' + this.fieldDef["name"] + '-' + (this.count + 1)).closest('li').show(); - this.count++; - if (this.count > 0) { - $(this.remove_btns[0]).show(); - } - if (this.count >= this.max) { - $(this.addFieldBtn).hide(); - } - }); + this.remove_btns = $('[id^="remove_field__' + this.fieldDef["name"] + '"]'); + if (this.count === 0) { + $(this.remove_btns[0]).hide(); + } + this.addFieldBtn = $("#add_field__" + this.fieldDef["name"]); + this.addFieldBtn.on("click", () => { + $('#s2id_' + this.fieldDef["name"] + '-' + (this.count + 1)).closest('li').show(); + this.count++; + if (this.count > 0) { + $(this.remove_btns[0]).show(); + } if (this.count >= this.max) { - this.addFieldBtn.hide(); + $(this.addFieldBtn).hide(); } + }); - $(this.remove_btns).each((idx, btn) => { - $(btn).on("click", (event) => { - for (let i = idx; i < this.count; i++) { - let data = $(this.fields[i + 1]).select2('data'); - if (data === null) { - data = {id: i, text: ""}; - } - $(this.fields[i]).select2('data', {id: data.id, text: data.text}); - } - this.count--; - $(this.fields[this.count + 1]).select2('data', {id: this.count + 1, text: ""}); - $('#s2id_' + this.fieldDef["name"] + '-' + (this.count + 1)).closest('li').hide(); - if (this.count === 0) { - $(this.remove_btns[0]).hide(); - } - if (this.count < this.max) { - $(this.addFieldBtn).show(); - } - }) - }) - }; + if (this.count >= this.max) { + this.addFieldBtn.hide(); + } - this._setupIndividualField = function() { - for (var idx = 0; idx < this.fields.length; idx++) { - let f = this.fields[idx]; - let jqf = $(f); - jqf.after($('')); - if (idx !== 0) { - jqf.attr("required", false); - jqf.attr("data-parsley-validate-if-empty", "true"); - if (!jqf.val()) { - jqf.parent().hide(); - } else { - this.count++; + $(this.remove_btns).each((idx, btn) => { + $(btn).on("click", (event) => { + for (let i = idx; i < this.count; i++) { + let data = $(this.fields[i + 1]).select2('data'); + if (data === null) { + data = {id: i, text: ""}; } + $(this.fields[i]).select2('data', {id: data.id, text: data.text}); } - } + this.count--; + $(this.fields[this.count + 1]).select2('data', {id: this.count + 1, text: ""}); + $('#s2id_' + this.fieldDef["name"] + '-' + (this.count + 1)).closest('li').hide(); + if (this.count === 0) { + $(this.remove_btns[0]).hide(); + } + if (this.count < this.max) { + $(this.addFieldBtn).show(); + } + }) + }) + }; - this.remove_btns = $('[id^="remove_field__' + this.fieldDef["name"] + '-"]'); - if (this.count === 0) { - $(this.remove_btns[0]).hide(); + this._setupIndividualField = function() { + for (var idx = 0; idx < this.fields.length; idx++) { + let f = this.fields[idx]; + let jqf = $(f); + jqf.after($('')); + if (idx !== 0) { + jqf.attr("required", false); + jqf.attr("data-parsley-validate-if-empty", "true"); + if (!jqf.val()) { + jqf.parent().hide(); + } else { + this.count++; + } } + } - this.addFieldBtn = $("#add_field__" + this.fieldDef["name"]); - this.addFieldBtn.on("click", () => { - let next_input = $('[id="' + this.fieldDef["name"] + '-' + (this.count + 1) +'"]').parent(); - // TODO: why .show() does not work? - $(next_input).show(); - this.count++; - if (this.count > 0) { - $(this.remove_btns[0]).show(); - } - if (this.count >= this.max) { - $(this.addFieldBtn).hide(); - } - }); + this.remove_btns = $('[id^="remove_field__' + this.fieldDef["name"] + '-"]'); + if (this.count === 0) { + $(this.remove_btns[0]).hide(); + } + this.addFieldBtn = $("#add_field__" + this.fieldDef["name"]); + this.addFieldBtn.on("click", () => { + let next_input = $('[id="' + this.fieldDef["name"] + '-' + (this.count + 1) +'"]').parent(); + // TODO: why .show() does not work? + $(next_input).show(); + this.count++; + if (this.count > 0) { + $(this.remove_btns[0]).show(); + } if (this.count >= this.max) { - this.addFieldBtn.hide(); + $(this.addFieldBtn).hide(); } + }); - $(this.remove_btns).each((idx, btn) => { - $(btn).on("click", (event) => { - for (let i = idx; i < this.count; i++) { - let data = $(this.fields[i + 1]).val(); - if (data === null) { - data = ""; - } - $(this.fields[i]).val(data); - } + if (this.count >= this.max) { + this.addFieldBtn.hide(); + } - this.count--; - $(this.fields[this.count + 1]).val(""); - let last_input = $('[id="' + this.fieldDef["name"] + '-' + (this.count + 1) +'"]').parent(); - $(last_input).hide(); - if (this.count === 0) { - $(this.remove_btns[0]).hide(); + $(this.remove_btns).each((idx, btn) => { + $(btn).on("click", (event) => { + for (let i = idx; i < this.count; i++) { + let data = $(this.fields[i + 1]).val(); + if (data === null) { + data = ""; } - if (this.count < this.max) { - $(this.addFieldBtn).show(); - } - }) - }); - }; + $(this.fields[i]).val(data); + } - this._setupRepeatingIndividual = function() { - let tag = this.fieldDef["input"] === "select" ? "select" : "input"; - this.fields = $(tag + '[id^="' + this.fieldDef["name"] + '-"]'); - this.count = 0; + this.count--; + $(this.fields[this.count + 1]).val(""); + let last_input = $('[id="' + this.fieldDef["name"] + '-' + (this.count + 1) +'"]').parent(); + $(last_input).hide(); + if (this.count === 0) { + $(this.remove_btns[0]).hide(); + } + if (this.count < this.max) { + $(this.addFieldBtn).show(); + } + }) + }); + }; - if (tag === "select"){ - this._setupIndividualSelect2(); - } else { - this._setupIndividualField(); - } - }; + this._setupRepeatingIndividual = function() { + let tag = this.fieldDef["input"] === "select" ? "select" : "input"; + this.fields = $(tag + '[id^="' + this.fieldDef["name"] + '-"]'); + this.count = 0; - this._setupRepeatingGroup = function() { - this.divs = $("div[name='" + this.fieldDef["name"] + "__group']"); - this.count = 0; - - for (var idx = 0; idx < this.divs.length; idx++) { - let div = $(this.divs[idx]); - div.append($('')); - - if (idx !== 0) { - let inputs = div.find(":input"); - var hasVal = false; - for (var j = 0; j < inputs.length; j++) { - $(inputs[j]).attr("required", false) - .attr("data-parsley-required-if", false) - .attr("data-parsley-validate-if-empty", "true"); - if ($(inputs[j]).val()) { - hasVal = true; - } - } - if (!hasVal) { - div.hide(); - } else { - this.count++; - } - } - } + if (tag === "select"){ + this._setupIndividualSelect2(); + } else { + this._setupIndividualField(); + } + }; - this.remove_btns = $('[id^="remove_field__' + this.fieldDef["name"] + '"]'); - if (this.count === 0) { - $(this.remove_btns[0]).hide(); - } + this._setupRepeatingGroup = function() { + this.divs = $("div[name='" + this.fieldDef["name"] + "__group']"); + this.count = 0; - this.addFieldBtn = $("#add_field__" + this.fieldDef["name"]); - this.addFieldBtn.on("click", () => { - $(this.divs[this.count + 1]).show(); - this.count++; - if (this.count > 0) { - $(this.remove_btns[0]).show(); + for (var idx = 0; idx < this.divs.length; idx++) { + let div = $(this.divs[idx]); + div.append($('')); + + if (idx !== 0) { + let inputs = div.find(":input"); + var hasVal = false; + for (var j = 0; j < inputs.length; j++) { + $(inputs[j]).attr("required", false) + .attr("data-parsley-required-if", false) + .attr("data-parsley-validate-if-empty", "true"); + if ($(inputs[j]).val()) { + hasVal = true; + } } - if (this.count === this.max) { - $(this.addFieldBtn).hide(); + if (!hasVal) { + div.hide(); + } else { + this.count++; } - }); + } + } - if (this.count >= this.max) { - this.addFieldBtn.hide(); + this.remove_btns = $('[id^="remove_field__' + this.fieldDef["name"] + '"]'); + if (this.count === 0) { + $(this.remove_btns[0]).hide(); + } + + this.addFieldBtn = $("#add_field__" + this.fieldDef["name"]); + this.addFieldBtn.on("click", () => { + $(this.divs[this.count + 1]).show(); + this.count++; + if (this.count > 0) { + $(this.remove_btns[0]).show(); + } + if (this.count === this.max) { + $(this.addFieldBtn).hide(); } + }); - $(this.remove_btns).each((idx, btn) => { - $(btn).on("click", () => { - let thisDiv = $(btn).parent(); - let nextDiv = $(thisDiv); - for (let i = idx; i < this.count; i++) { - thisDiv = nextDiv; - nextDiv = nextDiv.next(); - let thisInputs = $(thisDiv).find("select, input[id^='" + this.fieldDef["name"] + "']"); - let nextInputs = $(nextDiv).find("select, input[id^='" + this.fieldDef["name"] + "']"); - for (let j = 0; j < thisInputs.length; j++){ - let thisInput = $(thisInputs[j]); - let nextInput = $(nextInputs[j]); - if (thisInput.is("select")){ - let data = $(nextInput).select2('data'); - if (data === null) { - data = {id: i, text: ""}; - } - $(thisInput).select2('data', {id: data.id, text: data.text}); + if (this.count >= this.max) { + this.addFieldBtn.hide(); + } + $(this.remove_btns).each((idx, btn) => { + $(btn).on("click", () => { + let thisDiv = $(btn).parent(); + let nextDiv = $(thisDiv); + for (let i = idx; i < this.count; i++) { + thisDiv = nextDiv; + nextDiv = nextDiv.next(); + let thisInputs = $(thisDiv).find("select, input[id^='" + this.fieldDef["name"] + "']"); + let nextInputs = $(nextDiv).find("select, input[id^='" + this.fieldDef["name"] + "']"); + for (let j = 0; j < thisInputs.length; j++){ + let thisInput = $(thisInputs[j]); + let nextInput = $(nextInputs[j]); + if (thisInput.is("select")){ + let data = $(nextInput).select2('data'); + if (data === null) { + data = {id: i, text: ""}; } - else { - $(thisInputs[j]).val($(nextInputs[j]).val()); - } - } - } - this.count--; - $(this.divs[this.count + 1]).find("select, input[id^='" + this.fieldDef["name"] + "']").each((idx, inp) => { - if ($(inp).is("select")){ - $(inp).select2('data', {id: this.count + 1, text: ""}); + $(thisInput).select2('data', {id: data.id, text: data.text}); + } else { - $(inp).val(""); + $(thisInputs[j]).val($(nextInputs[j]).val()); } - }); - $(this.divs[this.count + 1]).hide(); - if (this.count === 0) { - $(this.remove_btns[0]).hide(); } - if (this.count < this.max) { - $(this.addFieldBtn).show(); + } + this.count--; + $(this.divs[this.count + 1]).find("select, input[id^='" + this.fieldDef["name"] + "']").each((idx, inp) => { + if ($(inp).is("select")){ + $(inp).select2('data', {id: this.count + 1, text: ""}); + } + else { + $(inp).val(""); } - }) + }); + $(this.divs[this.count + 1]).hide(); + if (this.count === 0) { + $(this.remove_btns[0]).hide(); + } + if (this.count < this.max) { + $(this.addFieldBtn).show(); + } }) - }; + }) + }; - this.init() - }, + this.init() + }, - newSelect : function(params) { - return edges.instantiate(formulaic.widgets.Select, params); - }, - Select : function(params) { - this.fieldDef = params.fieldDef; - this.form = params.formulaic; - this.args = params.args; + newSelect : function(params) { + return edges.instantiate(formulaic.widgets.Select, params); + }, + Select : function(params) { + this.fieldDef = params.fieldDef; + this.form = params.formulaic; + this.args = params.args; - this.ns = "formulaic-select"; - this.elements = false; + this.ns = "formulaic-select"; + this.elements = false; - this.init = function() { - let allow_clear = this.args.allow_clear || false; - this.elements = $("select[name$='" + this.fieldDef.name + "']"); - this.elements.select2({ - allowClear: allow_clear, - newOption: true, - placeholder: "Start typing…" - }); - $(this.elements).on("focus", formulaic.widgets._select2_shift_focus); - }; + this.init = function() { + let allow_clear = this.args.allow_clear || false; + this.elements = $("select[name$='" + this.fieldDef.name + "']"); + this.elements.select2({ + allowClear: allow_clear, + newOption: true, + placeholder: "Start typing…" + }); + $(this.elements).on("focus", formulaic.widgets._select2_shift_focus); + }; - this.init(); - }, - - newTagList : function(params) { - return edges.instantiate(formulaic.widgets.TagList, params); - }, - TagList : function(params) { - this.fieldDef = params.fieldDef; - this.form = params.formulaic; - this.args = params.args; - - this.ns = "formulaic-taglist"; - - this.init = function() { - var stopWords = edges.getParam(this.args.stopWords, []); - - var ajax = { - url: current_scheme + "//" + current_domain + "/autocomplete/journal/" + this.args["field"], - dataType: 'json', - data: function (term, page) { - return { - q: term - }; - }, - results: function (data, page) { - return {results: data["suggestions"].filter((x) => $.inArray(x.text.toLowerCase(), stopWords) === -1)}; - } - }; + this.init(); + }, - var csc = function (term) { - if ($.inArray(term.toLowerCase(), stopWords) !== -1) { - return null; - } - return {id: $.trim(term), text: $.trim(term)}; - }; + newTagList : function(params) { + return edges.instantiate(formulaic.widgets.TagList, params); + }, + TagList : function(params) { + this.fieldDef = params.fieldDef; + this.form = params.formulaic; + this.args = params.args; + this.ns = "formulaic-taglist"; - var initSel = function (element, callback) { - var initial = element.val(); - var entries = initial.split(",").map(x => x.trim()).filter(x => x !== ""); - var data = []; - for (var i = 0; i < entries.length; i++) { - data.push({id: entries[i], text: entries[i]}); - } - callback(data); - }; + this.init = function() { + var stopWords = edges.getParam(this.args.stopWords, []); + + var ajax = { + url: current_scheme + "//" + current_domain + "/autocomplete/journal/" + this.args["field"], + dataType: 'json', + data: function (term, page) { + return { + q: term + }; + }, + results: function (data, page) { + return {results: data["suggestions"].filter((x) => $.inArray(x.text.toLowerCase(), stopWords) === -1)}; + } + }; - // apply the create search choice - let selector = "[name='" + this.fieldDef.name + "']"; - $(selector).select2({ - multiple: true, - minimumInputLength: 1, - ajax: ajax, - createSearchChoice: csc, - initSelection: initSel, - placeholder: "Start typing…", - allowClear: false, - tags: true, - tokenSeparators: [',', ";"], - maximumSelectionSize: this.args["maximumSelectionSize"], - width: 'resolve' - }); + var csc = function (term) { + if ($.inArray(term.toLowerCase(), stopWords) !== -1) { + return null; + } + return {id: $.trim(term), text: $.trim(term)}; + }; - $(selector).on("focus", formulaic.widgets._select2_shift_focus); + var initSel = function (element, callback) { + var initial = element.val(); + var entries = initial.split(",").map(x => x.trim()).filter(x => x !== ""); + var data = []; + for (var i = 0; i < entries.length; i++) { + data.push({id: entries[i], text: entries[i]}); + } + callback(data); }; - this.init(); - }, + // apply the create search choice + let selector = "[name='" + this.fieldDef.name + "']"; + $(selector).select2({ + multiple: true, + minimumInputLength: 1, + ajax: ajax, + createSearchChoice: csc, + initSelection: initSel, + placeholder: "Start typing…", + allowClear: false, + tags: true, + tokenSeparators: [',', ";"], + maximumSelectionSize: this.args["maximumSelectionSize"], + width: 'resolve' + }); - newTagEntry : function(params) { - return edges.instantiate(formulaic.widgets.TagEntry, params); - }, - TagEntry : function(params) { - this.fieldDef = params.fieldDef; - this.form = params.formulaic; - this.args = params.args; + $(selector).on("focus", formulaic.widgets._select2_shift_focus); - this.ns = "formulaic-tagentry"; + }; - this.init = function() { - let selector = "[name='" + this.fieldDef.name + "']"; - $(selector).select2({ - minimumInputLength: 1, - tags: [], - tokenSeparators: [','], - width: 'resolve' - }); - $(selector).on("focus", formulaic.widgets._select2_shift_focus); - }; + this.init(); + }, - this.init(); - }, + newTagEntry : function(params) { + return edges.instantiate(formulaic.widgets.TagEntry, params); + }, + TagEntry : function(params) { + this.fieldDef = params.fieldDef; + this.form = params.formulaic; + this.args = params.args; + this.ns = "formulaic-tagentry"; + this.init = function() { + let selector = "[name='" + this.fieldDef.name + "']"; + $(selector).select2({ + minimumInputLength: 1, + tags: [], + tokenSeparators: [','], + width: 'resolve' + }); + $(selector).on("focus", formulaic.widgets._select2_shift_focus); + }; - newLoadEditors: function(params) { - return edges.instantiate(formulaic.widgets.LoadEditors, params); - }, + this.init(); + }, - LoadEditors: function(params) { - this.fieldDef = params.fieldDef; - this.params = params.args; - this.groupField = false; - this.editorField = false; - this.init = function() { - this.groupField = $("[name='" + this.fieldDef.name + "']"); - this.editorField = $("[name='" + this.params.field + "']"); - edges.on(this.groupField, "change", this, "updateEditors"); - }; + newLoadEditors: function(params) { + return edges.instantiate(formulaic.widgets.LoadEditors, params); + }, - this.updateEditors = function(element) { - var ed_group_name = $(element).val(); - var ed_query_url = "/admin/dropdown/eg_associates"; + LoadEditors: function(params) { + this.fieldDef = params.fieldDef; + this.params = params.args; - // var ed_group_name = $("#s2id_editor_group").find('span').text(); - var that = this; - $.ajax({ - type : "GET", - data : {egn : ed_group_name}, - dataType: "json", - url: ed_query_url, - success: function(resp) - { - // Get the options for the drop-down from our ajax request - var assoc_options = []; - if (resp != null) - { - assoc_options = [["", "No editor assigned"]]; + this.groupField = false; + this.editorField = false; - for (var i=0; i").attr("value", assoc_options[j][0]).text(assoc_options[j][1]) - ); + // var ed_group_name = $("#s2id_editor_group").find('span').text(); + var that = this; + $.ajax({ + type : "GET", + data : {egn : ed_group_name}, + dataType: "json", + url: ed_query_url, + success: function(resp) + { + // Get the options for the drop-down from our ajax request + var assoc_options = []; + if (resp != null) + { + assoc_options = [["", "No editor assigned"]]; + + for (var i=0; i").attr("value", assoc_options[j][0]).text(assoc_options[j][1]) + ); + } + } + }) + }; - let selector = "[name='" + this.fieldDef.name + "']"; + this.init(); + }, - $(selector).on("focus", formulaic.widgets._select2_shift_focus); + newAutocomplete: function(params){ + return edges.instantiate(formulaic.widgets.Autocomplete, params); + }, - if (include_input) { - // apply the create search choice - $(selector).select2({ - minimumInputLength: mininput, - ajax: ajax, - createSearchChoice: csc, - initSelection : initSel, - allowClear: allow_clear, - width: 'resolve' - }); - } else { - // go without the create search choice option - $(selector).select2({ - minimumInputLength: mininput, - ajax: ajax, - initSelection : initSel, - allowClear: allow_clear, - width: 'resolve' - }); + Autocomplete: function(params){ + this.fieldDef = params.fieldDef; + this.params = params.args; + + this.init = function() { + let doc_type = this.params.type || "journal"; + let doc_field = this.params.field; + let mininput = this.params.min_input === undefined ? 3 : this.params.min_input; + let include_input = this.params.include === undefined ? true : this.params.include; + let allow_clear = this.params.allow_clear_input === undefined ? true : this.params.allow_clear_input; + + let ajax = { + url: current_scheme + "//" + current_domain + "/autocomplete/" + doc_type + "/" + doc_field, + dataType: 'json', + data: function (term, page) { + return { + q: term + }; + }, + results: function (data, page) { + return { results: data["suggestions"] }; } + }; - $(selector).on("focus", formulaic.widgets._select2_shift_focus); + var csc = function(term) {return {"id":term, "text": term};}; + + var initSel = function (element, callback) { + var data = {id: element.val(), text: element.val()}; + callback(data); }; - this.init() - }, - newIssnLink : function(params) { - return edges.instantiate(formulaic.widgets.IssnLink, params) - }, - IssnLink : function(params) { - this.fieldDef = params.fieldDef; - this.form = params.formulaic; - this.issn = params.issn; + let selector = "[name='" + this.fieldDef.name + "']"; - this.ns = "formulaic-issnlink"; + $(selector).on("focus", formulaic.widgets._select2_shift_focus); - this.link = false; - this.url = "https://portal.issn.org/resource/ISSN/"; + if (include_input) { + // apply the create search choice + $(selector).select2({ + minimumInputLength: mininput, + ajax: ajax, + createSearchChoice: csc, + initSelection : initSel, + allowClear: allow_clear, + width: 'resolve' + }); + } else { + // go without the create search choice option + $(selector).select2({ + minimumInputLength: mininput, + ajax: ajax, + initSelection : initSel, + allowClear: allow_clear, + width: 'resolve' + }); + } - this.init = function() { - var elements = this.form.controlSelect.input( - {name: this.fieldDef.name}); - edges.on(elements, "keyup.IssnLink", this, "updateUrl"); + $(selector).on("focus", formulaic.widgets._select2_shift_focus); + }; - for (var i = 0; i < elements.length; i++) { - this.updateUrl(elements[i]); - } - }; + this.init() + }, + newIssnLink : function(params) { + return edges.instantiate(formulaic.widgets.IssnLink, params) + }, + IssnLink : function(params) { + this.fieldDef = params.fieldDef; + this.form = params.formulaic; + this.issn = params.issn; - this.updateUrl = function(element) { - var that = $(element); - var val = that.val(); - var id = edges.css_id(this.ns, this.fieldDef.name); + this.ns = "formulaic-issnlink"; - var match = val.match(/[d0-9]{4}-{0,1}[0-9]{3}[0-9xX]{1}/); - var url = this.url + val; + this.link = false; + this.url = "https://portal.issn.org/resource/ISSN/"; - if (val && match) { - if (this.link) { - this.link.text(url); - this.link.attr("href", url); - } else { - var classes = edges.css_classes(this.ns, "visit"); - that.after('

' + url + '

'); + this.init = function() { + var elements = this.form.controlSelect.input( + {name: this.fieldDef.name}); + edges.on(elements, "keyup.IssnLink", this, "updateUrl"); - var selector = edges.css_id_selector(this.ns, this.fieldDef.name); - this.link = $(selector, this.form.context); - } - } else if (this.link) { - this.link.remove(); - this.link = false; + for (var i = 0; i < elements.length; i++) { + this.updateUrl(elements[i]); + } + }; + + this.updateUrl = function(element) { + var that = $(element); + var val = that.val(); + var id = edges.css_id(this.ns, this.fieldDef.name); + + var match = val.match(/[d0-9]{4}-{0,1}[0-9]{3}[0-9xX]{1}/); + var url = this.url + val; + + if (val && match) { + if (this.link) { + this.link.text(url); + this.link.attr("href", url); + } else { + var classes = edges.css_classes(this.ns, "visit"); + that.after('

' + url + '

'); + + var selector = edges.css_id_selector(this.ns, this.fieldDef.name); + this.link = $(selector, this.form.context); } - }; + } else if (this.link) { + this.link.remove(); + this.link = false; + } + }; - this.init(); - }, - } - }; + this.init(); + }, + } +}; From 8874351e1522b22119c78d068290ba0f4e2be09d Mon Sep 17 00:00:00 2001 From: Aga Date: Tue, 18 Jul 2023 13:51:25 +0100 Subject: [PATCH 14/26] clean up part 2 --- portality/static/js/formulaic.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/portality/static/js/formulaic.js b/portality/static/js/formulaic.js index 55935d606d..5e08724644 100644 --- a/portality/static/js/formulaic.js +++ b/portality/static/js/formulaic.js @@ -1198,6 +1198,27 @@ var formulaic = { newClickableOwner : function(params) { return edges.instantiate(formulaic.widgets.ClickableOwner, params) }, + newClickToCopy : function(params) { + return edges.instantiate(formulaic.widgets.ClickToCopy, params) + }, + ClickToCopy : function(params) { + this.fieldDef = params.fieldDef; + this.init = function() { + var elements = $("#click-to-copy--" + this.fieldDef.name); + edges.on(elements, "click", this, "copy"); + }; + this.copy = function(element) { + let form = new doaj.af.BaseApplicationForm() + let value = form.determineFieldsValue(this.fieldDef.name) + let value_to_copy = form.convertValueToText(value); + navigator.clipboard.writeText(value_to_copy) + var confirmation = $("#copy-confirmation--" + this.fieldDef.name); + confirmation.text("Value copied: " + value_to_copy); + confirmation.show().delay(3000).fadeOut(); + }; + this.init(); + + }, ClickableOwner : function(params) { this.fieldDef = params.fieldDef; this.form = params.formulaic; From 0996481255198719edab47ff9d2c243d6cada8c8 Mon Sep 17 00:00:00 2001 From: Aga Date: Tue, 25 Jul 2023 09:22:00 +0100 Subject: [PATCH 15/26] change order of functions in formulaic --- portality/static/js/formulaic.js | 41 ++++++++++++++++---------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/portality/static/js/formulaic.js b/portality/static/js/formulaic.js index 5e08724644..58ddf4f403 100644 --- a/portality/static/js/formulaic.js +++ b/portality/static/js/formulaic.js @@ -1198,27 +1198,6 @@ var formulaic = { newClickableOwner : function(params) { return edges.instantiate(formulaic.widgets.ClickableOwner, params) }, - newClickToCopy : function(params) { - return edges.instantiate(formulaic.widgets.ClickToCopy, params) - }, - ClickToCopy : function(params) { - this.fieldDef = params.fieldDef; - this.init = function() { - var elements = $("#click-to-copy--" + this.fieldDef.name); - edges.on(elements, "click", this, "copy"); - }; - this.copy = function(element) { - let form = new doaj.af.BaseApplicationForm() - let value = form.determineFieldsValue(this.fieldDef.name) - let value_to_copy = form.convertValueToText(value); - navigator.clipboard.writeText(value_to_copy) - var confirmation = $("#copy-confirmation--" + this.fieldDef.name); - confirmation.text("Value copied: " + value_to_copy); - confirmation.show().delay(3000).fadeOut(); - }; - this.init(); - - }, ClickableOwner : function(params) { this.fieldDef = params.fieldDef; this.form = params.formulaic; @@ -1258,7 +1237,27 @@ var formulaic = { this.init(); }, + newClickToCopy : function(params) { + return edges.instantiate(formulaic.widgets.ClickToCopy, params) + }, + ClickToCopy : function(params) { + this.fieldDef = params.fieldDef; + this.init = function() { + var elements = $("#click-to-copy--" + this.fieldDef.name); + edges.on(elements, "click", this, "copy"); + }; + this.copy = function(element) { + let form = new doaj.af.BaseApplicationForm() + let value = form.determineFieldsValue(this.fieldDef.name) + let value_to_copy = form.convertValueToText(value); + navigator.clipboard.writeText(value_to_copy) + var confirmation = $("#copy-confirmation--" + this.fieldDef.name); + confirmation.text("Value copied: " + value_to_copy); + confirmation.show().delay(3000).fadeOut(); + }; + this.init(); + }, newTrimWhitespace : function(params) { return edges.instantiate(formulaic.widgets.TrimWhitespace, params) }, From 1fbe10c628bcfd4782cdae73b8f9a035bcfd8cb7 Mon Sep 17 00:00:00 2001 From: Steve Eardley Date: Fri, 4 Aug 2023 14:53:36 +0100 Subject: [PATCH 16/26] Fix a few warnings from the test run caused by our code --- .../test_application_processor_emails.py | 3 ++- doajtest/unit/test_formrender.py | 8 ++++++++ portality/bll/services/background_task_status.py | 2 +- portality/core.py | 4 ++-- portality/dao.py | 14 +++++++------- portality/tasks/async_workflow_notifications.py | 2 +- portality/tasks/harvester_helpers/epmc/client.py | 4 ++-- portality/tasks/helpers/background_helper.py | 4 ++-- portality/view/admin.py | 6 +++--- 9 files changed, 28 insertions(+), 19 deletions(-) diff --git a/doajtest/unit/application_processors/test_application_processor_emails.py b/doajtest/unit/application_processors/test_application_processor_emails.py index 44630b518c..05bd355700 100644 --- a/doajtest/unit/application_processors/test_application_processor_emails.py +++ b/doajtest/unit/application_processors/test_application_processor_emails.py @@ -65,13 +65,14 @@ def editor_account_pull(self, _id): ACTUAL_ACCOUNT_PULL = models.Account.pull # A regex string for searching the log entries -email_log_regex = 'template.*%s.*to:\[u{0,1}\'%s.*subject:.*%s' +email_log_regex = r'template.*%s.*to:\[u{0,1}\'%s.*subject:.*%s' # A string present in each email log entry (for counting them) email_count_string = 'Email template' NOTIFICATIONS_INTERCEPT = [] + class TestPublicApplicationEmails(DoajTestCase): def setUp(self): super(TestPublicApplicationEmails, self).setUp() diff --git a/doajtest/unit/test_formrender.py b/doajtest/unit/test_formrender.py index 1d655420af..f57ee83a61 100644 --- a/doajtest/unit/test_formrender.py +++ b/doajtest/unit/test_formrender.py @@ -6,11 +6,16 @@ # Form context for basic test ################################################################ + class TestForm(Form): + __test__ = False # Prevent collection by PyTest one = StringField("One") two = StringField("Two") + class TestRenderer(Renderer): + __test__ = False # Prevent collection by PyTest + def __init__(self): super(TestRenderer, self).__init__() self.FIELD_GROUPS = { @@ -20,7 +25,10 @@ def __init__(self): ] } + class TestContext(FormContext): + __test__ = False # Prevent collection by PyTest + def data2form(self): self.form = TestForm(formdata=self.form_data) diff --git a/portality/bll/services/background_task_status.py b/portality/bll/services/background_task_status.py index 486fdb1d84..ae0c6b7908 100644 --- a/portality/bll/services/background_task_status.py +++ b/portality/bll/services/background_task_status.py @@ -95,7 +95,7 @@ def create_queues_status(self, queue_name) -> dict: # prepare for err_msgs limited_sec = app.config.get('BG_MONITOR_LAST_COMPLETED', {}).get(queue_name) if limited_sec is None: - app.logger.warn(f'BG_MONITOR_LAST_COMPLETED for {queue_name} not found ') + app.logger.warning(f'BG_MONITOR_LAST_COMPLETED for {queue_name} not found ') err_msgs = [] if limited_sec is not None and last_completed_date: diff --git a/portality/core.py b/portality/core.py index 0a09313632..cc14ff79af 100644 --- a/portality/core.py +++ b/portality/core.py @@ -227,11 +227,11 @@ def initialise_index(app, conn, only_mappings=None): :return: """ if not app.config['INITIALISE_INDEX']: - app.logger.warn('INITIALISE_INDEX config var is not True, initialise_index command cannot run') + app.logger.warning('INITIALISE_INDEX config var is not True, initialise_index command cannot run') return if app.config.get("READ_ONLY_MODE", False) and app.config.get("SCRIPTS_READ_ONLY_MODE", False): - app.logger.warn("System is in READ-ONLY mode, initialise_index command cannot run") + app.logger.warning("System is in READ-ONLY mode, initialise_index command cannot run") return # get the app mappings diff --git a/portality/dao.py b/portality/dao.py index 14c5ad125f..40f14adc7c 100644 --- a/portality/dao.py +++ b/portality/dao.py @@ -136,7 +136,7 @@ def save(self, retries=0, back_off_factor=1, differentiate=False, blocking=False :return: """ if app.config.get("READ_ONLY_MODE", False) and app.config.get("SCRIPTS_READ_ONLY_MODE", False): - app.logger.warn("System is in READ-ONLY mode, save command cannot run") + app.logger.warning("System is in READ-ONLY mode, save command cannot run") return if retries > app.config.get("ES_RETRY_HARD_LIMIT", 1000): # an arbitrary large number @@ -220,7 +220,7 @@ def save(self, retries=0, back_off_factor=1, differentiate=False, blocking=False def delete(self): if app.config.get("READ_ONLY_MODE", False) and app.config.get("SCRIPTS_READ_ONLY_MODE", False): - app.logger.warn("System is in READ-ONLY mode, delete command cannot run") + app.logger.warning("System is in READ-ONLY mode, delete command cannot run") return # r = requests.delete(self.target() + self.id) @@ -313,7 +313,7 @@ def bulk(cls, documents: List[dict], idkey='id', refresh=False, action='index', """ # ~~->ReadOnlyMode:Feature~~ if app.config.get("READ_ONLY_MODE", False) and app.config.get("SCRIPTS_READ_ONLY_MODE", False): - app.logger.warn("System is in READ-ONLY mode, bulk command cannot run") + app.logger.warning("System is in READ-ONLY mode, bulk command cannot run") return if action not in ['index', 'update', 'delete']: @@ -363,7 +363,7 @@ def refresh(cls): :return: """ if app.config.get("READ_ONLY_MODE", False) and app.config.get("SCRIPTS_READ_ONLY_MODE", False): - app.logger.warn("System is in READ-ONLY mode, refresh command cannot run") + app.logger.warning("System is in READ-ONLY mode, refresh command cannot run") return # r = requests.post(cls.target() + '_refresh', headers=CONTENT_TYPE_JSON) @@ -449,7 +449,7 @@ def send_query(cls, qobj, retry=50, **kwargs): @classmethod def remove_by_id(cls, id): if app.config.get("READ_ONLY_MODE", False) and app.config.get("SCRIPTS_READ_ONLY_MODE", False): - app.logger.warn("System is in READ-ONLY mode, delete_by_id command cannot run") + app.logger.warning("System is in READ-ONLY mode, delete_by_id command cannot run") return # r = requests.delete(cls.target() + id) @@ -461,7 +461,7 @@ def remove_by_id(cls, id): @classmethod def delete_by_query(cls, query): if app.config.get("READ_ONLY_MODE", False) and app.config.get("SCRIPTS_READ_ONLY_MODE", False): - app.logger.warn("System is in READ-ONLY mode, delete_by_query command cannot run") + app.logger.warning("System is in READ-ONLY mode, delete_by_query command cannot run") return #r = requests.delete(cls.target() + "_query", data=json.dumps(query)) @@ -472,7 +472,7 @@ def delete_by_query(cls, query): @classmethod def destroy_index(cls): if app.config.get("READ_ONLY_MODE", False) and app.config.get("SCRIPTS_READ_ONLY_MODE", False): - app.logger.warn("System is in READ-ONLY mode, destroy_index command cannot run") + app.logger.warning("System is in READ-ONLY mode, destroy_index command cannot run") return # if app.config['ELASTIC_SEARCH_INDEX_PER_TYPE']: diff --git a/portality/tasks/async_workflow_notifications.py b/portality/tasks/async_workflow_notifications.py index 4bea0b1104..b0236af7ec 100644 --- a/portality/tasks/async_workflow_notifications.py +++ b/portality/tasks/async_workflow_notifications.py @@ -333,7 +333,7 @@ def associate_editor_notifications(emails_dict, limit=None): assoc_email = assoc.email except AttributeError: # There isn't an account for that id - app.logger.warn("No account found for ID {0}".format(assoc_id)) + app.logger.warning("No account found for ID {0}".format(assoc_id)) continue text = render_template('email/workflow_reminder_fragments/assoc_ed_age_frag', num_idle=idle, x_days=X_DAYS, num_very_idle=very_idle, y_weeks=Y_WEEKS, url=url) diff --git a/portality/tasks/harvester_helpers/epmc/client.py b/portality/tasks/harvester_helpers/epmc/client.py index fb742b0714..2957e9fe62 100644 --- a/portality/tasks/harvester_helpers/epmc/client.py +++ b/portality/tasks/harvester_helpers/epmc/client.py @@ -37,9 +37,9 @@ def check_epmc_version(resp_json): received_ver = resp_json['version'] configured_ver = app.config.get("EPMC_TARGET_VERSION") if received_ver != configured_ver: - app.logger.warn("Mismatching EPMC API version; recommend checking for changes. Expected '{0}' Found '{1}'".format(configured_ver, received_ver)) + app.logger.warning("Mismatching EPMC API version; recommend checking for changes. Expected '{0}' Found '{1}'".format(configured_ver, received_ver)) except KeyError: - app.logger.warn("Couldn't check EPMC API version; did not find 'version' key in response. Proceed with caution as the EPMC API may have changed.") + app.logger.warning("Couldn't check EPMC API version; did not find 'version' key in response. Proceed with caution as the EPMC API may have changed.") def to_keywords(s): diff --git a/portality/tasks/helpers/background_helper.py b/portality/tasks/helpers/background_helper.py index 2790475729..66a15343e8 100644 --- a/portality/tasks/helpers/background_helper.py +++ b/portality/tasks/helpers/background_helper.py @@ -26,7 +26,7 @@ def get_queue_id_by_task_queue(task_queue: RedisHuey): elif task_queue.name == main_queue.name: return constants.BGJOB_QUEUE_ID_MAIN else: - app.logger.warn(f'unknown task_queue[{task_queue}]') + app.logger.warning(f'unknown task_queue[{task_queue}]') return constants.BGJOB_QUEUE_ID_UNKNOWN @@ -141,7 +141,7 @@ def _load_bgtask_safe(_mi): return _mi.module_finder.find_spec(_mi.name).loader.load_module(_mi.name) except RuntimeError as e: if 'No configuration for scheduled action' in str(e): - app.logger.warn(f'config for {_mi.name} not found') + app.logger.warning(f'config for {_mi.name} not found') return None raise e diff --git a/portality/view/admin.py b/portality/view/admin.py index 9e39473b03..010907c3b1 100644 --- a/portality/view/admin.py +++ b/portality/view/admin.py @@ -68,7 +68,7 @@ def journals_list(): try: query = json.loads(request.values.get("q")) except: - app.logger.warn("Bad Request at admin/journals: " + str(request.values.get("q"))) + app.logger.warning("Bad Request at admin/journals: " + str(request.values.get("q"))) abort(400) # get the total number of journals to be affected @@ -89,7 +89,7 @@ def journals_list(): try: query = json.loads(request.data) except: - app.logger.warn("Bad Request at admin/journals: " + str(request.data)) + app.logger.warning("Bad Request at admin/journals: " + str(request.data)) abort(400) # get only the query part @@ -123,7 +123,7 @@ def articles_list(): try: query = json.loads(request.data) except: - app.logger.warn("Bad Request at admin/journals: " + str(request.data)) + app.logger.warning("Bad Request at admin/journals: " + str(request.data)) abort(400) # get only the query part From a2a310f75d646fc4a6bcfc458ece0feaff1a5961 Mon Sep 17 00:00:00 2001 From: Steve Eardley Date: Thu, 31 Aug 2023 17:43:00 +0100 Subject: [PATCH 17/26] 404 when there's no PDD or when it's mis-requested --- portality/view/doaj.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/portality/view/doaj.py b/portality/view/doaj.py index 5ac2f67f69..1292728293 100644 --- a/portality/view/doaj.py +++ b/portality/view/doaj.py @@ -198,8 +198,13 @@ def public_data_dump_redirect(record_type): if not current_user.has_role(constants.ROLE_PUBLIC_DATA_DUMP): abort(404) - target_data = models.Cache.get_public_data_dump().get(record_type, {}) - if target_data is None: + # Make sure the PDD exists + pdd = models.Cache.get_public_data_dump() + if pdd is None: + abort(404) + + target_data = pdd.get(record_type, {}) + if not target_data: abort(404) main_store = store.StoreFactory.get(constants.STORE__SCOPE__PUBLIC_DATA_DUMP) From c41f895c522c70ea0a48a2eb920b0f13b13e2695 Mon Sep 17 00:00:00 2001 From: Steve Eardley Date: Tue, 5 Sep 2023 11:47:31 +0100 Subject: [PATCH 18/26] update OAI test --- doajtest/unit/test_oaipmh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doajtest/unit/test_oaipmh.py b/doajtest/unit/test_oaipmh.py index b65d319bd0..bab8102499 100644 --- a/doajtest/unit/test_oaipmh.py +++ b/doajtest/unit/test_oaipmh.py @@ -245,7 +245,7 @@ def test_06_identify(self): records = t.xpath('/oai:OAI-PMH/oai:Identify', namespaces=self.oai_ns) assert len(records) == 1 assert records[0].xpath('//oai:repositoryName', namespaces=self.oai_ns)[0].text == 'Directory of Open Access Journals' - assert records[0].xpath('//oai:adminEmail', namespaces=self.oai_ns)[0].text == 'sysadmin@cottagelabs.com' + assert records[0].xpath('//oai:adminEmail', namespaces=self.oai_ns)[0].text == 'helpdesk+oai@doaj.org' assert records[0].xpath('//oai:granularity', namespaces=self.oai_ns)[0].text == 'YYYY-MM-DDThh:mm:ssZ' def test_07_bad_verb(self): From b5212dc9a6f0b97f67bbb73c581a3e91cea4f9a9 Mon Sep 17 00:00:00 2001 From: Aga Date: Tue, 5 Sep 2023 13:42:54 +0100 Subject: [PATCH 19/26] fix script output file --- ...230609_find_articles_with_invalid_issns.py | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/portality/scripts/230609_find_articles_with_invalid_issns.py b/portality/scripts/230609_find_articles_with_invalid_issns.py index c45b8c5d63..d9cf550a04 100644 --- a/portality/scripts/230609_find_articles_with_invalid_issns.py +++ b/portality/scripts/230609_find_articles_with_invalid_issns.py @@ -19,18 +19,16 @@ if __name__ == "__main__": - # import argparse - # - # parser = argparse.ArgumentParser() - # parser.add_argument("-o", "--out", help="output file path") - # args = parser.parse_args() - # - # if not args.out: - # print("Please specify an output file path with the -o option") - # parser.print_help() - # exit() + import argparse - out = "out.csv" + parser = argparse.ArgumentParser() + parser.add_argument("-o", "--out", help="output file path") + args = parser.parse_args() + + if not args.out: + print("Please specify an output file path with the -o option") + parser.print_help() + exit() with open(out, "w", encoding="utf-8") as f: writer = csv.writer(f) From 24dce28fc773a67b8e777e4187a70dc5a3dc9b33 Mon Sep 17 00:00:00 2001 From: Aga Date: Tue, 5 Sep 2023 13:51:39 +0100 Subject: [PATCH 20/26] change 'value copied' to 'copied' --- portality/static/js/formulaic.js | 2 +- portality/templates/application_form/_field.html | 2 +- portality/templates/application_form/_list.html | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/portality/static/js/formulaic.js b/portality/static/js/formulaic.js index 58ddf4f403..c970e12c18 100644 --- a/portality/static/js/formulaic.js +++ b/portality/static/js/formulaic.js @@ -1252,7 +1252,7 @@ var formulaic = { let value_to_copy = form.convertValueToText(value); navigator.clipboard.writeText(value_to_copy) var confirmation = $("#copy-confirmation--" + this.fieldDef.name); - confirmation.text("Value copied: " + value_to_copy); + confirmation.text("Copied: " + value_to_copy); confirmation.show().delay(3000).fadeOut(); }; this.init(); diff --git a/portality/templates/application_form/_field.html b/portality/templates/application_form/_field.html index 40be86ce55..2e6ce601a5 100644 --- a/portality/templates/application_form/_field.html +++ b/portality/templates/application_form/_field.html @@ -10,7 +10,7 @@ {% endif %} {% if f.has_widget("click_to_copy") %} Copy value - + {% endif %} {% if f.optional %}(Optional){% endif %} diff --git a/portality/templates/application_form/_list.html b/portality/templates/application_form/_list.html index 2f8fc00597..02567676cc 100644 --- a/portality/templates/application_form/_list.html +++ b/portality/templates/application_form/_list.html @@ -9,7 +9,7 @@ {% endif %} {% if f.has_widget("click_to_copy") %} Copy value - + {% endif %} {% if f.optional %}(Optional){% endif %} From b15c11448fca8018d6463994a747ceeb2e213357 Mon Sep 17 00:00:00 2001 From: Steve Eardley Date: Thu, 7 Sep 2023 10:55:33 +0100 Subject: [PATCH 21/26] Route application form stuff to new editor server --- deploy/nginx/doaj | 45 ++++++++++++++++++++++++++++++++++----------- production.cfg | 6 ++---- 2 files changed, 36 insertions(+), 15 deletions(-) diff --git a/deploy/nginx/doaj b/deploy/nginx/doaj index b52db3aea0..d906851ae1 100644 --- a/deploy/nginx/doaj +++ b/deploy/nginx/doaj @@ -36,17 +36,27 @@ map $http_user_agent $block_ua { ~*curl 1; } +# the public server (deprecated, use failover) upstream doaj_apps { - server 10.131.191.139:5050; + server 10.131.191.139:5050; #doaj-public-app-1 } + +# Background server runs async tasks upstream doaj_bg_apps { - #server 10.131.56.133:5050; #old bg machine - server 10.131.12.33:5050; + server 10.131.12.33:5050; #doaj-background-app-1 +} + +# Editor and admin site components +upstream doaj_ed_failover { + server 10.131.56.133:5050; #doaj-editor-app-1 + server 10.131.12.33:5050; #doaj-background-app-1 } + +# For public site components, try all servers upstream doaj_apps_failover { - server 10.131.191.139:5050; - #server 10.131.56.133:5050 backup; #old bg machine - server 10.131.12.33:5050 backup; + server 10.131.191.139:5050; #doaj-public-app-1 + server 10.131.12.33:5050 backup; #doaj-background-app-1 + server 10.131.56.133:5050; #doaj-editor-app-1 } upstream doaj_index { server 10.131.191.132:9200; @@ -144,9 +154,7 @@ server { proxy_buffering off; } - # for now we are going to send all login functions to the bg machine - # technically ONLY the routes that require file upload need to go to the bg machine - # but we think it is handy to separate them out, and later we could send them to other machines + # technically only the routes that require file upload need to go to the bg machine, but separate for consistency location /account { limit_req zone=general burst=10 nodelay; proxy_pass http://doaj_bg_apps; @@ -157,6 +165,19 @@ server { proxy_set_header X-Forwarded-Proto $scheme; proxy_buffering off; } + + # prefer the editor machine for application form work + location /admin/application { + limit_req zone=general burst=10 nodelay; + proxy_pass http://doaj_ed_failover; + proxy_redirect off; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_buffering off; + } + location /admin { # there are admin bulk actions that MUST go to bg machine limit_req zone=general burst=10 nodelay; proxy_pass http://doaj_bg_apps; @@ -167,9 +188,10 @@ server { proxy_set_header X-Forwarded-Proto $scheme; proxy_buffering off; } + location /editor { limit_req zone=general burst=10 nodelay; - proxy_pass http://doaj_bg_apps; + proxy_pass http://doaj_ed_failover; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; @@ -177,9 +199,10 @@ server { proxy_set_header X-Forwarded-Proto $scheme; proxy_buffering off; } + location /journal/readonly { limit_req zone=general burst=10 nodelay; - proxy_pass http://doaj_bg_apps; + proxy_pass http://doaj_ed_failover; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; diff --git a/production.cfg b/production.cfg index 897ff34004..d9d501f253 100644 --- a/production.cfg +++ b/production.cfg @@ -5,11 +5,9 @@ ELASTIC_SEARCH_HOST = "http://10.131.191.132:9200" # doaj-ind ELASTICSEARCH_HOSTS = [{'host': '10.131.191.132', 'port': 9200}, {'host': '10.131.191.133', 'port': 9200}] INDEX_PER_TYPE_SUBSTITUTE = '_doc' - # doaj-public-app-1 doaj-background-app-1 -APP_MACHINES_INTERNAL_IPS = ['10.131.191.139:5050', '10.131.12.33:5050'] + # doaj-public-app-1 doaj-background-app-1 doaj-editor-app-1 +APP_MACHINES_INTERNAL_IPS = ['10.131.191.139:5050', '10.131.12.33:5050', '10.131.56.133:5050'] - # doaj-public-app-1 doaj-bg-app-1 doaj-background-app-1 -#APP_MACHINES_INTERNAL_IPS = ['10.131.191.139:5050', '10.131.56.133:5050', '10.131.12.33:5050'] # The app is served via nginx / cloudlflare - they handle SSL SSL = False From d71425f01b30338607cc0d48f03e040255d740b8 Mon Sep 17 00:00:00 2001 From: Steve Eardley Date: Thu, 7 Sep 2023 11:04:07 +0100 Subject: [PATCH 22/26] Version bump for click to copy release --- portality/settings.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/portality/settings.py b/portality/settings.py index a02020f8ea..2bffdbab8a 100644 --- a/portality/settings.py +++ b/portality/settings.py @@ -9,7 +9,7 @@ # Application Version information # ~~->API:Feature~~ -DOAJ_VERSION = "6.3.15" +DOAJ_VERSION = "6.3.16" API_VERSION = "3.0.1" ###################################### diff --git a/setup.py b/setup.py index b97eee8d8b..a83bf6daf2 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name='doaj', - version='6.3.15', + version='6.3.16', packages=find_packages(), install_requires=[ "awscli==1.20.50", From 99ac2bc1840eebe4b3e6d29300398230c7ab28e6 Mon Sep 17 00:00:00 2001 From: Steve Eardley Date: Thu, 7 Sep 2023 12:24:21 +0100 Subject: [PATCH 23/26] Specifically /application/ for editor server --- deploy/nginx/doaj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/nginx/doaj b/deploy/nginx/doaj index d906851ae1..f09aeb805b 100644 --- a/deploy/nginx/doaj +++ b/deploy/nginx/doaj @@ -166,8 +166,8 @@ server { proxy_buffering off; } - # prefer the editor machine for application form work - location /admin/application { + # prefer the editor machine for application form work (but application_quick_reject goes to background async) + location ~* /admin/application/ { limit_req zone=general burst=10 nodelay; proxy_pass http://doaj_ed_failover; proxy_redirect off; From 159abc8e2f685de031fedb93c6a96b8c79cf07e3 Mon Sep 17 00:00:00 2001 From: Steve Eardley Date: Sun, 10 Sep 2023 11:15:34 +0100 Subject: [PATCH 24/26] ensure editor server is classified as backup for apps failover --- deploy/nginx/doaj | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/deploy/nginx/doaj b/deploy/nginx/doaj index f09aeb805b..4e6c3b0576 100644 --- a/deploy/nginx/doaj +++ b/deploy/nginx/doaj @@ -49,14 +49,14 @@ upstream doaj_bg_apps { # Editor and admin site components upstream doaj_ed_failover { server 10.131.56.133:5050; #doaj-editor-app-1 - server 10.131.12.33:5050; #doaj-background-app-1 + server 10.131.12.33:5050 backup; #doaj-background-app-1 } # For public site components, try all servers upstream doaj_apps_failover { - server 10.131.191.139:5050; #doaj-public-app-1 - server 10.131.12.33:5050 backup; #doaj-background-app-1 - server 10.131.56.133:5050; #doaj-editor-app-1 + server 10.131.191.139:5050; #doaj-public-app-1 + server 10.131.12.33:5050 backup; #doaj-background-app-1 + server 10.131.56.133:5050 backup; #doaj-editor-app-1 } upstream doaj_index { server 10.131.191.132:9200; @@ -131,6 +131,7 @@ server { proxy_set_header X-Forwarded-Proto $scheme; proxy_buffering off; } + location /search { if ($block_ua) {return 403;} limit_req zone=general burst=10 nodelay; @@ -210,7 +211,8 @@ server { proxy_set_header X-Forwarded-Proto $scheme; proxy_buffering off; } - location /publisher { # only /publisher/uploadfile MUST go to bg, and /publisher/uploadFile + + location /publisher { # only /publisher/uploadfile MUST go to background limit_req zone=general burst=10 nodelay; proxy_pass http://doaj_bg_apps; proxy_redirect off; @@ -220,7 +222,8 @@ server { proxy_set_header X-Forwarded-Proto $scheme; proxy_buffering off; } - location /service { + + location /service { # performs locks etc - handle on the background server limit_req zone=general burst=10 nodelay; proxy_pass http://doaj_bg_apps; proxy_redirect off; @@ -244,6 +247,7 @@ server { proxy_set_header X-Forwarded-Proto $scheme; proxy_buffering off; } + location /csv { limit_req zone=general burst=10 nodelay; proxy_pass http://doaj_bg_apps; @@ -258,6 +262,7 @@ server { location =/robots.txt { alias /home/cloo/doaj/src/doaj/deploy/robots-production.txt; } + location /static/ { alias /home/cloo/doaj/src/doaj/portality/static/; autoindex off; From e7e0abb1b6a3fcba49a85892976f6554213f2d31 Mon Sep 17 00:00:00 2001 From: Steve Eardley Date: Mon, 11 Sep 2023 12:49:27 +0100 Subject: [PATCH 25/26] necessary fixes for articles script --- .../230609_find_articles_with_invalid_issns.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/portality/scripts/230609_find_articles_with_invalid_issns.py b/portality/scripts/230609_find_articles_with_invalid_issns.py index d9cf550a04..8b857faa01 100644 --- a/portality/scripts/230609_find_articles_with_invalid_issns.py +++ b/portality/scripts/230609_find_articles_with_invalid_issns.py @@ -1,16 +1,13 @@ from portality import models from portality.bll.services import article as articlesvc from portality.bll import exceptions -from portality.core import es_connection -from portality.util import ipt_prefix -import esprit import csv IN_DOAJ = { "query": { "bool": { "must": [ - {"term" : {"admin.in_doaj":True}} + {"term": {"admin.in_doaj": True}} ] } } @@ -22,15 +19,10 @@ import argparse parser = argparse.ArgumentParser() - parser.add_argument("-o", "--out", help="output file path") + parser.add_argument("-o", "--out", help="output file path", required=True) args = parser.parse_args() - if not args.out: - print("Please specify an output file path with the -o option") - parser.print_help() - exit() - - with open(out, "w", encoding="utf-8") as f: + with open(args.out, "w", encoding="utf-8") as f: writer = csv.writer(f) writer.writerow(["ID", "PISSN", "EISSN", "Journals found with article's PISSN", "In doaj?", "Journals found with article's EISSN", "In doaj?", "Error"]) @@ -61,4 +53,4 @@ j_e_in_doaj.append(jobj.is_in_doaj()) else: j_e_in_doaj.append("n/a") - writer.writerow([id, pissn, eissn, j_p, j_p_in_doaj, j_e, j_e_in_doaj, str(e)]) \ No newline at end of file + writer.writerow([id, pissn, eissn, j_p, j_p_in_doaj, j_e, j_e_in_doaj, str(e)]) From 8c7e20574a8d04cedacd1d075bf20a97ed8f85fc Mon Sep 17 00:00:00 2001 From: Steve Eardley Date: Wed, 13 Sep 2023 13:45:22 +0100 Subject: [PATCH 26/26] Version bump for static pages --- portality/settings.py | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/portality/settings.py b/portality/settings.py index 2bffdbab8a..f2bdd5479f 100644 --- a/portality/settings.py +++ b/portality/settings.py @@ -9,7 +9,7 @@ # Application Version information # ~~->API:Feature~~ -DOAJ_VERSION = "6.3.16" +DOAJ_VERSION = "6.3.17" API_VERSION = "3.0.1" ###################################### diff --git a/setup.py b/setup.py index a83bf6daf2..fbcac71bc0 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name='doaj', - version='6.3.16', + version='6.3.17', packages=find_packages(), install_requires=[ "awscli==1.20.50",