aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--perllib/FixMyStreet/App/Controller/Auth.pm13
-rw-r--r--perllib/FixMyStreet/App/Controller/Dashboard.pm4
-rw-r--r--perllib/FixMyStreet/App/Controller/Offline.pm32
-rwxr-xr-xperllib/FixMyStreet/App/Controller/Questionnaire.pm9
-rw-r--r--perllib/FixMyStreet/App/Controller/Report.pm8
-rw-r--r--perllib/FixMyStreet/App/Controller/Reports.pm7
-rw-r--r--perllib/FixMyStreet/App/Controller/Root.pm20
-rw-r--r--perllib/FixMyStreet/App/Controller/Tokens.pm1
-rw-r--r--perllib/FixMyStreet/App/View/Web.pm7
-rw-r--r--perllib/FixMyStreet/Cobrand/Base.pm14
-rw-r--r--t/app/controller/moderate.t3
-rw-r--r--t/app/controller/questionnaire.t6
-rw-r--r--t/app/controller/report_inspect.t4
-rw-r--r--t/app/controller/report_updates.t6
-rw-r--r--templates/web/angus/maps/fms.html22
-rw-r--r--templates/web/base/common_footer_tags.html31
-rw-r--r--templates/web/base/common_scripts.html44
-rwxr-xr-xtemplates/web/base/errors/generic.html4
-rw-r--r--templates/web/base/front/javascript.html19
-rw-r--r--templates/web/base/header.html19
-rw-r--r--templates/web/base/index.html2
-rw-r--r--templates/web/base/maps/bing.html16
-rw-r--r--templates/web/base/maps/fms.html18
-rw-r--r--templates/web/base/maps/google-ol.html14
-rw-r--r--templates/web/base/maps/google.html10
-rw-r--r--templates/web/base/maps/mapquest-attribution.html17
-rw-r--r--templates/web/base/maps/osm-streetview.html17
-rw-r--r--templates/web/base/maps/osm-toner-lite.html19
-rw-r--r--templates/web/base/maps/osm.html17
-rw-r--r--templates/web/base/offline/appcache.html12
-rw-r--r--templates/web/base/offline/manifest.html17
-rw-r--r--templates/web/base/report/_inspect.html3
-rw-r--r--templates/web/base/report/_item.html3
-rw-r--r--templates/web/base/report/_main.html2
-rw-r--r--templates/web/base/report/photo-js.html6
-rwxr-xr-xtemplates/web/base/reports/index.html6
-rw-r--r--templates/web/bristol/footer_extra_js.html10
-rw-r--r--templates/web/bristol/maps/bristol.html14
-rw-r--r--templates/web/bromley/footer_extra_js.html4
-rw-r--r--templates/web/bromley/maps/bromley.html19
-rw-r--r--templates/web/fixmystreet-uk-councils/footer_extra_js.html6
-rw-r--r--templates/web/fixmystreet.com/about/posters.html6
-rw-r--r--templates/web/fixmystreet.com/footer_extra_js.html8
-rw-r--r--templates/web/fixmystreet.com/front/javascript.html19
-rw-r--r--templates/web/fixmystreet.com/header.html45
-rw-r--r--templates/web/oxfordshire/header.html5
-rw-r--r--templates/web/zurich/maps/zurich.html16
-rw-r--r--web/cobrands/angus/js.js8
-rw-r--r--web/cobrands/bristol/js.js4
-rw-r--r--web/cobrands/fixmystreet.com/base.scss35
-rw-r--r--web/cobrands/fixmystreet/fixmystreet.js3
-rw-r--r--web/cobrands/fixmystreet/offline.js409
-rw-r--r--web/cobrands/oxfordshire/base.scss1
-rw-r--r--web/cobrands/sass/_top-banner.scss49
54 files changed, 876 insertions, 237 deletions
diff --git a/perllib/FixMyStreet/App/Controller/Auth.pm b/perllib/FixMyStreet/App/Controller/Auth.pm
index 40cd163cf..c448f8749 100644
--- a/perllib/FixMyStreet/App/Controller/Auth.pm
+++ b/perllib/FixMyStreet/App/Controller/Auth.pm
@@ -271,9 +271,8 @@ sub facebook_callback: Path('/auth/Facebook') : Args(0) {
$access_token = $fb->get_access_token(code => $c->get_param('code'));
};
if ($@) {
- ($c->stash->{message} = $@) =~ s/at [^ ]*Auth.pm.*//;
- $c->stash->{template} = 'errors/generic.html';
- $c->detach;
+ (my $message = $@) =~ s/at [^ ]*Auth.pm.*//;
+ $c->detach('/page_error_500_internal_error', [ $message ]);
}
# save this token in session
@@ -339,9 +338,8 @@ sub twitter_callback: Path('/auth/Twitter') : Args(0) {
$twitter->request_access_token(verifier => $verifier);
};
if ($@) {
- ($c->stash->{message} = $@) =~ s/at [^ ]*Auth.pm.*//;
- $c->stash->{template} = 'errors/generic.html';
- $c->detach;
+ (my $message = $@) =~ s/at [^ ]*Auth.pm.*//;
+ $c->detach('/page_error_500_internal_error', [ $message ]);
}
my $info = $twitter->verify_credentials();
@@ -527,8 +525,7 @@ sub check_csrf_token : Private {
sub no_csrf_token : Private {
my ($self, $c) = @_;
- $c->stash->{message} = _('Unknown error');
- $c->stash->{template} = 'errors/generic.html';
+ $c->detach('/page_error_400_bad_request', []);
}
=head2 sign_out
diff --git a/perllib/FixMyStreet/App/Controller/Dashboard.pm b/perllib/FixMyStreet/App/Controller/Dashboard.pm
index 9189b28e5..fbe5a2dc9 100644
--- a/perllib/FixMyStreet/App/Controller/Dashboard.pm
+++ b/perllib/FixMyStreet/App/Controller/Dashboard.pm
@@ -57,9 +57,9 @@ sub example : Local : Args(0) {
}
};
if ($@) {
- $c->stash->{message} = _("There was a problem showing this page. Please try again later.") . ' ' .
+ my $message = _("There was a problem showing this page. Please try again later.") . ' ' .
sprintf(_('The error was: %s'), $@);
- $c->stash->{template} = 'errors/generic.html';
+ $c->detach('/page_error_500_internal_error', [ $message ]);
}
}
diff --git a/perllib/FixMyStreet/App/Controller/Offline.pm b/perllib/FixMyStreet/App/Controller/Offline.pm
new file mode 100644
index 000000000..9acb33f7e
--- /dev/null
+++ b/perllib/FixMyStreet/App/Controller/Offline.pm
@@ -0,0 +1,32 @@
+package FixMyStreet::App::Controller::Offline;
+use Moose;
+use namespace::autoclean;
+
+BEGIN { extends 'Catalyst::Controller'; }
+
+=head1 NAME
+
+FixMyStreet::App::Controller::Offline - Catalyst Controller
+
+=head1 DESCRIPTION
+
+Offline pages Catalyst Controller.
+
+=head1 METHODS
+
+=cut
+
+sub manifest : Path("/offline/appcache.manifest") {
+ my ($self, $c) = @_;
+ $c->res->content_type('text/cache-manifest; charset=utf-8');
+ $c->res->header(Cache_Control => 'no-cache, no-store');
+}
+
+sub appcache : Path("/offline/appcache") {
+ my ($self, $c) = @_;
+ $c->detach('/page_error_404_not_found', []) if keys %{$c->req->params};
+}
+
+__PACKAGE__->meta->make_immutable;
+
+1;
diff --git a/perllib/FixMyStreet/App/Controller/Questionnaire.pm b/perllib/FixMyStreet/App/Controller/Questionnaire.pm
index 017a552db..1b338732b 100755
--- a/perllib/FixMyStreet/App/Controller/Questionnaire.pm
+++ b/perllib/FixMyStreet/App/Controller/Questionnaire.pm
@@ -36,9 +36,8 @@ sub check_questionnaire : Private {
if ( $questionnaire->whenanswered ) {
my $problem_url = $c->cobrand->base_url_for_report( $problem ) . $problem->url;
my $contact_url = $c->uri_for( "/contact" );
- $c->stash->{message} = sprintf(_("You have already answered this questionnaire. If you have a question, please <a href='%s'>get in touch</a>, or <a href='%s'>view your problem</a>.\n"), $contact_url, $problem_url);
- $c->stash->{template} = 'errors/generic.html';
- $c->detach;
+ my $message = sprintf(_("You have already answered this questionnaire. If you have a question, please <a href='%s'>get in touch</a>, or <a href='%s'>view your problem</a>.\n"), $contact_url, $problem_url);
+ $c->detach('/page_error_400_bad_request', [ $message ]);
}
unless ( $problem->is_visible ) {
@@ -86,8 +85,8 @@ Display couldn't locate problem error message
sub missing_problem : Private {
my ( $self, $c ) = @_;
- $c->stash->{message} = _("I'm afraid we couldn't locate your problem in the database.\n");
- $c->stash->{template} = 'errors/generic.html';
+ my $message = _("I'm afraid we couldn't locate your problem in the database.\n");
+ $c->detach('/page_error_400_bad_request', [ $message ]);
}
sub submit_creator_fixed : Private {
diff --git a/perllib/FixMyStreet/App/Controller/Report.pm b/perllib/FixMyStreet/App/Controller/Report.pm
index 5a1cfbe54..3c251a5cb 100644
--- a/perllib/FixMyStreet/App/Controller/Report.pm
+++ b/perllib/FixMyStreet/App/Controller/Report.pm
@@ -408,10 +408,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/Reports.pm b/perllib/FixMyStreet/App/Controller/Reports.pm
index 813c2052d..f2c43b5ee 100644
--- a/perllib/FixMyStreet/App/Controller/Reports.pm
+++ b/perllib/FixMyStreet/App/Controller/Reports.pm
@@ -76,13 +76,12 @@ sub index : Path : Args(0) {
$c->stash->{open} = $j->{open};
};
if ($@) {
- $c->stash->{message} = _("There was a problem showing the All Reports page. Please try again later.");
+ my $message = _("There was a problem showing the All Reports page. Please try again later.");
if ($c->config->{STAGING_SITE}) {
- $c->stash->{message} .= '</p><p>Perhaps the bin/update-all-reports script needs running. Use: bin/update-all-reports</p><p>'
+ $message .= '</p><p>Perhaps the bin/update-all-reports script needs running. Use: bin/update-all-reports</p><p>'
. sprintf(_('The error was: %s'), $@);
}
- $c->stash->{template} = 'errors/generic.html';
- return;
+ $c->detach('/page_error_500_internal_error', [ $message ]);
}
# Down here so that error pages aren't cached.
diff --git a/perllib/FixMyStreet/App/Controller/Root.pm b/perllib/FixMyStreet/App/Controller/Root.pm
index 3d4c6a1ba..20a871b17 100644
--- a/perllib/FixMyStreet/App/Controller/Root.pm
+++ b/perllib/FixMyStreet/App/Controller/Root.pm
@@ -103,9 +103,25 @@ sub page_error_410_gone : Private {
sub page_error_403_access_denied : Private {
my ( $self, $c, $error_msg ) = @_;
+ $c->detach('page_error', [ $error_msg || _("Sorry, you don't have permission to do that."), 403 ]);
+}
+
+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 ]);
+}
+
+sub page_error_500_internal_error : Private {
+ my ( $self, $c, $error_msg ) = @_;
+ $c->detach('page_error', [ $error_msg, 500 ]);
+}
+
+sub page_error : Private {
+ my ($self, $c, $error_msg, $code) = @_;
$c->stash->{template} = 'errors/generic.html';
- $c->stash->{message} = $error_msg || _("Sorry, you don't have permission to do that.");
- $c->response->status(403);
+ $c->stash->{message} = $error_msg || _('Unknown error');
+ $c->response->status($code);
}
=head2 end
diff --git a/perllib/FixMyStreet/App/Controller/Tokens.pm b/perllib/FixMyStreet/App/Controller/Tokens.pm
index da017c57f..a1b0c57ba 100644
--- a/perllib/FixMyStreet/App/Controller/Tokens.pm
+++ b/perllib/FixMyStreet/App/Controller/Tokens.pm
@@ -348,6 +348,7 @@ sub token_too_old : Private {
my ( $self, $c ) = @_;
$c->stash->{token_not_found} = 1;
$c->stash->{template} = 'auth/token.html';
+ $c->response->status(400);
}
__PACKAGE__->meta->make_immutable;
diff --git a/perllib/FixMyStreet/App/View/Web.pm b/perllib/FixMyStreet/App/View/Web.pm
index f0bcad0be..b73fa82ef 100644
--- a/perllib/FixMyStreet/App/View/Web.pm
+++ b/perllib/FixMyStreet/App/View/Web.pm
@@ -140,16 +140,17 @@ sub escape_js {
my %version_hash;
sub version {
- my ( $self, $c, $file ) = @_;
+ my ( $self, $c, $file, $url ) = @_;
+ $url ||= $file;
_version_get_mtime($file);
if ($version_hash{$file} && $file =~ /\.js$/) {
# See if there's an auto.min.js version and use that instead if there is
(my $file_min = $file) =~ s/\.js$/.auto.min.js/;
_version_get_mtime($file_min);
- $file = $file_min if $version_hash{$file_min} >= $version_hash{$file};
+ $url = $file = $file_min if $version_hash{$file_min} >= $version_hash{$file};
}
my $admin = $self->template->context->stash->{admin} ? FixMyStreet->config('ADMIN_BASE_URL') : '';
- return "$admin$file?$version_hash{$file}";
+ return "$admin$url?$version_hash{$file}";
}
sub _version_get_mtime {
diff --git a/perllib/FixMyStreet/Cobrand/Base.pm b/perllib/FixMyStreet/Cobrand/Base.pm
index 5a9842233..a9eed0018 100644
--- a/perllib/FixMyStreet/Cobrand/Base.pm
+++ b/perllib/FixMyStreet/Cobrand/Base.pm
@@ -38,6 +38,20 @@ sub moniker {
return $last_part;
}
+=head2 asset_moniker
+
+ $moniker = $cobrand_class->asset_moniker();
+
+Same as moniker, except for the cobrand with the 'fixmystreet' moniker, when it
+returns 'fixmystreet.com', as to avoid confusion that's where its assets are.
+
+=cut
+
+sub asset_moniker {
+ my $self = shift;
+ return $self->moniker eq 'fixmystreet' ? 'fixmystreet.com' : $self->moniker;
+}
+
=head2 is_default
$bool = $cobrand->is_default();
diff --git a/t/app/controller/moderate.t b/t/app/controller/moderate.t
index 0ccfcf2c2..3a3c7a708 100644
--- a/t/app/controller/moderate.t
+++ b/t/app/controller/moderate.t
@@ -67,7 +67,8 @@ subtest 'Auth' => sub {
$mech->get_ok($REPORT_URL);
$mech->content_lacks('Moderat');
- $mech->get_ok('/contact?m=1&id=' . $report->id);
+ $mech->get('/contact?m=1&id=' . $report->id);
+ is $mech->res->code, 400;
$mech->content_lacks('Good bad bad bad');
};
diff --git a/t/app/controller/questionnaire.t b/t/app/controller/questionnaire.t
index b05f74225..f42908a3e 100644
--- a/t/app/controller/questionnaire.t
+++ b/t/app/controller/questionnaire.t
@@ -87,16 +87,19 @@ foreach my $test (
desc => 'User goes to questionnaire URL with a bad token',
token_extra => 'BAD',
content => "Sorry, that wasn&rsquo;t a valid link",
+ code => 400,
},
{
desc => 'User goes to questionnaire URL for a now-hidden problem',
state => 'hidden',
content => "we couldn't locate your problem",
+ code => 400,
},
{
desc => 'User goes to questionnaire URL for an already answered questionnaire',
answered => \'current_timestamp',
content => 'already answered this questionnaire',
+ code => 400,
},
) {
subtest $test->{desc} => sub {
@@ -106,7 +109,8 @@ foreach my $test (
$questionnaire->update;
(my $token = $token->token);
$token .= $test->{token_extra} if $test->{token_extra};
- $mech->get_ok("/Q/$token");
+ $mech->get("/Q/$token");
+ is $mech->res->code, $test->{code}, "Right status received";
$mech->content_contains( $test->{content} );
# Reset, no matter what test did
$report->state( 'confirmed' );
diff --git a/t/app/controller/report_inspect.t b/t/app/controller/report_inspect.t
index 4697cc9d1..1f11829b7 100644
--- a/t/app/controller/report_inspect.t
+++ b/t/app/controller/report_inspect.t
@@ -57,7 +57,7 @@ FixMyStreet::override_config {
};
subtest "test basic inspect submission" => sub {
- $mech->submit_form_ok({ button => 'save', with_fields => { traffic_information => 'Yes', state => 'Planned' } });
+ $mech->submit_form_ok({ button => 'save', with_fields => { traffic_information => 'Yes', state => 'Planned', save_inspected => undef } });
$report->discard_changes;
is $report->state, 'planned', 'report state changed';
is $report->get_extra_metadata('traffic_information'), 'Yes', 'report data changed';
@@ -201,7 +201,7 @@ FixMyStreet::override_config {
# which should cause it to be resent. We clear the host because
# otherwise testing stays on host() above.
$mech->clear_host;
- $mech->submit_form(button => 'save', with_fields => { category => 'Horses' });
+ $mech->submit_form(button => 'save', with_fields => { category => 'Horses', save_inspected => undef, });
$report->discard_changes;
is $report->category, "Horses", "Report in correct category";
diff --git a/t/app/controller/report_updates.t b/t/app/controller/report_updates.t
index 5a88097fa..f7544f0a1 100644
--- a/t/app/controller/report_updates.t
+++ b/t/app/controller/report_updates.t
@@ -1829,7 +1829,8 @@ for my $test (
subtest 'check have to be logged in for creator fixed questionnaire' => sub {
$mech->log_out_ok();
- $mech->get_ok( "/questionnaire/submit?problem=$report_id&reported=Yes" );
+ $mech->get( "/questionnaire/submit?problem=$report_id&reported=Yes" );
+ is $mech->res->code, 400, "got 400";
$mech->content_contains( "I'm afraid we couldn't locate your problem in the database." )
};
@@ -1838,7 +1839,8 @@ subtest 'check cannot answer other user\'s creator fixed questionnaire' => sub {
$mech->log_out_ok();
$mech->log_in_ok( $user2->email );
- $mech->get_ok( "/questionnaire/submit?problem=$report_id&reported=Yes" );
+ $mech->get( "/questionnaire/submit?problem=$report_id&reported=Yes" );
+ is $mech->res->code, 400, "got 400";
$mech->content_contains( "I'm afraid we couldn't locate your problem in the database." )
};
diff --git a/templates/web/angus/maps/fms.html b/templates/web/angus/maps/fms.html
index aed4d1764..1516ae05e 100644
--- a/templates/web/angus/maps/fms.html
+++ b/templates/web/angus/maps/fms.html
@@ -1,11 +1,11 @@
-[% map_js = BLOCK %]
-<!-- <script type="text/javascript" src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0&mkt=en-GB"></script> -->
-<script type="text/javascript" src="[% version('/js/OpenLayers/OpenLayers.angus.js') %]"></script>
-<script type="text/javascript" src="[% version('/js/map-OpenLayers.js') %]"></script>
-<script type="text/javascript" src="[% version('/js/map-bing-ol.js') %]"></script>
-<script type="text/javascript" src="[% version('/js/map-fms.js') %]"></script>
-<script src="[% version('/cobrands/fixmystreet/assets.js') %]"></script>
-<script src="[% version('/cobrands/angus/js.js') %]"></script>
-[% END %]
-
-[% map_html = INCLUDE maps/openlayers.html include_key = 1 %]
+[%
+map_js = [
+ version('/js/OpenLayers/OpenLayers.angus.js'),
+ version('/js/map-OpenLayers.js'),
+ version('/js/map-bing-ol.js'),
+ version('/js/map-fms.js'),
+ version('/cobrands/fixmystreet/assets.js'),
+ version('/cobrands/angus/js.js'),
+];
+map_html = INCLUDE maps/openlayers.html include_key = 1
+%]
diff --git a/templates/web/base/common_footer_tags.html b/templates/web/base/common_footer_tags.html
index 45872895b..01420c37d 100644
--- a/templates/web/base/common_footer_tags.html
+++ b/templates/web/base/common_footer_tags.html
@@ -1,28 +1,13 @@
-[% USE date %][% USE Math %]
-
[% TRY %][% PROCESS 'footer_extra.html' %][% CATCH file %][% END %]
-<script type="text/javascript" src="[% start %]/js/translation_strings.[% lang_code %].js?[% Math.int( date.now / 3600 ) %]"></script>
-<script type="text/javascript" src="[% version('/jslib/jquery-1.7.2.min.js') %]"></script>
+[% PROCESS 'common_scripts.html' %]
+
<!--[if lte IE 9]>
- <script type="text/javascript" src="[% version('/js/history.polyfill.min.js') %]"></script>
+ <script src="[% version('/js/history.polyfill.min.js') %]"></script>
<![endif]-->
-<script type="text/javascript" src="[% version('/js/validation_rules.js') %]"></script>
-<script src="[% version('/js/jquery.validate.min.js') %]" type="text/javascript" charset="utf-8"></script>
-<script type="text/javascript" src="[% version('/js/dropzone.min.js') %]"></script>
-<script type="text/javascript" src="[% version('/js/jquery.multi-select.js') %]"></script>
-
-<script type="text/javascript" src="[% version('/js/geo.min.js') %]"></script>
-<script type="text/javascript" src="[% version('/cobrands/fixmystreet/fixmystreet.js') %]"></script>
-
-[% map_js %]
-<script src="[% version('/cobrands/fixmystreet/map.js') %]"></script>
-
-[% IF admin %]
- <script src="[% version('/js/jquery-ui/js/jquery-ui-1.10.3.custom.min.js') %]"></script>
- <script type="text/javascript" src="[% version('/js/fixmystreet-admin.js') %]"></script>
+[% FOR script IN scripts ~%]
+ [% script = script.0 ? script : [ script ] ~%]
+ <script src="[% script.0 %]"
+ [%~ FOR attr IN script.1 %] [% attr.key %]="[% attr.value %]"[% END ~%]
+ ></script>
[% END %]
-
-[% extra_js %]
-
-[% TRY %][% PROCESS 'footer_extra_js.html' %][% CATCH file %][% END %]
diff --git a/templates/web/base/common_scripts.html b/templates/web/base/common_scripts.html
new file mode 100644
index 000000000..1d53f1d51
--- /dev/null
+++ b/templates/web/base/common_scripts.html
@@ -0,0 +1,44 @@
+[%
+
+USE date;
+USE Math;
+
+scripts = [];
+
+scripts.push(
+ start _ "/js/translation_strings." _ lang_code _ ".js?" _ Math.int( date.now / 3600 ),
+ version('/jslib/jquery-1.7.2.min.js'),
+ version('/js/validation_rules.js'),
+ version('/js/jquery.validate.min.js'),
+ version('/js/dropzone.min.js'),
+ version('/js/jquery.multi-select.js'),
+ version('/js/geo.min.js'),
+ version('/cobrands/fixmystreet/fixmystreet.js'),
+);
+
+FOR script IN map_js;
+ scripts.push(script);
+END;
+
+scripts.push(
+ version('/cobrands/fixmystreet/map.js'),
+ version('/cobrands/fixmystreet/offline.js'),
+);
+
+IF admin;
+ scripts.push(
+ version('/js/jquery-ui/js/jquery-ui-1.10.3.custom.min.js'),
+ version('/js/fixmystreet-admin.js'),
+ );
+END;
+
+FOR script IN extra_js;
+ scripts.push(script);
+END;
+
+TRY;
+ PROCESS 'footer_extra_js.html';
+CATCH file;
+END;
+
+~%]
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/templates/web/base/front/javascript.html b/templates/web/base/front/javascript.html
index 2795829a5..6b8e2a292 100644
--- a/templates/web/base/front/javascript.html
+++ b/templates/web/base/front/javascript.html
@@ -1,7 +1,12 @@
-[%# Assume using OpenStreetMap maps %]
-<script src="[% version('/js/yepnope.js') %]"></script>
-<script id="script_front" src="[% version('/cobrands/fixmystreet/front.js') %]"
- data-scripts="
- [%~ version('/js/OpenLayers/OpenLayers.fixmystreet.js') %],
- [%~ version('/js/map-OpenLayers.js') %],
- [%~ version('/js/map-OpenStreetMap.js') %]"></script>
+[%
+# Assume using OpenStreetMap maps
+map_js = [
+ version('/js/yepnope.js'),
+ [ version('/cobrands/fixmystreet/front.js'), {
+ id = 'script_front',
+ 'data-scripts' = version('/js/OpenLayers/OpenLayers.fixmystreet.js') _ ',' _
+ version('/js/map-OpenLayers.js') _ ',' _
+ version('/js/map-OpenStreetMap.js')
+ } ],
+]
+%]
diff --git a/templates/web/base/header.html b/templates/web/base/header.html
index c11e78b47..4e537a7ec 100644
--- a/templates/web/base/header.html
+++ b/templates/web/base/header.html
@@ -7,7 +7,10 @@
<!--[if IE 7]> <html class="no-js ie7 iel8"[% html_att %]><![endif]-->
<!--[if IE 8]> <html class="no-js ie8 iel8"[% html_att %]><![endif]-->
<!--[if IE 9]> <html class="no-js ie9"[% html_att %]><![endif]-->
-<!--[if gt IE 9]><!--><html class="no-js"[% html_att %]><!--<![endif]-->
+<!--[if gt IE 9]><!--><html class="no-js"[% html_att %]
+[% IF appcache ~%]
+ manifest="/offline/appcache.manifest"
+[%~ END %]><!--<![endif]-->
<head>
<meta name="viewport" content="initial-scale=1.0">
@@ -16,19 +19,11 @@
<meta name="mobileoptimized" content="0">
[% INCLUDE 'header_opengraph.html' %]
- [%
- # For clarity, the 'fixmystreet' moniker (for fixmystreet.com) puts
- # it stylesheets under fixmystreet.com
- IF c.cobrand.moniker == 'fixmystreet';
- SET css_dir = 'fixmystreet.com';
- ELSE;
- SET css_dir = c.cobrand.moniker;
- END %]
- <link rel="stylesheet" href="[% version('/cobrands/' _ css_dir _ '/base.css') %]">
- <link rel="stylesheet" href="[% version('/cobrands/' _ css_dir _ '/layout.css') %]" media="(min-width:48em)">
+ <link rel="stylesheet" href="[% version('/cobrands/' _ c.cobrand.asset_moniker _ '/base.css') %]">
+ <link rel="stylesheet" href="[% version('/cobrands/' _ c.cobrand.asset_moniker _ '/layout.css') %]" media="(min-width:48em)">
[% extra_css %]
<!--[if (lt IE 9) & (!IEMobile)]>
- <link rel="stylesheet" href="[% version('/cobrands/' _ css_dir _ '/layout.css') %]">
+ <link rel="stylesheet" href="[% version('/cobrands/' _ c.cobrand.asset_moniker _ '/layout.css') %]">
<![endif]-->
[% INCLUDE 'common_header_tags.html' %]
diff --git a/templates/web/base/index.html b/templates/web/base/index.html
index 0441b3efb..8cb127e6a 100644
--- a/templates/web/base/index.html
+++ b/templates/web/base/index.html
@@ -1,4 +1,4 @@
-[% map_js = PROCESS 'front/javascript.html' %]
+[% PROCESS 'front/javascript.html' %]
[% pre_container_extra = PROCESS 'around/postcode_form.html' %]
[% INCLUDE 'header.html', title = '', bodyclass = 'frontpage fullwidthpage' %]
diff --git a/templates/web/base/maps/bing.html b/templates/web/base/maps/bing.html
index 6af4c3562..59d012c4f 100644
--- a/templates/web/base/maps/bing.html
+++ b/templates/web/base/maps/bing.html
@@ -1,8 +1,8 @@
-[% map_js = BLOCK %]
-<!-- <script type="text/javascript" src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0&mkt=en-GB"></script> -->
-<script type="text/javascript" src="[% version('/js/OpenLayers/OpenLayers.fixmystreet.js') %]"></script>
-<script type="text/javascript" src="[% version('/js/map-OpenLayers.js') %]"></script>
-<script type="text/javascript" src="[% version('/js/map-bing-ol.js') %]"></script>
-[% END %]
-
-[% map_html = INCLUDE maps/openlayers.html %]
+[%
+map_js = [
+ version('/js/OpenLayers/OpenLayers.fixmystreet.js'),
+ version('/js/map-OpenLayers.js'),
+ version('/js/map-bing-ol.js'),
+];
+map_html = INCLUDE maps/openlayers.html
+%]
diff --git a/templates/web/base/maps/fms.html b/templates/web/base/maps/fms.html
index 03eb843da..e155ff778 100644
--- a/templates/web/base/maps/fms.html
+++ b/templates/web/base/maps/fms.html
@@ -1,9 +1,9 @@
-[% map_js = BLOCK %]
-<!-- <script type="text/javascript" src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0&mkt=en-GB"></script> -->
-<script type="text/javascript" src="[% version('/js/OpenLayers/OpenLayers.fixmystreet.js') %]"></script>
-<script type="text/javascript" src="[% version('/js/map-OpenLayers.js') %]"></script>
-<script type="text/javascript" src="[% version('/js/map-bing-ol.js') %]"></script>
-<script type="text/javascript" src="[% version('/js/map-fms.js') %]"></script>
-[% END %]
-
-[% map_html = INCLUDE maps/openlayers.html include_key = 1 %]
+[%
+map_js = [
+ version('/js/OpenLayers/OpenLayers.fixmystreet.js'),
+ version('/js/map-OpenLayers.js'),
+ version('/js/map-bing-ol.js'),
+ version('/js/map-fms.js'),
+];
+map_html = INCLUDE maps/openlayers.html include_key = 1
+%]
diff --git a/templates/web/base/maps/google-ol.html b/templates/web/base/maps/google-ol.html
index cccea5b24..e326bd713 100644
--- a/templates/web/base/maps/google-ol.html
+++ b/templates/web/base/maps/google-ol.html
@@ -1,9 +1,11 @@
-[% map_js = BLOCK %]
-<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?v=3"></script>
-<script type="text/javascript" src="[% version('/js/OpenLayers/OpenLayers.google.js') %]"></script>
-<script type="text/javascript" src="[% version('/js/map-OpenLayers.js') %]"></script>
-<script type="text/javascript" src="[% version('/js/map-google-ol.js') %]"></script>
-[% END %]
+[%
+map_js = [
+ "https://maps.googleapis.com/maps/api/js?v=3",
+ version('/js/OpenLayers/OpenLayers.google.js'),
+ version('/js/map-OpenLayers.js'),
+ version('/js/map-google-ol.js'),
+]
+%]
[% map_sub_links = BLOCK %]
<a class="hidden-nojs" id="map_layer_toggle" href="">[% loc('Satellite') %]</a>
diff --git a/templates/web/base/maps/google.html b/templates/web/base/maps/google.html
index eeb4c9837..c86c757fb 100644
--- a/templates/web/base/maps/google.html
+++ b/templates/web/base/maps/google.html
@@ -1,4 +1,3 @@
-[% map_js = BLOCK %]
<style>
#map_box img {
max-width: none;
@@ -7,9 +6,12 @@
color: #000;
}
</style>
-<script type="text/javascript" src="http://maps.googleapis.com/maps/api/js?sensor=false"></script>
-<script type="text/javascript" src="[% version('/js/map-google.js') %]"></script>
-[% END %]
+[%
+map_js = [
+ "http://maps.googleapis.com/maps/api/js?sensor=false",
+ version('/js/map-google.js'),
+]
+%]
[% map_html = BLOCK %]
<script nonce="[% csp_nonce %]">
diff --git a/templates/web/base/maps/mapquest-attribution.html b/templates/web/base/maps/mapquest-attribution.html
index ab4424cdd..e469901a8 100644
--- a/templates/web/base/maps/mapquest-attribution.html
+++ b/templates/web/base/maps/mapquest-attribution.html
@@ -1,9 +1,8 @@
-[% map_js = BLOCK %]
-<script type="text/javascript" src="[% version('/js/OpenLayers/OpenLayers.fixmystreet.js') %]"></script>
-<script type="text/javascript" src="[% version('/js/map-OpenLayers.js') %]"></script>
-<script type="text/javascript" src="[% version('/js/map-OpenStreetMap.js') %]"></script>
-[% END %]
-
-[% map_html = BLOCK %]
-[% INCLUDE maps/openlayers.html %]
-[% END %]
+[%
+map_js = [
+ version('/js/OpenLayers/OpenLayers.fixmystreet.js'),
+ version('/js/map-OpenLayers.js'),
+ version('/js/map-OpenStreetMap.js'),
+];
+map_html = INCLUDE maps/openlayers.html
+%]
diff --git a/templates/web/base/maps/osm-streetview.html b/templates/web/base/maps/osm-streetview.html
index 2ff3b4723..dcf45d3b6 100644
--- a/templates/web/base/maps/osm-streetview.html
+++ b/templates/web/base/maps/osm-streetview.html
@@ -1,9 +1,8 @@
-[% map_js = BLOCK %]
-<script type="text/javascript" src="[% version('/js/OpenLayers/OpenLayers.fixmystreet.js') %]"></script>
-<script type="text/javascript" src="[% version('/js/map-OpenLayers.js') %]"></script>
-<script type="text/javascript" src="[% version('/js/map-streetview.js') %]"></script>
-[% END %]
-
-[% map_html = BLOCK %]
-[% INCLUDE maps/openlayers.html %]
-[% END %]
+[%
+map_js = [
+ version('/js/OpenLayers/OpenLayers.fixmystreet.js'),
+ version('/js/map-OpenLayers.js'),
+ version('/js/map-streetview.js'),
+];
+map_html = INCLUDE maps/openlayers.html
+%]
diff --git a/templates/web/base/maps/osm-toner-lite.html b/templates/web/base/maps/osm-toner-lite.html
index 5e48f7569..6512eaf2c 100644
--- a/templates/web/base/maps/osm-toner-lite.html
+++ b/templates/web/base/maps/osm-toner-lite.html
@@ -1,10 +1,9 @@
-[% map_js = BLOCK %]
-<script type="text/javascript" src="[% version('/js/OpenLayers/OpenLayers.fixmystreet.js') %]"></script>
-<script type="text/javascript" src="https://stamen-maps.a.ssl.fastly.net/js/tile.stamen.js?v1.3.0"></script>
-<script type="text/javascript" src="[% version('/js/map-OpenLayers.js') %]"></script>
-<script type="text/javascript" src="[% version('/js/map-toner-lite.js') %]"></script>
-[% END %]
-
-[% map_html = BLOCK %]
-[% INCLUDE maps/openlayers.html %]
-[% END %]
+[%
+map_js = [
+ version('/js/OpenLayers/OpenLayers.fixmystreet.js'),
+ "https://stamen-maps.a.ssl.fastly.net/js/tile.stamen.js?v1.3.0",
+ version('/js/map-OpenLayers.js'),
+ version('/js/map-toner-lite.js'),
+];
+map_html = INCLUDE maps/openlayers.html
+%]
diff --git a/templates/web/base/maps/osm.html b/templates/web/base/maps/osm.html
index ab4424cdd..e469901a8 100644
--- a/templates/web/base/maps/osm.html
+++ b/templates/web/base/maps/osm.html
@@ -1,9 +1,8 @@
-[% map_js = BLOCK %]
-<script type="text/javascript" src="[% version('/js/OpenLayers/OpenLayers.fixmystreet.js') %]"></script>
-<script type="text/javascript" src="[% version('/js/map-OpenLayers.js') %]"></script>
-<script type="text/javascript" src="[% version('/js/map-OpenStreetMap.js') %]"></script>
-[% END %]
-
-[% map_html = BLOCK %]
-[% INCLUDE maps/openlayers.html %]
-[% END %]
+[%
+map_js = [
+ version('/js/OpenLayers/OpenLayers.fixmystreet.js'),
+ version('/js/map-OpenLayers.js'),
+ version('/js/map-OpenStreetMap.js'),
+];
+map_html = INCLUDE maps/openlayers.html
+%]
diff --git a/templates/web/base/offline/appcache.html b/templates/web/base/offline/appcache.html
new file mode 100644
index 000000000..5a8ba1463
--- /dev/null
+++ b/templates/web/base/offline/appcache.html
@@ -0,0 +1,12 @@
+[% INCLUDE 'header.html' appcache = 1 bodyclass = "fullwidthpage" %]
+
+<h1>Internet glitch</h1>
+
+<p>Sorry, we don’t have a good enough connection to fetch that page, or the
+page wasn’t found or there was a server error. Please try again later.</p>
+
+<ul class="item-list item-list--reports" id="offline_list"></ul>
+
+<div id="offline_clear"></div>
+
+[% INCLUDE 'footer.html' %]
diff --git a/templates/web/base/offline/manifest.html b/templates/web/base/offline/manifest.html
new file mode 100644
index 000000000..f5a9fddcc
--- /dev/null
+++ b/templates/web/base/offline/manifest.html
@@ -0,0 +1,17 @@
+CACHE MANIFEST
+
+[% PROCESS 'common_scripts.html' ~%]
+
+CACHE:
+[% version('/cobrands/' _ c.cobrand.asset_moniker _ '/base.css') %]
+[% version('/cobrands/' _ c.cobrand.asset_moniker _ '/layout.css') %]
+
+[% FOR script IN scripts ~%]
+ [%- script %]
+[% END %]
+
+NETWORK:
+*
+
+FALLBACK:
+/ [% version('../templates/web/base/offline/appcache.html', '/offline/appcache') %]
diff --git a/templates/web/base/report/_inspect.html b/templates/web/base/report/_inspect.html
index 06c3aab6c..84170a38c 100644
--- a/templates/web/base/report/_inspect.html
+++ b/templates/web/base/report/_inspect.html
@@ -136,7 +136,7 @@
[% IF permissions.report_inspect %]
<p>
<label class="label-containing-checkbox">
- <input type="checkbox" name="save_inspected" value="1" class="js-toggle-public-update" [% 'checked' IF save_inspected %]>
+ <input type="checkbox" name="save_inspected" value="1" class="js-toggle-public-update" checked>
[% loc('Save with a public update') %]
</label>
</p>
@@ -157,7 +157,6 @@
<p>
<input type="hidden" name="token" value="[% csrf_token %]">
- <a class="btn" href="[% c.uri_for( '/report', problem.id ) %]">[% loc('Cancel') %]</a>
<input class="btn btn-primary" type="submit" value="[% loc('Save changes') %]" data-value-original="[% loc('Save changes') %]" data-value-duplicate="[% loc('Save + close as duplicate') %]" name="save" />
</p>
</div>
diff --git a/templates/web/base/report/_item.html b/templates/web/base/report/_item.html
index 939dc5da6..02457e5a0 100644
--- a/templates/web/base/report/_item.html
+++ b/templates/web/base/report/_item.html
@@ -2,7 +2,8 @@
[% PROCESS 'admin/report_blocks.html' ~%]
[% END ~%]
-<li class="item-list__item item-list--reports__item [% item_extra_class %]" data-report-id="[% problem.id | html %]">
+<li class="item-list__item item-list--reports__item [% item_extra_class %]"
+ data-report-id="[% problem.id | html %]" data-lastupdate="[% problem.lastupdate %]">
<a href="[% c.cobrand.base_url_for_report( problem ) %][% problem.url %]">
[% IF problem.photo %]
<img class="img" height="60" width="90" src="[% problem.photos.first.url_fp %]" alt="">
diff --git a/templates/web/base/report/_main.html b/templates/web/base/report/_main.html
index 4de26535c..d5224f23e 100644
--- a/templates/web/base/report/_main.html
+++ b/templates/web/base/report/_main.html
@@ -5,7 +5,7 @@
<a href="[% c.uri_for( '/around', { lat => latitude, lon => longitude } ) %]"
class="problem-back js-back-to-report-list">[% loc('Back to all reports') %]</a>
-<div class="problem-header clearfix" problem-id="[% problem.id %]">
+<div class="problem-header clearfix" data-lastupdate="[% problem.lastupdate %]">
[% IF c.user.has_permission_to('planned_reports', problem.bodies_str_ids) %]
<form method="post" action="/my/planned/change" id="planned_form" class="hidden-label-target">
diff --git a/templates/web/base/report/photo-js.html b/templates/web/base/report/photo-js.html
index 05588d085..91b9930e7 100644
--- a/templates/web/base/report/photo-js.html
+++ b/templates/web/base/report/photo-js.html
@@ -1,6 +1,6 @@
[% extra_css = BLOCK %]
<link rel="stylesheet" href="[% version('/js/fancybox/jquery.fancybox-1.3.4.css') %]">
[% END %]
-[% extra_js = BLOCK %]
- <script src="[% version('/js/fancybox/jquery.fancybox-1.3.4.pack.js') %]" charset="utf-8"></script>
-[% END %]
+[% extra_js = [
+ version('/js/fancybox/jquery.fancybox-1.3.4.pack.js')
+] %]
diff --git a/templates/web/base/reports/index.html b/templates/web/base/reports/index.html
index 4a7d5a9c9..b07227144 100755
--- a/templates/web/base/reports/index.html
+++ b/templates/web/base/reports/index.html
@@ -1,6 +1,6 @@
-[% extra_js = BLOCK %]
- <script src="[% version('/js/jquery.fixedthead.js') %]"></script>
-[% END -%]
+[% extra_js = [
+ version('/js/jquery.fixedthead.js')
+] -%]
[% INCLUDE 'header.html', title = loc('Summary reports'), bodyclass => 'fullwidthpage' %]
<h1>[% loc('All Reports') %]</h1>
diff --git a/templates/web/bristol/footer_extra_js.html b/templates/web/bristol/footer_extra_js.html
index 6ba5e3100..1cfcf00f6 100644
--- a/templates/web/bristol/footer_extra_js.html
+++ b/templates/web/bristol/footer_extra_js.html
@@ -1,4 +1,6 @@
-<script src="[% version('/js/OpenLayers.Projection.OrdnanceSurvey.js') %]"></script>
-<script src="[% version('/cobrands/fixmystreet-uk-councils/js.js') %]"></script>
-<script src="[% version('/cobrands/fixmystreet/assets.js') %]"></script>
-<script src="[% version('/cobrands/bristol/js.js') %]"></script>
+[% scripts.push(
+ version('/js/OpenLayers.Projection.OrdnanceSurvey.js')
+ version('/cobrands/fixmystreet-uk-councils/js.js'),
+ version('/cobrands/fixmystreet/assets.js'),
+ version('/cobrands/bristol/js.js'),
+) %]
diff --git a/templates/web/bristol/maps/bristol.html b/templates/web/bristol/maps/bristol.html
index f49571a1d..08f6fba1c 100644
--- a/templates/web/bristol/maps/bristol.html
+++ b/templates/web/bristol/maps/bristol.html
@@ -1,9 +1,11 @@
-[% map_js = BLOCK %]
-<script type="text/javascript" src="[% version('/js/OpenLayers/OpenLayers.bristol.js') %]"></script>
-<script type="text/javascript" src="[% version('/js/map-OpenLayers.js') %]"></script>
-<script type="text/javascript" src="[% version('/js/map-wmts-base.js') %]"></script>
-<script type="text/javascript" src="[% version('/js/map-wmts-bristol.js') %]"></script>
-[% END %]
+[%
+map_js = [
+ version('/js/OpenLayers/OpenLayers.bristol.js'),
+ version('/js/map-OpenLayers.js'),
+ version('/js/map-wmts-base.js'),
+ version('/js/map-wmts-bristol.js'),
+]
+%]
[% map_html = BLOCK %]
[% INCLUDE maps/openlayers.html %]
diff --git a/templates/web/bromley/footer_extra_js.html b/templates/web/bromley/footer_extra_js.html
index 57066dbe8..ac03496a8 100644
--- a/templates/web/bromley/footer_extra_js.html
+++ b/templates/web/bromley/footer_extra_js.html
@@ -1 +1,3 @@
-<script src="[% version('/cobrands/bromley/a-z-nav.js') %]" charset="utf-8"></script>
+[% scripts.push(
+ version('/cobrands/bromley/a-z-nav.js'),
+) %]
diff --git a/templates/web/bromley/maps/bromley.html b/templates/web/bromley/maps/bromley.html
index aa5789c1c..c2ee0273f 100644
--- a/templates/web/bromley/maps/bromley.html
+++ b/templates/web/bromley/maps/bromley.html
@@ -1,9 +1,10 @@
-[% map_js = BLOCK %]
-<script type="text/javascript" src="[% version('/js/OpenLayers/OpenLayers.fixmystreet.js') %]"></script>
-<script type="text/javascript" src="[% version('/js/map-OpenLayers.js') %]"></script>
-<script type="text/javascript" src="[% version('/js/map-bing-ol.js') %]"></script>
-<script type="text/javascript" src="[% version('/js/map-fms.js') %]"></script>
-<script type="text/javascript" src="[% version('/cobrands/bromley/map.js') %]"></script>
-[% END %]
-
-[% map_html = INCLUDE maps/openlayers.html include_key = 1 %]
+[%
+map_js = [
+ version('/js/OpenLayers/OpenLayers.fixmystreet.js'),
+ version('/js/map-OpenLayers.js'),
+ version('/js/map-bing-ol.js'),
+ version('/js/map-fms.js'),
+ version('/cobrands/bromley/map.js'),
+];
+map_html = INCLUDE maps/openlayers.html include_key = 1
+%]
diff --git a/templates/web/fixmystreet-uk-councils/footer_extra_js.html b/templates/web/fixmystreet-uk-councils/footer_extra_js.html
index 493902ef0..1e7a38f8a 100644
--- a/templates/web/fixmystreet-uk-councils/footer_extra_js.html
+++ b/templates/web/fixmystreet-uk-councils/footer_extra_js.html
@@ -1,2 +1,4 @@
-<script src="[% version('/js/OpenLayers.Projection.OrdnanceSurvey.js') %]"></script>
-<script src="[% version('/cobrands/fixmystreet-uk-councils/js.js') %]"></script>
+[% scripts.push(
+ version('/js/OpenLayers.Projection.OrdnanceSurvey.js')
+ version('/cobrands/fixmystreet-uk-councils/js.js'),
+) %]
diff --git a/templates/web/fixmystreet.com/about/posters.html b/templates/web/fixmystreet.com/about/posters.html
index 1a9a4400c..c4cf16cd4 100644
--- a/templates/web/fixmystreet.com/about/posters.html
+++ b/templates/web/fixmystreet.com/about/posters.html
@@ -1,6 +1,6 @@
-[% extra_js = BLOCK %]
- <script src="[% version('/cobrands/fixmystreet.com/posters.js') %]"></script>
-[% END %]
+[% extra_js = [
+ version('/cobrands/fixmystreet.com/posters.js')
+] %]
[% extra_css = BLOCK %]
<link rel="stylesheet" href="[% version('/cobrands/fixmystreet.com/posters.css') %]">
[% END %]
diff --git a/templates/web/fixmystreet.com/footer_extra_js.html b/templates/web/fixmystreet.com/footer_extra_js.html
index 0d1cca04d..d03aa8657 100644
--- a/templates/web/fixmystreet.com/footer_extra_js.html
+++ b/templates/web/fixmystreet.com/footer_extra_js.html
@@ -1,3 +1,5 @@
-<script src="[% version('/js/OpenLayers.Projection.OrdnanceSurvey.js') %]"></script>
-<script src="[% version('/js/jquery.cookie.min.js') %]"></script>
-<script src="[% version('/cobrands/fixmystreet.com/js.js') %]"></script>
+[% scripts.push(
+ version('/js/OpenLayers.Projection.OrdnanceSurvey.js'),
+ version('/js/jquery.cookie.min.js'),
+ version('/cobrands/fixmystreet.com/js.js'),
+) %]
diff --git a/templates/web/fixmystreet.com/front/javascript.html b/templates/web/fixmystreet.com/front/javascript.html
index ac9faa309..baf7ebb64 100644
--- a/templates/web/fixmystreet.com/front/javascript.html
+++ b/templates/web/fixmystreet.com/front/javascript.html
@@ -1,7 +1,12 @@
-<script src="[% version('/js/yepnope.js') %]"></script>
-<script id="script_front" src="[% version('/cobrands/fixmystreet/front.js') %]"
- data-scripts="
- [%~ version('/js/OpenLayers/OpenLayers.fixmystreet.js') %],
- [%~ version('/js/map-OpenLayers.js') %],
- [%~ version('/js/map-bing-ol.js') %],
- [%~ version('/js/map-fms.js') %]"></script>
+[%
+map_js = [
+ version('/js/yepnope.js'),
+ [ version('/cobrands/fixmystreet/front.js'), {
+ id = "script_front",
+ 'data-scripts' = version('/js/OpenLayers/OpenLayers.fixmystreet.js') _ ',' _
+ version('/js/map-OpenLayers.js') _ ',' _
+ version('/js/map-bing-ol.js') _ ',' _
+ version('/js/map-fms.js')
+ } ],
+]
+%]
diff --git a/templates/web/fixmystreet.com/header.html b/templates/web/fixmystreet.com/header.html
new file mode 100644
index 000000000..48759d614
--- /dev/null
+++ b/templates/web/fixmystreet.com/header.html
@@ -0,0 +1,45 @@
+[% SET html_att = ' lang="' _ lang_code _ '"' -%]
+<!doctype html>
+<!--[if IE 7]> <html class="no-js ie7 iel8"[% html_att %]><![endif]-->
+<!--[if IE 8]> <html class="no-js ie8 iel8"[% html_att %]><![endif]-->
+<!--[if IE 9]> <html class="no-js ie9"[% html_att %]><![endif]-->
+<!--[if gt IE 9]><!--><html class="no-js"[% html_att %]
+[% IF appcache ~%]
+ manifest="/offline/appcache.manifest"
+[%~ END %]><!--<![endif]-->
+ <head>
+ <meta name="viewport" content="initial-scale=1.0">
+
+ <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
+ <meta name="HandHeldFriendly" content="true">
+ <meta name="mobileoptimized" content="0">
+
+ [% INCLUDE 'header_opengraph.html' %]
+ <link rel="stylesheet" href="[% version('/cobrands/fixmystreet.com/base.css') %]">
+ <link rel="stylesheet" href="[% version('/cobrands/fixmystreet.com/layout.css') %]" media="(min-width:48em)">
+ [% extra_css %]
+ <!--[if (lt IE 9) & (!IEMobile)]>
+ <link rel="stylesheet" href="[% version('/cobrands/fixmystreet.com/layout.css') %]">
+ <![endif]-->
+
+ [% INCLUDE 'common_header_tags.html' %]
+
+ [% IF c.req.uri.host == 'osm.fixmystreet.com' %]
+ <link rel="canonical" href="https://www.fixmystreet.com[% c.req.uri.path_query %]">
+ [% END %]
+
+ [% TRY %][% PROCESS 'header_extra.html' %][% CATCH file %][% END %]
+
+ </head>
+ [% TRY %][% PROCESS 'set_body_class.html' %][% CATCH file %][% END %]
+ <body class="[% bodyclass | html IF bodyclass %]">
+ <div class="top_banner top_banner--donate"><p>FixMyStreet is run by the charity mySociety. <a href="https://www.mysociety.org/donate/?utm_source=fixmystreet.com&utm_medium=banner&utm_campaign=december_2016&utm_content=banner+donate+now">Can you help support us with a small donation?</a></p></div>
+
+ <div class="wrapper">
+ <div class="table-cell">
+ [% INCLUDE 'header_site.html' %]
+
+ [% pre_container_extra %]
+
+ <div class="container">
+ <div class="content[% " $mainclass" | html IF mainclass %]" role="main">
diff --git a/templates/web/oxfordshire/header.html b/templates/web/oxfordshire/header.html
index 042222e1d..d8cb8d4bb 100644
--- a/templates/web/oxfordshire/header.html
+++ b/templates/web/oxfordshire/header.html
@@ -2,7 +2,10 @@
<!--[if IE 7]> <html class="no-js ie7 iel8" lang="[% lang_code %]"><![endif]-->
<!--[if IE 8]> <html class="no-js ie8 iel8" lang="[% lang_code %]"><![endif]-->
<!--[if IE 9]> <html class="no-js ie9" lang="[% lang_code %]"><![endif]-->
-<!--[if gt IE 9]><!--><html class="no-js" lang="[% lang_code %]"><!--<![endif]-->
+<!--[if gt IE 9]><!--><html class="no-js" lang="[% lang_code %]"
+[% IF appcache ~%]
+ manifest="/offline/appcache.manifest"
+[%~ END %]><!--<![endif]-->
<head>
<meta name="viewport" content="initial-scale=1.0">
diff --git a/templates/web/zurich/maps/zurich.html b/templates/web/zurich/maps/zurich.html
index 2f21c91a6..f85be4aef 100644
--- a/templates/web/zurich/maps/zurich.html
+++ b/templates/web/zurich/maps/zurich.html
@@ -1,10 +1,12 @@
-[% map_js = BLOCK %]
-<script type="text/javascript" src="[% version('/js/OpenLayers.2.11.zurich.js') %]"></script>
-<script type="text/javascript" src="[% version('/js/OpenLayers.Projection.CH1903Plus.js') %]"></script>
-<script type="text/javascript" src="[% version('/js/map-OpenLayers.js') %]"></script>
-<script type="text/javascript" src="[% version('/js/map-wmts-base.js') %]"></script>
-<script type="text/javascript" src="[% version('/js/map-wmts-zurich.js') %]"></script>
-[% END %]
+[%
+map_js = [
+ version('/js/OpenLayers.2.11.zurich.js'),
+ version('/js/OpenLayers.Projection.CH1903Plus.js'),
+ version('/js/map-OpenLayers.js'),
+ version('/js/map-wmts-base.js'),
+ version('/js/map-wmts-zurich.js'),
+]
+%]
[% map_sub_links = BLOCK %]
<a class="hidden-nojs" id="map_layer_toggle" href="">Stadtplan</a>
diff --git a/web/cobrands/angus/js.js b/web/cobrands/angus/js.js
index af70fd92f..f3e7bf211 100644
--- a/web/cobrands/angus/js.js
+++ b/web/cobrands/angus/js.js
@@ -1,3 +1,9 @@
+(function(){
+
+if (!fixmystreet.maps) {
+ return;
+}
+
$(fixmystreet.add_assets({
wfs_url: "https://data.angus.gov.uk/geoserver/services/wfs",
wfs_feature: "lighting_column_v",
@@ -13,3 +19,5 @@ $(fixmystreet.add_assets({
},
geometryName: 'g'
}));
+
+})();
diff --git a/web/cobrands/bristol/js.js b/web/cobrands/bristol/js.js
index 129037d23..1fc23d61a 100644
--- a/web/cobrands/bristol/js.js
+++ b/web/cobrands/bristol/js.js
@@ -1,5 +1,9 @@
(function(){
+if (!fixmystreet.maps) {
+ return;
+}
+
var options = {
wfs_url: "https://maps.bristol.gov.uk/arcgis/services/ext/FixMyStreetSupportData/MapServer/WFSServer",
wfs_feature: "COD_ASSETS_POINT",
diff --git a/web/cobrands/fixmystreet.com/base.scss b/web/cobrands/fixmystreet.com/base.scss
index e04465edc..d0a152627 100644
--- a/web/cobrands/fixmystreet.com/base.scss
+++ b/web/cobrands/fixmystreet.com/base.scss
@@ -7,40 +7,7 @@
@import "../sass/h5bp";
@import "_colours";
@import "../sass/base";
-
-.top_banner {
- color: $primary_text;
- background: $primary;
- p {
- margin: auto;
- padding: 0.5em 2em;
- max-width: 50em;
- text-align: center;
- }
- a {
- color: $primary_text;
- text-decoration: underline;
- }
-}
-
-.top_banner--donate {
- background: #bef;
-}
-
-// The banner interferes with the map moving/placement on mobile, and the top
-// bar navigation on desktop (which both assume that .wrapper is at the top of
-// the page) so hide there for now
-.mappage .top_banner--donate {
- display: none;
-}
-
-// This banner is only shown via JavaScript AJAX call
-.top_banner--country {
- display: none;
-}
-.top_banner__close {
- float: $right;
-}
+@import "../sass/top-banner";
#site-logo {
background: url('data:image/png;base64,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') no-repeat;
diff --git a/web/cobrands/fixmystreet/fixmystreet.js b/web/cobrands/fixmystreet/fixmystreet.js
index e4c5ba71c..dd9167185 100644
--- a/web/cobrands/fixmystreet/fixmystreet.js
+++ b/web/cobrands/fixmystreet/fixmystreet.js
@@ -316,6 +316,7 @@ $.extend(fixmystreet.set_up, {
$change = $form.find("input[name='change']" ),
$submit = $form.find("input[type='submit']" ),
$labels = $('label[for="' + $submit.attr('id') + '"]'),
+ problemId = $form.find("input[name='id']").val(),
data = $form.serialize() + '&ajax=1',
changeValue,
buttonLabel,
@@ -327,10 +328,12 @@ $.extend(fixmystreet.set_up, {
buttonLabel = $submit.data('label-remove');
buttonValue = $submit.data('value-remove');
$('.shortlisted-status').remove();
+ $(document).trigger('shortlist-add', problemId);
} else if (data.outcome == 'remove') {
changeValue = "add";
buttonLabel = $submit.data('label-add');
buttonValue = $submit.data('value-add');
+ $(document).trigger('shortlist-remove', problemId);
}
$change.val(changeValue);
$submit.val(buttonValue).attr('aria-label', buttonLabel);
diff --git a/web/cobrands/fixmystreet/offline.js b/web/cobrands/fixmystreet/offline.js
new file mode 100644
index 000000000..54ab12061
--- /dev/null
+++ b/web/cobrands/fixmystreet/offline.js
@@ -0,0 +1,409 @@
+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 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');
+ if (num === 0) {
+ banner.hide();
+ }
+
+ window.addEventListener("offline", function(e) {
+ $('#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();
+ toCache = l;
+ $('#offline_saving').html('Saving reports offline &ndash; <span>0</span>/' + toCache + '.');
+ },
+ progress: function() {
+ cachedSoFar += 1;
+ if (cachedSoFar === toCache) {
+ $('#offline_saving').text('Reports saved offline.');
+ } else {
+ $('#offline_saving span').text(cachedSoFar);
+ }
+ }
+ };
+})();
+
+fixmystreet.offlineData = (function() {
+ var data;
+
+ function getData() {
+ if (data === undefined) {
+ data = JSON.parse(localStorage.getItem('offlineData'));
+ if (!data) {
+ data = { cachedReports: {}, forms: [] };
+ }
+ }
+ return data;
+ }
+
+ function saveData() {
+ localStorage.setItem('offlineData', JSON.stringify(getData()));
+ }
+
+ 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();
+ },
+ clearForms: function(idx) {
+ getData().forms = [];
+ saveData();
+ fixmystreet.offlineBanner.update();
+ },
+ getCachedUrls: function() {
+ return Object.keys(getData().cachedReports);
+ },
+ isIndexed: function(url, lastupdate) {
+ if (lastupdate) {
+ return getData().cachedReports[url] === lastupdate;
+ }
+ return !!getData().cachedReports[url];
+ },
+ add: function(url, lastupdate) {
+ var data = getData();
+ data.cachedReports[url] = lastupdate || "-";
+ saveData();
+ },
+ remove: function(urls) {
+ var data = getData();
+ urls.forEach(function(url) {
+ delete data.cachedReports[url];
+ });
+ saveData();
+ }
+ };
+})();
+
+fixmystreet.cachet = (function(){
+ var urlsInProgress = {};
+
+ function cacheURL(url, type) {
+ urlsInProgress[url] = 1;
+
+ var ret;
+ if (type === 'image') {
+ ret = $.Deferred(function(deferred) {
+ var oReq = new XMLHttpRequest();
+ oReq.open("GET", url, true);
+ oReq.responseType = "blob";
+ oReq.onload = function(oEvent) {
+ var blob = oReq.response;
+ var reader = new window.FileReader();
+ reader.readAsDataURL(blob);
+ reader.onloadend = function() {
+ localStorage.setItem(url, reader.result);
+ delete urlsInProgress[url];
+ deferred.resolve(blob);
+ };
+ };
+ oReq.send();
+ });
+ } else {
+ ret = $.ajax(url).pipe(function(content, textStatus, jqXHR) {
+ localStorage.setItem(url, content);
+ delete urlsInProgress[url];
+ return content;
+ });
+ }
+ return ret;
+ }
+
+ function cacheReport(item) {
+ return cacheURL(item.url, 'html').pipe(function(html) {
+ var $reportPage = $(html);
+ var imagesToGet = [
+ item.url + '/map' // Static map image
+ ];
+ $reportPage.find('img').each(function(i, img) {
+ if (img.src.indexOf('/photo/') === -1 || fixmystreet.offlineData.isIndexed(img.src) || urlsInProgress[img.src]) {
+ return;
+ }
+ imagesToGet.push(img.src);
+ imagesToGet.push(img.src.replace('.jpeg', '.fp.jpeg'));
+ });
+ var imagePromises = imagesToGet.map(function(url) {
+ return cacheURL(url, 'image');
+ });
+ return $.when.apply(undefined, imagePromises).pipe(function() {
+ fixmystreet.offlineBanner.progress();
+ fixmystreet.offlineData.add(item.url, item.lastupdate);
+ }, function() {
+ fixmystreet.offlineBanner.progress();
+ fixmystreet.offlineData.add(item.url, item.lastupdate);
+ });
+ });
+ }
+
+ // Cache a list of reports offline
+ // This fetches the HTML and any img elements in that HTML
+ function cacheReports(items) {
+ fixmystreet.offlineBanner.startProgress(items.length);
+ var promises = items.map(function(item) {
+ return cacheReport(item);
+ });
+ return $.when.apply(undefined, promises);
+ }
+
+ return {
+ cacheReports: cacheReports
+ };
+})();
+
+fixmystreet.offline = (function() {
+ function getReportsFromList() {
+ var reports = $('.item-list__item').map(function(i, li) {
+ var $li = $(li),
+ url = $li.find('a')[0].pathname,
+ lastupdate = $li.data('lastupdate');
+ return { 'url': url, 'lastupdate': lastupdate };
+ }).get();
+ return reports;
+ }
+
+ function updateCachedReports() {
+ var toCache = [];
+ var toRemove = [];
+ var shouldBeCached = {};
+
+ localStorage.setItem('/my/planned', $('.item-list').html());
+
+ getReportsFromList().forEach(function(item, i) {
+ if (!fixmystreet.offlineData.isIndexed(item.url, item.lastupdate)) {
+ toCache.push(item);
+ }
+ shouldBeCached[item.url] = 1;
+ });
+
+ fixmystreet.offlineData.getCachedUrls().forEach(function(url) {
+ if ( !shouldBeCached[url] ) {
+ toRemove.push(url);
+ }
+ });
+
+ if (toRemove[0]) {
+ removeReports(toRemove);
+ }
+ if (toCache[0]) {
+ fixmystreet.cachet.cacheReports(toCache);
+ }
+ }
+
+ // Remove a list of reports from the offline cache
+ function removeReports(urls) {
+ var pathsRemoved = [];
+ urls.forEach(function(url) {
+ var html = localStorage.getItem(url);
+ var $reportPage = $(html);
+ localStorage.removeItem(url + '/map');
+ $reportPage.find('img').each(function(i, img) {
+ if (img.src.indexOf('/photo/') === -1) {
+ return;
+ }
+ localStorage.removeItem(img.src);
+ localStorage.removeItem(img.src.replace('.jpeg', '.fp.jpeg'));
+ });
+ localStorage.removeItem(url);
+ });
+ fixmystreet.offlineData.remove(urls);
+ }
+
+ function showReportFromCache(url) {
+ var html = localStorage.getItem(url);
+ if (!html) {
+ return false;
+ }
+ var map = localStorage.getItem(url + '/map');
+ var found = html.match(/<body[^>]*>[\s\S]*<\/body>/);
+ document.body.outerHTML = found[0];
+ $('#map_box').html('<img src="' + map + '">').css({ textAlign: 'center', height: 'auto' });
+ replaceImages('img');
+
+ $('.moderate-display.segmented-control, .shadow-wrap, #update_form, #report-cta, .mysoc-footer, .nav-wrapper').hide();
+
+ $('.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;
+ }
+
+ function replaceImages(selector) {
+ $(selector).each(function(i, img) {
+ if (img.src.indexOf('/photo/') > -1) {
+ var dataImg = localStorage.getItem(img.src);
+ if (dataImg) {
+ img.src = dataImg;
+ }
+ }
+ });
+ }
+
+ return {
+ replaceImages: replaceImages,
+ showReportFromCache: showReportFromCache,
+ removeReports: removeReports,
+ updateCachedReports: updateCachedReports
+ };
+
+})();
+
+if ($('#offline_list').length) {
+ // We are OFFLINE
+ var success = false;
+ if (location.pathname.indexOf('/report') === 0) {
+ success = fixmystreet.offline.showReportFromCache(location.pathname);
+ }
+ if (!success) {
+ var html = localStorage.getItem('/my/planned');
+ 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> ');
+ }
+ });
+ $('#offline_clear').html('<button id="js-clear-localStorage">Clear offline data</button>');
+ $('#js-clear-localStorage').click(function() {
+ fixmystreet.offline.removeReports(fixmystreet.offlineData.getCachedUrls());
+ fixmystreet.offlineData.clearForms();
+ localStorage.removeItem('/my/planned');
+ alert('Offline data cleared');
+ });
+ }
+ }
+ fixmystreet.offlineBanner.make(true);
+} else {
+ // Put the appcache manifest in a page in an iframe so that HTML pages
+ // aren't cached (thanks to Jake Archibald for documenting this!)
+ if (window.applicationCache && window.localStorage) {
+ $(document.body).prepend('<iframe src="/offline/appcache" style="position:absolute;top:-999em;visibility:hidden"></iframe>');
+ }
+
+ fixmystreet.offlineBanner.make(false);
+
+ // On /my/planned, when online, cache all shortlisted
+ if (location.pathname === '/my/planned') {
+ fixmystreet.offline.updateCachedReports();
+ }
+
+ // Catch additions and removals from the shortlist
+ $(document).on('shortlist-add', function(e, id) {
+ var lastupdate = $('.problem-header').data('lastupdate');
+ fixmystreet.cachet.cacheReports([{ 'url': '/report/' + id, 'lastupdate': lastupdate }]);
+ });
+ $(document).on('shortlist-remove', function(e, id) {
+ fixmystreet.offline.removeReports(['/report/' + id]);
+ });
+}
diff --git a/web/cobrands/oxfordshire/base.scss b/web/cobrands/oxfordshire/base.scss
index 72ddbd437..0cc621310 100644
--- a/web/cobrands/oxfordshire/base.scss
+++ b/web/cobrands/oxfordshire/base.scss
@@ -3,6 +3,7 @@
@import "../sass/mixins";
@import "../sass/base";
+@import "../sass/top-banner";
#site-header {
background: none;
diff --git a/web/cobrands/sass/_top-banner.scss b/web/cobrands/sass/_top-banner.scss
new file mode 100644
index 000000000..8677343c2
--- /dev/null
+++ b/web/cobrands/sass/_top-banner.scss
@@ -0,0 +1,49 @@
+.top_banner {
+ color: $primary_text;
+ background: $primary;
+ p {
+ margin: auto;
+ padding: 0.5em 2em;
+ max-width: 50em;
+ text-align: center;
+ }
+ a {
+ color: $primary_text;
+ text-decoration: underline;
+ }
+}
+
+.top_banner--donate {
+ background: #bef;
+}
+
+// The banner interferes with the map moving/placement on mobile, and the top
+// bar navigation on desktop (which both assume that .wrapper is at the top of
+// the page) so hide there for now
+.mappage .top_banner--donate {
+ display: none;
+}
+
+// This banner is only shown via JavaScript AJAX call
+.top_banner--country {
+ display: none;
+}
+
+.top_banner--offline {
+ position: fixed; left: 0; right: 0; top: 0; z-index: 100;
+ opacity: 0.9;
+ background: #c33;
+ color: #fff;
+}
+
+.top_banner--offline a {
+ color: #fff;
+}
+
+.top_banner--offline a:hover {
+ color: #000;
+}
+
+.top_banner__close {
+ float: $right;
+}