diff options
-rw-r--r-- | perllib/FixMyStreet/App/Controller/Report.pm | 8 | ||||
-rw-r--r-- | perllib/FixMyStreet/App/Controller/Root.pm | 1 | ||||
-rwxr-xr-x | templates/web/base/errors/generic.html | 4 | ||||
-rw-r--r-- | web/cobrands/fixmystreet/offline.js | 125 |
4 files changed, 133 insertions, 5 deletions
diff --git a/perllib/FixMyStreet/App/Controller/Report.pm b/perllib/FixMyStreet/App/Controller/Report.pm index f7ccddc70..481bc4ab8 100644 --- a/perllib/FixMyStreet/App/Controller/Report.pm +++ b/perllib/FixMyStreet/App/Controller/Report.pm @@ -385,10 +385,14 @@ sub inspect : Private { $problem->lastupdate( \'current_timestamp' ); $problem->update; if ( defined($update_text) ) { + my $timestamp = \'current_timestamp'; + if (my $saved_at = $c->get_param('saved_at')) { + $timestamp = DateTime->from_epoch( epoch => $saved_at ); + } $problem->add_to_comments( { text => $update_text, - created => \'current_timestamp', - confirmed => \'current_timestamp', + created => $timestamp, + confirmed => $timestamp, user_id => $c->user->id, name => $c->user->from_body->name, state => 'confirmed', diff --git a/perllib/FixMyStreet/App/Controller/Root.pm b/perllib/FixMyStreet/App/Controller/Root.pm index 1df249999..20a871b17 100644 --- a/perllib/FixMyStreet/App/Controller/Root.pm +++ b/perllib/FixMyStreet/App/Controller/Root.pm @@ -108,6 +108,7 @@ sub page_error_403_access_denied : Private { sub page_error_400_bad_request : Private { my ( $self, $c, $error_msg ) = @_; + $c->forward('/auth/get_csrf_token'); $c->detach('page_error', [ $error_msg, 400 ]); } diff --git a/templates/web/base/errors/generic.html b/templates/web/base/errors/generic.html index d0d1e2e00..241b310de 100755 --- a/templates/web/base/errors/generic.html +++ b/templates/web/base/errors/generic.html @@ -1,5 +1,9 @@ [% INCLUDE 'header.html', bodyclass = 'fullwidthpage', title = loc('Error') %] +[% IF csrf_token ~%] +<input type="hidden" name="token" value="[% csrf_token %]"> +[% END ~%] + <div class="confirmation-header confirmation-header--failure"> <h1>[% loc('Error') %]</h1> <p>[% message %]</p> diff --git a/web/cobrands/fixmystreet/offline.js b/web/cobrands/fixmystreet/offline.js index 23db5d472..c875c9b84 100644 --- a/web/cobrands/fixmystreet/offline.js +++ b/web/cobrands/fixmystreet/offline.js @@ -2,13 +2,80 @@ fixmystreet.offlineBanner = (function() { var toCache = 0; var cachedSoFar = 0; + function formText() { + var num = fixmystreet.offlineData.getForms().length; + return num + ' form' + (num===1 ? '' : 's'); + } + + function onlineText() { + return 'You have <a id="oFN" href=""><span>' + formText() + '</span> saved to submit</a>.'; + } + + function offlineText() { + return 'You are offline \u2013 <span>' + formText() + '</span> saved.'; + } + return { make: function(offline) { - var banner = ['<div class="top_banner top_banner--offline"><p><span id="offline_saving">']; + var num = fixmystreet.offlineData.getForms().length; + var banner = ['<div class="top_banner top_banner--offline"><p><span id="offline_saving"></span> <span id="offline_forms">']; + if (offline || num > 0) { + banner.push(offline ? offlineText() : onlineText()); + } banner.push('</span></p></div>'); banner = $(banner.join('')); banner.prependTo('.content'); - banner.hide(); + if (!offline && num === 0) { + banner.hide(); + } + + window.addEventListener("offline", function(e) { + $('.top_banner--offline').slideDown(); + $('#offline_forms').html(offlineText()); + }); + + window.addEventListener("online", function(e) { + $('#offline_forms').html(onlineText()); + }); + + function nextForm(DataOrJqXHR, textStatus, jqXHROrErrorThrown) { + fixmystreet.offlineData.shiftForm(); + $(document).dequeue('postForm'); + } + + function postForm(url, data) { + return $.ajax({ url: url, data: data, type: 'POST' }).done(nextForm); + } + + $(document).on('click', '#oFN', function(e) { + e.preventDefault(); + fixmystreet.offlineData.getForms().forEach(function(form) { + $(document).queue('postForm', function() { + postForm(form[0], form[1]).fail(function(jqXHR) { + if (jqXHR.status !== 400) { + return nextForm(); + } + // In case the request failed due to out-of-date CSRF token, + // try once more with a new token given in the error response. + var m = jqXHR.responseText.match(/name="token" value="([^"]*)"/); + if (!m) { + return nextForm(); + } + var token = m[1]; + if (!token) { + return nextForm(); + } + var param = form[1].replace(/&token=[^&]*/, '&token=' + token); + return postForm(form[0], param).fail(nextForm); + }); + }); + }); + $(document).dequeue('postForm'); + }); + }, + update: function() { + $('.top_banner--offline').slideDown(); + $('#offline_forms span').text(formText()); }, startProgress: function(l) { $('.top_banner--offline').slideDown(); @@ -33,7 +100,7 @@ fixmystreet.offlineData = (function() { if (data === undefined) { data = JSON.parse(localStorage.getItem('offlineData')); if (!data) { - data = { cachedReports: {} }; + data = { cachedReports: {}, forms: [] }; } } return data; @@ -44,6 +111,22 @@ fixmystreet.offlineData = (function() { } return { + getForms: function() { + return getData().forms; + }, + addForm: function(action, formData) { + var forms = getData().forms; + if (!forms.length || formData != forms[forms.length - 1][1]) { + forms.push([action, formData]); + saveData(); + } + fixmystreet.offlineBanner.update(); + }, + shiftForm: function(idx) { + getData().forms.shift(); + saveData(); + fixmystreet.offlineBanner.update(); + }, getCachedUrls: function() { return Object.keys(getData().cachedReports); }, @@ -216,6 +299,29 @@ fixmystreet.offline = (function() { $('.js-back-to-report-list').attr('href', '/my/planned'); + // Refill form with saved data if there is any + var savedForm; + fixmystreet.offlineData.getForms().forEach(function(form) { + if (form[0].endsWith(url)) { + savedForm = form[1]; + } + }); + if (savedForm) { + savedForm.replace(/\+/g, '%20').split('&').forEach(function(kv) { + kv = kv.split('=', 2); + if (kv[0] != 'save_inspected' && kv[0] != 'public_update' && kv[0] != 'save') { + $('[name=' + kv[0] + ']').val(decodeURIComponent(kv[1])); + } + }); + } + + $('#report_inspect_form').submit(function() { + var data = $(this).serialize() + '&save=1&saved_at=' + Math.floor(+new Date() / 1000); + fixmystreet.offlineData.addForm(this.action, data); + location.href = '/my/planned?saved=1'; + return false; + }); + return true; } @@ -250,7 +356,20 @@ if ($('#offline_list').length) { if (html) { $('#offline_list').before('<h2>Your offline reports</h2>'); $('#offline_list').html(html); + if (location.search.indexOf('saved=1') > 0) { + $('#offline_list').before('<p class="form-success">Your form has been saved offline for submission when back online.</p>'); + } fixmystreet.offline.replaceImages('#offline_list img'); + var offlineForms = fixmystreet.offlineData.getForms(); + var savedForms = {}; + offlineForms.forEach(function(form) { + savedForms[form[0]] = 1; + }); + $('#offline_list a').each(function(i, a) { + if (savedForms[a.href]) { + $(this).find('h3').prepend('<em>Form data saved</em> '); + } + }); } } fixmystreet.offlineBanner.make(true); |