Improve the version checking part of the changelog feature

- There is no client caching of the changelog/version anymore. Instead, server returns the expiration date of its cache, and that is used by the client as well.
- There is now a "Check now" button on the client that appears when data is stale. This means that info is fetched only once and never refreshed (it was refreshed every hour before) unless the user explicitly wants to check latest version, which in turn is as stale as server info is, i.e. 15 minutes max.
- Button style is shared with the "Join a channel" feature, `.btn-small` (not `.btn-sm` to be explicit that this is not a Bootstrap thing).
- Version checker content is now centralized in the `version_checker` template, instead of being partially in the checker template, partially in the Help template,  and partially in the code.
- A "Try again" button lets user attempt to fetch info instead of forcing them to reload the page.
- Use Flexbox to display a nicer version checker: icon is slightly bigger, and button is always aligned on the right.
- Changelog logic has been removed from `lounge.js` and moved into the component file.
- Changelog template is only passed what it needs instead of everything the server gives us.
- Public version now displays version checker, since server is caching things.
- Cleaner code overall.
This commit is contained in:
Jérémie Astori 2017-12-24 18:18:24 -05:00
parent d2106f1782
commit 238e894377
No known key found for this signature in database
GPG Key ID: B9A4F245CD67BDE8
10 changed files with 136 additions and 55 deletions

View File

@ -137,6 +137,10 @@ kbd {
cursor: pointer; /* This is useful for `<button>` elements */ cursor: pointer; /* This is useful for `<button>` elements */
} }
.btn-small {
padding: 5px 13px;
}
.btn:disabled, .btn:disabled,
.btn:hover, .btn:hover,
.btn:focus { .btn:focus {
@ -235,7 +239,7 @@ kbd {
#chat .nick .from::before, #chat .nick .from::before,
#chat .action .from::before, #chat .action .from::before,
#chat .toggle-button::after, #chat .toggle-button::after,
.changelog-version::before, #version-checker::before,
.context-menu-item::before, .context-menu-item::before,
#help .website-link::before, #help .website-link::before,
#help .documentation-link::before, #help .documentation-link::before,
@ -991,9 +995,7 @@ kbd {
#sidebar .join-form .btn { #sidebar .join-form .btn {
display: block; display: block;
width: 80%; width: 80%;
padding: 1px;
margin: auto; margin: auto;
height: 29px;
} }
#sidebar .add-channel-tooltip { #sidebar .add-channel-tooltip {
@ -1635,47 +1637,70 @@ part/quit messages where we don't load previews (adds a blank line otherwise) */
padding-bottom: 7px; padding-bottom: 7px;
} }
.changelog-version { #version-checker {
display: block; display: flex;
padding: 16px; align-items: center;
padding: 10px;
margin-bottom: 16px; margin-bottom: 16px;
border-radius: 2px; border-radius: 2px;
background-color: #d9edf7;
color: #31708f;
transition: color 0.2s, background-color 0.2s; transition: color 0.2s, background-color 0.2s;
} }
.changelog-version::before { #version-checker p,
margin-right: 6px; #version-checker button {
margin-bottom: 0;
}
#version-checker p {
flex: 1;
padding-top: 6px;
padding-bottom: 6px;
}
#version-checker::before {
margin-left: 6px;
margin-right: 12px;
font-size: 1.2em;
}
#version-checker.loading {
background-color: #d9edf7;
color: #31708f;
}
#version-checker.loading::before {
content: "\f250"; /* http://fontawesome.io/icon/hourglass-o/ */ content: "\f250"; /* http://fontawesome.io/icon/hourglass-o/ */
} }
.changelog-version.new-version { #version-checker.new-version {
color: #8a6d3b; color: #8a6d3b;
background-color: #fcf8e3; background-color: #fcf8e3;
} }
.changelog-version.new-version::before { #version-checker.new-version::before {
content: "\f087"; /* http://fontawesome.io/icon/thumbs-o-up/ */ content: "\f087"; /* http://fontawesome.io/icon/thumbs-o-up/ */
} }
.changelog-version.error { #version-checker.error {
color: #a94442; color: #a94442;
background-color: #f2dede; background-color: #f2dede;
} }
.changelog-version.error::before { #version-checker.error::before {
margin-right: 6px;
content: "\f06a"; /* http://fontawesome.io/icon/exclamation-circle/ */ content: "\f06a"; /* http://fontawesome.io/icon/exclamation-circle/ */
} }
.changelog-version.up-to-date { #version-checker.up-to-date {
background-color: #dff0d8; background-color: #dff0d8;
color: #3c763d; color: #3c763d;
} }
.changelog-version.up-to-date::before { #version-checker.up-to-date #check-now {
margin-right: 6px; /* "Check now" button is hidden until data expires */
display: none;
}
#version-checker.up-to-date::before {
content: "\f00c"; /* http://fontawesome.io/icon/check/ */ content: "\f00c"; /* http://fontawesome.io/icon/check/ */
} }

View File

@ -20,6 +20,7 @@ const utils = require("./utils");
require("./webpush"); require("./webpush");
require("./keybinds"); require("./keybinds");
require("./clipboard"); require("./clipboard");
const Changelog = require("./socket-events/changelog");
const JoinChannel = require("./join-channel"); const JoinChannel = require("./join-channel");
$(function() { $(function() {
@ -332,8 +333,6 @@ $(function() {
$(this).closest(".msg.condensed").toggleClass("closed"); $(this).closest(".msg.condensed").toggleClass("closed");
}); });
let changelogRequestedAt = 0;
const openWindow = function openWindow(e, data) { const openWindow = function openWindow(e, data) {
var self = $(this); var self = $(this);
var target = self.data("target"); var target = self.data("target");
@ -426,12 +425,7 @@ $(function() {
} }
if (target === "#help" || target === "#changelog") { if (target === "#help" || target === "#changelog") {
const now = Date.now(); Changelog.requestIfNeeded();
// Don't check more than once an hour
if (now - changelogRequestedAt > 3600 * 1000) {
changelogRequestedAt = now;
socket.emit("changelog");
}
} }
focus(); focus();

View File

@ -4,19 +4,60 @@ const $ = require("jquery");
const socket = require("../socket"); const socket = require("../socket");
const templates = require("../../views"); const templates = require("../../views");
socket.on("changelog", function(data) { module.exports = {
const container = $("#changelog-version-container"); requestIfNeeded,
};
if (data.latest) { // Requests version information if it hasn't been retrieved before (or if it has
container.addClass("new-version"); // been removed from the page, i.e. when clicking on "Check now". Displays a
container.html(templates.new_version(data)); // loading state until received.
} else if (data.current.changelog) { function requestIfNeeded() {
container.addClass("up-to-date"); if ($("#version-checker").is(":empty")) {
container.text("The Lounge is up to date!"); renderVersionChecker({status: "loading"});
} else { socket.emit("changelog");
container.addClass("error"); }
container.text("An error has occurred, try to reload the page.");
} }
$("#changelog").html(templates.windows.changelog(data)); socket.on("changelog", function(data) {
// 1. Release notes window for the current version
$("#changelog").html(templates.windows.changelog(data.current));
// 2. Version checker visible in Help window
let status;
if (data.latest) {
status = "new-version";
} else if (data.current.changelog) {
status = "up-to-date";
} else {
status = "error";
}
renderVersionChecker({
latest: data.latest,
status,
}); });
// When there is a button to refresh the checker available, display it when
// data is expired. Before that, server would return same information anyway.
if (data.expiresAt) {
setTimeout(
() => $("#version-checker #check-now").show(),
data.expiresAt - Date.now()
);
}
});
// When clicking the "Check now" button, remove current checker information and
// request a new one. Loading will be displayed in the meantime.
$("#help").on("click", "#check-now", () => {
$("#version-checker").empty();
requestIfNeeded();
});
// Given a status and latest release information, update the version checker
// (CSS class and content)
function renderVersionChecker({status, latest}) {
$("#version-checker").attr("class", status)
.html(templates.version_checker({latest, status}));
}

View File

@ -31,7 +31,6 @@ module.exports = {
chan: require("./chan.tpl"), chan: require("./chan.tpl"),
chat: require("./chat.tpl"), chat: require("./chat.tpl"),
new_version: require("./new_version.tpl"),
contextmenu_divider: require("./contextmenu_divider.tpl"), contextmenu_divider: require("./contextmenu_divider.tpl"),
contextmenu_item: require("./contextmenu_item.tpl"), contextmenu_item: require("./contextmenu_item.tpl"),
date_marker: require("./date-marker.tpl"), date_marker: require("./date-marker.tpl"),
@ -50,4 +49,5 @@ module.exports = {
user: require("./user.tpl"), user: require("./user.tpl"),
user_filtered: require("./user_filtered.tpl"), user_filtered: require("./user_filtered.tpl"),
user_name: require("./user_name.tpl"), user_name: require("./user_name.tpl"),
version_checker: require("./version_checker.tpl"),
}; };

View File

@ -1,5 +1,5 @@
<form id="join-channel-{{id}}" class="join-form" method="post" action="" autocomplete="off"> <form id="join-channel-{{id}}" class="join-form" method="post" action="" autocomplete="off">
<input type="text" class="input" name="channel" placeholder="Channel" pattern="[^\s]+" maxlength="200" title="The channel name may not contain spaces" required> <input type="text" class="input" name="channel" placeholder="Channel" pattern="[^\s]+" maxlength="200" title="The channel name may not contain spaces" required>
<input type="password" class="input" name="key" placeholder="Password (optional)" pattern="[^\s]+" maxlength="200" title="The channel password may not contain spaces"> <input type="password" class="input" name="key" placeholder="Password (optional)" pattern="[^\s]+" maxlength="200" title="The channel password may not contain spaces">
<button type="submit" class="btn joinchan:submit" data-id="{{id}}">Join</button> <button type="submit" class="btn btn-small" data-id="{{id}}">Join</button>
</form> </form>

View File

@ -1,6 +0,0 @@
The Lounge <b>{{latest.version}}</b>{{#if latest.prerelease}} (pre-release){{/if}}
is now available.
<a href="{{latest.url}}" target="_blank" rel="noopener">
Read more on GitHub
</a>

View File

@ -0,0 +1,27 @@
{{#equal status "loading"}}
<p>
Checking for updates...
</p>
{{else equal status "new-version"}}
<p>
The Lounge <b>{{latest.version}}</b>{{#if latest.prerelease}} (pre-release){{/if}}
is now available.
<br>
<a href="{{latest.url}}" target="_blank" rel="noopener">
Read more on GitHub
</a>
</p>
{{else equal status "up-to-date"}}
<p>
The Lounge is up to date!
</p>
<button id="check-now" class="btn btn-small">Check now</button>
{{else equal status "error"}}
<p>
Information about latest releases could not be retrieved.
</p>
<button id="check-now" class="btn btn-small">Try again</button>
{{/equal}}

View File

@ -4,15 +4,15 @@
<div class="container"> <div class="container">
<a href="#" id="back-to-help" data-target="#help">« Help</a> <a href="#" id="back-to-help" data-target="#help">« Help</a>
{{#if current}} {{#if version}}
<h1 class="title">Release notes for {{current.version}}</h1> <h1 class="title">Release notes for {{version}}</h1>
{{#if current.changelog}} {{#if changelog}}
<h3>Introduction</h3> <h3>Introduction</h3>
<div class="changelog-text">{{{current.changelog}}}</div> <div class="changelog-text">{{{changelog}}}</div>
{{else}} {{else}}
<p>Unable to retrieve releases from GitHub.</p> <p>Unable to retrieve releases from GitHub.</p>
<p><a href="https://github.com/thelounge/lounge/releases/tag/v{{current.version}}" target="_blank" rel="noopener">View release notes for this version on GitHub</a></p> <p><a href="https://github.com/thelounge/lounge/releases/tag/v{{version}}" target="_blank" rel="noopener">View release notes for this version on GitHub</a></p>
{{/if}} {{/if}}
{{else}} {{else}}
<p>Loading changelog…</p> <p>Loading changelog…</p>

View File

@ -13,11 +13,7 @@
</h2> </h2>
<div class="about"> <div class="about">
{{#unless public}} <div id="version-checker"></div>
<p id="changelog-version-container" class="changelog-version">
Checking for updates...
</p>
{{/unless}}
{{#if gitCommit}} {{#if gitCommit}}
<p> <p>

View File

@ -3,6 +3,8 @@
const pkg = require("../../package.json"); const pkg = require("../../package.json");
const request = require("request"); const request = require("request");
const TIME_TO_LIVE = 15 * 60 * 1000; // 15 minutes, in milliseconds
module.exports = { module.exports = {
fetch, fetch,
}; };
@ -67,12 +69,14 @@ function fetch(callback) {
} }
} }
// Emptying cached information after 15 minutes // Add expiration date to the data to send to the client for later refresh
versions.expiresAt = Date.now() + TIME_TO_LIVE;
// Emptying cached information after reaching said expiration date
setTimeout(() => { setTimeout(() => {
delete versions.current.changelog; delete versions.current.changelog;
delete versions.latest; delete versions.latest;
}, 15 * 60 * 1000 }, TIME_TO_LIVE);
);
callback(versions); callback(versions);
}); });