Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add active content warning in jQuery mode #467

Merged
merged 1 commit into from
Jan 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions www/css/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,18 @@
width: 200px;
}

.alert {
margin-bottom: 0 !important;
}

.alert-link {
text-decoration: underline;
}

#alertBoxHeader {
text-align: center;
}

@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
Expand Down
23 changes: 21 additions & 2 deletions www/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,8 @@ <h2>Configuration</h2>
<div id="openLocalFiles" style="display: none;">
Please select the .zim file (or all the .zimaa, .zimab etc in case of a split ZIM file)<br />
<input type="file" id="archiveFiles" multiple class="btn" accept=".zim,.dat,.idx,.txt,.zimaa,.zimab,.zimac,.zimad,.zimae,.zimaf,.zimag,.zimah,.zimai,.zimaj,.zimak,.zimal,.zimam,.ziman,.zimao,.zimap,.zimaq,.zimar,.zimas,.zimat,.zimau,.zimav,.zimaw,.zimax,.zimay,.zimaz,.zimba,.zimbb,.zimbc,.zimbd,.zimbe,.zimbf,.zimbg,.zimbh,.zimbi,.zimbj,.zimbk,.zimbl,.zimbm,.zimbn,.zimbo,.zimbp,.zimbq,.zimbr,.zimbs,.zimbt,.zimbu,.zimbv,.zimbw,.zimbx,.zimby,.zimbz" /><br />
<strong>Only Mediawiki-based (wiki*.zim* files, like wikipedia) and StackExchange contents have been tested</strong> for now. Audio/video/dynamic contents are not supported for now.<br />
<strong>Only Mediawiki-based (wiki*.zim* files, like wikipedia), StackExchange and some video-based ZIMs (e.g. TEDx) have been tested</strong>.
Dynamic content is not currently supported in jQuery mode.<br />
</div>
<div id="scanningForArchives" style="display: none;">
<br /> Scanning for archives... Please wait <img src="img/spinner.gif" alt="Please wait..." />
Expand All @@ -216,6 +217,22 @@ <h2>Configuration</h2>
</div>

<div class="container">
<div class="row">
<h3>Display settings</h3>
<div class="column">
<div class="panel panel-info" id="displaySettingsDiv">
<div class="panel-heading">Display options:</div>
<div class="panel-body">
<div class="checkbox">
<label>
<input type="checkbox" name="hideActiveContentWarning" id="hideActiveContentWarningCheck">
<strong>Permanently hide active content warning</strong> (for experienced users)
</label>
</div>
</div>
</div>
</div>
</div>
<div class="row">
<h3>Expert settings</h3>
<div class="column">
Expand All @@ -236,7 +253,7 @@ <h3>Expert settings</h3>
</div>
</div>
</div>
<div class="panel panel-info" id="apiStatusDiv">
<div class="panel panel-warning" id="apiStatusDiv">
<div class="panel-heading">API Status</div>
<div class="panel-body">
<div id="serviceWorkerStatus"></div>
Expand All @@ -261,6 +278,8 @@ <h3>Expert settings</h3>
<div id="articleList" class="list-group">
</div>
</div>
<!-- Bootstrap alert box -->
<div id="alertBoxHeader"></div>
<iframe id="articleContent" class="articleIFrame" src="article.html"></iframe>
</article>
<footer>
Expand Down
53 changes: 48 additions & 5 deletions www/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,18 @@ define(['jquery', 'zimArchiveLoader', 'util', 'uiUtil', 'cookies','abstractFiles
*/
var selectedArchive = null;

/**
* A global parameter object for storing variables that need to be remembered between page loads
* or across different functions
*
* @type Object
*/
var params = {};

// Set parameters and associated UI elements from cookie
params['hideActiveContentWarning'] = cookies.getItem('hideActiveContentWarning') === 'true';
document.getElementById('hideActiveContentWarningCheck').checked = params.hideActiveContentWarning;

/**
* Resize the IFrame height, so that it fills the whole available height in the window
*/
Expand Down Expand Up @@ -159,6 +171,7 @@ define(['jquery', 'zimArchiveLoader', 'util', 'uiUtil', 'cookies','abstractFiles
$('#articleListWithHeader').hide();
$("#searchingArticles").hide();
$('#articleContent').hide();
$('.alert').hide();
refreshAPIStatus();
return false;
});
Expand All @@ -178,22 +191,31 @@ define(['jquery', 'zimArchiveLoader', 'util', 'uiUtil', 'cookies','abstractFiles
$('#articleListWithHeader').hide();
$("#searchingArticles").hide();
$('#articleContent').hide();
$('.alert').hide();
return false;
});
$('input:radio[name=contentInjectionMode]').on('change', function(e) {
// Do the necessary to enable or disable the Service Worker
setContentInjectionMode(this.value);
});

$('input:checkbox[name=hideActiveContentWarning]').on('change', function (e) {
params.hideActiveContentWarning = this.checked ? true : false;
cookies.setItem('hideActiveContentWarning', params.hideActiveContentWarning, Infinity);
});

/**
* Displays of refreshes the API status shown to the user
*/
function refreshAPIStatus() {
var apiStatusPanel = document.getElementById('apiStatusDiv');
apiStatusPanel.classList.remove('panel-success', 'panel-warning');
var apiPanelClass = 'panel-success';
if (isMessageChannelAvailable()) {
$('#messageChannelStatus').html("MessageChannel API available");
$('#messageChannelStatus').removeClass("apiAvailable apiUnavailable")
.addClass("apiAvailable");
} else {
apiPanelClass = 'panel-warning';
$('#messageChannelStatus').html("MessageChannel API unavailable");
$('#messageChannelStatus').removeClass("apiAvailable apiUnavailable")
.addClass("apiUnavailable");
Expand All @@ -204,15 +226,19 @@ define(['jquery', 'zimArchiveLoader', 'util', 'uiUtil', 'cookies','abstractFiles
$('#serviceWorkerStatus').removeClass("apiAvailable apiUnavailable")
.addClass("apiAvailable");
} else {
apiPanelClass = 'panel-warning';
$('#serviceWorkerStatus').html("ServiceWorker API available, but not registered");
$('#serviceWorkerStatus').removeClass("apiAvailable apiUnavailable")
.addClass("apiUnavailable");
}
} else {
apiPanelClass = 'panel-warning';
$('#serviceWorkerStatus').html("ServiceWorker API unavailable");
$('#serviceWorkerStatus').removeClass("apiAvailable apiUnavailable")
.addClass("apiUnavailable");
}
apiStatusPanel.classList.add(apiPanelClass);

}

var contentInjectionMode;
Expand Down Expand Up @@ -640,6 +666,7 @@ define(['jquery', 'zimArchiveLoader', 'util', 'uiUtil', 'cookies','abstractFiles
*/
function searchDirEntriesFromPrefix(prefix) {
if (selectedArchive !== null && selectedArchive.isReady()) {
$('#activeContent').alert('close');
selectedArchive.findDirEntriesWithPrefix(prefix.trim(), MAX_SEARCH_RESULT_SIZE, populateListOfArticles);
} else {
$('#searchingArticles').hide();
Expand Down Expand Up @@ -713,12 +740,11 @@ define(['jquery', 'zimArchiveLoader', 'util', 'uiUtil', 'cookies','abstractFiles
$("#searchingArticles").show();
if (dirEntry.isRedirect()) {
selectedArchive.resolveRedirect(dirEntry, readArticle);
}
else {
} else {
params.isLandingPage = false;
readArticle(dirEntry);
}
}
else {
} else {
alert("Data files not set");
}
}
Expand Down Expand Up @@ -823,6 +849,13 @@ define(['jquery', 'zimArchiveLoader', 'util', 'uiUtil', 'cookies','abstractFiles
// remove any relative or absolute path from ZIM-style URLs.
// DEV: If you want to support more namespaces, add them to the END of the character set [-IJ] (not to the beginning)
var regexpTagsWithZimUrl = /(<(?:img|script|link|video|audio|source|track)\b[^>]*?\s)(?:src|href)(\s*=\s*["'])(?:\.\.\/|\/)+(?=[-IJ]\/)/ig;
// Regex below tests the html of an article for active content [kiwix-js #466]
// It inspects every <script> block in the html and matches in the following cases: 1) the script loads a UI application called app.js;
// 2) the script block has inline content that does not contain "importScript()" or "toggleOpenSection" (these strings are used widely
// in our fully supported wikimedia ZIMs, so they are excluded); 3) the script block is not of type "math" (these are MathJax markup
// scripts used extensively in Stackexchange ZIMs). Note that the regex will match ReactJS <script type="text/html"> markup, which is
// common in unsupported packaged UIs, e.g. PhET ZIMs.
var regexpActiveContent = /<script\b(?:(?![^>]+src\b)|(?=[^>]+src\b=["'][^"']+?app\.js))(?!>[^<]+(?:importScript\(\)|toggleOpenSection))(?![^>]+type\s*=\s*["'](?:math\/|[^"']*?math))/i;

// Cache for CSS styles contained in ZIM.
// It significantly speeds up subsequent page display. See kiwix-js issue #335
Expand All @@ -836,6 +869,11 @@ define(['jquery', 'zimArchiveLoader', 'util', 'uiUtil', 'cookies','abstractFiles
* @param {String} htmlArticle
*/
function displayArticleContentInIframe(dirEntry, htmlArticle) {
// Display Bootstrap warning alert if the landing page contains active content
if (!params.hideActiveContentWarning && params.isLandingPage) {
if (regexpActiveContent.test(htmlArticle)) uiUtil.displayActiveContentWarning();
}

// Replaces ZIM-style URLs of img, script, link and media tags with a data-kiwixurl to prevent 404 errors [kiwix-js #272 #376]
// This replacement also processes the URL to remove the path so that the URL is ready for subsequent jQuery functions
htmlArticle = htmlArticle.replace(regexpTagsWithZimUrl, '$1data-kiwixurl$2');
Expand Down Expand Up @@ -1132,6 +1170,8 @@ define(['jquery', 'zimArchiveLoader', 'util', 'uiUtil', 'cookies','abstractFiles
$("#searchingArticles").hide();
alert("Article with title " + title + " not found in the archive");
} else {
params.isLandingPage = false;
$('#activeContent').alert('close');
readArticle(dirEntry);
}
}).fail(function(e) { alert("Error reading article with title " + title + " : " + e); });
Expand All @@ -1145,6 +1185,8 @@ define(['jquery', 'zimArchiveLoader', 'util', 'uiUtil', 'cookies','abstractFiles
alert("Error finding random article.");
} else {
if (dirEntry.namespace === 'A') {
params.isLandingPage = false;
$('#activeContent').alert('close');
readArticle(dirEntry);
} else {
// If the random title search did not end up on an article,
Expand All @@ -1164,6 +1206,7 @@ define(['jquery', 'zimArchiveLoader', 'util', 'uiUtil', 'cookies','abstractFiles
$("#welcomeText").show();
} else {
if (dirEntry.namespace === 'A') {
params.isLandingPage = true;
readArticle(dirEntry);
} else {
console.error("The main page of this archive does not seem to be an article");
Expand Down
38 changes: 37 additions & 1 deletion www/js/lib/uiUtil.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,48 @@ define([], function() {
return url.replace(regexpRemoveUrlParameters, "$1");
}

/**
* Displays a Bootstrap warning alert with information about how to access content in a ZIM with unsupported active UI
*/
function displayActiveContentWarning() {
// We have to add the alert box in code, because Bootstrap removes it completely from the DOM when the user dismisses it
var alertHTML =
'<div id="activeContent" class="alert alert-warning alert-dismissible fade in">' +
'<a href="#" class="close" data-dismiss="alert" aria-label="close">&times;</a>' +
'<strong>Unable to display active content:</strong> This ZIM is not fully supported in jQuery mode.<br />' +
'Content may be available by searching above (type a space or a letter of the alphabet), or else ' +
'<a id="swModeLink" href="#contentInjectionModeDiv" class="alert-link">switch to Service Worker mode</a> ' +
'if your platform supports it. &nbsp;[<a id="stop" href="#displaySettingsDiv" class="alert-link">Permanently hide</a>]' +
'</div>';
document.getElementById('alertBoxHeader').innerHTML = alertHTML;
['swModeLink', 'stop'].forEach(function(id) {
// Define event listeners for both hyperlinks in alert box: these take the user to the Config tab and highlight
// the options that the user needs to select
document.getElementById(id).addEventListener('click', function () {
var elementID = id === 'stop' ? 'hideActiveContentWarningCheck' : 'serviceworkerModeRadio';
var thisLabel = document.getElementById(elementID).parentNode;
thisLabel.style.borderColor = 'red';
thisLabel.style.borderStyle = 'solid';
var btnHome = document.getElementById('btnHome');
[thisLabel, btnHome].forEach(function (ele) {
// Define event listeners to cancel the highlighting both on the highlighted element and on the Home tab
ele.addEventListener('mousedown', function () {
thisLabel.style.borderColor = '';
thisLabel.style.borderStyle = '';
});
});
document.getElementById('btnConfigure').click();
});
});
}

/**
* Functions and classes exposed by this module
*/
return {
feedNodeWithBlob: feedNodeWithBlob,
replaceCSSLinkWithInlineCSS: replaceCSSLinkWithInlineCSS,
removeUrlParameters: removeUrlParameters
removeUrlParameters: removeUrlParameters,
displayActiveContentWarning: displayActiveContentWarning
};
});