From 8d64b155d1bbb1d8e28d28878d29cf05574b58b9 Mon Sep 17 00:00:00 2001 From: Alice Butcher Date: Fri, 17 Feb 2023 11:19:15 +0000 Subject: [PATCH 1/3] chore: small fixes to align with other extensions --- .pre-commit-config.yaml | 7 +++++++ docs/_scripts/gen_api_pages.py | 7 ++++++- mkdocs.yml | 2 ++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ad689f8..2d3b017 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,3 +1,4 @@ +exclude: /(vendor|dist)/ repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.3.0 @@ -25,3 +26,9 @@ repos: # these can't be pulled directly from the config atm, not sure why args: ["-i", "--wrap-summaries=88", "--wrap-descriptions=88", "--pre-summary-newline", "--make-summary-multi-line"] + - repo: https://github.com/pre-commit/mirrors-prettier + rev: v3.0.0-alpha.4 + hooks: + - id: prettier + types_or: [ javascript, vue, less, sass, scss, css ] + args: [ '--single-quote' ] diff --git a/docs/_scripts/gen_api_pages.py b/docs/_scripts/gen_api_pages.py index c1dcbe5..ddb3f7a 100644 --- a/docs/_scripts/gen_api_pages.py +++ b/docs/_scripts/gen_api_pages.py @@ -18,6 +18,11 @@ py_files = sorted(Path(root).rglob('*.py')) for path in py_files: + try: + path.relative_to('ckanext/contact/migration') + except ValueError: + continue + module_path = path.relative_to(root).with_suffix('') doc_path = path.relative_to(root).with_suffix('.md') full_doc_path = Path('API', doc_path) @@ -35,7 +40,7 @@ nav[parts] = doc_path.as_posix() - with mkdocs_gen_files.open(full_doc_path, "w") as fd: + with mkdocs_gen_files.open(full_doc_path, 'w') as fd: ident = '.'.join(parts) fd.write(f'::: ckanext.{ident}') diff --git a/mkdocs.yml b/mkdocs.yml index c3c4f80..2665242 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -37,3 +37,5 @@ markdown_extensions: - admonition - pymdownx.details - pymdownx.superfences + - toc: + permalink: ¶ From 41059f136739574a387973867db3050f887cda31 Mon Sep 17 00:00:00 2001 From: Alice Butcher Date: Fri, 17 Feb 2023 11:23:40 +0000 Subject: [PATCH 2/3] style: reformat with prettier --- .../theme/assets/modules/form-contact.js | 71 ++-- .../theme/assets/modules/modal-contact.js | 315 +++++++++--------- .../theme/assets/scripts/recaptcha_helpers.js | 154 +++++---- 3 files changed, 280 insertions(+), 260 deletions(-) diff --git a/ckanext/contact/theme/assets/modules/form-contact.js b/ckanext/contact/theme/assets/modules/form-contact.js index fb8e470..fcc8f93 100644 --- a/ckanext/contact/theme/assets/modules/form-contact.js +++ b/ckanext/contact/theme/assets/modules/form-contact.js @@ -7,42 +7,45 @@ *
...
* */ -ckan.module('form-contact', function($, _) { - let self = null; +ckan.module('form-contact', function ($, _) { + let self = null; - return { - /** - * Initialisation function for this module, just sets up some variables and sets up the - * event listeners once the recaptcha library is ready. - */ - initialize: function() { - self = this; + return { + /** + * Initialisation function for this module, just sets up some variables and sets up the + * event listeners once the recaptcha library is ready. + */ + initialize: function () { + self = this; - // setup the recaptcha context - self.context = window.contacts_recaptcha.load(self.options.key, self.options.action); - if (self.context) { - // add a callback on the form's submission if the context is created, if it isn't we - // don't need to hijack the submit functionality on the form and can just let it go - // through as normal - self.el.on('submit', self.onSubmit); - } - }, + // setup the recaptcha context + self.context = window.contacts_recaptcha.load( + self.options.key, + self.options.action, + ); + if (self.context) { + // add a callback on the form's submission if the context is created, if it isn't we + // don't need to hijack the submit functionality on the form and can just let it go + // through as normal + self.el.on('submit', self.onSubmit); + } + }, - /** - * Called when the form is submitted. Note that this callback is only attached if a - * recaptcha context is created, otherwise the default form submit function is used. - */ - onSubmit: function(event) { - // stop the form going through - event.preventDefault(); + /** + * Called when the form is submitted. Note that this callback is only attached if a + * recaptcha context is created, otherwise the default form submit function is used. + */ + onSubmit: function (event) { + // stop the form going through + event.preventDefault(); - // add the token to the form and then submit it when ready - self.context.addToken(self.el).then(function(token) { - // remove the listener we added so that we don't create an infinite loop - self.el.off('submit', self.onSubmit); - // submit the form as normal - self.el.submit(); - }); - }, - }; + // add the token to the form and then submit it when ready + self.context.addToken(self.el).then(function (token) { + // remove the listener we added so that we don't create an infinite loop + self.el.off('submit', self.onSubmit); + // submit the form as normal + self.el.submit(); + }); + }, + }; }); diff --git a/ckanext/contact/theme/assets/modules/modal-contact.js b/ckanext/contact/theme/assets/modules/modal-contact.js index d4c596e..d9151b1 100644 --- a/ckanext/contact/theme/assets/modules/modal-contact.js +++ b/ckanext/contact/theme/assets/modules/modal-contact.js @@ -7,163 +7,174 @@ * Contact * */ -ckan.module('modal-contact', function($, _) { - let self; - - return { - /** - * Initialises the module by setting up the recaptcha if necessary and setting up event - * listeners. - */ - initialize: function() { - self = this; - self.modal = null; - self.messages = { - 'onSuccess': _('Thank you for contacting us, and we will try and reply as soon ' + - 'as possible.
Unfortunately due to the number of enquiries the Museum ' + - 'receives, we cannot always reply in person to every one.'), - 'onError': _('Sorry, there was an error sending the email. Please try again later.') - }; - // define the template if it is not passed - self.options.template = self.options.template || 'contact_form.html'; - self.el.on('click', self._onClick); - }, - - /** - * Loads and displays the contact form modal. - */ - show: function() { - self.sandbox.client.getTemplate('contact_form.html', self.options, function(html) { - // initialise the recaptcha context. By doing this here in the show function we - // avoid showing the recaptcha badge on the page before the user has even given an - // indication that they want to contact us which avoids confusion - self.context = window.contacts_recaptcha.load(self.options.key, - self.options.action); - - self.modal = $(html); - // add a close button to the modal - self.modal.find('.modal-header :header') - .append(''); - // hook onto the submit event of the form - self.modal.find('form').submit(function(event) { - event.preventDefault(); - - let form = self.modal.find('form'); - if (self.context) { - self.context.addToken(form).then(function(token) { - self.sendForm(form); - }); - } else { - self.sendForm(form); - } - - // TODO: Add cancel button - }); - - // make sure the popover in the contact form works - if ($.fn.popover !== undefined) { - self.modal.find('[data-target="popover"]').popover(); - } - - self.modal.modal().appendTo(self.sandbox.body); - }); - - }, - - /** - * Sends the form's data to the server. - * - * @param form the form element to harvest the data from - */ - sendForm: function(form) { - $.ajax({ - url: '/contact/ajax', - type: 'POST', - data: form.serialize(), - success: function(results) { - if (results.success) { - // it worked, woo! - self.hide(); - self.flash_success(self.i18n(self.messages.onSuccess)) - } else if (!$.isEmptyObject(results.errors)) { - // there were errors in the inputs from the user, likely missing values - self.processFormError(form, results.errors) - } else if (!!results.recaptcha_error) { - // the recaptcha failed - self.hide(); - self.flash_error(results.recaptcha_error); - } else { - // if we get here then something went wrong server side, probably when - // sending the email - self.hide(); - self.flash_error(self.i18n(self.messages.onError)); - } - } - }); - }, - - /** - * Process errors returned from form submission process. - */ - processFormError: function(form, errors) { - // remove all errors & classes - form.find('.error-block').remove(); - form.find('.error').removeClass('error'); +ckan.module('modal-contact', function ($, _) { + let self; + + return { + /** + * Initialises the module by setting up the recaptcha if necessary and setting up event + * listeners. + */ + initialize: function () { + self = this; + self.modal = null; + self.messages = { + onSuccess: _( + 'Thank you for contacting us, and we will try and reply as soon ' + + 'as possible.
Unfortunately due to the number of enquiries the Museum ' + + 'receives, we cannot always reply in person to every one.', + ), + onError: _( + 'Sorry, there was an error sending the email. Please try again later.', + ), + }; + // define the template if it is not passed + self.options.template = self.options.template || 'contact_form.html'; + self.el.on('click', self._onClick); + }, + + /** + * Loads and displays the contact form modal. + */ + show: function () { + self.sandbox.client.getTemplate( + 'contact_form.html', + self.options, + function (html) { + // initialise the recaptcha context. By doing this here in the show function we + // avoid showing the recaptcha badge on the page before the user has even given an + // indication that they want to contact us which avoids confusion + self.context = window.contacts_recaptcha.load( + self.options.key, + self.options.action, + ); + + self.modal = $(html); + // add a close button to the modal + self.modal + .find('.modal-header :header') + .append(''); + // hook onto the submit event of the form + self.modal.find('form').submit(function (event) { + event.preventDefault(); - // loop through all the errors, adding the error message and error classes - for (let k in errors) { - let controls = form.find("[name='" + k + "']").parent('.controls'); - controls.append('' + errors[k] + ''); - controls.parent('.control-group').addClass('error'); + let form = self.modal.find('form'); + if (self.context) { + self.context.addToken(form).then(function (token) { + self.sendForm(form); + }); + } else { + self.sendForm(form); } - }, - /** - * Hides the modal. - */ - hide: function() { - if (self.modal) { - self.modal.modal('hide'); - } - }, + // TODO: Add cancel button + }); - /** - * Flash the given message as an error. - * - * @param message the message - */ - flash_error: function(message) { - self.flash(message, 'alert-error') - }, - - /** - * Flash the given message as a success. - * - * @param message the message - */ - flash_success: function(message) { - self.flash(message, 'alert-success') - }, + // make sure the popover in the contact form works + if ($.fn.popover !== undefined) { + self.modal.find('[data-target="popover"]').popover(); + } - /** - * Create a flash and display it. - * - * @param message the flash message - * @param category the type of flash to show, this is used as the css class - */ - flash: function(message, category) { - $('.flash-messages') - .append('
' + message + '
'); + self.modal.modal().appendTo(self.sandbox.body); }, - - /** - * Event handler for clicking on the element. - * - * @private - */ - _onClick: function(event) { - event.preventDefault(); - self.show(); + ); + }, + + /** + * Sends the form's data to the server. + * + * @param form the form element to harvest the data from + */ + sendForm: function (form) { + $.ajax({ + url: '/contact/ajax', + type: 'POST', + data: form.serialize(), + success: function (results) { + if (results.success) { + // it worked, woo! + self.hide(); + self.flash_success(self.i18n(self.messages.onSuccess)); + } else if (!$.isEmptyObject(results.errors)) { + // there were errors in the inputs from the user, likely missing values + self.processFormError(form, results.errors); + } else if (!!results.recaptcha_error) { + // the recaptcha failed + self.hide(); + self.flash_error(results.recaptcha_error); + } else { + // if we get here then something went wrong server side, probably when + // sending the email + self.hide(); + self.flash_error(self.i18n(self.messages.onError)); + } }, - }; + }); + }, + + /** + * Process errors returned from form submission process. + */ + processFormError: function (form, errors) { + // remove all errors & classes + form.find('.error-block').remove(); + form.find('.error').removeClass('error'); + + // loop through all the errors, adding the error message and error classes + for (let k in errors) { + let controls = form.find("[name='" + k + "']").parent('.controls'); + controls.append('' + errors[k] + ''); + controls.parent('.control-group').addClass('error'); + } + }, + + /** + * Hides the modal. + */ + hide: function () { + if (self.modal) { + self.modal.modal('hide'); + } + }, + + /** + * Flash the given message as an error. + * + * @param message the message + */ + flash_error: function (message) { + self.flash(message, 'alert-error'); + }, + + /** + * Flash the given message as a success. + * + * @param message the message + */ + flash_success: function (message) { + self.flash(message, 'alert-success'); + }, + + /** + * Create a flash and display it. + * + * @param message the flash message + * @param category the type of flash to show, this is used as the css class + */ + flash: function (message, category) { + $('.flash-messages').append( + '
' + message + '
', + ); + }, + + /** + * Event handler for clicking on the element. + * + * @private + */ + _onClick: function (event) { + event.preventDefault(); + self.show(); + }, + }; }); diff --git a/ckanext/contact/theme/assets/scripts/recaptcha_helpers.js b/ckanext/contact/theme/assets/scripts/recaptcha_helpers.js index 97b7569..05381f2 100644 --- a/ckanext/contact/theme/assets/scripts/recaptcha_helpers.js +++ b/ckanext/contact/theme/assets/scripts/recaptcha_helpers.js @@ -1,6 +1,7 @@ // define a closure for ckanext-contacts recaptcha functionality, but only if it hasn't been defined -window.contacts_recaptcha = window.contacts_recaptcha || (function() { - +window.contacts_recaptcha = + window.contacts_recaptcha || + (function () { /** * Constructor for a recaptcha context. * @@ -12,77 +13,82 @@ window.contacts_recaptcha = window.contacts_recaptcha || (function() { * @param action the recaptcha action to use * @constructor */ - const RecaptchaContext = function(key, action) { - let self = this; + const RecaptchaContext = function (key, action) { + let self = this; - // setup some basic attributes - self.key = key; - self.action = action; + // setup some basic attributes + self.key = key; + self.action = action; - // couple of promises for the script load and the grecaptcha ready check - self.grecaptcha_load = $.Deferred(); - self.grecaptcha_ready = $.Deferred(); + // couple of promises for the script load and the grecaptcha ready check + self.grecaptcha_load = $.Deferred(); + self.grecaptcha_ready = $.Deferred(); - // only load the grecaptcha script if necessary - if (!window.grecaptcha) { - $.getScript('https://www.google.com/recaptcha/api.js?render=' + key, function() { - self.grecaptcha_load.resolve(); - }); - } else { + // only load the grecaptcha script if necessary + if (!window.grecaptcha) { + $.getScript( + 'https://www.google.com/recaptcha/api.js?render=' + key, + function () { self.grecaptcha_load.resolve(); - } + }, + ); + } else { + self.grecaptcha_load.resolve(); + } - // once the recaptcha script is loaded, resolve on ready - self.grecaptcha_load.then(function() { - grecaptcha.ready(function() { - self.grecaptcha_ready.resolve(); - }); + // once the recaptcha script is loaded, resolve on ready + self.grecaptcha_load.then(function () { + grecaptcha.ready(function () { + self.grecaptcha_ready.resolve(); }); + }); - /** - * Request a recaptcha token from Google and then add it onto the given form element. - * - * @param formElement the form element to add the token hidden input element to - * @returns promise which when resolved provides the token - */ - self.addToken = function(formElement) { - let tokenPromise = $.Deferred(); + /** + * Request a recaptcha token from Google and then add it onto the given form element. + * + * @param formElement the form element to add the token hidden input element to + * @returns promise which when resolved provides the token + */ + self.addToken = function (formElement) { + let tokenPromise = $.Deferred(); - // once the load and ready promises have been resolved, we can start our work - $.when(self.grecaptcha_load, self.grecaptcha_ready).then(function() { - // create a promise for the async call to Google to get a recaptcha token - let recaptchaPromise = grecaptcha.execute(self.key, {action: self.action}); - // add the token to the form when it's ready - recaptchaPromise.then(function(token) { - self.addTokenToForm(formElement, token); - }); - // and resolve the token we've returned to the caller, with the token - recaptchaPromise.then(tokenPromise.resolve); - }); - return tokenPromise; - }; + // once the load and ready promises have been resolved, we can start our work + $.when(self.grecaptcha_load, self.grecaptcha_ready).then(function () { + // create a promise for the async call to Google to get a recaptcha token + let recaptchaPromise = grecaptcha.execute(self.key, { + action: self.action, + }); + // add the token to the form when it's ready + recaptchaPromise.then(function (token) { + self.addTokenToForm(formElement, token); + }); + // and resolve the token we've returned to the caller, with the token + recaptchaPromise.then(tokenPromise.resolve); + }); + return tokenPromise; + }; - /** - * Called when we get the token back from Google and adds it in a hidden element to the - * given form element. If there was already a token on the form then it is replaced, this is - * essential to ensure we don't submit the same token to Google twice as this will be - * rejected. - * - * @param formElement the form element to add the hidden token input to - * @param token the recaptcha token - */ - self.addTokenToForm = function(formElement, token) { - let input = formElement.find('input[name=g-recaptcha-response]'); - if (input.length === 0) { - // create an input element so that we can pass the token in the form submission - input = $(''); - // add the input to the form - formElement.prepend(input); - } - input.attr('value', token); - }; + /** + * Called when we get the token back from Google and adds it in a hidden element to the + * given form element. If there was already a token on the form then it is replaced, this is + * essential to ensure we don't submit the same token to Google twice as this will be + * rejected. + * + * @param formElement the form element to add the hidden token input to + * @param token the recaptcha token + */ + self.addTokenToForm = function (formElement, token) { + let input = formElement.find('input[name=g-recaptcha-response]'); + if (input.length === 0) { + // create an input element so that we can pass the token in the form submission + input = $(''); + // add the input to the form + formElement.prepend(input); + } + input.attr('value', token); + }; - return self; + return self; }; /** @@ -97,8 +103,8 @@ window.contacts_recaptcha = window.contacts_recaptcha || (function() { * @param action the action value * @returns {boolean} */ - const isRecaptchaEnabled = function(key, action) { - return key && action && key !== 'None' && action !== 'None'; + const isRecaptchaEnabled = function (key, action) { + return key && action && key !== 'None' && action !== 'None'; }; /** @@ -108,17 +114,17 @@ window.contacts_recaptcha = window.contacts_recaptcha || (function() { * @param action * @returns {RecaptchaContext|boolean} */ - const load = function(key, action) { - if (isRecaptchaEnabled(key, action)) { - return new RecaptchaContext(key, action); - } else { - return false; - } + const load = function (key, action) { + if (isRecaptchaEnabled(key, action)) { + return new RecaptchaContext(key, action); + } else { + return false; + } }; // return the module interface return { - load: load, - isRecaptchaEnabled: isRecaptchaEnabled + load: load, + isRecaptchaEnabled: isRecaptchaEnabled, }; -}()); + })(); From 9c23cb1e651fd84de126b0936928b6f5e3838b31 Mon Sep 17 00:00:00 2001 From: Alice Butcher Date: Fri, 17 Feb 2023 13:54:48 +0000 Subject: [PATCH 3/3] docs: fix api docs generation script --- docs/_scripts/gen_api_pages.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/_scripts/gen_api_pages.py b/docs/_scripts/gen_api_pages.py index ddb3f7a..ad1e41a 100644 --- a/docs/_scripts/gen_api_pages.py +++ b/docs/_scripts/gen_api_pages.py @@ -20,8 +20,9 @@ for path in py_files: try: path.relative_to('ckanext/contact/migration') - except ValueError: continue + except ValueError: + pass module_path = path.relative_to(root).with_suffix('') doc_path = path.relative_to(root).with_suffix('.md')