mirror of
https://github.com/kiwix/kiwix-js.git
synced 2025-09-09 23:34:20 -04:00
Both MV2 and MV3 are built for Chromium. Only MV2 for Firefox for now (until Service Workers are supported as backgroundscript.js).
This commit is contained in:
parent
bdfe50562c
commit
bd7393e921
@ -17,6 +17,8 @@ module.exports = {
|
||||
'no-extra-parens': 1,
|
||||
'no-unused-expressions': 1,
|
||||
'no-unused-vars': 1,
|
||||
'n/no-callback-literal': 0
|
||||
'n/no-callback-literal': 0,
|
||||
'object-shorthand': 0,
|
||||
'multiline-ternary': 0
|
||||
}
|
||||
}
|
||||
|
@ -19,11 +19,11 @@ Please follow these guidelines when contributing:
|
||||
- be sure to test your fix in both "JQuery" mode and "Service Worker" mode (see Configuration);
|
||||
- run the Unit tests (see below) in at least the above browsers.
|
||||
|
||||
If all the tests are working fine, you can finally test the extension versions, like this:
|
||||
If all the tests are working fine, you can finally test the extension versions. Plese note that we are using Manifest V3 for the Chromium extensions, and Manifest V2
|
||||
for the Firefox extension, so there are different instructions for the two browser families:
|
||||
|
||||
- Remove the '-WIP' from the version key from the manifest.json file present in the root of this repo;
|
||||
- In Chromium, you can install the extension by loading the root folder with Extensions -> Load Unpacked (with Developer Mode turned ON) -> select the root folder of the repository;
|
||||
- In Firefox, you can load an extension with Manage Your Extensions -> Debug Add-ons -> Load Temporary Add-on, and then pick any file in the repository.
|
||||
- In Firefox, you need to rename manifest.json to manifest.v3.json, and then rename manifest.v2.json to manifest.json. Then you can load the extension with Manage Your Extensions -> Debug Add-ons -> Load Temporary Add-on, and then pick any file in the repository.
|
||||
|
||||
If your feature works and tests are passing, make a PR, describe the testing you have done, and ask for a code review.
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/**
|
||||
* backgroundscript.js: Background script for the WebExtension
|
||||
* backgroundscript.js: Background script for the WebExtension Manifest V2
|
||||
*
|
||||
* Copyright 2017 Mossroy and contributors
|
||||
* License GPL v3:
|
||||
@ -20,22 +20,20 @@
|
||||
* along with Kiwix (file LICENSE-GPLv3.txt). If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
/* global chrome, browser */
|
||||
|
||||
// In order to work on both Firefox and Chromium/Chrome (and derivatives).
|
||||
// browser and chrome variables expose almost the same APIs
|
||||
var genericBrowser;
|
||||
if (typeof browser !== 'undefined') {
|
||||
// Firefox
|
||||
genericBrowser = browser;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
// Chromium/Chrome
|
||||
genericBrowser = chrome;
|
||||
}
|
||||
|
||||
genericBrowser.browserAction.onClicked.addListener(handleClick);
|
||||
|
||||
function handleClick(event) {
|
||||
genericBrowser.tabs.create({
|
||||
url: genericBrowser.runtime.getURL('/www/index.html')
|
||||
genericBrowser.browserAction.onClicked.addListener(function () {
|
||||
var newURL = chrome.runtime.getURL('www/index.html');
|
||||
chrome.tabs.create({ url: newURL });
|
||||
});
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
{
|
||||
"manifest_version": 2,
|
||||
"manifest_version": 3,
|
||||
"name": "Kiwix",
|
||||
"version": "3.8.1",
|
||||
|
||||
"description": "Kiwix : offline Wikipedia reader",
|
||||
"description": "Kiwix Offline Browser",
|
||||
|
||||
"icons": {
|
||||
"16": "www/img/icons/kiwix-16.png",
|
||||
@ -16,7 +16,7 @@
|
||||
"128": "www/img/icons/kiwix-128.png"
|
||||
},
|
||||
|
||||
"browser_action": {
|
||||
"action": {
|
||||
"default_icon": {
|
||||
"16": "www/img/icons/kiwix-16.png",
|
||||
"19": "www/img/icons/kiwix-19.png",
|
||||
@ -27,21 +27,23 @@
|
||||
"default_title": "Kiwix"
|
||||
},
|
||||
|
||||
"applications": {
|
||||
"gecko": {
|
||||
"id": "kiwix-html5-unlisted@kiwix.org"
|
||||
}
|
||||
},
|
||||
|
||||
"web_accessible_resources": ["www/index.html"],
|
||||
|
||||
"background": {
|
||||
"scripts": ["webextension/backgroundscript.js"]
|
||||
"service_worker": "service-worker.js"
|
||||
},
|
||||
|
||||
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
|
||||
"permissions": ["storage", "activeTab", "scripting"],
|
||||
|
||||
"author": "mossroy",
|
||||
"content_security_policy": {
|
||||
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self';",
|
||||
"sandbox": "sandbox allow-scripts allow-downloads allow-forms allow-popups allow-modals; script-src 'self' 'unsafe-inline' 'unsafe-eval'; child-src 'self';"
|
||||
},
|
||||
|
||||
"web_accessible_resources": [{
|
||||
"resources": ["www/index.html", "www/article.html"],
|
||||
"matches": ["https://*.kiwix.org/*"]
|
||||
}],
|
||||
|
||||
"author": "Kiwix",
|
||||
"homepage_url": "https://www.kiwix.org",
|
||||
"offline_enabled": true
|
||||
}
|
||||
|
47
manifest.v2.json
Normal file
47
manifest.v2.json
Normal file
@ -0,0 +1,47 @@
|
||||
{
|
||||
"manifest_version": 2,
|
||||
"name": "Kiwix",
|
||||
"version": "3.8.1",
|
||||
|
||||
"description": "Kiwix : offline Wikipedia reader",
|
||||
|
||||
"icons": {
|
||||
"16": "www/img/icons/kiwix-16.png",
|
||||
"19": "www/img/icons/kiwix-19.png",
|
||||
"32": "www/img/icons/kiwix-32.png",
|
||||
"38": "www/img/icons/kiwix-38.png",
|
||||
"48": "www/img/icons/kiwix-48.png",
|
||||
"64": "www/img/icons/kiwix-64.png",
|
||||
"90": "www/img/icons/kiwix-90.png",
|
||||
"128": "www/img/icons/kiwix-128.png"
|
||||
},
|
||||
|
||||
"browser_action": {
|
||||
"default_icon": {
|
||||
"16": "www/img/icons/kiwix-16.png",
|
||||
"19": "www/img/icons/kiwix-19.png",
|
||||
"32": "www/img/icons/kiwix-32.png",
|
||||
"38": "www/img/icons/kiwix-38.png",
|
||||
"64": "www/img/icons/kiwix-64.png"
|
||||
},
|
||||
"default_title": "Kiwix"
|
||||
},
|
||||
|
||||
"applications": {
|
||||
"gecko": {
|
||||
"id": "kiwix-html5-unlisted@kiwix.org"
|
||||
}
|
||||
},
|
||||
|
||||
"web_accessible_resources": ["www/index.html"],
|
||||
|
||||
"background": {
|
||||
"scripts": ["backgroundscript.js"]
|
||||
},
|
||||
|
||||
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
|
||||
|
||||
"author": "mossroy",
|
||||
"homepage_url": "https://www.kiwix.org",
|
||||
"offline_enabled": true
|
||||
}
|
@ -55,7 +55,7 @@ fi
|
||||
# Copy only the necessary files in a temporary directory
|
||||
mkdir -p tmp
|
||||
rm -rf tmp/*
|
||||
cp -r www webextension manifest.json manifest.webapp LICENSE-GPLv3.txt service-worker.js README.md tmp/
|
||||
cp -r www manifest.json manifest.v2.json manifest.webapp LICENSE-GPLv3.txt service-worker.js README.md tmp/
|
||||
# Remove unwanted files
|
||||
rm -f tmp/www/js/lib/libzim-*dev.*
|
||||
|
||||
@ -64,8 +64,10 @@ rm -f tmp/www/js/lib/libzim-*dev.*
|
||||
regexpNumericVersion='^[0-9\.]+$'
|
||||
if [[ $VERSION =~ $regexpNumericVersion ]] ; then
|
||||
sed -i -e "s/$VERSION_TO_REPLACE/$VERSION/" tmp/manifest.json
|
||||
sed -i -e "s/$VERSION_TO_REPLACE/$VERSION/" tmp/manifest.v2.json
|
||||
else
|
||||
sed -i -e "s/$VERSION_TO_REPLACE/$MAJOR_NUMERIC_VERSION/" tmp/manifest.json
|
||||
sed -i -e "s/$VERSION_TO_REPLACE/$MAJOR_NUMERIC_VERSION/" tmp/manifest.v2.json
|
||||
fi
|
||||
sed -i -e "s/$VERSION_TO_REPLACE/$VERSION/" tmp/manifest.webapp
|
||||
sed -i -e "s/$VERSION_TO_REPLACE/$VERSION/" tmp/service-worker.js
|
||||
@ -73,17 +75,26 @@ sed -i -e "s/$VERSION_TO_REPLACE/$VERSION/" tmp/www/js/app.js
|
||||
|
||||
mkdir -p build
|
||||
rm -rf build/*
|
||||
# Package for Chromium/Chrome
|
||||
scripts/package_chrome_extension.sh $DRYRUN $TAG -v $VERSION
|
||||
# Package for Chromium/Chrome with Manifest V3
|
||||
scripts/package_chrome_extension.sh -m 3 $DRYRUN $TAG -v $VERSION
|
||||
# Package for Chromium/Chrome with Manifest V2
|
||||
cp backgroundscript.js tmp/
|
||||
rm tmp/manifest.json
|
||||
mv tmp/manifest.v2.json tmp/manifest.json
|
||||
scripts/package_chrome_extension.sh -m 2 $DRYRUN $TAG -v $VERSION
|
||||
|
||||
# Package for Firefox and Firefox OS
|
||||
# We have to put a unique version string inside the manifest.json (which Chrome might not have accepted)
|
||||
# So we take the original manifest again, and replace the version inside it again
|
||||
cp manifest.json tmp/
|
||||
# So we take the original manifest v2 again, and replace the version inside it again
|
||||
cp manifest.v2.json tmp/manifest.json
|
||||
sed -i -e "s/$VERSION_TO_REPLACE/$VERSION_FOR_MOZILLA_MANIFEST/" tmp/manifest.json
|
||||
echo ""
|
||||
scripts/package_firefox_extension.sh $DRYRUN $TAG -v $VERSION
|
||||
echo ""
|
||||
scripts/package_firefoxos_app.sh $DRYRUN $TAG -v $VERSION
|
||||
cp -f ubuntu_touch/* tmp/
|
||||
sed -i -e "s/$VERSION_TO_REPLACE/$VERSION/" tmp/manifest.json
|
||||
echo ""
|
||||
scripts/package_ubuntu_touch_app.sh $DRYRUN $TAG -v $VERSION
|
||||
|
||||
# Change permissions on source files to match those expected by the server
|
||||
|
@ -3,19 +3,23 @@ BASEDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"/..
|
||||
cd "$BASEDIR"
|
||||
|
||||
# Reading arguments
|
||||
while getopts tdv: option; do
|
||||
while getopts m:tdv: option; do
|
||||
case "${option}" in
|
||||
m) MV=$OPTARG;; # Optionally indicates the manifest version we're using (2 or 3); if present, the version will be added to filename
|
||||
t) TAG="-t";; # Indicates that we're releasing a public version from a tag
|
||||
d) DRYRUN="-d";; # Indicates a dryrun test, that does not modify anything on the network
|
||||
v) VERSION=${OPTARG};;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -n $MV ]; then
|
||||
echo -e "\nManifest version requested: $MV"
|
||||
VERSION="MV$MV-$VERSION"
|
||||
fi
|
||||
echo "Packaging unsigned Chrome extension, version $VERSION"
|
||||
cd tmp
|
||||
zip -r ../build/kiwix-chrome-unsigned-extension-$VERSION.zip www webextension manifest.json LICENSE-GPLv3.txt service-worker.js README.md
|
||||
zip -r ../build/kiwix-chrome-unsigned-extension-$VERSION.zip www manifest.json LICENSE-GPLv3.txt service-worker.js README.md
|
||||
cd ..
|
||||
if [ "${TAG}zz" == "zz" ]; then
|
||||
if [ -z $TAG ]; then
|
||||
# Package the extension with Chrome or Chromium, if we're not packaging a public version
|
||||
if hash chromium-browser 2>/dev/null
|
||||
then
|
||||
@ -28,6 +32,7 @@ if [ "${TAG}zz" == "zz" ]; then
|
||||
echo "Signing the extension for $CHROME_BIN, version $VERSION"
|
||||
$CHROME_BIN --no-sandbox --pack-extension=tmp --pack-extension-key=./scripts/kiwix-html5.pem
|
||||
mv tmp.crx build/kiwix-chrome-signed-extension-$VERSION.crx
|
||||
ls -l build/kiwix-chrome-signed-extension-$VERSION.crx
|
||||
else
|
||||
echo "This unsigned extension must be manually uploaded to Google to be signed and distributed from their store"
|
||||
fi
|
||||
|
@ -23,6 +23,8 @@
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/* global chrome */
|
||||
|
||||
/**
|
||||
* App version number - ENSURE IT MATCHES VALUE IN app.js
|
||||
* DEV: Changing this will cause the browser to recognize that the Service Worker has changed, and it will
|
||||
@ -62,7 +64,6 @@ var useAssetsCache = true;
|
||||
*/
|
||||
var useAppCache = true;
|
||||
|
||||
|
||||
/**
|
||||
* A regular expression that matches the Content-Types of assets that may be stored in ASSETS_CACHE
|
||||
* Add any further Content-Types you wish to cache to the regexp, separated by '|'
|
||||
@ -99,74 +100,84 @@ const regexpByteRangeHeader = /^\s*bytes=(\d+)-/;
|
||||
/**
|
||||
* The list of files that the app needs in order to run entirely from offline code
|
||||
*/
|
||||
let precacheFiles = [
|
||||
".", // This caches the redirect to www/index.html, in case a user launches the app from its root directory
|
||||
"manifest.json",
|
||||
"service-worker.js",
|
||||
"www/css/app.css",
|
||||
"www/css/bootstrap.css",
|
||||
"www/css/kiwixJS_invert.css",
|
||||
"www/css/kiwixJS_mwInvert.css",
|
||||
"www/css/transition.css",
|
||||
"www/img/icons/kiwix-256.png",
|
||||
"www/img/icons/kiwix-32.png",
|
||||
"www/img/icons/kiwix-60.png",
|
||||
"www/img/spinner.gif",
|
||||
"www/img/Icon_External_Link.png",
|
||||
"www/index.html",
|
||||
"www/article.html",
|
||||
"www/main.html",
|
||||
"www/js/app.js",
|
||||
"www/js/init.js",
|
||||
"www/js/lib/abstractFilesystemAccess.js",
|
||||
"www/js/lib/arrayFromPolyfill.js",
|
||||
"www/js/lib/bootstrap.bundle.js",
|
||||
"www/js/lib/filecache.js",
|
||||
"www/js/lib/jquery-3.7.0.slim.min.js",
|
||||
"www/js/lib/promisePolyfill.js",
|
||||
"www/js/lib/require.js",
|
||||
"www/js/lib/settingsStore.js",
|
||||
"www/js/lib/uiUtil.js",
|
||||
"www/js/lib/utf8.js",
|
||||
"www/js/lib/util.js",
|
||||
"www/js/lib/xzdec_wrapper.js",
|
||||
"www/js/lib/zstddec_wrapper.js",
|
||||
"www/js/lib/zimArchive.js",
|
||||
"www/js/lib/zimArchiveLoader.js",
|
||||
"www/js/lib/zimDirEntry.js",
|
||||
"www/js/lib/zimfile.js",
|
||||
"www/js/lib/fontawesome/fontawesome.js",
|
||||
"www/js/lib/fontawesome/solid.js"
|
||||
const precacheFiles = [
|
||||
'.', // This caches the redirect to www/index.html, in case a user launches the app from its root directory
|
||||
'manifest.json',
|
||||
'service-worker.js',
|
||||
'www/css/app.css',
|
||||
'www/css/bootstrap.css',
|
||||
'www/css/kiwixJS_invert.css',
|
||||
'www/css/kiwixJS_mwInvert.css',
|
||||
'www/css/transition.css',
|
||||
'www/img/icons/kiwix-256.png',
|
||||
'www/img/icons/kiwix-32.png',
|
||||
'www/img/icons/kiwix-60.png',
|
||||
'www/img/spinner.gif',
|
||||
'www/img/Icon_External_Link.png',
|
||||
'www/index.html',
|
||||
'www/article.html',
|
||||
'www/main.html',
|
||||
'www/js/app.js',
|
||||
'www/js/init.js',
|
||||
'www/js/lib/abstractFilesystemAccess.js',
|
||||
'www/js/lib/arrayFromPolyfill.js',
|
||||
'www/js/lib/bootstrap.bundle.js',
|
||||
'www/js/lib/filecache.js',
|
||||
'www/js/lib/jquery-3.7.0.slim.min.js',
|
||||
'www/js/lib/promisePolyfill.js',
|
||||
'www/js/lib/require.js',
|
||||
'www/js/lib/settingsStore.js',
|
||||
'www/js/lib/uiUtil.js',
|
||||
'www/js/lib/utf8.js',
|
||||
'www/js/lib/util.js',
|
||||
'www/js/lib/xzdec_wrapper.js',
|
||||
'www/js/lib/zstddec_wrapper.js',
|
||||
'www/js/lib/zimArchive.js',
|
||||
'www/js/lib/zimArchiveLoader.js',
|
||||
'www/js/lib/zimDirEntry.js',
|
||||
'www/js/lib/zimfile.js',
|
||||
'www/js/lib/fontawesome/fontawesome.js',
|
||||
'www/js/lib/fontawesome/solid.js'
|
||||
];
|
||||
|
||||
if ('WebAssembly' in self) {
|
||||
precacheFiles.push(
|
||||
"www/js/lib/xzdec-wasm.js",
|
||||
"www/js/lib/xzdec-wasm.wasm",
|
||||
"www/js/lib/zstddec-wasm.js",
|
||||
"www/js/lib/zstddec-wasm.wasm",
|
||||
"www/js/lib/libzim-wasm.js",
|
||||
"www/js/lib/libzim-wasm.wasm"
|
||||
'www/js/lib/xzdec-wasm.js',
|
||||
'www/js/lib/xzdec-wasm.wasm',
|
||||
'www/js/lib/zstddec-wasm.js',
|
||||
'www/js/lib/zstddec-wasm.wasm',
|
||||
'www/js/lib/libzim-wasm.js',
|
||||
'www/js/lib/libzim-wasm.wasm'
|
||||
);
|
||||
} else {
|
||||
precacheFiles.push(
|
||||
"www/js/lib/xzdec-asm.js",
|
||||
"www/js/lib/zstddec-asm.js",
|
||||
"www/js/lib/libzim-asm.js"
|
||||
'www/js/lib/xzdec-asm.js',
|
||||
'www/js/lib/zstddec-asm.js',
|
||||
'www/js/lib/libzim-asm.js'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* If we're in a Chromium extension, add a listener to launch the tab when the icon is clicked
|
||||
*/
|
||||
if (typeof chrome !== 'undefined' && chrome.action) {
|
||||
chrome.action.onClicked.addListener(function () {
|
||||
var newURL = chrome.runtime.getURL('www/index.html');
|
||||
chrome.tabs.create({ url: newURL });
|
||||
});
|
||||
}
|
||||
|
||||
// Process install event
|
||||
self.addEventListener("install", function (event) {
|
||||
console.debug("[SW] Install Event processing");
|
||||
self.addEventListener('install', function (event) {
|
||||
console.debug('[SW] Install Event processing');
|
||||
// DEV: We can't skip waiting because too many params are loaded at an early stage from the old file before the new one can activate...
|
||||
// self.skipWaiting();
|
||||
// We try to circumvent the browser's cache by adding a header to the Request, and it ensures all files are explicitly versioned
|
||||
var requests = precacheFiles.map(function (urlPath) {
|
||||
return new Request(urlPath + '?v' + appVersion, { cache: 'no-cache' });
|
||||
});
|
||||
if (!regexpExcludedURLSchema.test(requests[0].url)) event.waitUntil(
|
||||
caches.open(APP_CACHE).then(function (cache) {
|
||||
if (!regexpExcludedURLSchema.test(requests[0].url)) {
|
||||
event.waitUntil(caches.open(APP_CACHE).then(function (cache) {
|
||||
return Promise.all(
|
||||
requests.map(function (request) {
|
||||
return fetch(request).then(function (response) {
|
||||
@ -178,8 +189,8 @@ self.addEventListener("install", function (event) {
|
||||
});
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
// Allow sw to control current page
|
||||
@ -211,7 +222,7 @@ let fetchCaptureEnabled = false;
|
||||
*/
|
||||
self.addEventListener('fetch', function (event) {
|
||||
// Only cache GET requests
|
||||
if (event.request.method !== "GET") return;
|
||||
if (event.request.method !== 'GET') return;
|
||||
var rqUrl = event.request.url;
|
||||
var urlObject = new URL(rqUrl);
|
||||
// Test the URL with parameters removed
|
||||
@ -231,7 +242,7 @@ self.addEventListener('fetch', function (event) {
|
||||
// The response was not found in the cache so we look for it in the ZIM
|
||||
// and add it to the cache if it is an asset type (css or js)
|
||||
if (cache === ASSETS_CACHE && regexpZIMUrlWithNamespace.test(strippedUrl)) {
|
||||
let range = event.request.headers.get('range');
|
||||
const range = event.request.headers.get('range');
|
||||
return fetchUrlFromZIM(urlObject, range).then(function (response) {
|
||||
// Add css or js assets to ASSETS_CACHE (or update their cache entries) unless the URL schema is not supported
|
||||
if (regexpCachedContentTypes.test(response.headers.get('Content-Type')) &&
|
||||
@ -252,7 +263,7 @@ self.addEventListener('fetch', function (event) {
|
||||
}
|
||||
return response;
|
||||
}).catch(function (error) {
|
||||
console.debug("[SW] Network request failed and no cache.", error);
|
||||
console.debug('[SW] Network request failed and no cache.', error);
|
||||
});
|
||||
}
|
||||
})
|
||||
@ -287,7 +298,7 @@ self.addEventListener('fetch', function (event) {
|
||||
if (useAppCache !== oldValue) console.debug('[SW] Use of appCache was switched to: ' + useAppCache);
|
||||
}
|
||||
if (event.data.action === 'getCacheNames') {
|
||||
event.ports[0].postMessage({ 'app': APP_CACHE, 'assets': ASSETS_CACHE });
|
||||
event.ports[0].postMessage({ app: APP_CACHE, assets: ASSETS_CACHE });
|
||||
}
|
||||
if (event.data.action.checkCache) {
|
||||
// Checks and returns the caching strategy: checkCache key should contain a sample URL string to test
|
||||
@ -347,9 +358,9 @@ function fetchUrlFromZIM(urlObject, range) {
|
||||
// So it's probably better to send all we have: hopefully it will avoid some subsequent requests of
|
||||
// the browser to get the following chunks (which would trigger some other complete reads in the ZIM file)
|
||||
// This might be improved in the future with the libzim wasm backend, that should be able to handle ranges.
|
||||
let partsOfRangeHeader = regexpByteRangeHeader.exec(range);
|
||||
let begin = partsOfRangeHeader[1];
|
||||
let end = contentLength - 1;
|
||||
const partsOfRangeHeader = regexpByteRangeHeader.exec(range);
|
||||
const begin = partsOfRangeHeader[1];
|
||||
const end = contentLength - 1;
|
||||
slicedData = slicedData.slice(begin);
|
||||
|
||||
headers.set('Content-Range', 'bytes ' + begin + '-' + end + '/' + contentLength);
|
||||
@ -360,7 +371,7 @@ function fetchUrlFromZIM(urlObject, range) {
|
||||
// HTTP status is usually 200, but has to bee 206 when partial content (range) is sent
|
||||
status: range ? 206 : 200,
|
||||
statusText: 'OK',
|
||||
headers: headers
|
||||
headers
|
||||
};
|
||||
|
||||
var httpResponse = new Response(slicedData, responseInit);
|
||||
@ -374,8 +385,8 @@ function fetchUrlFromZIM(urlObject, range) {
|
||||
}
|
||||
};
|
||||
outgoingMessagePort.postMessage({
|
||||
'action': 'askForContent',
|
||||
'title': titleWithNameSpace
|
||||
action: 'askForContent',
|
||||
title: titleWithNameSpace
|
||||
}, [messageChannel.port2]);
|
||||
});
|
||||
}
|
||||
@ -388,10 +399,14 @@ function fetchUrlFromZIM(urlObject, range) {
|
||||
*/
|
||||
function fromCache (cache, requestUrl) {
|
||||
// Prevents use of Cache API if user has disabled it
|
||||
if (!(useAppCache && cache === APP_CACHE || useAssetsCache && cache === ASSETS_CACHE)) return Promise.reject('disabled');
|
||||
if (!(useAppCache && cache === APP_CACHE || useAssetsCache && cache === ASSETS_CACHE)) {
|
||||
return Promise.reject(new Error('disabled'));
|
||||
}
|
||||
return caches.open(cache).then(function (cacheObj) {
|
||||
return cacheObj.match(requestUrl).then(function (matching) {
|
||||
if (!matching || matching.status === 404) return Promise.reject('no-match');
|
||||
if (!matching || matching.status === 404) {
|
||||
return Promise.reject(new Error('no-match'));
|
||||
}
|
||||
console.debug('[SW] Supplying ' + requestUrl + ' from ' + cache + '...');
|
||||
return matching;
|
||||
});
|
||||
@ -407,8 +422,9 @@ function fromCache(cache, requestUrl) {
|
||||
*/
|
||||
function updateCache (cache, request, response) {
|
||||
// Prevents use of Cache API if user has disabled it
|
||||
if (!response.ok || !(useAppCache && cache === APP_CACHE || useAssetsCache && cache === ASSETS_CACHE))
|
||||
if (!response.ok || !(useAppCache && cache === APP_CACHE || useAssetsCache && cache === ASSETS_CACHE)) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return caches.open(cache).then(function (cacheObj) {
|
||||
console.debug('[SW] Adding ' + (request.url || request) + ' to ' + cache + '...');
|
||||
return cacheObj.put(request, response);
|
||||
|
@ -32,7 +32,6 @@
|
||||
|
||||
define(['jquery', 'zimArchiveLoader', 'uiUtil', 'settingsStore', 'abstractFilesystemAccess'],
|
||||
function ($, zimArchiveLoader, uiUtil, settingsStore, abstractFilesystemAccess) {
|
||||
|
||||
/**
|
||||
* The delay (in milliseconds) between two "keepalive" messages sent to the ServiceWorker (so that it is not stopped
|
||||
* by the browser, and keeps the MessageChannel to communicate with the application)
|
||||
@ -108,7 +107,7 @@ define(['jquery', 'zimArchiveLoader', 'uiUtil', 'settingsStore','abstractFilesys
|
||||
(isServiceWorkerAvailable() ? 'serviceworker' : 'jquery');
|
||||
// A parameter to circumvent anti-fingerprinting technology in browsers that do not support WebP natively by substituting images
|
||||
// directly with the canvas elements produced by the WebP polyfill [kiwix-js #835]. NB This is only currently used in jQuery mode.
|
||||
params['useCanvasElementsForWebpTranscoding']; // Value is determined in uiUtil.determineCanvasElementsWorkaround(), called when setting the content injection mode
|
||||
params['useCanvasElementsForWebpTranscoding'] = null; // Value is determined in uiUtil.determineCanvasElementsWorkaround(), called when setting the content injection mode
|
||||
|
||||
// An object to hold the current search and its state (allows cancellation of search across modules)
|
||||
appstate['search'] = {
|
||||
@ -182,11 +181,11 @@ define(['jquery', 'zimArchiveLoader', 'uiUtil', 'settingsStore','abstractFilesys
|
||||
document.getElementById('bypassAppCacheCheck').checked = !params.appCache;
|
||||
document.getElementById('appVersion').textContent = 'Kiwix ' + params.appVersion;
|
||||
// We check here if we have to warn the user that we switched to ServiceWorkerMode
|
||||
// This is only needed if the ServiceWorker mode is available, or we are in a Firefox Extension that supports Service Workers
|
||||
// This is only needed if the ServiceWorker mode is available, or we are in an Extension that supports Service Workers
|
||||
// outside of the extension environment, AND the user's settings are stuck on jQuery mode, AND the user has not already been
|
||||
// alerted about the switch to ServiceWorker mode by default
|
||||
if ((isServiceWorkerAvailable() || isMessageChannelAvailable() && /^moz-extension:/i.test(window.location.protocol))
|
||||
&& params.contentInjectionMode === 'jquery' && !params.defaultModeChangeAlertDisplayed) {
|
||||
if ((isServiceWorkerAvailable() || isMessageChannelAvailable() && /^(moz|chrome)-extension:/i.test(window.location.protocol)) &&
|
||||
params.contentInjectionMode === 'jquery' && !params.defaultModeChangeAlertDisplayed) {
|
||||
// Attempt to upgrade user to ServiceWorker mode
|
||||
params.contentInjectionMode = 'serviceworker';
|
||||
} else if (params.contentInjectionMode === 'serviceworker') {
|
||||
@ -329,16 +328,12 @@ define(['jquery', 'zimArchiveLoader', 'uiUtil', 'settingsStore','abstractFilesys
|
||||
$('#prefix').on('keyup', function (e) {
|
||||
if (selectedArchive !== null && selectedArchive.isReady()) {
|
||||
// Prevent processing by keyup event if we already handled the keypress in keydown event
|
||||
if (keyPressHandled)
|
||||
keyPressHandled = false;
|
||||
else
|
||||
onKeyUpPrefix(e);
|
||||
if (keyPressHandled) { keyPressHandled = false; } else { onKeyUpPrefix(e); }
|
||||
}
|
||||
});
|
||||
// Restore the search results if user goes back into prefix field
|
||||
$('#prefix').on('focus', function () {
|
||||
if (document.getElementById('prefix').value !== '')
|
||||
document.getElementById('articleListWithHeader').style.display = '';
|
||||
if (document.getElementById('prefix').value !== '') { document.getElementById('articleListWithHeader').style.display = ''; }
|
||||
});
|
||||
// Hide the search results if user moves out of prefix field
|
||||
$('#prefix').on('blur', function () {
|
||||
@ -505,7 +500,7 @@ define(['jquery', 'zimArchiveLoader', 'uiUtil', 'settingsStore','abstractFilesys
|
||||
refreshCacheStatus();
|
||||
});
|
||||
document.getElementById('disableDragAndDropCheck').addEventListener('change', function () {
|
||||
params.disableDragAndDrop = this.checked ? true : false;
|
||||
params.disableDragAndDrop = !!this.checked;
|
||||
settingsStore.setItem('disableDragAndDrop', params.disableDragAndDrop, Infinity);
|
||||
uiUtil.systemAlert('<p>We will now attempt to reload the app to apply the new setting.</p>' +
|
||||
'<p>(If you cancel, then the setting will only be applied when you next start the app.)</p>', 'Reload app', true).then(function (result) {
|
||||
@ -515,20 +510,20 @@ define(['jquery', 'zimArchiveLoader', 'uiUtil', 'settingsStore','abstractFilesys
|
||||
});
|
||||
});
|
||||
$('input:checkbox[name=hideActiveContentWarning]').on('change', function () {
|
||||
params.hideActiveContentWarning = this.checked ? true : false;
|
||||
params.hideActiveContentWarning = !!this.checked;
|
||||
settingsStore.setItem('hideActiveContentWarning', params.hideActiveContentWarning, Infinity);
|
||||
});
|
||||
$('input:checkbox[name=showUIAnimations]').on('change', function () {
|
||||
params.showUIAnimations = this.checked ? true : false;
|
||||
params.showUIAnimations = !!this.checked;
|
||||
settingsStore.setItem('showUIAnimations', params.showUIAnimations, Infinity);
|
||||
});
|
||||
$('input:checkbox[name=useHomeKeyToFocusSearchBar]').on('change', function () {
|
||||
params.useHomeKeyToFocusSearchBar = this.checked ? true : false;
|
||||
params.useHomeKeyToFocusSearchBar = !!this.checked;
|
||||
settingsStore.setItem('useHomeKeyToFocusSearchBar', params.useHomeKeyToFocusSearchBar, Infinity);
|
||||
switchHomeKeyToFocusSearchBar();
|
||||
});
|
||||
$('input:checkbox[name=openExternalLinksInNewTabs]').on('change', function () {
|
||||
params.openExternalLinksInNewTabs = this.checked ? true : false;
|
||||
params.openExternalLinksInNewTabs = !!this.checked;
|
||||
settingsStore.setItem('openExternalLinksInNewTabs', params.openExternalLinksInNewTabs, Infinity);
|
||||
});
|
||||
document.getElementById('appThemeSelect').addEventListener('change', function (e) {
|
||||
@ -578,7 +573,6 @@ define(['jquery', 'zimArchiveLoader', 'uiUtil', 'settingsStore','abstractFilesys
|
||||
uiUtil.checkUpdateStatus(appstate);
|
||||
}, 10000);
|
||||
|
||||
|
||||
// Adds an event listener to kiwix logo and bottom navigation bar which gets triggered when these elements are dragged.
|
||||
// Returning false prevents their dragging (which can cause some unexpected behavior)
|
||||
// Doing that in javascript is the only way to make it cross-browser compatible
|
||||
@ -602,8 +596,7 @@ define(['jquery', 'zimArchiveLoader', 'uiUtil', 'settingsStore','abstractFilesys
|
||||
var isIframeAccessible = true;
|
||||
try {
|
||||
iframeContentWindow.removeEventListener('keydown', focusPrefixOnHomeKey);
|
||||
}
|
||||
catch (err) {
|
||||
} catch (err) {
|
||||
console.error('The iframe is probably not accessible', err);
|
||||
isIframeAccessible = false;
|
||||
}
|
||||
@ -615,10 +608,8 @@ define(['jquery', 'zimArchiveLoader', 'uiUtil', 'settingsStore','abstractFilesys
|
||||
// only for initial empty iFrame loaded using `src` attribute
|
||||
// in any other case listener gets removed on reloading of iFrame content
|
||||
iframeContentWindow.addEventListener('keydown', focusPrefixOnHomeKey);
|
||||
}
|
||||
// when the feature is not active
|
||||
else {
|
||||
// remove event listener for window(outside iframe)
|
||||
} else {
|
||||
// When the feature is not active, remove event listener for window (outside iframe)
|
||||
window.removeEventListener('keydown', focusPrefixOnHomeKey);
|
||||
// if feature is deactivated and no zim content is loaded yet
|
||||
iframeContentWindow.removeEventListener('keydown', focusPrefixOnHomeKey);
|
||||
@ -859,7 +850,7 @@ define(['jquery', 'zimArchiveLoader', 'uiUtil', 'settingsStore','abstractFilesys
|
||||
uriParams += '&appTheme=' + params.appTheme;
|
||||
uriParams += '&showUIAnimations=' + params.showUIAnimations;
|
||||
window.location.href = params.referrerExtensionURL + '/www/index.html' + uriParams;
|
||||
'Beam me down, Scotty!';
|
||||
console.log('Beam me down, Scotty!');
|
||||
};
|
||||
uiUtil.systemAlert(message, 'Warning!', true).then(function (response) {
|
||||
if (response) {
|
||||
@ -1018,8 +1009,7 @@ define(['jquery', 'zimArchiveLoader', 'uiUtil', 'settingsStore','abstractFilesys
|
||||
try {
|
||||
var dummyMessageChannel = new MessageChannel();
|
||||
if (dummyMessageChannel) return true;
|
||||
}
|
||||
catch (e) {
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
@ -1039,8 +1029,9 @@ define(['jquery', 'zimArchiveLoader', 'uiUtil', 'settingsStore','abstractFilesys
|
||||
// DEV: See explanation below for why we access localStorage directly here
|
||||
var PWASuccessfullyLaunched = localStorage.getItem(params.keyPrefix + 'PWA_launch') === 'success';
|
||||
var allowInternetAccess = settingsStore.getItem('allowInternetAccess') === 'true';
|
||||
var message = params.defaultModeChangeAlertDisplayed ? '<p>To enable the Service Worker, we ' :
|
||||
('<p>We shall attempt to switch you to ServiceWorker mode (this is now the default). ' +
|
||||
var message = params.defaultModeChangeAlertDisplayed
|
||||
? '<p>To enable the Service Worker, we '
|
||||
: ('<p>We shall attempt to switch you to ServiceWorker mode (this is now the default). ' +
|
||||
'It supports more types of ZIM archives and is much more robust.</p><p>We ');
|
||||
message += 'need one-time access to our secure server so that the app can re-launch as a Progressive Web App (PWA). ' +
|
||||
'If available, the PWA will work offline, but will auto-update periodically when online as per the ' +
|
||||
@ -1063,7 +1054,7 @@ define(['jquery', 'zimArchiveLoader', 'uiUtil', 'settingsStore','abstractFilesys
|
||||
// regarding the location of the key to be able to retrieve it in init.js before settingsStore is initialized
|
||||
localStorage.setItem(params.keyPrefix + 'PWA_launch', 'fail');
|
||||
window.location.href = params.PWAServer + 'www/index.html' + uriParams;
|
||||
'Beam me up, Scotty!';
|
||||
console.log('Beam me up, Scotty!');
|
||||
};
|
||||
var checkPWAIsOnline = function () {
|
||||
uiUtil.spinnerDisplay(true, 'Checking server access...');
|
||||
@ -1169,7 +1160,7 @@ define(['jquery', 'zimArchiveLoader', 'uiUtil', 'settingsStore','abstractFilesys
|
||||
document.getElementById('articleListWithHeader').style.display = 'none';
|
||||
$('#articleContent').contents().empty();
|
||||
|
||||
if (title && !(''===title)) {
|
||||
if (title && !(title === '')) {
|
||||
goToArticle(title);
|
||||
} else if (titleSearch && titleSearch !== '') {
|
||||
document.getElementById('prefix').value = titleSearch;
|
||||
@ -1255,9 +1246,9 @@ define(['jquery', 'zimArchiveLoader', 'uiUtil', 'settingsStore','abstractFilesys
|
||||
if (storages.length === 1) {
|
||||
selectedStorage = storages[0];
|
||||
} else {
|
||||
uiUtil.systemAlert('Something weird happened with the DeviceStorage API : found a directory without prefix : '
|
||||
+ archiveDirectory + ', but there were ' + storages.length
|
||||
+ ' storages found with getDeviceStorages instead of 1', 'Error: unprefixed directory');
|
||||
uiUtil.systemAlert('Something weird happened with the DeviceStorage API : found a directory without prefix : ' +
|
||||
archiveDirectory + ', but there were ' + storages.length +
|
||||
' storages found with getDeviceStorages instead of 1', 'Error: unprefixed directory');
|
||||
}
|
||||
}
|
||||
resetCssCache();
|
||||
@ -1269,7 +1260,6 @@ define(['jquery', 'zimArchiveLoader', 'uiUtil', 'settingsStore','abstractFilesys
|
||||
// callbackError which is called in case of an error
|
||||
uiUtil.systemAlert(message, label);
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -1321,7 +1311,6 @@ define(['jquery', 'zimArchiveLoader', 'uiUtil', 'settingsStore','abstractFilesys
|
||||
function handleIframeDrop (e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
||||
}
|
||||
|
||||
function handleFileDrop (packet) {
|
||||
@ -1390,7 +1379,7 @@ define(['jquery', 'zimArchiveLoader', 'uiUtil', 'settingsStore','abstractFilesys
|
||||
request.response.name = url;
|
||||
resolve(request.response);
|
||||
} else {
|
||||
reject('HTTP status ' + request.status + ' when reading ' + url);
|
||||
reject(new Error('HTTP status ' + request.status + ' when reading ' + url));
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -1444,7 +1433,7 @@ define(['jquery', 'zimArchiveLoader', 'uiUtil', 'settingsStore','abstractFilesys
|
||||
appstate.search.status = 'cancelled';
|
||||
// Initiate a new search object and point appstate.search to it (the zimArchive search object will continue to point to the old object)
|
||||
// DEV: Technical explanation: the appstate.search is a pointer to an underlying object assigned in memory, and we are here defining a new object
|
||||
// in memory {'prefix': prefix, 'status': 'init', .....}, and pointing appstate.search to it; the old search object that was passed to selectedArchive
|
||||
// in memory {prefix: prefix, status: 'init', .....}, and pointing appstate.search to it; the old search object that was passed to selectedArchive
|
||||
// (zimArchive.js) continues to exist in the scope of the functions initiated by the previous search until all Promises have returned
|
||||
appstate.search = { prefix: prefix, status: 'init', type: '', size: params.maxSearchResultsSize };
|
||||
var activeContent = document.getElementById('activeContent');
|
||||
@ -1479,7 +1468,7 @@ define(['jquery', 'zimArchiveLoader', 'uiUtil', 'settingsStore','abstractFilesys
|
||||
} else if (nbDirEntry >= params.maxSearchResultsSize) {
|
||||
message = 'First ' + params.maxSearchResultsSize + ' articles found (refine your search).';
|
||||
} else {
|
||||
message = 'Finished. ' + (nbDirEntry ? nbDirEntry : 'No') + ' articles found' + (
|
||||
message = 'Finished. ' + (nbDirEntry || 'No') + ' articles found' + (
|
||||
reportingSearch.type === 'basic' ? ': try fewer words for full search.' : '.'
|
||||
);
|
||||
}
|
||||
@ -1608,8 +1597,7 @@ define(['jquery', 'zimArchiveLoader', 'uiUtil', 'settingsStore','abstractFilesys
|
||||
|
||||
if (iframeArticleContent.contentWindow) {
|
||||
// Configure home key press to focus #prefix only if the feature is in active state
|
||||
if (params.useHomeKeyToFocusSearchBar)
|
||||
iframeArticleContent.contentWindow.addEventListener('keydown', focusPrefixOnHomeKey);
|
||||
if (params.useHomeKeyToFocusSearchBar) { iframeArticleContent.contentWindow.addEventListener('keydown', focusPrefixOnHomeKey); }
|
||||
if (params.openExternalLinksInNewTabs) {
|
||||
// Add event listener to iframe window to check for links to external resources
|
||||
iframeArticleContent.contentWindow.addEventListener('click', function (event) {
|
||||
@ -1800,10 +1788,10 @@ define(['jquery', 'zimArchiveLoader', 'uiUtil', 'settingsStore','abstractFilesys
|
||||
|
||||
var iframeContentDocument = iframeArticleContent.contentDocument;
|
||||
if (!iframeContentDocument && window.location.protocol === 'file:') {
|
||||
uiUtil.systemAlert('You seem to be opening kiwix-js with the file:// protocol, which is blocked by your browser for security reasons.'
|
||||
+ '<br/><br/>The easiest way to run it is to download and run it as a browser extension (from the vendor store).'
|
||||
+ '<br/><br/>Else you can open it through a web server : either through a local one (http://localhost/...) or through a remote one (but you need SSL : https://webserver/...)'
|
||||
+ "<br/><br/>Another option is to force your browser to accept that (but you'll open a security breach) : on Chrome, you can start it with --allow-file-access-from-files command-line argument; on Firefox, you can set privacy.file_unique_origin to false in about:config");
|
||||
uiUtil.systemAlert('You seem to be opening kiwix-js with the file:// protocol, which is blocked by your browser for security reasons.' +
|
||||
'<br/><br/>The easiest way to run it is to download and run it as a browser extension (from the vendor store).' +
|
||||
'<br/><br/>Else you can open it through a web server : either through a local one (http://localhost/...) or through a remote one (but you need SSL : https://webserver/...)' +
|
||||
"<br/><br/>Another option is to force your browser to accept that (but you'll open a security breach) : on Chrome, you can start it with --allow-file-access-from-files command-line argument; on Firefox, you can set privacy.file_unique_origin to false in about:config");
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1816,9 +1804,11 @@ define(['jquery', 'zimArchiveLoader', 'uiUtil', 'settingsStore','abstractFilesys
|
||||
docBody = docBody ? docBody[0] : null;
|
||||
if (docBody) {
|
||||
// Add any missing classes stripped from the <html> tag
|
||||
if (htmlCSS) htmlCSS.forEach(function (cl) {
|
||||
if (htmlCSS) {
|
||||
htmlCSS.forEach(function (cl) {
|
||||
docBody.classList.add(cl);
|
||||
});
|
||||
}
|
||||
// Deflect drag-and-drop of ZIM file on the iframe to Config
|
||||
docBody.addEventListener('dragover', handleIframeDragover);
|
||||
docBody.addEventListener('drop', handleIframeDrop);
|
||||
@ -1846,8 +1836,7 @@ define(['jquery', 'zimArchiveLoader', 'uiUtil', 'settingsStore','abstractFilesys
|
||||
}
|
||||
if (iframeArticleContent.contentWindow) {
|
||||
// Configure home key press to focus #prefix only if the feature is in active state
|
||||
if (params.useHomeKeyToFocusSearchBar)
|
||||
iframeArticleContent.contentWindow.addEventListener('keydown', focusPrefixOnHomeKey);
|
||||
if (params.useHomeKeyToFocusSearchBar) { iframeArticleContent.contentWindow.addEventListener('keydown', focusPrefixOnHomeKey); }
|
||||
// when unloaded remove eventListener to avoid memory leaks
|
||||
iframeArticleContent.contentWindow.onunload = function () {
|
||||
iframeArticleContent.contentWindow.removeEventListener('keydown', focusPrefixOnHomeKey);
|
||||
@ -1998,7 +1987,7 @@ define(['jquery', 'zimArchiveLoader', 'uiUtil', 'settingsStore','abstractFilesys
|
||||
selectedArchive.getDirEntryByPath(url).then(function (dirEntry) {
|
||||
if (!dirEntry) {
|
||||
cssCache.set(url, ''); // Prevent repeated lookups of this unfindable asset
|
||||
throw 'DirEntry ' + typeof dirEntry;
|
||||
throw new Error('DirEntry ' + typeof dirEntry);
|
||||
}
|
||||
var mimetype = dirEntry.getMimetype();
|
||||
var readFile = /^text\//i.test(mimetype) ? selectedArchive.readUtf8File : selectedArchive.readBinaryFile;
|
||||
@ -2069,7 +2058,7 @@ define(['jquery', 'zimArchiveLoader', 'uiUtil', 'settingsStore','abstractFilesys
|
||||
var source = mediaSource.getAttribute('src');
|
||||
source = source ? uiUtil.deriveZimUrlFromRelativeUrl(source, baseUrl) : null;
|
||||
// We have to exempt text tracks from using deriveZimUrlFromRelativeurl due to a bug in Firefox [kiwix-js #496]
|
||||
source = source ? source : decodeURIComponent(mediaSource.dataset.kiwixurl);
|
||||
source = source || decodeURIComponent(mediaSource.dataset.kiwixurl);
|
||||
if (!source || !regexpZIMUrlWithNamespace.test(source)) {
|
||||
if (source) console.error('No usable media source was found for: ' + source);
|
||||
return;
|
||||
@ -2115,13 +2104,13 @@ define(['jquery', 'zimArchiveLoader', 'uiUtil', 'settingsStore','abstractFilesys
|
||||
var stateObj = {};
|
||||
var urlParameters;
|
||||
var stateLabel;
|
||||
if (title && !(''===title)) {
|
||||
if (title && !(title === '')) {
|
||||
// Prevents creating a double history for the same page
|
||||
if (history.state && history.state.title === title) return;
|
||||
stateObj.title = title;
|
||||
urlParameters = '?title=' + title;
|
||||
stateLabel = 'Wikipedia Article : ' + title;
|
||||
} else if (titleSearch && !(''===titleSearch)) {
|
||||
} else if (titleSearch && !(titleSearch === '')) {
|
||||
stateObj.titleSearch = titleSearch;
|
||||
urlParameters = '?titleSearch=' + titleSearch;
|
||||
stateLabel = 'Wikipedia search : ' + titleSearch;
|
||||
@ -2131,7 +2120,6 @@ define(['jquery', 'zimArchiveLoader', 'uiUtil', 'settingsStore','abstractFilesys
|
||||
window.history.pushState(stateObj, stateLabel, urlParameters);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extracts the content of the given article pathname, or a downloadable file, from the ZIM
|
||||
*
|
||||
@ -2226,5 +2214,4 @@ define(['jquery', 'zimArchiveLoader', 'uiUtil', 'settingsStore','abstractFilesys
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
@ -19,8 +19,12 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Kiwix (file LICENSE-GPLv3.txt). If not, see <http://www.gnu.org/licenses/>
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
/* eslint-disable no-global-assign */
|
||||
/* global $, define, webpMachine, webpHero, params */
|
||||
|
||||
// DEV: Put your RequireJS definition in the rqDef array below, and any function exports in the function parenthesis of the define statement
|
||||
// We need to do it this way in order to load WebP polyfills conditionally. The WebP polyfills are only needed by a few old browsers, so loading them
|
||||
// only if needed saves approximately 1MB of memory.
|
||||
@ -32,7 +36,6 @@ if (webpMachine) {
|
||||
}
|
||||
|
||||
define(rqDef, function (settingsStore, util) {
|
||||
|
||||
/**
|
||||
* Displays a Bootstrap alert or confirm dialog box depending on the options provided
|
||||
*
|
||||
@ -51,7 +54,7 @@ define(rqDef, function(settingsStore, util) {
|
||||
label = label || (isConfirm ? 'Confirmation' : 'Message');
|
||||
return util.PromiseQueue.enqueue(function () {
|
||||
return new Promise(function (resolve, reject) {
|
||||
if (!message) reject('Missing body message');
|
||||
if (!message) reject(new Error('Missing body message'));
|
||||
// Set the text to the modal and its buttons
|
||||
document.getElementById('approveConfirm').textContent = approveConfirmLabel;
|
||||
document.getElementById('declineConfirm').textContent = declineConfirmLabel;
|
||||
@ -339,20 +342,22 @@ define(rqDef, function(settingsStore, util) {
|
||||
function displayFileDownloadAlert (title, download, contentType, content) {
|
||||
var downloadAlert = document.getElementById('downloadAlert');
|
||||
downloadAlert.style.display = 'block';
|
||||
if (!downloadAlertSetup) downloadAlert.querySelector('button[data-hide]').addEventListener('click', function() {
|
||||
if (!downloadAlertSetup) {
|
||||
downloadAlert.querySelector('button[data-hide]').addEventListener('click', function () {
|
||||
// We are setting up the alert for the first time
|
||||
downloadAlert.style.display = 'none';
|
||||
});
|
||||
}
|
||||
downloadAlertSetup = true;
|
||||
// Download code adapted from https://stackoverflow.com/a/19230668/9727685
|
||||
// Set default contentType if none was provided
|
||||
if (!contentType) contentType = 'application/octet-stream';
|
||||
var a = document.createElement('a');
|
||||
var blob = new Blob([content], { 'type': contentType });
|
||||
var blob = new Blob([content], { type: contentType });
|
||||
// If the filename to use for saving has not been specified, construct it from title
|
||||
var filename = download === true ? title.replace(/^.*\/([^\/]+)$/, '$1') : download;
|
||||
var filename = download === true ? title.replace(/^.*\/([^/]+)$/, '$1') : download;
|
||||
// Make filename safe
|
||||
filename = filename.replace(/[\/\\:*?"<>|]/g, '_');
|
||||
filename = filename.replace(/[/\\:*?"<>|]/g, '_');
|
||||
a.href = window.URL.createObjectURL(blob);
|
||||
a.target = '_blank';
|
||||
a.type = contentType;
|
||||
@ -364,8 +369,9 @@ define(rqDef, function(settingsStore, util) {
|
||||
alertMessage.innerHTML = '<strong>Download</strong> If the download does not start, please tap the following link: ';
|
||||
// We have to add the anchor to a UI element for Firefox to be able to click it programmatically: see https://stackoverflow.com/a/27280611/9727685
|
||||
alertMessage.appendChild(a);
|
||||
try { a.click(); }
|
||||
catch (err) {
|
||||
try {
|
||||
a.click();
|
||||
} catch (err) {
|
||||
// If the click fails, user may be able to download by manually clicking the link
|
||||
// But for IE11 we need to force use of the saveBlob method with the onclick event
|
||||
if (window.navigator && window.navigator.msSaveBlob) {
|
||||
@ -406,9 +412,11 @@ define(rqDef, function(settingsStore, util) {
|
||||
});
|
||||
}
|
||||
}
|
||||
if (updateAlert) updateAlert.querySelector('button[data-hide]').addEventListener('click', function () {
|
||||
if (updateAlert) {
|
||||
updateAlert.querySelector('button[data-hide]').addEventListener('click', function () {
|
||||
updateAlert.style.display = 'none';
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a server is accessible by attempting to load a test image from the server
|
||||
@ -453,11 +461,12 @@ define(rqDef, function(settingsStore, util) {
|
||||
*/
|
||||
function isElementInView (el, fully) {
|
||||
var rect = el.getBoundingClientRect();
|
||||
if (fully)
|
||||
if (fully) {
|
||||
return rect.top > 0 && rect.bottom < window.innerHeight && rect.left > 0 && rect.right < window.innerWidth;
|
||||
else
|
||||
} else {
|
||||
return rect.top < window.innerHeight && rect.bottom > 0 && rect.left < window.innerWidth && rect.right > 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the animation effect between various sections
|
||||
@ -481,7 +490,7 @@ define(rqDef, function(settingsStore, util) {
|
||||
*
|
||||
*/
|
||||
function applyAnimationToSection (section) {
|
||||
if (section == 'home') {
|
||||
if (section === 'home') {
|
||||
if (!$('#configuration').is(':hidden')) {
|
||||
document.getElementById('configuration').classList.add('slideOut_R');
|
||||
setTimeout(function () {
|
||||
@ -498,7 +507,7 @@ define(rqDef, function(settingsStore, util) {
|
||||
setTimeout(function () {
|
||||
document.getElementById('articleContent').style.display = '';
|
||||
}, 300);
|
||||
} else if (section == 'config') {
|
||||
} else if (section === 'config') {
|
||||
if (!$('#about').is(':hidden')) {
|
||||
$('#about').addClass('slideOut_R');
|
||||
$('#configuration').addClass('slideIn_R');
|
||||
@ -515,7 +524,7 @@ define(rqDef, function(settingsStore, util) {
|
||||
setTimeout(function () {
|
||||
document.getElementById('configuration').style.display = '';
|
||||
}, 300);
|
||||
} else if (section == 'about') {
|
||||
} else if (section === 'about') {
|
||||
if (!$('#configuration').is(':hidden')) {
|
||||
document.getElementById('configuration').classList.add('slideOut_L');
|
||||
setTimeout(function () {
|
||||
@ -613,7 +622,7 @@ define(rqDef, function(settingsStore, util) {
|
||||
// If we are in Config and a real document has been loaded already, expose return link so user can see the result of the change
|
||||
// DEV: The Placeholder string below matches the dummy article.html that is loaded before any articles are loaded
|
||||
if (document.getElementById('liConfigureNav').classList.contains('active') && doc &&
|
||||
doc.title !== "Placeholder for injecting an article into the iframe") {
|
||||
doc.title !== 'Placeholder for injecting an article into the iframe') {
|
||||
showReturnLink();
|
||||
}
|
||||
}
|
||||
@ -655,8 +664,8 @@ define(rqDef, function(settingsStore, util) {
|
||||
function reportSearchProviderToAPIStatusPanel (provider) {
|
||||
var providerAPI = document.getElementById('searchProviderStatus');
|
||||
if (providerAPI) { // NB we need this so that tests don't fail
|
||||
providerAPI.textContent = 'Search Provider: ' + (/^fulltext/.test(provider) ? 'Title + Xapian [' + provider + ']' :
|
||||
/^title/.test(provider) ? 'Title only [' + provider + ']' : 'Not initialized');
|
||||
providerAPI.textContent = 'Search Provider: ' + (/^fulltext/.test(provider) ? 'Title + Xapian [' + provider + ']'
|
||||
: /^title/.test(provider) ? 'Title only [' + provider + ']' : 'Not initialized');
|
||||
providerAPI.className = /^fulltext/.test(provider) ? 'apiAvailable' : !/ERROR/.test(provider) ? 'apiUnavailable' : 'apiBroken';
|
||||
}
|
||||
}
|
||||
@ -682,8 +691,9 @@ define(rqDef, function(settingsStore, util) {
|
||||
message += '</p><p style="word-break:break-all;">' + clickedAnchor.href + '</p>';
|
||||
systemAlert(message, 'Opening external link', true).then(function (response) {
|
||||
if (response) {
|
||||
if (!target)
|
||||
if (!target) {
|
||||
target = '_blank';
|
||||
}
|
||||
window.open(clickedAnchor.href, target);
|
||||
}
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user