mirror of
https://github.com/codeninjasllc/discourse.git
synced 2024-11-23 15:48:43 -05:00
Merge pull request #2455 from riking/error-route
Fancy error page for topic lists
This commit is contained in:
commit
7ac4372427
16 changed files with 269 additions and 9 deletions
107
app/assets/javascripts/discourse/controllers/exception.js.es6
Normal file
107
app/assets/javascripts/discourse/controllers/exception.js.es6
Normal file
|
@ -0,0 +1,107 @@
|
|||
|
||||
var ButtonBackBright = {
|
||||
classes: "btn-primary",
|
||||
action: "back",
|
||||
key: "errors.buttons.back"
|
||||
},
|
||||
ButtonBackDim = {
|
||||
classes: "",
|
||||
action: "back",
|
||||
key: "errors.buttons.back"
|
||||
},
|
||||
ButtonTryAgain = {
|
||||
classes: "btn-primary",
|
||||
action: "tryLoading",
|
||||
key: "errors.buttons.again"
|
||||
},
|
||||
ButtonLoadPage = {
|
||||
classes: "btn-primary",
|
||||
action: "tryLoading",
|
||||
key: "errors.buttons.fixed"
|
||||
};
|
||||
|
||||
/**
|
||||
The controller for the nice error page
|
||||
|
||||
@class ExceptionController
|
||||
@extends Discourse.ObjectController
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
export default Discourse.ObjectController.extend({
|
||||
thrown: null,
|
||||
lastTransition: null,
|
||||
|
||||
isNetwork: function() {
|
||||
// never made it on the wire
|
||||
if (this.get('thrown.readyState') === 0) return true;
|
||||
// timed out
|
||||
if (this.get('thrown.jqTextStatus') === "timeout") return true;
|
||||
return false;
|
||||
}.property(),
|
||||
isServer: Em.computed.gte('thrown.status', 500),
|
||||
isUnknown: Em.computed.none('isNetwork', 'isServer'),
|
||||
|
||||
// TODO
|
||||
// make ajax requests to /srv/status with exponential backoff
|
||||
// if one succeeds, set networkFixed to true, which puts a "Fixed!" message on the page
|
||||
networkFixed: false,
|
||||
loading: false,
|
||||
|
||||
_init: function() {
|
||||
this.set('loading', false);
|
||||
}.on('init'),
|
||||
|
||||
reason: function() {
|
||||
if (this.get('isNetwork')) {
|
||||
return I18n.t('errors.reasons.network');
|
||||
} else if (this.get('isServer')) {
|
||||
return I18n.t('errors.reasons.server');
|
||||
} else {
|
||||
// TODO
|
||||
return I18n.t('errors.reasons.unknown');
|
||||
}
|
||||
}.property('isNetwork', 'isServer', 'isUnknown'),
|
||||
|
||||
requestUrl: Em.computed.alias('thrown.requestedUrl'),
|
||||
|
||||
desc: function() {
|
||||
if (this.get('networkFixed')) {
|
||||
return I18n.t('errors.desc.network_fixed');
|
||||
} else if (this.get('isNetwork')) {
|
||||
return I18n.t('errors.desc.network');
|
||||
} else if (this.get('isServer')) {
|
||||
return I18n.t('errors.desc.server', this.get('thrown.statusText'));
|
||||
} else {
|
||||
// TODO
|
||||
return I18n.t('errors.desc.unknown');
|
||||
}
|
||||
}.property('networkFixed', 'isNetwork', 'isServer', 'isUnknown'),
|
||||
|
||||
enabledButtons: function() {
|
||||
if (this.get('networkFixed')) {
|
||||
return [ButtonLoadPage];
|
||||
} else if (this.get('isNetwork')) {
|
||||
return [ButtonBackDim, ButtonTryAgain];
|
||||
} else if (this.get('isServer')) {
|
||||
return [ButtonBackBright];
|
||||
} else {
|
||||
return [ButtonBackBright, ButtonTryAgain];
|
||||
}
|
||||
}.property('networkFixed', 'isNetwork', 'isServer', 'isUnknown'),
|
||||
|
||||
actions: {
|
||||
back: function() {
|
||||
window.history.back();
|
||||
},
|
||||
|
||||
tryLoading: function() {
|
||||
this.set('loading', true);
|
||||
var self = this;
|
||||
Em.run.schedule('afterRender', function() {
|
||||
self.get('lastTransition').retry();
|
||||
self.set('loading', false);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
|
@ -385,6 +385,16 @@ Discourse.TopicController = Discourse.ObjectController.extend(Discourse.Selected
|
|||
});
|
||||
},
|
||||
|
||||
retryLoading: function() {
|
||||
var self = this;
|
||||
self.set('retrying', true);
|
||||
this.get('postStream').refresh().then(function() {
|
||||
self.set('retrying', false);
|
||||
}, function() {
|
||||
self.set('retrying', false);
|
||||
});
|
||||
},
|
||||
|
||||
toggleWiki: function(post) {
|
||||
post.toggleProperty('wiki');
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ Discourse.Ajax = Em.Mixin.create({
|
|||
};
|
||||
|
||||
var oldError = args.error;
|
||||
args.error = function(xhr) {
|
||||
args.error = function(xhr, textStatus) {
|
||||
|
||||
// note: for bad CSRF we don't loop an extra request right away.
|
||||
// this allows us to eliminate the possibility of having a loop.
|
||||
|
@ -62,9 +62,13 @@ Discourse.Ajax = Em.Mixin.create({
|
|||
Discourse.Session.current().set('csrfToken', null);
|
||||
}
|
||||
|
||||
// If it's a parseerror, don't reject
|
||||
// If it's a parsererror, don't reject
|
||||
if (xhr.status === 200) return args.success(xhr);
|
||||
|
||||
// Fill in some extra info
|
||||
xhr.jqTextStatus = textStatus;
|
||||
xhr.requestedUrl = url;
|
||||
|
||||
Ember.run(promise, promise.reject, xhr);
|
||||
if (oldError) oldError(xhr);
|
||||
};
|
||||
|
|
|
@ -249,6 +249,7 @@ Discourse.PostStream = Em.Object.extend({
|
|||
self.setProperties({ loadingFilter: false, loaded: true });
|
||||
}).catch(function(result) {
|
||||
self.errorLoading(result);
|
||||
throw result;
|
||||
});
|
||||
},
|
||||
hasLoadedData: Em.computed.and('hasPosts', 'hasStream'),
|
||||
|
|
|
@ -10,6 +10,19 @@ Discourse.ApplicationRoute = Em.Route.extend({
|
|||
|
||||
actions: {
|
||||
|
||||
error: function(err, transition) {
|
||||
if (err.status === 404) {
|
||||
// 404
|
||||
this.intermediateTransitionTo('unknown');
|
||||
return;
|
||||
}
|
||||
|
||||
var exceptionController = this.controllerFor('exception');
|
||||
exceptionController.setProperties({ lastTransition: transition, thrown: err });
|
||||
|
||||
this.intermediateTransitionTo('exception');
|
||||
},
|
||||
|
||||
showLogin: function() {
|
||||
if (Discourse.get("isReadOnly")) {
|
||||
bootbox.alert(I18n.t("read_only_mode.login_disabled"));
|
||||
|
|
|
@ -13,6 +13,9 @@ Discourse.Route.buildRoutes(function() {
|
|||
router.route(page, { path: '/' + page });
|
||||
});
|
||||
|
||||
// Error page
|
||||
this.route('exception', { path: '/exception' });
|
||||
|
||||
// Topic routes
|
||||
this.resource('topic', { path: '/t/:slug/:id' }, function() {
|
||||
this.route('fromParams', { path: '/' });
|
||||
|
|
13
app/assets/javascripts/discourse/routes/exception_route.js
Normal file
13
app/assets/javascripts/discourse/routes/exception_route.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
/**
|
||||
Client-side pseudo-route for showing an error page.
|
||||
|
||||
@class ExceptionRoute
|
||||
@extends Discourse.Route
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
Discourse.ExceptionRoute = Discourse.Route.extend({
|
||||
serialize: function() {
|
||||
return "";
|
||||
}
|
||||
});
|
|
@ -89,7 +89,7 @@ Discourse.TopicRoute = Discourse.Route.extend({
|
|||
}
|
||||
}, 150),
|
||||
|
||||
willTransition: function() { this.set("isTransitioning", true); }
|
||||
willTransition: function() { this.set("isTransitioning", true); return true; }
|
||||
|
||||
},
|
||||
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
<div class="container">
|
||||
<div class="error-page">
|
||||
<div class="face">:(</div>
|
||||
<div class="reason">{{reason}}</div>
|
||||
<div class="url">
|
||||
{{i18n errors.prev_page}} <a {{bind-attr href=requestUrl}} data-auto-route="true">{{requestUrl}}</a>
|
||||
</div>
|
||||
<div class="desc">
|
||||
{{#if networkFixed}}
|
||||
<i class="fa fa-check-circle"></i>
|
||||
{{/if}}
|
||||
|
||||
{{desc}}
|
||||
</div>
|
||||
<div class="buttons">
|
||||
{{#each buttonData in enabledButtons}}
|
||||
<button class="btn {{unbound buttonData.classes}}" {{action buttonData.action}}>{{boundI18n buttonData.key}}</button>
|
||||
{{/each}}
|
||||
{{#if loading}}
|
||||
<i class="fa fa-spin fa-spinner"></i>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -117,17 +117,19 @@
|
|||
{{else}}
|
||||
{{#if hasError}}
|
||||
<div class='container'>
|
||||
{{#if errorBodyHtml}}
|
||||
<div class="topic-error">
|
||||
{{{errorBodyHtml}}}
|
||||
{{/if}}
|
||||
|
||||
{{#if message}}
|
||||
<div class="message">
|
||||
<h2>{{message}}</h2>
|
||||
{{message}}
|
||||
{{#unless currentUser}}
|
||||
<button {{action showLogin}} class='btn btn-primary btn-small'>{{i18n log_in}}</button>
|
||||
{{/unless}}
|
||||
{{/if}}
|
||||
<button class="btn btn-primary topic-retry" {{action retryLoading}}>{{i18n errors.buttons.again}}</button>
|
||||
</div>
|
||||
{{#if retrying}}
|
||||
<div class='spinner'>{{i18n loading}}</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{else}}
|
||||
|
|
30
app/assets/stylesheets/common/base/exception.scss
Normal file
30
app/assets/stylesheets/common/base/exception.scss
Normal file
|
@ -0,0 +1,30 @@
|
|||
.error-page {
|
||||
text-align: center;
|
||||
padding-top: 2em;
|
||||
|
||||
.face {
|
||||
font-size: 60px;
|
||||
height: 60px;
|
||||
}
|
||||
.reason {
|
||||
font-size: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
.url {
|
||||
font-style: italic;
|
||||
font-size: 11px;
|
||||
}
|
||||
.desc {
|
||||
margin-top: 16px;
|
||||
.fa-check-circle {
|
||||
color: $success;
|
||||
}
|
||||
}
|
||||
.buttons {
|
||||
margin-top: 15px;
|
||||
|
||||
button {
|
||||
margin: 0 20px;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -84,6 +84,23 @@ a:hover.reply-new {
|
|||
}
|
||||
}
|
||||
|
||||
.topic-error {
|
||||
padding: 18px;
|
||||
width: 60%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
font-size: 24px;
|
||||
text-align: center;
|
||||
line-height: 1.1em;
|
||||
|
||||
.topic-retry {
|
||||
display: block;
|
||||
margin-top: 28px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
|
||||
#topic-closing-info {
|
||||
border-top: 1px solid scale-color-diff();
|
||||
padding-top: 10px;
|
||||
|
|
|
@ -146,6 +146,22 @@
|
|||
}
|
||||
}
|
||||
|
||||
.topic-error {
|
||||
padding: 18px;
|
||||
width: 90%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
font-size: 24px;
|
||||
line-height: 1.1em;
|
||||
|
||||
.topic-retry {
|
||||
display: block;
|
||||
margin-top: 20px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
|
||||
#topic-progress-wrapper.docked {
|
||||
position: absolute;
|
||||
}
|
||||
|
|
|
@ -16,4 +16,8 @@ class ForumsController < ApplicationController
|
|||
raise "WAT - #{Time.now.to_s}"
|
||||
end
|
||||
|
||||
def home_redirect
|
||||
redirect_to '/'
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -468,6 +468,21 @@ en:
|
|||
the_topic: "the topic"
|
||||
|
||||
loading: "Loading..."
|
||||
errors:
|
||||
prev_page: "while trying to load"
|
||||
reasons:
|
||||
network: "Network Error"
|
||||
server: "Server Error: {{code}}"
|
||||
unknown: "Error"
|
||||
desc:
|
||||
network: "Please check your connection."
|
||||
network_fixed: "Looks like it's back."
|
||||
server: "Something went wrong."
|
||||
unknown: "Something went wrong."
|
||||
buttons:
|
||||
back: "Go Back"
|
||||
again: "Try Again"
|
||||
fixed: "Load Page"
|
||||
close: "Close"
|
||||
assets_changed_confirm: "This site was just updated. Refresh now for the latest version?"
|
||||
read_only_mode:
|
||||
|
|
|
@ -377,6 +377,7 @@ Discourse::Application.routes.draw do
|
|||
get "onebox" => "onebox#show"
|
||||
|
||||
get "error" => "forums#error"
|
||||
get "exception" => "list#latest"
|
||||
|
||||
get "message-bus/poll" => "message_bus#poll"
|
||||
|
||||
|
|
Loading…
Reference in a new issue