Merge pull request #1897 from thelounge/astorije/improve-version-checker

Improve the version checking and changelog features
This commit is contained in:
Jérémie Astori 2017-12-25 17:57:27 -05:00 committed by GitHub
commit 1fc2051c1d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 152 additions and 61 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

@ -99,7 +99,7 @@ function handleImageInPreview(content, container) {
const imageViewer = $("#image-viewer"); const imageViewer = $("#image-viewer");
$("#chat").on("click", ".toggle-thumbnail", function(event, data = {}) { $("#windows").on("click", ".toggle-thumbnail", function(event, data = {}) {
const link = $(this); const link = $(this);
// Passing `data`, specifically `data.pushState`, to not add the action to the // Passing `data`, specifically `data.pushState`, to not add the action to the
@ -158,7 +158,7 @@ function openImageViewer(link, {pushState = true} = {}) {
imageViewer.html(templates.image_viewer({ imageViewer.html(templates.image_viewer({
image: link.find("img").attr("src"), image: link.find("img").attr("src"),
link: link.attr("href"), link: link.attr("href"),
type: link.parent().hasClass("toggle-type-image") ? "image" : "link", type: link.parent().hasClass("toggle-type-link") ? "link" : "image",
hasPreviousImage: previousImage.length > 0, hasPreviousImage: previousImage.length > 0,
hasNextImage: nextImage.length > 0, hasNextImage: nextImage.length > 0,
})); }));
@ -171,10 +171,14 @@ function openImageViewer(link, {pushState = true} = {}) {
// History management // History management
if (pushState) { if (pushState) {
const clickTarget = let clickTarget = "";
`#${link.closest(".msg").attr("id")} ` + // Images can be in a message (channel URL previews) or not (window URL
`a.toggle-thumbnail[href="${link.attr("href")}"] ` + // preview, e.g. changelog). This is sub-optimal and needs improvement to
"img"; // make image preview more generic and not specific for channel previews.
if (link.closest(".msg").length > 0) {
clickTarget = `#${link.closest(".msg").attr("id")} `;
}
clickTarget += `a.toggle-thumbnail[href="${link.attr("href")}"] img`;
history.pushState({clickTarget}, null, null); history.pushState({clickTarget}, null, null);
} }
} }

View file

@ -4,19 +4,66 @@ const $ = require("jquery");
const socket = require("../socket"); const socket = require("../socket");
const templates = require("../../views"); const templates = require("../../views");
module.exports = {
requestIfNeeded,
};
// Requests version information if it hasn't been retrieved before (or if it has
// been removed from the page, i.e. when clicking on "Check now". Displays a
// loading state until received.
function requestIfNeeded() {
if ($("#version-checker").is(":empty")) {
renderVersionChecker({status: "loading"});
socket.emit("changelog");
}
}
socket.on("changelog", function(data) { socket.on("changelog", function(data) {
const container = $("#changelog-version-container"); // 1. Release notes window for the current version
$("#changelog").html(templates.windows.changelog(data.current));
const links = $("#changelog .changelog-text a");
// Make sure all links will open a new tab instead of exiting the application
links.attr("target", "_blank");
// Add required metadata to image links, to support built-in image viewer
links.has("img").addClass("toggle-thumbnail");
// 2. Version checker visible in Help window
let status;
if (data.latest) { if (data.latest) {
container.addClass("new-version"); status = "new-version";
container.html(templates.new_version(data));
} else if (data.current.changelog) { } else if (data.current.changelog) {
container.addClass("up-to-date"); status = "up-to-date";
container.text("The Lounge is up to date!");
} else { } else {
container.addClass("error"); status = "error";
container.text("An error has occurred, try to reload the page.");
} }
$("#changelog").html(templates.windows.changelog(data)); 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);
}); });