diff --git a/.eslintrc.cjs b/.eslintrc.cjs index c00a10fe..467c3378 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -19,6 +19,7 @@ module.exports = { 'no-unused-vars': 1, 'n/no-callback-literal': 0, 'object-shorthand': 0, + 'one-var': 0, 'multiline-ternary': 0, 'no-extend-native': 0, 'no-global-assign': 0 diff --git a/.github/workflows/build-electron.yml b/.github/workflows/build-electron.yml index 28f07047..b836ed6b 100644 --- a/.github/workflows/build-electron.yml +++ b/.github/workflows/build-electron.yml @@ -53,7 +53,7 @@ jobs: echo "Changing to the dist directory" cd dist && pwd # Get archive name - packagedFile="$(grep -m1 'params\[.packagedFile' www/js/init.js | sed -E 's/^[^"]+"([^"]+\.zim)".+/\1/')" + packagedFile=$(grep -m1 'params\[.packagedFile' www/js/init.js | sed -E "s/^.+'([^']+\.zim)'.+/\1/") # If file doesn't exist in FS, download it if [ ! -f "archives/$packagedFile" ]; then # Generalize the name if cron_launched and download it @@ -144,7 +144,7 @@ jobs: run: | echo "Changing to the dist directory" cd dist && pwd - $packagedFile = (Select-String 'packagedFile' "www\js\init.js" -List) -ireplace '^[^"]+"([^"]+\.zim)".+', '$1' + $packagedFile = (Select-String 'packagedFile' "www\js\init.js" -List) -ireplace "^.+'([^']+\.zim)'.+", '$1' if ($packagedFile -and ! (Test-Path "archives\$packagedFile" -PathType Leaf)) { # File not in archives, so generalize the name (if nightly) and download it Write-Host "`nDownloading https://download.kiwix.org/zim/$packagedFile" @@ -190,7 +190,7 @@ jobs: } # To ensure there is enough disk space, we can delete the archive that is no longer needed rm -r dist/archives - ./scripts/Create-DraftRelease -buildonly -tag_name $INPUT_VERSION_E -portableonly -nobundle -wingetprompt N + ./scripts/Create-DraftRelease -buildonly -tag_name $INPUT_VERSION_E -portableonly -nobundle -wingetprompt N -nobranchcheck } - name: Publish packages if: github.event.inputs.target != 'artefacts' @@ -241,7 +241,7 @@ jobs: run: | echo "Changing to the dist directory" cd dist && pwd - $packagedFile = (Select-String 'packagedFile' "www\js\init.js" -List) -ireplace '^[^"]+"([^"]+\.zim)".+', '$1' + $packagedFile = (Select-String 'packagedFile' "www\js\init.js" -List) -ireplace "^.+'([^']+\.zim)'.+", '$1' if ($packagedFile -and ! (Test-Path "archives\$packagedFile" -PathType Leaf)) { # File not in archives, so generalize the name (if nightly) and download it Write-Host "`nDownloading https://download.kiwix.org/zim/$packagedFile" diff --git a/package.json b/package.json index ff35facc..2fe318d7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "kiwix-js-electron", "productName": "Kiwix JS Electron", - "version": "2.5.4-E", + "version": "2.5.4-N", "description": "Kiwix JS packaged for the Electron framework", "main": "main.cjs", "type": "module", @@ -88,6 +88,7 @@ "index.html", "CHANGELOG.md", "LICENCE", + "manifest.json", "www/**", "preload.cjs", "main.cjs", @@ -97,10 +98,13 @@ "scripts": { "serve": "vite", "preview": "del-cli dist && npm run build-src && vite preview", - "build": "del-cli dist && npm run build-src && npm run build-min", + "prebuild": "del-cli dist", + "build": "rollup --config --file dist/www/js/bundle.js && rollup --config --file dist/www/js/bundle.min.js --environment BUILD:production", + "prebuild-min": "del-cli dist", "build-min": "rollup --config --file dist/www/js/bundle.min.js --environment BUILD:production", + "prebuild-src": "del-cli dist", "build-src": "rollup --config --file dist/www/js/bundle.js", - "del-dist": "del-cli dist", + "del": "del-cli dist", "start": "electron .", "dist-win": "electron-builder build --win --projectDir dist", "dist-win-nsis": "electron-builder build --win NSIS:x64 NSIS:ia32 --projectDir dist", @@ -154,3 +158,5 @@ "electron-updater": "^6.1.0" } } + + diff --git a/package.json.nwjs b/package.json.nwjs index 4d7afaf7..fe298930 100644 --- a/package.json.nwjs +++ b/package.json.nwjs @@ -16,18 +16,20 @@ "start": "run --x86 --mirror https://dl.nwjs.io/ .", "dist-win-x86": "build --tasks win-x86 --mirror https://dl.nwjs.io/ .", "dist-win-x64": "build --tasks win-x64 --mirror https://dl.nwjs.io/ .", - "dist": "@powershell -NoProfile -ExecutionPolicy Unrestricted -Command ./scripts/Build_NWJS.ps1" + "dist": "@powershell -NoProfile -ExecutionPolicy Unrestricted -Command ./scripts/Build-NWJS.ps1" }, "build-xp": { "nwVersion": "0.14.7", "output": "bld/nwjs/win-x86-xp", "files": [ - "archives/**", "service-worker.js", "index.html", "CHANGELOG.md", "LICENCE", + "manifest.json", "www/**", + "archives/README.md", + "archives/wikip*.*", "!**/*.dev.{js,wasm}" ], "win": { @@ -38,12 +40,14 @@ "nwVersion": "0.72.0", "output": "bld/nwjs/win-x64", "files": [ - "archives/**", "service-worker.js", "index.html", "CHANGELOG.md", "LICENCE", + "manifest.json", "www/**", + "archives/README.md", + "archives/wikip*.*", "!**/*.dev.{js,wasm}" ], "win": { @@ -58,6 +62,7 @@ "index.html", "CHANGELOG.md", "LICENCE", + "manifest.json", "www/**", "archives/README.md", "archives/wikip*.*", diff --git a/scripts/Build-NWJS.ps1 b/scripts/Build-NWJS.ps1 index cd7919c4..8ef0309c 100644 --- a/scripts/Build-NWJS.ps1 +++ b/scripts/Build-NWJS.ps1 @@ -1,6 +1,7 @@ [CmdletBinding()] param ( - [switch]$only32bit = $false + [switch]$only32bit = $false, + [switch]$usesdk = $false ) $builds = @("win-ia32", "win-xp") if (-Not $only32bit) { @@ -41,17 +42,21 @@ foreach ($build in $builds) { $folderTarget = "$PSScriptRoot\..\dist\bld\nwjs\$build-$version" $target = "$folderTarget\kiwix_js_windows$sep$appBuild" $fullTarget = "$target-$build" + $sdk = "" + if ($usesdk) { + $sdk = "-sdk" + } $ZipFolder = "$PSScriptRoot\..\dist\node_modules\nwjs-builder-phoenix\caches\" - $ZipLocation = $ZipFolder + "nwjs-v$version-$build.zip" + $ZipLocation = $ZipFolder + "nwjs$sdk-v$version-$build.zip" $UnzipLocation = "$ZipLocation-extracted\" - $buildLocation = "$ZipLocation-extracted\nwjs-v$version-$build\" + $buildLocation = "$ZipLocation-extracted\nwjs$sdk-v$version-$build\" if (-Not (Test-Path $ZipFolder -PathType Container)) { mkdir $ZipFolder } if (-Not (Test-Path $buildLocation -PathType Container)) { # We need to download and/or unzip the release, as it is not available if (-Not (Test-Path $ZipLocation -PathType Leaf)) { - $serverFile = "https://dl.nwjs.io/v$version/nwjs-v$version-$build.zip" + $serverFile = "https://dl.nwjs.io/v$version/nwjs$sdk-v$version-$build.zip" "Downloading $serverFile" Invoke-WebRequest -Uri $serverFile -OutFile $ZipLocation } @@ -72,7 +77,7 @@ foreach ($build in $builds) { # Copy latest binary x64 cp $buildLocation\* $fullTarget -Recurse $root = $PSScriptRoot -replace 'scripts.*$', '' - cp $root\dist\package.json, $root\dist\service-worker.js, $root\dist\index.html, $root\CHANGELOG.md, $root\LICENSE, $root\dist\www $fullTarget -Recurse + cp $root\dist\package.json, $root\dist\service-worker.js, $root\dist\index.html, $root\CHANGELOG.md, $root\LICENSE, $root\manifest.json, $root\dist\www $fullTarget -Recurse # Remove unwanted files # del $fullTarget\www\js\lib\libzim-*.dev.* "Copying archive..." @@ -101,3 +106,4 @@ foreach ($build in $builds) { Compress-Archive "$PSScriptRoot\..\dist\bld\nwjs\$build-$version\*" "$PSScriptRoot\..\dist\bld\nwjs\$foldername.zip" -Force "Build $OBuild finished.`n" } + diff --git a/scripts/Create-DraftRelease.ps1 b/scripts/Create-DraftRelease.ps1 index 80d9ed5a..57604be1 100644 --- a/scripts/Create-DraftRelease.ps1 +++ b/scripts/Create-DraftRelease.ps1 @@ -11,7 +11,8 @@ param ( [string]$electronbuild = "", # 'local' or 'cloud' [switch]$portableonly = $false, # If set, only the portable electron build will be built. Implies local electron build. [switch]$updatewinget = $false, - [string]$wingetprompt = "" # Provide an override response (Y/N) to the winget prompt at the end of the script - for automation + [string]$wingetprompt = "", # Provide an override response (Y/N) to the winget prompt at the end of the script - for automation + [switch]$nobranchcheck = $false # If set, will not check that the current branch is correct for the type of app to build ) # DEV: To build Electron packages for all platforms and NWJS for XP and Vista in a single release, use, e.g., "v1.3.0E+N" (Electron + NWJS) # DEV: To build UWP + Electron in a single release (for WikiMed or Wikivoyage), use "v1.3.0+E" (plus Electron) @@ -185,8 +186,12 @@ if ($numeric_tag -ne $file_tag_numeric) { $actual_branch = git rev-parse --abbrev-ref HEAD if ($branch -ne $actual_branch) { Write-Host "`nError! The branch you are on [$actual_branch] does not match the type of app you wish to build [$branch]!" -ForegroundColor Red - Write-Host "Please switch to the correct branch and try again.`n" -ForegroundColor Red - exit 1 + if (-not $nobranchcheck) { + Write-Host "Please switch to the correct branch and try again.`n" -ForegroundColor Red + exit 1 + } else { + Write-Host "Continuing anyway as you have specified the -nobranchcheck option.`n" -ForegroundColor Yellow + } } # Determine type of Electron build if any @@ -290,7 +295,7 @@ if (-Not ($dryrun -or $buildonly)) { if (-Not $nobundle) { "`nBuilding production bundle with rollup..." if (-Not $dryrun) { - & npm run del-dist && npm run build-min + & npm run build-min } else { "[DRYRUN] & npm run build" } @@ -428,7 +433,7 @@ if ($dryrun -or $buildonly -or $release.assets_url -imatch '^https:') { } } # Check for the existence of the requested packaged archive - $packagedFile = (Select-String 'packagedFile' "dist\www\js\init.js" -List) -ireplace '^[^"]+"([^"]+\.zim)".+', '$1' + $packagedFile = (Select-String 'packagedFile' "dist\www\js\init.js" -List) -ireplace "^.+['`"]([^'`"]+\.zim)['`"].+", '$1' if ($packagedFile -and ! (Test-Path "dist\archives\$packagedFile" -PathType Leaf)) { # File not in archives $downloadArchiveChk = Read-Host "`nWe could not find the packaged archive, do you wish to download it? Y/N" diff --git a/service-worker.js b/service-worker.js index 83f1549f..f1e21d8f 100644 --- a/service-worker.js +++ b/service-worker.js @@ -60,7 +60,7 @@ var useAssetsCache = true; * This is an expert setting in Configuration * @type {Boolean} */ - var useAppCache = true; +var useAppCache = true; /** * A Boolean that governs whether images are displayed diff --git a/www/index.html b/www/index.html index 91a74a5c..26a63618 100644 --- a/www/index.html +++ b/www/index.html @@ -742,6 +742,7 @@
diff --git a/www/js/app.js b/www/js/app.js
index 5bf58056..2bbbabdb 100644
--- a/www/js/app.js
+++ b/www/js/app.js
@@ -55,7 +55,7 @@ const DELAY_BETWEEN_KEEPALIVE_SERVICEWORKER = 30000;
*/
// The global parameter and app state objects are defined in init.js
-/* global params, appstate, nw, electronAPI, Windows, webpMachine */
+/* global params, appstate, nw, electronAPI, Windows, webpMachine, dialog */
// Placeholders for the article container, the article window and the article DOM
var articleContainer = document.getElementById('articleContent');
@@ -80,6 +80,9 @@ appstate['search'] = {
params['storeType'] = null;
params['storeType'] = settingsStore.getBestAvailableStorageAPI();
+// A parameter to determine whether the webkitdirectory API is available
+params['webkitdirectory'] = util.webkitdirectorySupported();
+
// Placeholder for the alert box header element, so it can be displayed and hidden easily
const alertBoxHeader = document.getElementById('alertBoxHeader');
@@ -754,6 +757,11 @@ document.getElementById('btnHome').addEventListener('click', function () {
var currentArchive = document.getElementById('currentArchive');
var currentArchiveLink = document.getElementById('currentArchiveLink');
var openCurrentArchive = document.getElementById('openCurrentArchive');
+var archiveFilesLegacy = document.getElementById('archiveFilesLegacy');
+var archiveDirLegacy = document.getElementById('archiveDirLegacy');
+if (!params.webkitdirectory) {
+ document.getElementById('archiveDirLegacy').style.display = 'none';
+}
function setTab (activeBtn) {
// Highlight the selected section in the navbar
@@ -776,8 +784,8 @@ function setTab (activeBtn) {
} else {
cssUIThemeGetOrSet(determinedTheme);
}
- if (typeof Windows === 'undefined' && typeof window.showOpenFilePicker !== 'function' && !window.dialog) {
- // If not UWP, File System Access API, or Electron methods, display legacy File Select
+ if (typeof Windows === 'undefined' && typeof window.showOpenFilePicker !== 'function' && !window.dialog && !params.webkitdirectory) {
+ // If not UWP, File System Access API, webkitdirectory API or Electron methods, display legacy File Select
document.getElementById('archiveFilesDiv').style.display = 'none';
document.getElementById('archivesFound').style.display = 'none';
document.getElementById('instructions').style.display = appstate.selectedArchive ? 'none' : 'block';
@@ -1063,8 +1071,11 @@ function getNativeFSHandle (callback) {
} else {
// We have failed to load a picked archive via the File System API, but if params.storedFilePath exists, then the archive
// was launched with Electron APIs, so we can get the folder that way
- if (params.storedFilePath) params.pickedFolder = params.storedFilePath.replace(/[\\/]+[^\\/]+$/, '');
- searchForArchivesInPreferencesOrStorage();
+ if (params.storedFile && params.storedFilePath) params.pickedFolder = params.pickedFolder = params.storedFilePath.replace(/[^\\/]+$/, '');
+ scanNodeFolderforArchives(params.pickedFolder, function () {
+ // We now have the list of archives in the dropdown, so we try to select the storedFile
+ setLocalArchiveFromArchiveList(params.storedFile);
+ });
}
}
});
@@ -1081,7 +1092,7 @@ document.getElementById('btnAbout').addEventListener('click', function () {
}
// Check if we're 'unclicking' the button
var searchDiv = document.getElementById('about');
- if (searchDiv.style.display != 'none') {
+ if (searchDiv.style.display !== 'none') {
setTab();
return;
}
@@ -1122,10 +1133,17 @@ function selectArchive (list) {
// Void any previous picked file to prevent it launching
if (params.pickedFile && params.pickedFile.name !== selected) {
params.pickedFile = '';
+ params.storedFile = '';
}
- if (!window.fs && window.showOpenFilePicker) {
+ if (window.showOpenFilePicker) {
getNativeFSHandle(function (handle) {
if (!handle) {
+ if (window.fs && params.storedFilePath) {
+ // Fall back to using the Electron APIs
+ params.pickedFolder = params.storedFilePath.replace(/[^\\/]+$/, '');
+ setLocalArchiveFromArchiveList(selected);
+ return;
+ }
console.error('No handle was retrieved');
uiUtil.systemAlert('We could not get a handle to the previously picked file or folder!
' +
'This is probably because the contents of the folder have changed. Please try picking it again.');
@@ -1149,8 +1167,27 @@ function selectArchive (list) {
});
}
});
+ } else if (typeof MSApp === 'undefined' && !window.fs && params.webkitdirectory) {
+ // If we don't have any picked files or directories...
+ if (!archiveDirLegacy.files.length && !archiveFilesLegacy.files.length) {
+ appstate.waitForFileSelect = selected;
+ // No files are set, so we need to ask user to select the file or directory again
+ if (params.pickedFolder || document.getElementById('archiveList').options.length > 1) {
+ archiveDirLegacy.click();
+ } else {
+ archiveFilesLegacy.click();
+ }
+ } else {
+ console.debug('Files are set, attempting to select ' + selected);
+ params.pickedFile = selected;
+ if (archiveDirLegacy.files.length) {
+ params.pickedFolder = archiveDirLegacy.files[0].webkitRelativePath.replace(/\/[^/]*$/, '');
+ params.pickedFile = '';
+ }
+ setLocalArchiveFromArchiveList(selected);
+ }
} else {
- setLocalArchiveFromArchiveList([selected]);
+ setLocalArchiveFromArchiveList(selected);
}
setTimeout(function () {
document.getElementById('openLocalFiles').style.display = 'none';
@@ -1161,7 +1198,30 @@ function selectArchive (list) {
}
// Legacy file picker is used as a fallback when all other pickers are unavailable
-document.getElementById('archiveFilesLegacy').addEventListener('change', setLocalArchiveFromFileSelect);
+archiveFilesLegacy.addEventListener('change', function (files) {
+ var filesArray = Array.from(files.target.files);
+ params.pickedFolder = null;
+ params.pickedFile = filesArray[0];
+ params.storedFile = params.pickedFile.name.replace(/\.zim\w\w$/i, '.zimaa');
+ if (params.webkitdirectory) {
+ settingsStore.setItem('pickedFolder', '', Infinity);
+ processFilesArray(filesArray);
+ }
+ var selected = params.storedFile;
+ if (appstate.waitForFileSelect) {
+ selected = appstate.waitForFileSelect;
+ appstate.waitForFileSelect = null;
+ // Select the selected file in the dropdown list of archives
+ document.getElementById('archiveList').value = selected;
+ console.debug('Files are set, attempting to select ' + selected);
+ }
+ if (!window.fs && params.webkitdirectory) {
+ // populateDropDownListOfArchives([params.pickedFile], true);
+ setLocalArchiveFromArchiveList(selected);
+ } else {
+ setLocalArchiveFromFileList(files.target.files);
+ }
+});
// But in preference, use UWP, File System Access API
document.getElementById('archiveFile').addEventListener('click', function () {
if (typeof Windows !== 'undefined' && typeof Windows.Storage !== 'undefined') {
@@ -1173,9 +1233,45 @@ document.getElementById('archiveFile').addEventListener('click', function () {
} else if (window.fs && window.dialog) {
// Electron file picker if showOpenFilePicker is not available
dialog.openFile();
+ } else {
+ // Legacy file picker
+ archiveFilesLegacy.click();
}
});
-document.getElementById('archiveFiles').addEventListener('click', function () {
+// Legacy webkitdirectory file picker is used as a fallback when File System Access API is unavailable
+archiveDirLegacy.addEventListener('change', function (files) {
+ if (files.target.files.length) {
+ var filesArray = Array.from(files.target.files);
+ // Supports reading in NWJS/Electron frameworks that have a path property on the File object
+ var path = filesArray[0] ? filesArray[0].path ? filesArray[0].path : filesArray[0].webkitRelativePath : '';
+ params.pickedFile = null;
+ var oldDir = params.pickedFolder;
+ params.pickedFolder = path.replace(/[^\\/]*$/, '');
+ // If we're picking a different directroy, don't look for the previously picked file in it
+ if (params.pickedFolder !== oldDir) {
+ params.storedFile = null;
+ params.storedFilePath = null;
+ }
+ settingsStore.setItem('pickedFolder', params.pickedFolder, Infinity);
+ if (document.getElementById('archiveList').options.length === 0) {
+ params.storedFile = null;
+ }
+ processFilesArray(filesArray);
+ var selected = '';
+ if (appstate.waitForFileSelect) {
+ selected = appstate.waitForFileSelect;
+ appstate.waitForFileSelect = null;
+ // Select the selected file in the dropdown list of archives
+ document.getElementById('archiveList').value = selected;
+ console.debug('Files are set, attempting to select ' + selected);
+ }
+ if (selected) setLocalArchiveFromArchiveList(selected);
+ } else {
+ appstate.waitForFileSelect = null;
+ console.log('User cancelled directory picker, or chose a directory with no files');
+ }
+});
+document.getElementById('archiveFiles').addEventListener('click', function (e) {
if (typeof Windows !== 'undefined' && typeof Windows.Storage !== 'undefined') {
// UWP FolderPicker
pickFolderUWP();
@@ -1185,7 +1281,10 @@ document.getElementById('archiveFiles').addEventListener('click', function () {
} else if (window.fs && window.dialog) {
// Electron fallback
dialog.openDirectory();
-}
+ } else if (params.webkitdirectory) {
+ // Legacy webkitdirectory file picker
+ archiveDirLegacy.click();
+ }
});
document.getElementById('btnRefresh').addEventListener('click', function () {
// Refresh list of archives
@@ -1197,6 +1296,8 @@ document.getElementById('btnRefresh').addEventListener('click', function () {
scanUWPFolderforArchives(params.pickedFolder)
} else if (window.fs) {
scanNodeFolderforArchives(params.pickedFolder);
+ } else if (params.webkitdirectory) {
+ document.getElementById('archiveFiles').click();
}
} else if (typeof window.showOpenFilePicker === 'function' && !params.pickedFile) {
getNativeFSHandle(function (fsHandle) {
@@ -1804,9 +1905,9 @@ function cssUIThemeGetOrSet (value, getOnly) {
var elements;
if (value == 'dark') {
document.getElementsByTagName('body')[0].classList.add('dark');
- document.getElementById('archiveFilesLegacy').classList.add('dark');
+ archiveFilesLegacy.classList.add('dark');
document.getElementById('footer').classList.add('darkfooter');
- document.getElementById('archiveFilesLegacy').classList.remove('btn');
+ archiveFilesLegacy.classList.remove('btn');
document.getElementById('findInArticle').classList.add('dark');
document.getElementById('prefix').classList.add('dark');
elements = document.querySelectorAll('.settings');
@@ -1820,8 +1921,8 @@ function cssUIThemeGetOrSet (value, getOnly) {
document.getElementsByTagName('body')[0].classList.remove('dark');
document.getElementById('search-article').classList.remove('dark');
document.getElementById('footer').classList.remove('darkfooter');
- document.getElementById('archiveFilesLegacy').classList.remove('dark');
- document.getElementById('archiveFilesLegacy').classList.add('btn');
+ archiveFilesLegacy.classList.remove('dark');
+ archiveFilesLegacy.classList.add('btn');
document.getElementById('findInArticle').classList.remove('dark');
document.getElementById('prefix').classList.remove('dark');
elements = document.querySelectorAll('.settings');
@@ -2535,7 +2636,7 @@ var storages = [];
function searchForArchivesInPreferencesOrStorage () {
// First see if the list of archives is stored in the cookie
var listOfArchivesFromCookie = settingsStore.getItem('listOfArchives');
- if (listOfArchivesFromCookie !== null && listOfArchivesFromCookie !== undefined && listOfArchivesFromCookie !== '') {
+ if (listOfArchivesFromCookie) {
var directories = listOfArchivesFromCookie.split('|');
populateDropDownListOfArchives(directories);
} else {
@@ -2591,7 +2692,8 @@ if ($.isFunction(navigator.getDeviceStorages)) {
}
if (storages !== null && storages.length > 0 ||
typeof Windows !== 'undefined' && typeof Windows.Storage !== 'undefined' ||
- typeof window.fs !== 'undefined' || typeof window.showOpenFilePicker === 'function') {
+ typeof window.fs !== 'undefined' || typeof window.showOpenFilePicker === 'function' ||
+ params.webkitdirectory) {
if (window.fs && !(params.pickedFile || params.pickedFolder)) {
// Below we compare the prefix of the files, i.e. the generic filename without date, so we can smoothly deal with upgrades
if (params.packagedFile && params.storedFile.replace(/(^[^-]+all).+/, '$1') === params.packagedFile.replace(/(^[^-]+all).+/, '$1')) {
@@ -2604,12 +2706,12 @@ if (storages !== null && storages.length > 0 ||
} else if (/\/archives\//.test(params.storedFilePath) && ~params.storedFilePath.indexOf(params.storedFile)) {
// We're in an Electron / NWJS app, and there is a stored file in the archive, but it's not the packaged archive!
// Probably there is more than one archive in the archive folder, so we are forced to use .fs code
- console.warn('There may be more than one archive in the directory ' + params.storedFilePath.replace(/[^\/]+$/, ''));
+ console.warn('There may be more than one archive in the directory ' + params.storedFilePath.replace(/[^\\/]+$/, ''));
params.pickedFile = params.storedFile;
}
}
if (!params.pickedFile) {
- if (params.storedFile && window.showOpenFilePicker) {
+ if (params.storedFile && (window.showOpenFilePicker)) {
// We are in an app with support for File System Access API, so we can't auto-load the file, show file pickers
document.getElementById('btnConfigure').click();
} else {
@@ -2618,9 +2720,8 @@ if (storages !== null && storages.length > 0 ||
} else if (typeof Windows !== 'undefined' && typeof Windows.Storage !== 'undefined') {
console.log('Loading picked file for UWP app...');
processPickedFileUWP(params.pickedFile);
- // } else if (launchArguments && 'launchQueue' in window) {
- // // The app was launched with a file
- // processNativeFileHandle(params.pickedFile);
+ } else if (!window.fs && params.webkitdirectory) {
+ searchForArchivesInPreferencesOrStorage();
} else {
// @AUTOLOAD packaged archive in Electron and NWJS packaged apps
// We need to read the packaged file using the node File System API (so user doesn't need to pick it on startup)
@@ -2676,7 +2777,6 @@ if (storages !== null && storages.length > 0 ||
uiUtil.systemAlert(message);
}, 10);
}
-
});
document.getElementById('hideFileSelectors').style.display = params.showFileSelectors ? 'inline' : 'none';
}
@@ -2751,16 +2851,16 @@ function populateDropDownListOfArchives (archiveDirectories, displayOnly) {
comboArchiveList.options[i] = new Option(archiveDirectory, archiveDirectory);
}
}
- // Store the list of archives in a cookie, to avoid rescanning at each start
+ // Store the list of archives in settingsStore, to avoid rescanning at each start
settingsStore.setItem('listOfArchives', archiveDirectories.join('|'), Infinity);
comboArchiveList.size = comboArchiveList.length > 15 ? 15 : comboArchiveList.length;
// Kiwix-Js-Windows #23 - remove dropdown caret if only one archive
if (comboArchiveList.length > 1) comboArchiveList.removeAttribute('multiple');
- if (comboArchiveList.length == 1) comboArchiveList.setAttribute('multiple', '1');
+ if (comboArchiveList.length === 1) comboArchiveList.setAttribute('multiple', '1');
if (comboArchiveList.options.length > 0) {
// If we're doing a rescan, then don't attempt to jump to the last selected archive, but leave selectors open
var lastSelectedArchive = params.rescan ? '' : params.storedFile;
- if (lastSelectedArchive !== null && lastSelectedArchive !== undefined && lastSelectedArchive !== '') {
+ if (lastSelectedArchive) {
// || comboArchiveList.options.length == 1
// Either we have previously chosen a file, or there is only one file
// Attempt to select the corresponding item in the list, if it exists
@@ -2777,24 +2877,26 @@ function populateDropDownListOfArchives (archiveDirectories, displayOnly) {
// We can't find lastSelectedArchive in the archive list
// Let's first check if this is a Store UWP/PWA that has a different archive package from that last selected
// (or from that indicated in init.js)
- if (typeof Windows !== 'undefined' && typeof Windows.Storage !== 'undefined' &&
- params.packagedFile && settingsStore.getItem('lastSelectedArchive') !== params.packagedFile) {
- // We didn't pick this file previously, so select first one in list
- params.storedFile = archiveDirectories[0];
- params.fileVersion = ~params.fileVersion.indexOf(params.storedFile.replace(/\.zim\w?\w?$/i, '')) ? params.fileVersion : params.storedFile;
- setLocalArchiveFromArchiveList(params.storedFile);
- } else {
- // It's genuinely no longer available, so let's ask the user to pick it
- var message = '
We could not find the archive ' + lastSelectedArchive + '!
Please select its location...
'; - if (typeof Windows !== 'undefined' && typeof Windows.Storage !== 'undefined') - message += 'Note: If you drag-drop an archive into this UWP app, then it will have to be dragged again each time you launch the app. Try double-clicking on the archive instead, or select it using the controls on this page.
'; - if (document.getElementById('configuration').style.display == 'none') { - document.getElementById('btnConfigure').click(); - } - uiUtil.systemAlert(message).then(function () { - displayFileSelect(); - }); + // if (typeof Windows !== 'undefined' && typeof Windows.Storage !== 'undefined' && + // params.packagedFile && settingsStore.getItem('lastSelectedArchive') !== params.packagedFile) { + // // We didn't pick this file previously, so select first one in list + // params.storedFile = archiveDirectories[0]; + // params.fileVersion = ~params.fileVersion.indexOf(params.storedFile.replace(/\.zim\w?\w?$/i, '')) ? params.fileVersion : params.storedFile; + // setLocalArchiveFromArchiveList(params.storedFile); + // } + // Warn user that the file they wanted is no longer available + var message = 'We could not find the archive ' + lastSelectedArchive + '!
Please select its location...
'; + if (params.webkitdirectory && !window.fs || typeof Windows !== 'undefined' && typeof Windows.Storage !== 'undefined') { + message += 'Note: If you drag-drop ' + (window.showOpenFilePicker ? 'a split' : 'an') + ' archive into this app, then it will have to be dragged again each time you launch the app. Try '; + message += typeof Windows !== 'undefined' ? 'double-clicking on the archive instead, or ' : ''; + message += 'selecting it using the controls on this page.
'; } + if (document.getElementById('configuration').style.display === 'none') { + document.getElementById('btnConfigure').click(); + } + uiUtil.systemAlert(message).then(function () { + displayFileSelect(); + }); } } usage.style.display = 'none'; @@ -2886,64 +2988,14 @@ function setLocalArchiveFromArchiveList (archive) { openCurrentArchive.style.display = 'inline'; return; } else if (params.pickedFolder.kind === 'directory') { - return processNativeDirHandle(params.pickedFolder, function (fileHandles) { - var fileHandle; - var fileset = []; - if (fileHandles) { - for (var i = 0; i < fileHandles.length; i++) { - if (fileHandles[i].name == archive) { - fileHandle = fileHandles[i]; - break; - } - } - if (fileHandle) { - // Deal with split archives - if (/\.zim\w\w$/i.test(fileHandle.name)) { - var genericFileName = fileHandle.name.replace(/(\.zim)\w\w$/i, '$1'); - var testFileName = new RegExp(genericFileName + '\\w\\w$'); - for (i = 0; i < fileHandles.length; i++) { - if (testFileName.test(fileHandles[i].name)) { - // This gets a JS File object from a file handle - fileset.push(fileHandles[i].getFile().then(function (file) { - return file; - })); - } - } - } else { - // Deal with single unslpit archive - fileset.push(fileHandle.getFile().then(function (file) { - return file; - })); - } - if (fileset.length) { - // Wait for all getFile Promises to resolve - Promise.all(fileset).then(function (resolvedFiles) { - setLocalArchiveFromFileList(resolvedFiles); - }); - } else { - console.error('There was an error reading the picked file(s)!'); - } - } else { - console.error('The picked file could not be found in the selected folder!'); - var archiveList = []; - for (i = 0; i < fileHandles.length; i++) { - if (/\.zim(aa)?$/i.test(fileHandles[i].name)) { - archiveList.push(fileHandles[i].name); - } - } - populateDropDownListOfArchives(archiveList); - document.getElementById('btnConfigure').click(); - } - } else { - console.log('There was an error obtaining the file handle(s).'); - } + return processNativeDirHandle(params.pickedFolder, function (files) { + processDirectoryOfFiles(files, archive); }); } openCurrentArchive.style.display = 'none'; }).catch(function () { openCurrentArchive.style.display = 'inline'; }); - return; } else if (window.fs) { if (params.pickedFile) { setLocalArchiveFromFileList([params.pickedFile]); @@ -2957,31 +3009,41 @@ function setLocalArchiveFromArchiveList (archive) { } else { setLocalArchiveFromFileList(selectedFiles); } + }).catch(function (err) { + console.error(err); }); } else { uiUtil.systemAlert('We could not find the location of the file ' + archive + '. This can happen if you dragged and dropped a file into the app. Please use the file or folder pickers instead.'); - if (document.getElementById('configuration').style.display == 'none') + if (document.getElementById('configuration').style.display === 'none') { document.getElementById('btnConfigure').click(); + } displayFileSelect(); } } return; + } else if (params.pickedFolder && params.webkitdirectory && archiveDirLegacy.files.length) { + processDirectoryOfFiles(archiveDirLegacy.files, archive); + return; } else { // Check if user previously picked a specific file rather than a folder if (params.pickedFile && typeof MSApp !== 'undefined') { try { selectedStorage = MSApp.createFileFromStorageFile(params.pickedFile); setLocalArchiveFromFileList([selectedStorage]); - return; } catch (err) { // Probably user has moved or deleted the previously selected file uiUtil.systemAlert('The previously picked archive can no longer be found!'); console.error('Picked archive not found: ' + err); } + return; } else if (params.pickedFile && typeof window.showOpenFilePicker === 'function') { // Native FS API for single file setLocalArchiveFromFileList([params.pickedFile]); return; + } else if (params.pickedFile && params.webkitdirectory) { + // Webkitdirectory API for single file + setLocalArchiveFromFileList(archiveFilesLegacy.files); + return; } } // There was no picked file or folder, so we'll try setting the default localStorage @@ -3056,6 +3118,67 @@ function setLocalArchiveFromArchiveList (archive) { } } +function processDirectoryOfFiles (fileHandles, archive) { + var fileHandle; + var fileset = []; + if (fileHandles) { + for (var i = 0; i < fileHandles.length; i++) { + if (fileHandles[i].name === archive) { + fileHandle = fileHandles[i]; + break; + } + } + if (fileHandle) { + // Deal with split archives + if (/\.zim\w\w$/i.test(fileHandle.name)) { + var genericFileName = fileHandle.name.replace(/(\.zim)\w\w$/i, '$1'); + var testFileName = new RegExp(genericFileName + '\\w\\w$'); + for (i = 0; i < fileHandles.length; i++) { + if (testFileName.test(fileHandles[i].name)) { + if (fileHandles[i].getFile) { + // This gets a JS File object from a file handle + fileset.push(fileHandles[i].getFile().then(function (file) { + return file; + })); + } else { + fileset.push(fileHandles[i]); + } + } + } + } else { + // Deal with single unslpit archive + if (fileHandle.getFile) { + fileset.push(fileHandle.getFile().then(function (file) { + return file; + })); + } else { + fileset.push(fileHandle); + } + } + if (fileset.length) { + // Wait for all getFile Promises to resolve + Promise.all(fileset).then(function (resolvedFiles) { + setLocalArchiveFromFileList(resolvedFiles); + }); + } else { + console.error('There was an error reading the picked file(s)!'); + } + } else { + console.error('The picked file could not be found in the selected folder!'); + var archiveList = []; + for (i = 0; i < fileHandles.length; i++) { + if (/\.zim(aa)?$/i.test(fileHandles[i].name)) { + archiveList.push(fileHandles[i].name); + } + } + populateDropDownListOfArchives(archiveList); + document.getElementById('btnConfigure').click(); + } + } else { + console.log('There was an error obtaining the file handle(s).'); + } +} + if (!params.disableDragAndDrop) { // Define globalDropZone (universal drop area) and configDropZone (highlighting area on Config page) var globalDropZone = document.getElementById('search-article'); @@ -3082,8 +3205,6 @@ function displayFileSelect () { UWPInstructions.style.display = 'block'; } document.getElementById('rescanStorage').style.display = 'none'; - // This handles use of the file picker - document.getElementById('archiveFiles').addEventListener('change', setLocalArchiveFromFileSelect); } function handleGlobalDragover (e) { @@ -3102,7 +3223,6 @@ function handleIframeDragover (e) { function handleIframeDrop (e) { e.stopPropagation(); e.preventDefault(); - return; } function handleFileDrop (packet) { @@ -3110,6 +3230,7 @@ function handleFileDrop (packet) { packet.preventDefault(); configDropZone.style.border = ''; var items = packet.dataTransfer.items; + // When dropping multiple files (e.g. a split archive), we cannot use the File System Access API if (items && items.length === 1 && items[0].kind === 'file' && typeof items[0].getAsFileSystemHandle !== 'undefined') { items[0].getAsFileSystemHandle().then(function (handle) { if (handle.kind === 'file') { @@ -3123,10 +3244,12 @@ function handleFileDrop (packet) { document.getElementById('openLocalFiles').style.display = 'none'; document.getElementById('rescanStorage').style.display = 'block'; document.getElementById('usage').style.display = 'none'; + // We have to void the previous picked folder, because dragged files don't have a folder + // This also prevents a file-not-found alert to the user when picking a new directory + params.pickedFolder = null; + settingsStore.setItem('pickedFolder', '', Infinity); params.rescan = false; setLocalArchiveFromFileList(files); - // This clears the display of any previously picked archive in the file selector - document.getElementById('archiveFilesLegacy').value = ''; } } @@ -3267,9 +3390,11 @@ function processNativeDirHandle (dirHandle, callback) { if (callback) archiveList.push(entry); // Hide all parts of split file except first in UI else if (/\.zim(aa)?$/.test(entry.name)) archiveList.push(entry.name); - if (!params.pickedFolder.path) entry.getFile().then(function (file) { - params.pickedFolder.path = file.path; - }) + if (!params.pickedFolder.path) { + entry.getFile().then(function (file) { + params.pickedFolder.path = file.path; + }); + } } iterateAsyncDirEntryArray(); } else { @@ -3324,7 +3449,15 @@ function scanUWPFolderforArchives (folder) { params.pickedFolder = folder; // Query the folder. var query = folder.createFileQuery(); - query.getFilesAsync().done(processFilesArray); + query.getFilesAsync().done(function (files) { + processFilesArray(files, function (resolvedFiles) { + // If there is only one file in the folder, we should load it + if ((resolvedFiles.length === 1 || params.storedFile) && !params.rescan) { + var fileToLoad = params.storedFile || resolvedFiles[0].name; + setLocalArchiveFromArchiveList(fileToLoad); + } + }); + }); } else { // The picker was dismissed with no selected file console.log('User closed folder picker without picking a file'); @@ -3335,19 +3468,15 @@ function processFilesArray (files, callback) { // Display file list var archiveDisplay = document.getElementById('chooseArchiveFromLocalStorage'); if (files) { - var filteredFiles = []; var archiveList = []; files.forEach(function (file) { - if (/\.zim(aa)?$/i.test(file.fileType) || /\.zim(aa)?$/i.test(file)) { + if (/\.zim(aa)?$/i.test(file.fileType) || /\.zim(aa)?$/i.test(file) || /\.zim(aa)?$/i.test(file.name)) { archiveList.push(file.name || file); } - if (/\.zim(\w\w)?$/i.test(file.fileType) || /\.zim(\w\w)?$/i.test(file)) { - filteredFiles.push(file); - } }); if (archiveList.length) { document.getElementById('noZIMFound').style.display = 'none'; - populateDropDownListOfArchives(archiveList); + populateDropDownListOfArchives(archiveList, true); if (callback) callback(files, archiveList); return; } @@ -3362,8 +3491,9 @@ function processFilesArray (files, callback) { function setLocalArchiveFromFileList (files) { if (!files.length) { - if (document.getElementById('configuration').style.display == 'none') - document.getElementById('btnConfigure').click(); + if (document.getElementById('configuration').style.display == 'none') { + document.getElementById('btnConfigure').click(); + } displayFileSelect(); return; } @@ -3381,6 +3511,10 @@ function setLocalArchiveFromFileList (files) { if (typeof window.fs !== 'undefined' && files[i].path) { files[i].readMode = 'electron'; console.log('File path is: ' + files[i].path); + if (files.length === 1 || params.firstFileIndex) { + params.pickedFile = files[i].path; + settingsStore.setItem('pickedFile', params.pickedFile, Infinity); + } } } // Check that user hasn't picked just part of split ZIM @@ -3436,8 +3570,22 @@ function setLocalArchiveFromFileList (files) { } // The archive is set : go back to home page to start searching params.storedFile = archive._file._files[0].name; + params.storedFilePath = archive._file._files[0].path ? archive._file._files[0].path : ''; settingsStore.setItem('lastSelectedArchive', params.storedFile, Infinity); - settingsStore.setItem('lastSelectedArchivePath', archive._file._files[0].path ? archive._file._files[0].path : '', Infinity); + settingsStore.setItem('lastSelectedArchivePath', params.storedFilePath, Infinity); + // If we have dragged and dropped files into an Electron app, we should have access to the path, so we should store it + if (params.storedFilePath) { + params.pickedFolder = null; + params.pickedFile = params.storedFilePath; + settingsStore.setItem('pickedFolder', '', Infinity); + settingsStore.setItem('pickedFile', params.pickedFile, Infinity); + populateDropDownListOfArchives([params.storedFile], true); + settingsStore.setItem('listOfArchives', encodeURI(params.storedFile), Infinity); + // We have to remove the file handle to prevent it from launching next time + cache.idxDB('delete', 'pickedFSHandle', function () { + console.debug('File handle deleted'); + }); + } var reloadLink = document.getElementById('reloadPackagedArchive'); if (reloadLink) { if (params.packagedFile != params.storedFile) { @@ -3512,6 +3660,8 @@ function loadPackagedArchive () { params.storedFile = params.packagedFile; setLocalArchiveFromFileList(fileObjects); populateDropDownListOfArchives(fileNames, true); + }).catch(function (err) { + console.error(err); }); // createFakeFileObjectNode(params.packagedFile, params.archivePath + '/' + params.packagedFile, processFakeFile); } @@ -3522,7 +3672,14 @@ function loadPackagedArchive () { * Sets the localArchive from the File selects populated by user */ function setLocalArchiveFromFileSelect () { - setLocalArchiveFromFileList(document.getElementById('archiveFilesLegacy').files); + setLocalArchiveFromFileList(archiveFilesLegacy.files); + params.rescan = false; +} +/** + * Sets the localArchive from the directory selected by user + */ +function setLocalArchiveFromDirSelect () { + setLocalArchiveFromFileList(archiveDirLegacy.files); params.rescan = false; } @@ -3575,6 +3732,7 @@ function readNodeDirectoryAndCreateNodeFileObjects (folder, file) { var selectedFileSet = [], selectedFileNamesSet = []; var count = 0; var fileHandle = typeof file === 'string' ? file : file[0]; + // Electron may need to handle the path differently if (folder === params.archivePath && /^file:/i.test(window.location.protocol)) { folder = decodeURIComponent(window.location.href.replace(/www\/[^/?#]+(?:[?#].*)?$/, '') + folder); } diff --git a/www/js/init.js b/www/js/init.js index 0ea20489..68480c87 100644 --- a/www/js/init.js +++ b/www/js/init.js @@ -23,6 +23,8 @@ 'use strict'; +/* global Windows, launchArguments */ + // Set a global error handler to prevent app crashes window.onerror = function (msg, url, line, col, error) { console.error('Error caught in app [' + url + ':' + line + ']:\n' + msg, error); @@ -45,57 +47,58 @@ var params = {}; /** * A global state object - * + * * @type Object */ var appstate = {}; -/******** UPDATE VERSION IN service-worker.js TO MATCH VERSION AND CHECK PWASERVER BELOW!!!!!!! *******/ -params['appVersion'] = "2.5.4"; //DEV: Manually update this version when there is a new release: it is compared to the Settings Store "appVersion" in order to show first-time info, and the cookie is updated in app.js -/******* UPDATE THIS ^^^^^^ IN service worker AND PWA-SERVER BELOW !! ********************/ -params['packagedFile'] = getSetting('packagedFile') || "wikipedia_en_100_mini_2023-06.zim"; //For packaged Kiwix JS (e.g. with Wikivoyage file), set this to the filename (for split files, give the first chunk *.zimaa) and place file(s) in default storage -params['archivePath'] = "archives"; //The directory containing the packaged archive(s) (relative to app's root directory) -params['fileVersion'] = getSetting('fileVersion') || "wikipedia_en_100_mini_2023-06.zim (2 June 2023)"; //This will be displayed in the app - optionally include date of ZIM file + +// ******** UPDATE VERSION IN service-worker.js TO MATCH VERSION AND CHECK PWASERVER BELOW!!!!!!! ******* +params['appVersion'] = '2.5.4'; // DEV: Manually update this version when there is a new release: it is compared to the Settings Store "appVersion" in order to show first-time info, and the cookie is updated in app.js +// ******* UPDATE THIS ^^^^^^ IN service worker AND PWA-SERVER BELOW !! ******************** +params['packagedFile'] = getSetting('packagedFile') || 'wikipedia_en_100_mini_2023-06.zim'; // For packaged Kiwix JS (e.g. with Wikivoyage file), set this to the filename (for split files, give the first chunk *.zimaa) and place file(s) in default storage +params['archivePath'] = 'archives'; // The directory containing the packaged archive(s) (relative to app's root directory) +params['fileVersion'] = getSetting('fileVersion') || 'wikipedia_en_100_mini_2023-06.zim (2 June 2023)'; // This will be displayed in the app - optionally include date of ZIM file // List of known start pages cached in the FS: params['cachedStartPages'] = { 'wikipedia_en_medicine-app_maxi': 'A/Wikipedia:WikiProject_Medicine/Open_Textbook_of_Medicine2', - 'wikipedia_en_medicine_maxi': 'A/Wikipedia:WikiProject_Medicine/Open_Textbook_of_Medicine2', + wikipedia_en_medicine_maxi: 'A/Wikipedia:WikiProject_Medicine/Open_Textbook_of_Medicine2', // 'mdwiki_en_all_maxi': 'A/Wikipedia:WikiProject_Medicine/Open_Textbook_of_Medicine2', - 'wikivoyage_en_all_maxi': 'A/Main_Page' + wikivoyage_en_all_maxi: 'A/Main_Page' }; -params['kiwixDownloadLink'] = "https://download.kiwix.org/zim/"; //Include final slash -params['kiwixHiddenDownloadLink'] = "https://master.download.kiwix.org/zim/"; -/******* DEV: ENSURE SERVERS BELOW ARE LISTED IN package.appxmanifest ************/ -params['PWAServer'] = "https://pwa.kiwix.org/"; // Production server +params['kiwixDownloadLink'] = 'https://download.kiwix.org/zim/'; // Include final slash +params['kiwixHiddenDownloadLink'] = 'https://master.download.kiwix.org/zim/'; +/** ***** DEV: ENSURE SERVERS BELOW ARE LISTED IN package.appxmanifest ************/ +params['PWAServer'] = 'https://pwa.kiwix.org/'; // Production server // params['PWAServer'] = "https://kiwix.github.io/kiwix-js-windows/dist/"; // Test server params['storeType'] = getBestAvailableStorageAPI(); params['appType'] = getAppType(); params['keyPrefix'] = 'kiwixjs-'; // Prefix to use for localStorage keys // Maximum number of article titles to return (range is 5 - 100, default 30) params['maxSearchResultsSize'] = ~~(getSetting('maxSearchResultsSize') || 30); -params['relativeFontSize'] = ~~(getSetting('relativeFontSize') || 100); //Sets the initial font size for articles (as a percentage) - user can adjust using zoom buttons -params['relativeUIFontSize'] = ~~(getSetting('relativeUIFontSize') || 100); //Sets the initial font size for UI (as a percentage) - user can adjust using slider in Config -params['cssSource'] = getSetting('cssSource') || "auto"; //Set default to "auto", "desktop" or "mobile" -params['removePageMaxWidth'] = getSetting('removePageMaxWidth') != null ? getSetting('removePageMaxWidth') : "auto"; //Set default for removing max-width restriction on Wikimedia pages ("auto" = removed in desktop, not in mobile; true = always remove; false = never remove) -params['displayHiddenBlockElements'] = getSetting('displayHiddenBlockElements') !== null ? getSetting('displayHiddenBlockElements') : "auto"; //Set default for displaying hidden block elements ("auto" = displayed in Wikimedia archives in mobile style) -params['openAllSections'] = getSetting('openAllSections') != null ? getSetting('openAllSections') : true; //Set default for opening all sections in ZIMs that have collapsible sections and headings ("auto" = let CSS decide according to screen width; true = always open until clicked by user; false = always closed until clicked by user) -params['cssCache'] = getSetting('cssCache') != null ? getSetting('cssCache') : true; //Set default to true to use cached CSS, false to use Zim only -params['cssTheme'] = getSetting('cssTheme') || 'light'; //Set default to 'auto', 'light', 'dark' or 'invert' to use respective themes for articles -params['cssUITheme'] = getSetting('cssUITheme') || 'light'; //Set default to 'auto', 'light' or 'dark' to use respective themes for UI' +params['relativeFontSize'] = ~~(getSetting('relativeFontSize') || 100); // Sets the initial font size for articles (as a percentage) - user can adjust using zoom buttons +params['relativeUIFontSize'] = ~~(getSetting('relativeUIFontSize') || 100); // Sets the initial font size for UI (as a percentage) - user can adjust using slider in Config +params['cssSource'] = getSetting('cssSource') || 'auto'; // Set default to "auto", "desktop" or "mobile" +params['removePageMaxWidth'] = getSetting('removePageMaxWidth') != null ? getSetting('removePageMaxWidth') : 'auto'; // Set default for removing max-width restriction on Wikimedia pages ("auto" = removed in desktop, not in mobile; true = always remove; false = never remove) +params['displayHiddenBlockElements'] = getSetting('displayHiddenBlockElements') !== null ? getSetting('displayHiddenBlockElements') : 'auto'; // Set default for displaying hidden block elements ("auto" = displayed in Wikimedia archives in mobile style) +params['openAllSections'] = getSetting('openAllSections') != null ? getSetting('openAllSections') : true; // Set default for opening all sections in ZIMs that have collapsible sections and headings ("auto" = let CSS decide according to screen width; true = always open until clicked by user; false = always closed until clicked by user) +params['cssCache'] = getSetting('cssCache') != null ? getSetting('cssCache') : true; // Set default to true to use cached CSS, false to use Zim only +params['cssTheme'] = getSetting('cssTheme') || 'light'; // Set default to 'auto', 'light', 'dark' or 'invert' to use respective themes for articles +params['cssUITheme'] = getSetting('cssUITheme') || 'light'; // Set default to 'auto', 'light' or 'dark' to use respective themes for UI' params['resetDisplayOnResize'] = getSetting('resetDisplayOnResize') == true; // Default for the display reset feature that fixes bugs with secondary displays -params['imageDisplay'] = getSetting('imageDisplay') != null ? getSetting('imageDisplay') : true; //Set default to display images from Zim -params['manipulateImages'] = getSetting('manipulateImages') != null ? getSetting('manipulateImages') : true; //Makes dataURIs by default instead of BLOB URIs for images -params['linkToWikimediaImageFile'] = getSetting('linkToWikimediaImageFile') == true; //Links images to Wikimedia online version if ZIM archive is a Wikipedia archive -params['hideToolbars'] = getSetting('hideToolbars') != null ? getSetting('hideToolbars') : true; //Set default to true (hides both), 'top' (hides top only), or false (no hiding) -params['rememberLastPage'] = getSetting('rememberLastPage') != null ? getSetting('rememberLastPage') : true; //Set default option to remember the last visited page between sessions +params['imageDisplay'] = getSetting('imageDisplay') != null ? getSetting('imageDisplay') : true; // Set default to display images from Zim +params['manipulateImages'] = getSetting('manipulateImages') != null ? getSetting('manipulateImages') : true; // Makes dataURIs by default instead of BLOB URIs for images +params['linkToWikimediaImageFile'] = getSetting('linkToWikimediaImageFile') == true; // Links images to Wikimedia online version if ZIM archive is a Wikipedia archive +params['hideToolbars'] = getSetting('hideToolbars') != null ? getSetting('hideToolbars') : true; // Set default to true (hides both), 'top' (hides top only), or false (no hiding) +params['rememberLastPage'] = getSetting('rememberLastPage') != null ? getSetting('rememberLastPage') : true; // Set default option to remember the last visited page between sessions params['assetsCache'] = getSetting('assetsCache') != null ? getSetting('assetsCache') : true; // Whether to use cache by default or not params['appCache'] = getSetting('appCache') !== false; // Will be true by default unless explicitly set to false -params['useMathJax'] = getSetting('useMathJax') != null ? getSetting('useMathJax') : true; //Set default to true to display math formulae with MathJax, false to use fallback SVG images only -//params['showFileSelectors'] = getCookie('showFileSelectors') != null ? getCookie('showFileSelectors') : false; //Set to true to display hidden file selectors in packaged apps -params['showFileSelectors'] = true; //False will cause file selectors to be hidden on each load of the app (by ignoring cookie) +params['useMathJax'] = getSetting('useMathJax') != null ? getSetting('useMathJax') : true; // Set default to true to display math formulae with MathJax, false to use fallback SVG images only +// params['showFileSelectors'] = getCookie('showFileSelectors') != null ? getCookie('showFileSelectors') : false; //Set to true to display hidden file selectors in packaged apps +params['showFileSelectors'] = true; // False will cause file selectors to be hidden on each load of the app (by ignoring cookie) params['hideActiveContentWarning'] = getSetting('hideActiveContentWarning') != null ? getSetting('hideActiveContentWarning') : false; params['allowHTMLExtraction'] = getSetting('allowHTMLExtraction') == true; -params['alphaChar'] = getSetting('alphaChar') || 'A'; //Set default start of alphabet string (used by the Archive Index) -params['omegaChar'] = getSetting('omegaChar') || 'Z'; //Set default end of alphabet string +params['alphaChar'] = getSetting('alphaChar') || 'A'; // Set default start of alphabet string (used by the Archive Index) +params['omegaChar'] = getSetting('omegaChar') || 'Z'; // Set default end of alphabet string params['contentInjectionMode'] = getSetting('contentInjectionMode') || ((navigator.serviceWorker && !window.nw) ? 'serviceworker' : 'jquery'); // Deafault to SW mode if the browser supports it params['allowInternetAccess'] = getSetting('allowInternetAccess'); // Access disabled unless user specifically asked for it: NB allow this value to be null as we use it later params['openExternalLinksInNewTabs'] = getSetting('openExternalLinksInNewTabs') !== null ? getSetting('openExternalLinksInNewTabs') : true; // Parameter to turn on/off opening external links in new tab @@ -104,7 +107,9 @@ params['windowOpener'] = getSetting('windowOpener'); // 'tab|window|false' A set params['rightClickType'] = getSetting('rightClickType'); // 'single|double|false' A setting that determines whether a single or double right-click is used to open a new window/tab params['navButtonsPos'] = getSetting('navButtonsPos') || 'bottom'; // 'top|bottom' A setting that determines where the back-forward nav buttons appear -//Do not touch these values unless you know what they do! Some are global variables, some are set programmatically +// Do not touch these values unless you know what they do! Some are global variables, some are set programmatically +params['cacheAPI'] = 'kiwixjs-assetsCache'; // Set the global Cache API database or cache name here, and synchronize with Service Worker +params['cacheIDB'] = 'kiwix-assetsCache'; // Set the global IndexedDB database here (Slightly different name to disambiguate) params['imageDisplayMode'] = params.imageDisplay ? 'progressive' : 'manual'; params['storedFile'] = getSetting('lastSelectedArchive'); params.storedFile = launchArguments ? launchArguments.files[0].name : params.storedFile || params['packagedFile'] || ''; @@ -114,16 +119,16 @@ params['storedFilePath'] = getSetting('lastSelectedArchivePath'); params.storedFilePath = params.storedFilePath ? decodeURIComponent(params.storedFilePath) : params.archivePath + '/' + params.packagedFile; params.storedFilePath = launchArguments ? launchArguments.files[0].path || '' : params.storedFilePath; params.originalPackagedFile = params.packagedFile; -params['localStorage'] = ""; -params['pickedFile'] = launchArguments ? launchArguments.files[0] : ""; -params['pickedFolder'] = ""; +params['localStorage'] = ''; +params['pickedFile'] = launchArguments ? launchArguments.files[0] : ''; +params['pickedFolder'] = ''; params['themeChanged'] = params['themeChanged'] || false; params['printIntercept'] = false; params['printInterception'] = false; params['appIsLaunching'] = true; // Allows some routines to tell if the app has just been launched params['PWAInstalled'] = window.matchMedia('(display-mode: standalone)').matches; // Because user may reset the app, we have to test for standalone mode -params['falFileToken'] = "zimfile"; // UWP support -params['falFolderToken'] = "zimfilestore"; // UWP support +params['falFileToken'] = 'zimfile'; // UWP support +params['falFolderToken'] = 'zimfilestore'; // UWP support params.pagesLoaded = 0; // Page counter used to show PWA Install Prompt only after user has played with the app for a while params.localUWPSettings = /UWP/.test(params.appType) ? Windows.Storage.ApplicationData.current.localSettings.values : null; appstate['target'] = 'iframe'; // The target for article loads (this should always be 'iframe' initially, and will only be changed as a result of user action) @@ -218,24 +223,24 @@ document.getElementById('manipulateImagesCheck').checked = params.manipulateImag document.getElementById('removePageMaxWidthCheck').checked = params.removePageMaxWidth === true; // Will be false if false or auto document.getElementById('removePageMaxWidthCheck').indeterminate = params.removePageMaxWidth === 'auto'; document.getElementById('removePageMaxWidthCheck').readOnly = params.removePageMaxWidth === 'auto'; -document.getElementById('pageMaxWidthState').textContent = (params.removePageMaxWidth === "auto" ? "auto" : params.removePageMaxWidth ? "always" : "never"); +document.getElementById('pageMaxWidthState').textContent = (params.removePageMaxWidth === 'auto' ? 'auto' : params.removePageMaxWidth ? 'always' : 'never'); document.getElementById('displayHiddenBlockElementsCheck').checked = params.displayHiddenBlockElements === true; document.getElementById('displayHiddenBlockElementsCheck').indeterminate = params.displayHiddenBlockElements === 'auto'; document.getElementById('displayHiddenBlockElementsCheck').readOnly = params.displayHiddenBlockElements === 'auto'; -document.getElementById('displayHiddenElementsState').textContent = (params.displayHiddenBlockElements === "auto" ? "auto" : params.displayHiddenBlockElements ? "always" : "never"); +document.getElementById('displayHiddenElementsState').textContent = (params.displayHiddenBlockElements === 'auto' ? 'auto' : params.displayHiddenBlockElements ? 'always' : 'never'); document.getElementById('openAllSectionsCheck').checked = params.openAllSections; document.getElementById('linkToWikimediaImageFileCheck').checked = params.linkToWikimediaImageFile; document.getElementById('useOSMCheck').checked = /openstreetmap/.test(params.mapsURI); -document.getElementById('cssUIDarkThemeCheck').checked = params.cssUITheme == "dark"; // Will be true, or false if light or auto -document.getElementById('cssUIDarkThemeCheck').indeterminate = params.cssUITheme == "auto"; -document.getElementById('cssUIDarkThemeCheck').readOnly = params.cssUITheme == "auto"; +document.getElementById('cssUIDarkThemeCheck').checked = params.cssUITheme == 'dark'; // Will be true, or false if light or auto +document.getElementById('cssUIDarkThemeCheck').indeterminate = params.cssUITheme == 'auto'; +document.getElementById('cssUIDarkThemeCheck').readOnly = params.cssUITheme == 'auto'; document.getElementById('cssUIDarkThemeState').innerHTML = params.cssUITheme; document.getElementById('cssWikiDarkThemeCheck').checked = /dark|invert/.test(params.cssTheme); -document.getElementById('cssWikiDarkThemeCheck').indeterminate = params.cssTheme == "auto"; -document.getElementById('cssWikiDarkThemeCheck').readOnly = params.cssTheme == "auto"; +document.getElementById('cssWikiDarkThemeCheck').indeterminate = params.cssTheme == 'auto'; +document.getElementById('cssWikiDarkThemeCheck').readOnly = params.cssTheme == 'auto'; document.getElementById('cssWikiDarkThemeState').innerHTML = params.cssTheme; -document.getElementById('darkInvert').style.display = /dark|invert|darkReader/i.test(params.cssTheme) ? "inline" : "none"; -document.getElementById('darkDarkReader').style.display = params.contentInjectionMode === 'serviceworker' && /dark|invert|darkReader/i.test(params.cssTheme) ? "inline" : "none"; +document.getElementById('darkInvert').style.display = /dark|invert|darkReader/i.test(params.cssTheme) ? 'inline' : 'none'; +document.getElementById('darkDarkReader').style.display = params.contentInjectionMode === 'serviceworker' && /dark|invert|darkReader/i.test(params.cssTheme) ? 'inline' : 'none'; document.getElementById('cssWikiDarkThemeInvertCheck').checked = params.cssTheme == 'invert'; document.getElementById('cssWikiDarkThemeDarkReaderCheck').checked = params.cssTheme == 'darkReader'; document.getElementById('resetDisplayOnResizeCheck').checked = params.resetDisplayOnResize; @@ -248,17 +253,17 @@ document.getElementById('omegaCharTxt').value = params.omegaChar; document.getElementById('titleSearchRange').value = params.maxSearchResultsSize; document.getElementById('titleSearchRangeVal').innerHTML = params.maxSearchResultsSize; document.getElementById('hideToolbarsCheck').checked = params.hideToolbars === true; // Will be false if false or 'top' -document.getElementById('hideToolbarsCheck').indeterminate = params.hideToolbars === "top"; -document.getElementById('hideToolbarsCheck').readOnly = params.hideToolbars === "top"; -document.getElementById('hideToolbarsState').innerHTML = (params.hideToolbars === "top" ? "top" : params.hideToolbars ? "both" : "never"); +document.getElementById('hideToolbarsCheck').indeterminate = params.hideToolbars === 'top'; +document.getElementById('hideToolbarsCheck').readOnly = params.hideToolbars === 'top'; +document.getElementById('hideToolbarsState').innerHTML = (params.hideToolbars === 'top' ? 'top' : params.hideToolbars ? 'both' : 'never'); document.getElementById('openExternalLinksInNewTabsCheck').checked = params.openExternalLinksInNewTabs; document.getElementById('disableDragAndDropCheck').checked = params.disableDragAndDrop; document.getElementById('debugLibzimASMDrop').value = params.debugLibzimASM || ''; if (params.windowOpener === null) { // Setting has never been activated, so determine a sensible default - params.windowOpener = /UWP/.test(params.appType) && params.contentInjectionMode === 'jquery' ? false : + params.windowOpener = /UWP/.test(params.appType) && params.contentInjectionMode === 'jquery' ? false : /iOS/.test(params.appType) ? false : ('MSBlobBuilder' in window || params.PWAInstalled) ? 'window' : // IE11/Edge Legacy/UWP work best in window mode, not in tab mode, as does installed PWA! - /PWA/.test(params.appType) ? 'tab' : false; + /PWA/.test(params.appType) ? 'tab' : false; } if (params.windowOpener) params.allowHTMLExtraction = false; document.getElementById('allowHTMLExtractionCheck').checked = params.allowHTMLExtraction; @@ -302,7 +307,7 @@ if (params.packagedFileStub && params.appVersion !== getSetting('appVersion') && deleteSetting('listOfArchives'); params.localStorageUpgradeNeeded = true; } -if (params.storedFile && typeof Windows !== 'undefined' && typeof Windows.Storage !== 'undefined') { //UWP +if (params.storedFile && typeof Windows !== 'undefined' && typeof Windows.Storage !== 'undefined') { // UWP var futureAccessList = Windows.Storage.AccessCache.StorageApplicationPermissions.futureAccessList; Windows.ApplicationModel.Package.current.installedLocation.getFolderAsync(params.archivePath).done(function (appFolder) { params.localStorage = appFolder; @@ -310,13 +315,13 @@ if (params.storedFile && typeof Windows !== 'undefined' && typeof Windows.Storag futureAccessList.getFolderAsync(params.falFolderToken).done(function (pickedFolder) { params.pickedFolder = params.localStorageUpgradeNeeded ? params.localStorage : pickedFolder; }, function (err) { - console.error("The previously picked folder is no longer accessible: " + err.message); + console.error('The previously picked folder is no longer accessible: ' + err.message); }); } }, function (err) { - console.error("This app doesn't appear to have access to local storage!"); + console.error(new Error("This app doesn't appear to have access to local storage!")); }); - //If we don't already have a picked file (e.g. by launching app with click on a ZIM file), then retrieve it from futureAccessList if possible + // If we don't already have a picked file (e.g. by launching app with click on a ZIM file), then retrieve it from futureAccessList if possible var listOfArchives = getSetting('listOfArchives'); // But don't get the picked file if we already have access to the folder and the file is in it! if (listOfArchives && ~listOfArchives.indexOf(params.storedFile) && params.pickedFolder) { @@ -327,7 +332,7 @@ if (params.storedFile && typeof Windows !== 'undefined' && typeof Windows.Storag futureAccessList.getFileAsync(params.falFileToken).done(function (file) { if (file.name === params.storedFile) params.pickedFile = file; }, function (err) { - console.error("The previously picked file is no longer accessible: " + err.message); + console.error('The previously picked file is no longer accessible: ' + err.message); }); } } @@ -412,7 +417,7 @@ function getSetting(name) { // Use localStorage instead result = localStorage.getItem(params.keyPrefix + name); } - return result === null || result === "undefined" ? null : result === "true" ? true : result === "false" ? false : result; + return result === null || result === 'undefined' ? null : result === 'true' ? true : result === 'false' ? false : result; } function setSetting(name, val) { diff --git a/www/js/lib/cache.js b/www/js/lib/cache.js index e097ef7f..44313829 100644 --- a/www/js/lib/cache.js +++ b/www/js/lib/cache.js @@ -1,71 +1,73 @@ /** * cache.js : Provide a cache for assets from the ZIM archive using indexedDB, localStorage or memory cache - * + * * Copyright 2018 Mossroy, Jaifroid and contributors * License GPL v3: - * + * * This file is part of Kiwix. - * + * * Kiwix is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. - * + * * Kiwix is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - * + * * You should have received a copy of the GNU General Public License * along with Kiwix (file LICENSE-GPLv3.txt). If not, see