From e126ea20b089fc90a1e2d8b1f845568f13b0a751 Mon Sep 17 00:00:00 2001
From: RG
Date: Tue, 10 Oct 2023 21:04:27 +0530
Subject: [PATCH 01/45] [ADD] event listener for FS API
---
www/js/app.js | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/www/js/app.js b/www/js/app.js
index d59e3c855..f0a914811 100644
--- a/www/js/app.js
+++ b/www/js/app.js
@@ -1270,7 +1270,18 @@ function displayFileSelect () {
globalDropZone.addEventListener('drop', handleFileDrop);
}
// This handles use of the file picker
- document.getElementById('archiveFiles').addEventListener('change', setLocalArchiveFromFileSelect);
+ if (typeof window.showOpenFilePicker === 'function') {
+ document.getElementById('archiveFiles').addEventListener('click', function (e) {
+ e.preventDefault();
+ window.showOpenFilePicker({ multiple: true }).then(function (fileHandle) {
+ fileHandle[0].getFile().then(function (file) {
+ setLocalArchiveFromFileList([file]);
+ });
+ });
+ })
+ } else {
+ document.getElementById('archiveFiles').addEventListener('change', setLocalArchiveFromFileSelect);
+ }
}
function handleGlobalDragover (e) {
From cae1b9bd849d48dbad4abb0142871a782656214b Mon Sep 17 00:00:00 2001
From: RG
Date: Tue, 10 Oct 2023 21:11:41 +0530
Subject: [PATCH 02/45] [ADD] cache API forked from `kiwix-js-windows`
---
.gitignore | 1 +
www/js/lib/cache.js | 975 ++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 976 insertions(+)
create mode 100644 www/js/lib/cache.js
diff --git a/.gitignore b/.gitignore
index 69d180f3b..7db72b2ca 100644
--- a/.gitignore
+++ b/.gitignore
@@ -58,3 +58,4 @@ www-ghdeploy
/.vs/
/.vscode/
scripts/github_token
+.prettierrc
diff --git a/www/js/lib/cache.js b/www/js/lib/cache.js
new file mode 100644
index 000000000..33654ce84
--- /dev/null
+++ b/www/js/lib/cache.js
@@ -0,0 +1,975 @@
+/**
+ * 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
+ */
+
+/* globals params, appstate, caches, assetsCache */
+
+'use strict';
+import settingsStore from './settingsStore.js';
+import uiUtil from './uiUtil.js';
+
+const CACHEAPI = params.cacheAPI; // Set the database or cache name here, and synchronize with Service Worker
+const CACHEIDB = params.cacheIDB; // Slightly different name to disambiguate
+var objStore = 'kiwix-assets'; // Name of the object store
+const APPCACHE = 'kiwix-appCache-' + params.appVersion; // Ensure this is the same as in Service Worker
+
+// DEV: Regex below defines the permitted MIME types for the cache; add further types as needed
+var regexpMimeTypes = /\b(?:javascript|css|ico|html)\b/;
+
+/**
+ * Tests the enviornment's caching capabilities and sets assetsCache.capability to the supported level
+ *
+ * @param {Function} callback Function to indicate that the capability level has been set
+ */
+function test (callback) {
+ // Test for indexedDB capability
+ if (typeof assetsCache.capability !== 'undefined') {
+ callback(true);
+ return;
+ }
+ // Set baseline capability
+ assetsCache.capability = 'memory';
+ idxDB('count', function (result) {
+ if (result !== false) {
+ assetsCache.capability = 'indexedDB|' + assetsCache.capability;
+ } else {
+ console.log('inexedDB is not supported');
+ }
+ // Test for Cache API
+ if ('caches' in window && /https?:/i.test(window.location.protocol)) {
+ assetsCache.capability = 'cacheAPI|' + assetsCache.capability;
+ } else {
+ console.log('CacheAPI is not supported' + (/https?:/i.test(window.location.protocol) ? ''
+ : ' with the ' + window.location.protocol + ' protocol'));
+ }
+ // Test for localCache capability (this is a fallback, indexedDB is preferred because it permits more storage)
+ if (typeof Storage !== 'undefined') {
+ try {
+ // If localStorage is really supported, this won't produce an error
+ var item = window.localStorage.length;
+ assetsCache.capability = assetsCache.capability + '|localStorage';
+ } catch (err) {
+ console.log('localStorage is not supported');
+ }
+ }
+ console.log('Setting storage type to ' + assetsCache.capability.match(/^[^|]+/)[0]);
+ if (/localStorage/.test(assetsCache.capability)) {
+ console.debug("DEV: 'UnknownError' may be produced as part of localStorage capability detection");
+ }
+ callback(result);
+ });
+}
+
+/**
+ * Counts the numnber of cached assets
+ *
+ * @param {Function} callback which will receive an array containing [cacheType, cacheCount]
+ */
+function count (callback) {
+ test(function (result) {
+ var type = null;
+ var description = null;
+ var cacheCount = null;
+
+ switch (assetsCache.capability.match(/^[^|]+/)[0]) {
+ case 'memory':
+ type = 'memory';
+ description = 'Memory';
+ cacheCount = assetsCache.size;
+ break;
+ case 'localStorage':
+ type = 'localStorage';
+ description = 'LocalStorage';
+ cacheCount = localStorage.length;
+ break;
+ case 'indexedDB':
+ type = 'indexedDB';
+ description = 'IndexedDB';
+ // Sometimes we already have the count as a result of test, so no need to look again
+ if (typeof result !== 'boolean' && (result === 0 || result > 0)) {
+ cacheCount = result;
+ } else {
+ idxDB('count', function (cacheCount) {
+ callback({ type: type, description: description, count: cacheCount });
+ });
+ }
+ break;
+ case 'cacheAPI':
+ type = 'cacheAPI';
+ description = 'CacheAPI';
+ caches.open(CACHEAPI).then(function (cache) {
+ cache.keys().then(function (keys) {
+ callback({ type: type, description: description, count: keys.length });
+ });
+ });
+ break;
+ default:
+ // User has turned off caching
+ type = 'none';
+ description = 'None';
+ cacheCount = 'null';
+ }
+
+ if (cacheCount || cacheCount === 0) {
+ callback({ type: type, description: description, count: cacheCount });
+ }
+ });
+ // Refresh instructions to Service Worker
+ if (navigator.serviceWorker && navigator.serviceWorker.controller) {
+ // Create a Message Channel
+ var channel = new MessageChannel();
+ navigator.serviceWorker.controller.postMessage({
+ action: {
+ assetsCache: params.assetsCache ? 'enable' : 'disable',
+ appCache: params.appCache ? 'enable' : 'disable',
+ checkCache: window.location.href
+ }
+ }, [channel.port2]);
+ }
+}
+
+/**
+ * Opens an IndexedDB database and adds or retrieves a key-value pair to it, or performs utility commands
+ * on the database
+ *
+ * @param {String} keyOrCommand The key of the value to be written or read, or commands 'clear' (clears objStore),
+ * 'count' (counts number of objects in objStore), 'delete' (deletes a record with key passed in valueOrCallback),
+ * 'deleteNonCurrent' (deletes all databases that do not match CACHEIDB - but only works in Chromium currently)
+ * @param {Variable} valueOrCallback The value to write, or a callback function for read and command transactions
+ * @param {Function} callback Callback for write transactions only - mandatory for delete and write transactions
+ */
+function idxDB (keyOrCommand, valueOrCallback, callback) {
+ var value = callback ? valueOrCallback : null;
+ var rtnFn = callback || valueOrCallback;
+ if (typeof window.indexedDB === 'undefined') {
+ rtnFn(false);
+ return;
+ }
+
+ // Delete all non-curren IdxDB databases (only works in Chromium currently)
+ if (keyOrCommand === 'deleteNonCurrent') {
+ if (indexedDB.databases) {
+ var result = 0;
+ indexedDB.databases().then(function (dbs) {
+ dbs.forEach(function (db) {
+ if (db.name !== CACHEIDB) {
+ result++;
+ indexedDB.deleteDatabase(db.name);
+ }
+ });
+ }).then(function () {
+ rtnFn(result);
+ });
+ } else {
+ rtnFn(false);
+ }
+ return;
+ }
+
+ // Open (or create) the database
+ var open = indexedDB.open(CACHEIDB, 1);
+
+ open.onerror = function (e) {
+ // Suppress error reporting if testing (older versions of Firefox support indexedDB but cannot use it with
+ // the file:// protocol, so will report an error)
+ if (assetsCache.capability !== 'test') {
+ console.error('IndexedDB failed to open: ' + open.error.message);
+ }
+ rtnFn(false);
+ };
+
+ // Create the schema
+ open.onupgradeneeded = function () {
+ var db = open.result;
+ var store = db.createObjectStore(objStore);
+ };
+
+ open.onsuccess = function () {
+ // Start a new transaction
+ var db = open.result;
+
+ // Set the store to readwrite or read only according to presence or not of value variable
+ var tx = value !== null || /clear|delete/.test(keyOrCommand) ? db.transaction(objStore, 'readwrite') : db.transaction(objStore);
+ var store = tx.objectStore(objStore);
+
+ var processData;
+ // Process commands
+ if (keyOrCommand === 'clear') {
+ // Delete all keys and values in the store
+ processData = store.clear();
+ } else if (keyOrCommand === 'count') {
+ // Count the objects in the store
+ processData = store.count();
+ } else if (keyOrCommand === 'delete') {
+ // Delete the record with key set to value
+ processData = store.delete(value);
+ } else {
+ // Request addition or retrieval of data
+ processData = value !== null ? store.put(value, keyOrCommand) : store.get(keyOrCommand);
+ }
+ // Call the callback with the result
+ processData.onsuccess = function (e) {
+ if (keyOrCommand === 'delete') {
+ rtnFn(true);
+ } else {
+ rtnFn(processData.result);
+ }
+ };
+ processData.onerror = function (e) {
+ console.error('IndexedDB command failed: ' + processData.error);
+ rtnFn(false);
+ };
+
+ // Close the db when the transaction is done
+ tx.oncomplete = function () {
+ db.close();
+ };
+ };
+}
+
+/**
+ * Opens a CacheAPI cache and adds or retrieves a key-value pair to it, or performs utility commands
+ * on the cache. This interface also allows the use of callbacks inside the Cache Promise API for ease of
+ * interoperability with the interface for idxDB code above.
+ *
+ * @param {String} keyOrCommand The key of the value to be written or read, or commands 'clear' (clears cache),
+ * 'delete' (deletes a record with key passed in valueOrCallback)
+ * @param {Variable} valueOrCallback The value to write, or a callback function for read and command transactions
+ * @param {Function} callback Callback for write transactions only
+ * @param {String} mimetype The MIME type of any content to be stored
+ */
+function cacheAPI (keyOrCommand, valueOrCallback, callback, mimetype) {
+ var value = callback ? valueOrCallback : null;
+ var rtnFn = callback || valueOrCallback;
+ // Process commands
+ if (keyOrCommand === 'clear') {
+ caches.delete(CACHEAPI).then(rtnFn);
+ } else if (keyOrCommand === 'delete') {
+ caches.open(CACHEAPI).then(function (cache) {
+ cache.delete(value).then(rtnFn);
+ });
+ } else if (value === null) {
+ // Request retrieval of data
+ caches.open(CACHEAPI).then(function (cache) {
+ cache.match('../' + keyOrCommand).then(function (response) {
+ if (!response) {
+ rtnFn(null);
+ } else {
+ response.text().then(function (data) {
+ rtnFn(data);
+ });
+ }
+ }).catch(function (err) {
+ console.error('Unable to match assets from Cache API!', err);
+ rtnFn(null);
+ });
+ });
+ } else {
+ // Request storing of data in cache
+ caches.open(CACHEAPI).then(function (cache) {
+ var contentLength;
+ if (typeof value === 'string') {
+ var m = encodeURIComponent(value).match(/%[89ABab]/g);
+ contentLength = value.length + (m ? m.length : 0);
+ } else {
+ contentLength = value.byteLength || value.length;
+ }
+ var headers = new Headers();
+ if (contentLength) headers.set('Content-Length', contentLength);
+ // Prevent CORS issues in PWAs
+ // if (contentLength) headers.set('Access-Control-Allow-Origin', '*');
+ headers.set('Content-Security-Policy', 'sandbox allow-scripts allow-same-origin allow-modals allow-popups allow-forms');
+ if (mimetype) headers.set('Content-Type', mimetype);
+ var responseInit = {
+ status: 200,
+ statusText: 'OK',
+ headers: headers
+ };
+ var httpResponse = new Response(value, responseInit);
+ cache.put('../' + keyOrCommand, httpResponse).then(function () {
+ rtnFn(true);
+ }).catch(function (err) {
+ console.error('Unable to store assets in Cache API!', err);
+ rtnFn(null);
+ });
+ });
+ }
+}
+
+/**
+ * Stores information about the last visited page in a cookie and, if available, in localStorage or indexedDB
+ *
+ * @param {String} zimFile The filename (or name of first file in set) of the ZIM archive
+ * @param {String} article The URL of the article (including namespace)
+ * @param {String} content The content of the page to be stored
+ * @param {Function} callback Callback function to report the outcome of the operation
+ */
+function setArticle (zimFile, article, content, callback) {
+ // Prevent storage if user has deselected the option in Configuration
+ if (!params.rememberLastPage) {
+ callback(-1);
+ return;
+ }
+ settingsStore.setItem(zimFile, article, Infinity);
+ setItem(zimFile, content, 'text/html', function (response) {
+ callback(response);
+ });
+}
+
+/**
+ * Retrieves article contents from cache only if the article's key has been stored in settings store
+ * (since checking the store is synchronous, it prevents unnecessary async cache lookups)
+ *
+ * @param {String} zimFile The filename (or name of first file in set) of the ZIM archive
+ * @param {String} article The URL of the article to be retrieved (including namespace)
+ * @param {Function} callback The function to call with the result
+ */
+function getArticle (zimFile, article, callback) {
+ if (settingsStore.getItem(zimFile) === article) {
+ getItem(zimFile, callback);
+ } else {
+ callback(false);
+ }
+}
+
+/**
+ * Caches the contents of an asset in memory or local storage
+ *
+ * @param {String} key The database key of the asset to cache
+ * @param {String} contents The file contents to be stored in the cache
+ * @param {String} mimetype The MIME type of the contents
+ * @param {Function} callback Callback function to report outcome of operation
+ * @param {Boolean} isAsset Optional indicator that a file is an asset
+ */
+function setItem (key, contents, mimetype, callback, isAsset) {
+ // Prevent use of storage if user has deselected the option in Configuration
+ // or if the asset is of the wrong type
+ if (params.assetsCache === false || !regexpMimeTypes.test(mimetype)) {
+ callback(-1);
+ return;
+ }
+ // Check if we're actually setting an article
+ var keyArticle = key.match(/([^/]+)\/([AC]\/.+$)/);
+ if (keyArticle && !isAsset && /\bx?html\b/i.test(mimetype) && !/\.(png|gif|jpe?g|css|js|mpe?g|webp|webm|woff2?|eot|mp[43])(\?|$)/i.test(key)) { // We're setting an article, so go to setArticle function
+ setArticle(keyArticle[1], keyArticle[2], contents, callback);
+ return;
+ }
+ if (/^localStorage/.test(assetsCache.capability)) {
+ localStorage.setItem(key, contents);
+ } else {
+ assetsCache.set(key, contents);
+ }
+ if (/^indexedDB/.test(assetsCache.capability)) {
+ idxDB(key, contents, function (result) {
+ callback(result);
+ });
+ } else if (/^cacheAPI/.test(assetsCache.capability)) {
+ cacheAPI(key, contents, function (result) {
+ callback(result);
+ }, mimetype);
+ } else {
+ callback(key);
+ }
+}
+
+/**
+ * Retrieves a ZIM file asset that has been cached with the addItem function
+ * either from the memory cache or local storage
+ *
+ * @param {String} key The database key of the asset to retrieve
+ * @param {Function} callback The function to call with the result
+ */
+function getItem (key, callback) {
+ // Only look up assets of the type stored in the cache
+ if (params.assetsCache === false) {
+ callback(false);
+ return;
+ }
+ // Check if we're actually calling an article
+ // DEV: With new ZIM types, we can't know we're retrieving an article...
+ // var keyArticle = key.match(/([^/]+)\/(A\/.+$)/);
+ // if (keyArticle) { // We're retrieving an article, so go to getArticle function
+ // getArticle(keyArticle[1], keyArticle[2], callback);
+ // return;
+ // }
+ var contents = null;
+ if (assetsCache.has(key)) {
+ contents = assetsCache.get(key);
+ callback(contents);
+ } else if (/^localStorage/.test(assetsCache.capability)) {
+ contents = localStorage.getItem(key);
+ callback(contents);
+ } else if (/^cacheAPI/.test(assetsCache.capability)) {
+ cacheAPI(key, function (contents) {
+ callback(contents);
+ });
+ } else if (/^indexedDB/.test(assetsCache.capability)) {
+ idxDB(key, function (contents) {
+ if (typeof contents !== 'undefined') {
+ // Also store in fast memory cache to prevent repaints
+ assetsCache.set(key, contents);
+ }
+ callback(contents);
+ });
+ } else {
+ callback(contents);
+ }
+}
+
+/**
+ * Gets an item from the cache, or extracts it from the ZIM if it is not cached. After extracting
+ * an item from the ZIM, it is added to the cache if it is of the type specified in regexpKeyTypes.
+ *
+ * @param {Object} selectedArchive The ZIM archive picked by the user
+ * @param {String} key The cache key of the item to retrieve
+ * @param {Object} dirEntry If the item's dirEntry has already been looked up, it can optionally be
+ * supplied here (saves a redundant dirEntry lookup)
+ * @returns {Promise} A Promise for the content
+ */
+function getItemFromCacheOrZIM (selectedArchive, key, dirEntry) {
+ return new Promise(function (resolve, reject) {
+ // First check if the item is already in the cache
+ var title = key.replace(/^[^/]+\//, '');
+ getItem(key, function (result) {
+ if (result !== null && result !== false && typeof result !== 'undefined') {
+ // console.debug("Cache supplied " + title);
+ if (/\.css$/.test(title)) {
+ assetsCache.cssLoading--;
+ if (assetsCache.cssLoading <= 0) {
+ document.getElementById('articleContent').style.display = 'block';
+ }
+ }
+ resolve(result);
+ return;
+ }
+ // Bypass getting dirEntry if we already have it
+ var getDirEntry = dirEntry ? Promise.resolve()
+ : selectedArchive.getDirEntryByPath(title);
+ // Read data from ZIM
+ getDirEntry.then(function (resolvedDirEntry) {
+ if (dirEntry) resolvedDirEntry = dirEntry;
+ if (resolvedDirEntry === null) {
+ console.log('Error: asset file not found: ' + title);
+ resolve(null);
+ } else {
+ var mimetype = resolvedDirEntry.getMimetype();
+ if (resolvedDirEntry.nullify) {
+ console.debug('Zimit filter prevented access to ' + resolvedDirEntry.url + '. Storing empty contents in cache.');
+ setItem(key, '', mimetype, function () { });
+ resolve('');
+ return;
+ }
+ var shortTitle = key.replace(/[^/]+\//g, '').substring(0, 18);
+ // Since there was no result, post UI messages and look up asset in ZIM
+ if (/\bx?html\b/.test(mimetype) && !resolvedDirEntry.isAsset &&
+ !/\.(png|gif|jpe?g|svg|css|js|mpe?g|webp|webm|woff2?|eot|mp[43])(\?|$)/i.test(resolvedDirEntry.url)) {
+ uiUtil.pollSpinner('Loading ' + shortTitle + '...');
+ } else if (/(css|javascript|video|vtt)/i.test(mimetype)) {
+ uiUtil.pollSpinner('Getting ' + shortTitle + '...');
+ }
+ // Set the read function to use according to filetype
+ var readFile = /\b(?:x?html|css|javascript)\b/i.test(mimetype)
+ ? selectedArchive.readUtf8File : selectedArchive.readBinaryFile;
+ readFile(resolvedDirEntry, function (fileDirEntry, content) {
+ if (regexpMimeTypes.test(mimetype)) {
+ console.debug('Cache retrieved ' + title + ' from ZIM');
+ // Process any pre-cache transforms
+ content = transform(content, title.replace(/^.*\.([^.]+)$/, '$1'));
+ }
+ // Hide article while it is rendering
+ if (!fileDirEntry.isAsset && /\bx?html\b/i.test(mimetype) && !/\.(png|gif|jpe?g|svg|css|js|mpe?g|webp|webm|woff2?|eot|mp[34])(\?|$)/i.test(key)) {
+ // Count CSS so we can attempt to show article before JS/images are fully loaded
+ var cssCount = content.match(/<(?:link)[^>]+?href=["']([^"']+)[^>]+>/ig);
+ assetsCache.cssLoading = cssCount ? cssCount.length : 0;
+ if (assetsCache.cssLoading) document.getElementById('articleContent').style.display = 'none';
+ }
+ if (/\bcss\b/i.test(mimetype)) {
+ assetsCache.cssLoading--;
+ if (assetsCache.cssLoading <= 0) {
+ document.getElementById('articleContent').style.display = 'block';
+ }
+ }
+ setItem(key, content, mimetype, function (result) {
+ if (result === -1) {
+ // Cache rejected item due to user settings
+ } else if (result) {
+ console.log('Cache: stored asset ' + title);
+ } else {
+ console.error('Cache: failed to store asset ' + title);
+ }
+ }, fileDirEntry.isAsset);
+ resolve(content);
+ });
+ }
+ }).catch(function (e) {
+ reject(new Error('Could not find DirEntry for asset ' + title, e));
+ });
+ });
+ });
+}
+
+/**
+ * Clears caches (including cookie) according to the scope represented by the 'items' variable
+ *
+ * @param {String} items 'lastpages' (last visited pages of various archives), 'all' or 'reset'
+ * @param {Function} callback Callback function to report the number of items cleared
+ */
+function clear (items, callback) {
+ if (!/lastpages|all|reset/.test(items)) {
+ if (callback) callback(false);
+ return;
+ }
+ // Delete cookie entries with a key containing '.zim' or '.zimaa' etc. followed by article namespace
+ var itemsCount = 0;
+ var key;
+ var capability = assetsCache.capability;
+ var zimRegExp = /(?:^|;)\s*([^=]+)=([^;]*)/ig;
+ var currentCookies = document.cookie;
+ var cookieCrumb = zimRegExp.exec(currentCookies);
+ while (cookieCrumb !== null) {
+ if (/\.zim\w{0,2}=/i.test(decodeURIComponent(cookieCrumb[0]))) {
+ key = cookieCrumb[1];
+ // This expiry date will cause the browser to delete the cookie on next page refresh
+ document.cookie = key + '=;expires=Thu, 21 Sep 1979 00:00:01 UTC;';
+ if (items === 'lastpages') {
+ assetsCache.delete(key);
+ // See note on loose test below
+ if (/localStorage/.test(capability)) {
+ localStorage.removeItem(key);
+ }
+ if (/indexedDB/.test(capability)) {
+ idxDB('delete', key, function () { });
+ }
+ if (/cacheAPI/.test(capability)) {
+ cacheAPI('delete', key, function () { });
+ }
+ itemsCount++;
+ }
+ }
+ cookieCrumb = zimRegExp.exec(currentCookies);
+ }
+ if (items === 'all' || items === 'reset') {
+ var result;
+ if (/^(memory|indexedDB|cacheAPI)/.test(capability)) {
+ itemsCount += assetsCache.size;
+ result = 'assetsCache';
+ }
+ // Delete and reinitialize assetsCache
+ assetsCache = new Map();
+ assetsCache.capability = capability;
+ // Loose test here ensures we clear localStorage even if it wasn't being used in this session
+ if (/localStorage/.test(capability)) {
+ if (items === 'reset') {
+ itemsCount += localStorage.length;
+ localStorage.clear();
+ } else {
+ for (var i = localStorage.length; i--;) {
+ key = localStorage.key(i);
+ if (/\.zim\w{0,2}/i.test(key)) {
+ localStorage.removeItem(key);
+ itemsCount++;
+ }
+ }
+ }
+ result = result ? result + ' and localStorage' : 'localStorage';
+ }
+ // Loose test here ensures we clear indexedDB even if it wasn't being used in this session
+ if (/indexedDB/.test(capability)) {
+ result = result ? result + ' and indexedDB' : 'indexedDB';
+ idxDB('count', function (number) {
+ itemsCount += number;
+ idxDB('clear', function () {
+ result = result ? result + ' (' + itemsCount + ' items deleted)' : 'no assets to delete';
+ console.log('cache.clear: ' + result);
+ if (!/^cacheAPI/.test(capability) && callback) callback(itemsCount);
+ });
+ });
+ }
+ // No need to use loose test here because cacheAPI trumps the others
+ if (/^cacheAPI/.test(capability)) {
+ result = result ? result + ' and cacheAPI' : 'cacheAPI';
+ count(function (number) {
+ itemsCount += number[1];
+ cacheAPI('clear', function () {
+ result = result ? result + ' (' + itemsCount + ' items deleted)' : 'no assets to delete';
+ console.log('cache.clear: ' + result);
+ if (callback) callback(itemsCount);
+ });
+ });
+ }
+ }
+ if (!/^cacheAPI|indexedDB/.test(capability)) {
+ result = result ? result + ' (' + itemsCount + ' items deleted)' : 'no assets to delete';
+ console.log('cache.clear: ' + result);
+ if (callback) callback(itemsCount);
+ }
+}
+
+/**
+ * Replaces all assets that have the given attribute in the html string with inline tags containing content
+ * from the cache entries corresponding to the given zimFile
+ * Function is intended for link or script tags, but could be extended
+ * Returns the substituted html in the callback function (even if no substitutions were made)
+ *
+ * @param {String} html The html string to process
+ * @param {String} tags The html tag or tags ('link|script') containing the asset to replace;
+ * multiple tags must be separated with a pipe
+ * @param {String} attribute The attribute that stores the URL to be substituted
+ * @param {String} zimFile The name of the ZIM file (or first file in the file set)
+ * @param {Object} selectedArchive The archive selected by the user in app.js
+ * @param {Function} callback The function to call with the substituted html
+ */
+function replaceAssetRefsWithUri (html, tags, attribute, zimFile, selectedArchive, callback) {
+ // Creates an array of all link tags that have the given attribute
+ var regexpTagsWithAttribute = new RegExp('<(?:' + tags + ')[^>]+?' + attribute + '=["\']([^"\']+)[^>]+>', 'ig');
+ var titles = [];
+ var tagArray = regexpTagsWithAttribute.exec(html);
+ while (tagArray !== null) {
+ titles.push([tagArray[0],
+ decodeURIComponent(tagArray[1])]);
+ tagArray = regexpTagsWithAttribute.exec(html);
+ }
+ if (!titles.length) {
+ callback(html);
+ }
+ // Iterate through the erray of titles, populating the HTML string with substituted tags containing
+ // a reference to the content from the Cache or from the ZIM
+ assetsCache.busy = titles.length;
+ titles.forEach(function (title) {
+ getItemFromCacheOrZIM(selectedArchive, zimFile + '/' + title[1], function (assetContent) {
+ assetsCache.busy--;
+ if (assetContent || assetContent === '') {
+ var newAssetTag = uiUtil.createNewAssetElement(title[0], attribute, assetContent);
+ html = html.replace(title[0], newAssetTag);
+ }
+ if (!assetsCache.busy) callback(html);
+ });
+ });
+}
+
+/**
+ * Provides "Server Side" transformation of textual content "served" to app.js
+ * For performance reasons, this is only hooked into content extracted from the ZIM: the transformed
+ * content will then be cached in its transformed state
+ *
+ * @param {String} string The string to transform
+ * @param {String} filter An optional filter: only transforms which match the filter will be executed
+ * @returns {String} The tranformed content
+ */
+function transform (string, filter) {
+ switch (filter) {
+ case 'html':
+ // Filter to remove any BOM (causes quirks mode in browser)
+ string = string.replace(/^[^<]*/, '');
+
+ // Filter to open all heading sections
+ string = string.replace(/(class=["'][^"']*?collapsible-(?:heading|block)(?!\s+open-block))/g,
+ '$1 open-block');
+
+ break;
+ }
+ return string;
+}
+
+/**
+ * Provide method to verify File System Access API permissions
+ *
+ * @param {Object} fileHandle The file handle that we wish to verify with the Native Filesystem API
+ * @param {Boolean} withWrite Indicates read only or read/write persmissions
+ * @returns {Promise} A Promise for a Boolean value indicating whether permission has been granted or not
+ */
+function verifyPermission (fileHandle, withWrite) {
+ if (params.useOPFS) return Promise.resolve(true); // No permission prompt required for OPFS
+ var opts = withWrite ? { mode: 'readwrite' } : {};
+ return fileHandle.queryPermission(opts).then(function (permission) {
+ if (permission === 'granted') return true;
+ return fileHandle.requestPermission(opts).then(function (permission) {
+ if (permission === 'granted') return true;
+ console.error('Permission for ' + fileHandle.name + ' was not granted: ' + permission);
+ return false;
+ }).catch(function (error) {
+ console.warn('Cannot use previously picked file handle programmatically (this is normal) ' + fileHandle.name, error);
+ });
+ });
+}
+
+/**
+ * Download an archive directly into the picked folder (primarily for use with the Origin Private File System)
+ *
+ * @param {String} archiveName The name of the archive to download (will be used as the filename)
+ * @param {String} archiveUrl An optional URL to download the archive from (if not supplied, will use params.kiwixDownloadLink)
+ * @param {Function} callback Callback function to report the progress of the download
+ * @returns {Promise} A Promise for a FileHandle object representing the downloaded file
+ */
+function downloadArchiveToPickedFolder (archiveName, archiveUrl, callback) {
+ archiveUrl = archiveUrl || params.kiwixDownloadLink + archiveName;
+ if (params.pickedFolder && params.pickedFolder.getFileHandle) {
+ return verifyPermission(params.pickedFolder, true).then(function (permission) {
+ if (permission) {
+ return params.pickedFolder.getFileHandle(archiveName, { create: true }).then(function (fileHandle) {
+ return fileHandle.createWritable().then(function (writer) {
+ return fetch(archiveUrl).then(function (response) {
+ if (!response.ok) {
+ return writer.close().then(function () {
+ // Delete the file
+ params.pickedFolder.removeEntry(archiveName).then(function () {
+ throw new Error('HTTP error, status = ' + response.status);
+ });
+ });
+ }
+ var loaded = 0;
+ var reported = 0;
+ return new Response(
+ new ReadableStream({
+ start: function (controller) {
+ var reader = response.body.getReader();
+ var processResult = function (result) {
+ if (result.done) {
+ return controller.close();
+ }
+ loaded += result.value.byteLength;
+ if (loaded - reported >= 1048576) { // 1024 * 1024
+ reported = loaded;
+ if (callback) {
+ callback(reported);
+ } else console.debug('Downloaded ' + reported + ' bytes so far...');
+ }
+ controller.enqueue(result.value);
+ return reader.read().then(processResult);
+ };
+ return reader.read().then(processResult);
+ }
+ })
+ ).body.pipeTo(writer).then(function () {
+ if (callback) callback('completed');
+ return true;
+ }).catch(function (err) {
+ console.error('Error downloading archive', err);
+ if (callback) callback('error');
+ writer.close().then(function () {
+ // Delete the file
+ params.pickedFolder.removeEntry(archiveName).then(function () {
+ throw err;
+ });
+ });
+ });
+ });
+ });
+ });
+ } else {
+ throw (new Error('Write permission not granted!'));
+ }
+ }).catch(function (err) {
+ console.error('Error downloading archive', err);
+ throw err;
+ });
+ } else {
+ return Promise.reject(new Error('No picked folder available!'));
+ }
+}
+
+/**
+ * Imports the picked files into the OPFS file system
+ *
+ * @param {Array} files An array of File objects to import
+ */
+function importOPFSEntries (files) {
+ return Promise.all(files.map(function (file) {
+ return params.pickedFolder.getFileHandle(file.name, { create: true }).then(function (fileHandle) {
+ return fileHandle.createWritable().then(function (writer) {
+ uiUtil.pollOpsPanel(' Please wait: Importing ' + file.name + '...', true);
+ return writer.write(file).then(function () {
+ uiUtil.pollOpsPanel(' Please wait: Imported ' + file.name + '...', true);
+ return writer.close();
+ });
+ });
+ });
+ }));
+}
+
+/**
+ * Exports an entry from the OPFS file system to the user-visible file system
+ *
+ * @param {String} name The filename of the entry to export
+ * @returns {Promise} A Promise for a Boolean value indicating whether the export was successful
+ */
+function exportOPFSEntry (name) {
+ if (navigator && navigator.storage && 'getDirectory' in navigator.storage) {
+ return navigator.storage.getDirectory().then(function (dir) {
+ return dir.getFileHandle(name).then(function (fileHandle) {
+ try {
+ // Obtain a file handle to a new file in the user-visible file system
+ // with the same name as the file in the origin private file system.
+ return window.showSaveFilePicker({
+ suggestedName: fileHandle.name || ''
+ }).then(function (saveHandle) {
+ return saveHandle.createWritable().then(function (writable) {
+ return fileHandle.getFile().then(function (file) {
+ return writable.write(file).then(function () {
+ writable.close();
+ return true;
+ });
+ });
+ });
+ });
+ } catch (err) {
+ console.error(err.name, err.message);
+ return false;
+ }
+ }).catch(function (err) {
+ console.error('Unable to get file handle from OPFS', err);
+ return false;
+ });
+ }).catch(function (err) {
+ console.error('Unable to get directory from OPFS', err);
+ return false;
+ });
+ }
+}
+
+/**
+ * Deletes an entry from the OPFS file system
+ *
+ * @param {String} name The filename of the entry to delete
+ */
+function deleteOPFSEntry (name) {
+ if (navigator && navigator.storage && 'getDirectory' in navigator.storage) {
+ return navigator.storage.getDirectory().then(function (dirHandle) {
+ return iterateOPFSEntries().then(function (entries) {
+ var baseName = name.replace(/\.zim[^.]*$/i, '');
+ entries.forEach(function (entry) {
+ if (~entry.name.indexOf(baseName)) {
+ return dirHandle.removeEntry(entry.name).then(function () {
+ console.log('Deleted ' + entry.name + ' from OPFS');
+ populateOPFSStorageQuota();
+ }).catch(function (err) {
+ console.error('Unable to delete ' + entry.name + ' from OPFS', err);
+ });
+ }
+ });
+ }).catch(function (err) {
+ console.error('Unable to get directory from OPFS', err);
+ });
+ }).catch(function (err) {
+ console.error('Unable to get directory from OPFS', err);
+ });
+ }
+}
+
+/**
+ * Iterates the OPFS file system and returns an array of entries found
+ *
+ * @returns {Promise} A Promise for an array of entries in the OPFS file system
+ */
+function iterateOPFSEntries () {
+ if (navigator && navigator.storage && 'getDirectory' in navigator.storage) {
+ return navigator.storage.getDirectory().then(function (dirHandle) {
+ var archiveEntries = [];
+ var entries = dirHandle.entries();
+ var promisesForEntries = [];
+ // Push the pormise for each entry to the promises array
+ var pushPromises = new Promise(function (resolve) {
+ (function iterate () {
+ return entries.next().then(function (result) {
+ if (!result.done) {
+ // Process the entry, then continue iterating
+ var entry = result.value[1];
+ archiveEntries.push(entry);
+ promisesForEntries.push(result);
+ iterate();
+ } else {
+ return resolve(true);
+ }
+ });
+ })();
+ });
+ return pushPromises.then(function () {
+ return Promise.all(promisesForEntries).then(function () {
+ return archiveEntries;
+ }).catch(function (err) {
+ console.error('Unable to iterate OPFS entries', err);
+ });
+ });
+ });
+ }
+}
+
+/**
+ * Gets the OPFS storage quota and populates the OPFSQuota panel
+ *
+ * @returns {Promise} A Promise that populates the OPFSQuota panel
+ */
+function populateOPFSStorageQuota () {
+ if (navigator && navigator.storage && ('estimate' in navigator.storage)) {
+ return navigator.storage.estimate().then(function (estimate) {
+ var percent = ((estimate.usage / estimate.quota) * 100).toFixed(2);
+ appstate.OPFSQuota = estimate.quota - estimate.usage;
+ document.getElementById('OPFSQuota').innerHTML =
+ 'OPFS storage quota: Used: ' + percent + '%; Remaining: ' +
+ (appstate.OPFSQuota / 1024 / 1024 / 1024).toFixed(2) + ' GB';
+ });
+ }
+}
+
+/**
+ * Wraps a semaphor in a Promise. A function can signal that it is done by setting a sempahor to true,
+ * if it has first set it to false at the outset of the procedure. Ensure no other functions use the same
+ * sempahor. The semaphor must be an object key of the app-wide assetsCache object.
+ *
+ * @param {String} semaphor The name of a semaphor key in the assetsCache object
+ * @param {String|Object} value An optional value or object to pass in the resolved promise
+ * @returns {Promise} A promise that resolves when assetsCache[semaphor] is true
+ */
+function wait (semaphor, value) {
+ var p = new Promise(function (resolve) {
+ setTimeout(function awaitCache () {
+ if (assetsCache[semaphor]) {
+ return resolve(value);
+ }
+ setTimeout(awaitCache, 300);
+ }, 0);
+ });
+ return p;
+}
+
+export default {
+ APPCACHE: APPCACHE,
+ CACHEAPI: CACHEAPI,
+ test: test,
+ count: count,
+ idxDB: idxDB,
+ cacheAPI: cacheAPI,
+ setArticle: setArticle,
+ getArticle: getArticle,
+ setItem: setItem,
+ getItem: getItem,
+ clear: clear,
+ wait: wait,
+ getItemFromCacheOrZIM: getItemFromCacheOrZIM,
+ replaceAssetRefsWithUri: replaceAssetRefsWithUri,
+ verifyPermission: verifyPermission,
+ downloadArchiveToPickedFolder: downloadArchiveToPickedFolder,
+ importOPFSEntries: importOPFSEntries,
+ exportOPFSEntry: exportOPFSEntry,
+ deleteOPFSEntry: deleteOPFSEntry,
+ iterateOPFSEntries: iterateOPFSEntries,
+ populateOPFSStorageQuota: populateOPFSStorageQuota
+};
From 050815bf484f54177d7071a11e6aba1f8761407f Mon Sep 17 00:00:00 2001
From: RG
Date: Wed, 11 Oct 2023 18:40:10 +0530
Subject: [PATCH 03/45] [ADD] Zim File handler saving in indexDB
---
www/js/app.js | 26 ++++++++++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/www/js/app.js b/www/js/app.js
index f0a914811..dc427fad4 100644
--- a/www/js/app.js
+++ b/www/js/app.js
@@ -35,6 +35,7 @@ import uiUtil from './lib/uiUtil.js';
import settingsStore from './lib/settingsStore.js';
import abstractFilesystemAccess from './lib/abstractFilesystemAccess.js';
import translateUI from './lib/translateUI.js';
+import cache from './lib/cache.js';
if (params.abort) {
// If the app was loaded only to pass a message from the remote code, then we exit immediately
@@ -166,6 +167,7 @@ function resizeIFrame () {
document.addEventListener('DOMContentLoaded', function () {
getDefaultLanguageAndTranslateApp();
resizeIFrame();
+ loadPreviousZimFile();
});
window.addEventListener('resize', resizeIFrame);
@@ -1250,6 +1252,26 @@ function resetCssCache () {
}
}
+async function loadPreviousZimFile () {
+ const openFile = await uiUtil.systemAlert('Do you want to load the previously selected zim file?', 'Load previous zim file', true, 'No', 'Yes', 'No')
+ // .then(function (response) {
+ // })
+ if (!openFile) return
+ // If a old zim file is already selected, we set it as the localArchive
+ cache.idxDB('files', async function (filesHandlers) {
+ // console.log("FILE HANDLE", a);
+ // refer to this article ; https://developer.chrome.com/articles/file-system-access/
+ const files = [];
+ for (let index = 0; index < filesHandlers.length; index++) {
+ const fileHandler = filesHandlers[index];
+ await fileHandler.requestPermission();
+ files.push(await fileHandler.getFile())
+ }
+ console.log(files);
+ setLocalArchiveFromFileList(files);
+ })
+}
+
/**
* Displays the zone to select files from the archive
*/
@@ -1276,6 +1298,10 @@ function displayFileSelect () {
window.showOpenFilePicker({ multiple: true }).then(function (fileHandle) {
fileHandle[0].getFile().then(function (file) {
setLocalArchiveFromFileList([file]);
+
+ cache.idxDB('files', fileHandle, function (a) {
+ // console.log(a);
+ })
});
});
})
From e3963751e52a90760395fe0433382db668dda9a3 Mon Sep 17 00:00:00 2001
From: RG
Date: Thu, 12 Oct 2023 16:43:34 +0530
Subject: [PATCH 04/45] [FINALIZE] File system api picker and saving handles in
indexDB
---
www/js/app.js | 67 +++++++++++++++++++++++++++++++++------------------
1 file changed, 43 insertions(+), 24 deletions(-)
diff --git a/www/js/app.js b/www/js/app.js
index dc427fad4..a7cd95e12 100644
--- a/www/js/app.js
+++ b/www/js/app.js
@@ -1252,24 +1252,28 @@ function resetCssCache () {
}
}
-async function loadPreviousZimFile () {
- const openFile = await uiUtil.systemAlert('Do you want to load the previously selected zim file?', 'Load previous zim file', true, 'No', 'Yes', 'No')
- // .then(function (response) {
- // })
- if (!openFile) return
- // If a old zim file is already selected, we set it as the localArchive
- cache.idxDB('files', async function (filesHandlers) {
- // console.log("FILE HANDLE", a);
- // refer to this article ; https://developer.chrome.com/articles/file-system-access/
- const files = [];
- for (let index = 0; index < filesHandlers.length; index++) {
- const fileHandler = filesHandlers[index];
- await fileHandler.requestPermission();
- files.push(await fileHandler.getFile())
- }
- console.log(files);
- setLocalArchiveFromFileList(files);
- })
+/**
+ * Loads the Previously selected zim file via IndexedDB
+ */
+function loadPreviousZimFile () {
+ if (typeof window.showOpenFilePicker === 'function') {
+ cache.idxDB('zimFile', async function (fileHandler) {
+ // console.log(fileHandler);
+ if (!fileHandler) return console.info('There is no previous zim file in DB')
+
+ const openFile = await uiUtil.systemAlert('Do you want to load the previously selected zim file?', 'Load previous zim file', true, 'No', 'Yes', 'No')
+ if (!openFile) {
+ cache.idxDB('zimFile', undefined, function () {
+ // reset all zim files in DB
+ })
+ return console.log('User Dont want to load previous zim file')
+ }
+
+ // refer to this article for easy explanation https://developer.chrome.com/articles/file-system-access/
+ const isGranted = await fileHandler.requestPermission();
+ if (isGranted === 'granted') setLocalArchiveFromFileList([await fileHandler.getFile()]);
+ })
+ }
}
/**
@@ -1295,12 +1299,12 @@ function displayFileSelect () {
if (typeof window.showOpenFilePicker === 'function') {
document.getElementById('archiveFiles').addEventListener('click', function (e) {
e.preventDefault();
- window.showOpenFilePicker({ multiple: true }).then(function (fileHandle) {
- fileHandle[0].getFile().then(function (file) {
+ window.showOpenFilePicker({ multiple: false }).then(function (fileHandle) {
+ const selectedFile = fileHandle[0]
+ selectedFile.getFile().then(function (file) {
setLocalArchiveFromFileList([file]);
-
- cache.idxDB('files', fileHandle, function (a) {
- // console.log(a);
+ cache.idxDB('zimFile', selectedFile, function () {
+ // file saved in DB
})
});
});
@@ -1327,7 +1331,7 @@ function handleIframeDrop (e) {
e.preventDefault();
}
-function handleFileDrop (packet) {
+async function handleFileDrop (packet) {
packet.stopPropagation();
packet.preventDefault();
configDropZone.style.border = '';
@@ -1338,6 +1342,21 @@ function handleFileDrop (packet) {
setLocalArchiveFromFileList(files);
// This clears the display of any previously picked archive in the file selector
document.getElementById('archiveFiles').value = null;
+
+ if (typeof window.showOpenFilePicker === 'function') {
+ // Only runs when browser support File System API
+ const fileInfo = await packet.dataTransfer.items[0]
+ if (fileInfo.kind === 'file') {
+ const fileHandle = await fileInfo.getAsFileSystemHandle();
+ cache.idxDB('zimFile', fileHandle, function () {
+ // save file in DB
+ });
+ }
+ }
+ // will be later on used
+ // if (fileInfo.kind === 'directory'){
+ // const dirHandle = fileInfo.getAsFileSystemHandle();
+ // }
}
document.getElementById('libraryBtn').addEventListener('click', function (e) {
From decafcd22a7b0af04e21e15c927acbb4a43e506f Mon Sep 17 00:00:00 2001
From: RG
Date: Fri, 13 Oct 2023 00:36:29 +0530
Subject: [PATCH 05/45] [ADD] Select for zim files
---
www/index.html | 3 +++
www/js/app.js | 47 ++++++++++++++++++++++++++++++++++-------------
2 files changed, 37 insertions(+), 13 deletions(-)
diff --git a/www/index.html b/www/index.html
index e1dc67f55..ee800ef9f 100644
--- a/www/index.html
+++ b/www/index.html
@@ -467,6 +467,9 @@
+
+ Please select the archive you want to use :
-
- Rescans your SD Cards and internal memory
+
+
+
+ Rescans your SD Cards and internal memory
+
diff --git a/www/js/app.js b/www/js/app.js
index 5e5f1df6c..d9b3f0d7d 100644
--- a/www/js/app.js
+++ b/www/js/app.js
@@ -1161,6 +1161,7 @@ window.onpopstate = function (event) {
function populateDropDownListOfArchives (archiveDirectories) {
document.getElementById('scanningForArchives').style.display = 'none';
document.getElementById('chooseArchiveFromLocalStorage').style.display = '';
+ document.getElementById('rescanButtonAndText').style.display = '';
var comboArchiveList = document.getElementById('archiveList');
comboArchiveList.options.length = 0;
for (var i = 0; i < archiveDirectories.length; i++) {
@@ -1266,7 +1267,7 @@ function displayFileSelect () {
document.getElementById('openLocalFiles').style.display = 'block';
if (isFileSystemAPISupported || isWebkitSupported) {
- document.getElementById('zimSelectDropdown').style.display = '';
+ document.getElementById('chooseArchiveFromLocalStorage').style.display = '';
document.getElementById('folderSelect').style.display = '';
}
@@ -1285,7 +1286,7 @@ function displayFileSelect () {
globalDropZone.addEventListener('drop', handleFileDrop);
}
- document.getElementById('zimSelectDropdown').addEventListener('change', async function (e) {
+ document.getElementById('archiveList').addEventListener('change', async function (e) {
// handle zim selection from dropdown if multiple files are loaded via webkitdirectory or filesystem api
if (isFileSystemAPISupported) {
const files = await fileSystem.getSelectedZimFromCache(e.target.value)
@@ -1312,6 +1313,7 @@ function displayFileSelect () {
filenames.push(file.name);
}
webKitFileList = e.target.files;
+ // populateDropDownListOfArchives(filenames);
await fileSystem.updateZimDropdownOptions({ fileOrDirHandle: null, files: filenames }, '');
})
}
diff --git a/www/js/lib/fileSystem.js b/www/js/lib/fileSystem.js
index 79233b177..f7dbaf761 100644
--- a/www/js/lib/fileSystem.js
+++ b/www/js/lib/fileSystem.js
@@ -14,7 +14,7 @@ import cache from './cache.js';
* @returns {Promise>} Array of unique filenames (if a split zim is considered a single file)
*/
async function updateZimDropdownOptions (fileSystemHandler, selectedFile) {
- const select = document.getElementById('zimSelectDropdown');
+ const select = document.getElementById('archiveList');
let options = '';
let count = 0;
if (fileSystemHandler.files.length !== 0) options += '';
@@ -26,8 +26,9 @@ async function updateZimDropdownOptions (fileSystemHandler, selectedFile) {
}
});
select.innerHTML = options;
- document.getElementById('zimSelectDropdown').value = selectedFile;
+ document.getElementById('archiveList').value = selectedFile;
document.getElementById('numberOfFilesDisplay').innerText = count;
+ document.getElementById('fileCountDisplay').style.display = '';
}
/**
From b01f962fd394dbdf496f0e9a4a53b188fa2afa4b Mon Sep 17 00:00:00 2001
From: RG
Date: Sun, 22 Oct 2023 19:14:15 +0530
Subject: [PATCH 23/45] [REFACTOR] saving and getting filenames from
localstorage
---
www/index.html | 7 ++--
www/js/app.js | 4 +--
www/js/lib/fileSystem.js | 70 +++++++++++++---------------------------
3 files changed, 27 insertions(+), 54 deletions(-)
diff --git a/www/index.html b/www/index.html
index 90361f226..96c057d44 100644
--- a/www/index.html
+++ b/www/index.html
@@ -493,11 +493,10 @@
Configuration
0
- archives found in selected location.
+ archives found in selected location.
-
- Please select the archive you want to use :
+
Please select the archive you want to use :
+
diff --git a/www/js/app.js b/www/js/app.js
index d9b3f0d7d..13aa18a48 100644
--- a/www/js/app.js
+++ b/www/js/app.js
@@ -1314,7 +1314,7 @@ function displayFileSelect () {
}
webKitFileList = e.target.files;
// populateDropDownListOfArchives(filenames);
- await fileSystem.updateZimDropdownOptions({ fileOrDirHandle: null, files: filenames }, '');
+ await fileSystem.updateZimDropdownOptions(filenames, '');
})
}
if (isFileSystemAPISupported) {
@@ -1329,7 +1329,7 @@ function displayFileSelect () {
document.getElementById('archiveFiles').addEventListener('change', async function (e) {
if (isWebkitSupported || isFileSystemAPISupported) {
const activeFilename = e.target.files[0].name;
- await fileSystem.updateZimDropdownOptions({ files: [activeFilename] }, activeFilename);
+ await fileSystem.updateZimDropdownOptions([activeFilename], activeFilename);
}
setLocalArchiveFromFileSelect();
diff --git a/www/js/lib/fileSystem.js b/www/js/lib/fileSystem.js
index f7dbaf761..345911063 100644
--- a/www/js/lib/fileSystem.js
+++ b/www/js/lib/fileSystem.js
@@ -1,25 +1,19 @@
// refer to this article for easy explanation of File System API https://developer.chrome.com/articles/file-system-access/
-/**
- * @typedef {Object} FileSystemHandlers
- * @property {Array} files All the File names to be shown in the dropdown
- * @property {Object} fileOrDirHandle The FileSystemHandle of the selected file or directory
- */
-
import cache from './cache.js';
/**
- * @param {FileSystemHandlers} fileSystemHandler The FileSystemHandlers object containing filenames and File/Directory handle
+ * @param {Array} files All the File names to be shown in the dropdown
* @param {string} selectedFile The name of the file to be selected in the dropdown
* @returns {Promise>} Array of unique filenames (if a split zim is considered a single file)
*/
-async function updateZimDropdownOptions (fileSystemHandler, selectedFile) {
+async function updateZimDropdownOptions (files, selectedFile) {
const select = document.getElementById('archiveList');
let options = '';
let count = 0;
- if (fileSystemHandler.files.length !== 0) options += '';
+ if (files.length !== 0) options += '';
- fileSystemHandler.files.forEach((fileName) => {
+ files.forEach((fileName) => {
if (fileName.endsWith('.zim') || fileName.endsWith('.zimaa')) {
options += ``;
count++;
@@ -41,13 +35,9 @@ async function selectDirectoryFromPickerViaFileSystemApi () {
fileNames.push(entry.name);
}
- /** @type FileSystemHandlers */
- const FSHandler = {
- fileOrDirHandle: handle,
- files: fileNames
- };
- updateZimDropdownOptions(FSHandler, '');
- cache.idxDB('zimFiles', FSHandler, function () {
+ localStorage.setItem('zimFilenames', fileNames.join('|'))
+ updateZimDropdownOptions(fileNames, '');
+ cache.idxDB('zimFiles', handle, function () {
// save file in DB
});
}
@@ -61,15 +51,10 @@ async function selectFileFromPickerViaFileSystemApi () {
const [selectedFile] = fileHandles;
const file = await selectedFile.getFile();
- /** @type FileSystemHandlers */
- const FSHandler = {
- fileOrDirHandle: selectedFile,
- files: [selectedFile.name]
- };
- cache.idxDB('zimFiles', FSHandler, function () {
+ cache.idxDB('zimFiles', selectedFile, function () {
// file saved in DB
});
- updateZimDropdownOptions(FSHandler, selectedFile.name);
+ updateZimDropdownOptions([selectedFile.name], selectedFile.name);
return [file];
}
@@ -80,12 +65,12 @@ async function selectFileFromPickerViaFileSystemApi () {
*/
function getSelectedZimFromCache (selectedFilename) {
return new Promise((resolve, _reject) => {
- cache.idxDB('zimFiles', async function (FSHandler) {
- if ((await FSHandler.fileOrDirHandle.queryPermission()) !== 'granted') await FSHandler.fileOrDirHandle.requestPermission();
+ cache.idxDB('zimFiles', async function (fileOrDirHandle) {
+ if ((await fileOrDirHandle.queryPermission()) !== 'granted') await fileOrDirHandle.requestPermission();
- if (FSHandler.fileOrDirHandle.kind === 'directory') {
+ if (fileOrDirHandle.kind === 'directory') {
const files = [];
- for await (const entry of FSHandler.fileOrDirHandle.values()) {
+ for await (const entry of fileOrDirHandle.values()) {
const filenameWithoutExtension = selectedFilename.replace(/\.zim\w\w$/i, '');
const regex = new RegExp(`\\${filenameWithoutExtension}.zim\\w\\w$`, 'i');
if (regex.test(entry.name) || entry.name === selectedFilename) {
@@ -94,7 +79,7 @@ function getSelectedZimFromCache (selectedFilename) {
}
resolve(files);
} else {
- const file = await FSHandler.fileOrDirHandle.getFile();
+ const file = await fileOrDirHandle.getFile();
resolve([file]);
}
});
@@ -129,10 +114,8 @@ function getSelectedZimFromWebkitList (webKitFileList, filename) {
*/
function loadPreviousZimFile () {
if (typeof window.showOpenFilePicker === 'function') {
- cache.idxDB('zimFiles', async function (FSHandler) {
- if (!FSHandler) return console.info('There is no previous zim file in DB');
- updateZimDropdownOptions(FSHandler, '');
- });
+ const filenames = localStorage.getItem('zimFilenames');
+ if (filenames) updateZimDropdownOptions(filenames.split('|'), '');
}
}
@@ -149,14 +132,9 @@ async function handleFolderDropViaFileSystemAPI (packet) {
const fileInfo = packet.dataTransfer.items[0];
const fileOrDirHandle = await fileInfo.getAsFileSystemHandle();
if (fileOrDirHandle.kind === 'file') {
- /** @type FileSystemHandlers */
- const FSHandler = {
- fileOrDirHandle: fileOrDirHandle,
- files: [fileOrDirHandle.name]
- };
- cache.idxDB('zimFiles', FSHandler, function () {
+ cache.idxDB('zimFiles', fileOrDirHandle, function () {
// save file in DB
- updateZimDropdownOptions(FSHandler, fileOrDirHandle.name);
+ updateZimDropdownOptions([fileOrDirHandle.name], fileOrDirHandle.name);
});
return true;
}
@@ -165,13 +143,9 @@ async function handleFolderDropViaFileSystemAPI (packet) {
for await (const entry of fileOrDirHandle.values()) {
fileNames.push(entry.name);
}
- /** @type FileSystemHandlers */
- const FSHandler = {
- fileOrDirHandle: fileOrDirHandle,
- files: fileNames
- };
- cache.idxDB('zimFiles', FSHandler, function () {
- updateZimDropdownOptions(FSHandler, '');
+ localStorage.setItem('zimFilenames', fileNames.join('|'))
+ cache.idxDB('zimFiles', fileOrDirHandle, function () {
+ updateZimDropdownOptions(fileNames, '');
// save file in DB
});
return false;
@@ -194,7 +168,7 @@ async function handleFolderDropViaWebkit (event) {
const files = await getFilesFromReader(reader);
const fileNames = [];
files.forEach((file) => fileNames.push(file.name));
- await updateZimDropdownOptions({ files: fileNames }, '');
+ await updateZimDropdownOptions(fileNames, '');
return { loadZim: false, files: files };
}
}
From 677890717287ceb08078d1e27b7d3bfacf99cc52 Mon Sep 17 00:00:00 2001
From: RG
Date: Mon, 23 Oct 2023 18:42:12 +0530
Subject: [PATCH 24/45] [REFACTOR] `fileSystem.js` code moved to
`abstractFilesystemAccess.js` [REFACTOR] github handle updated
---
tests/e2e/spec/gutenberg_ro.e2e.spec.js | 3 +-
www/js/app.js | 24 +--
www/js/lib/abstractFilesystemAccess.js | 259 +++++++++++++++++++++---
www/js/lib/fileSystem.js | 213 -------------------
4 files changed, 247 insertions(+), 252 deletions(-)
delete mode 100644 www/js/lib/fileSystem.js
diff --git a/tests/e2e/spec/gutenberg_ro.e2e.spec.js b/tests/e2e/spec/gutenberg_ro.e2e.spec.js
index 9c76368a1..08e854f13 100644
--- a/tests/e2e/spec/gutenberg_ro.e2e.spec.js
+++ b/tests/e2e/spec/gutenberg_ro.e2e.spec.js
@@ -1,7 +1,7 @@
/**
* legacy-ray_charles.e2e.spec.js : End-to-end tests implemented with Selenium WebDriver and Mocha
*
- * Copyright 2023 Jaifroid, RG7279805 and contributors
+ * Copyright 2023 Jaifroid, Rishabhg71 and contributors
* Licence GPL v3:
*
* This file is part of Kiwix.
@@ -192,7 +192,6 @@ function runTests (driver, modes) {
const archiveFiles = await driver.findElement(By.id('archiveFiles'));
if (!isFileLoaded) await archiveFiles.sendKeys(gutenbergRoBaseFile);
filesLength = await driver.executeScript('return document.getElementById("archiveFiles").files.length');
-
// In new browsers Files are loaded using the FileSystem API, so we have to set the local archives using JavaScript
// which were selected using the file input
await driver.executeScript('window.setLocalArchiveFromFileSelect();');
diff --git a/www/js/app.js b/www/js/app.js
index 13aa18a48..083bab394 100644
--- a/www/js/app.js
+++ b/www/js/app.js
@@ -35,8 +35,6 @@ import uiUtil from './lib/uiUtil.js';
import settingsStore from './lib/settingsStore.js';
import abstractFilesystemAccess from './lib/abstractFilesystemAccess.js';
import translateUI from './lib/translateUI.js';
-import cache from './lib/cache.js';
-import fileSystem from './lib/fileSystem.js';
if (params.abort) {
// If the app was loaded only to pass a message from the remote code, then we exit immediately
@@ -168,7 +166,7 @@ function resizeIFrame () {
document.addEventListener('DOMContentLoaded', function () {
getDefaultLanguageAndTranslateApp();
resizeIFrame();
- fileSystem.loadPreviousZimFile();
+ abstractFilesystemAccess.loadPreviousZimFile();
});
window.addEventListener('resize', resizeIFrame);
@@ -1262,8 +1260,8 @@ function displayFileSelect () {
const isFileSystemAPISupported = typeof window.showOpenFilePicker === 'function'
const isWebkitSupported = 'webkitdirectory' in document.createElement('input')
- console.debug('[DEBUG]: File system api supported', isFileSystemAPISupported);
- console.debug('[DEBUG]: Webkit supported', isWebkitSupported);
+ console.debug('File system api supported', isFileSystemAPISupported);
+ console.debug('Webkit supported', isWebkitSupported);
document.getElementById('openLocalFiles').style.display = 'block';
if (isFileSystemAPISupported || isWebkitSupported) {
@@ -1289,10 +1287,10 @@ function displayFileSelect () {
document.getElementById('archiveList').addEventListener('change', async function (e) {
// handle zim selection from dropdown if multiple files are loaded via webkitdirectory or filesystem api
if (isFileSystemAPISupported) {
- const files = await fileSystem.getSelectedZimFromCache(e.target.value)
+ const files = await abstractFilesystemAccess.getSelectedZimFromCache(e.target.value)
setLocalArchiveFromFileList(files);
} else {
- const files = fileSystem.getSelectedZimFromWebkitList(webKitFileList, e.target.value)
+ const files = abstractFilesystemAccess.getSelectedZimFromWebkitList(webKitFileList, e.target.value)
setLocalArchiveFromFileList(files);
}
});
@@ -1301,7 +1299,7 @@ function displayFileSelect () {
// Handles Folder selection when showDirectoryPicker is supported
document.getElementById('folderSelect').addEventListener('click', async function (e) {
e.preventDefault();
- await fileSystem.selectDirectoryFromPickerViaFileSystemApi()
+ await abstractFilesystemAccess.selectDirectoryFromPickerViaFileSystemApi()
})
}
if (isWebkitSupported && !isFileSystemAPISupported) {
@@ -1314,14 +1312,14 @@ function displayFileSelect () {
}
webKitFileList = e.target.files;
// populateDropDownListOfArchives(filenames);
- await fileSystem.updateZimDropdownOptions(filenames, '');
+ await abstractFilesystemAccess.updateZimDropdownOptions(filenames, '');
})
}
if (isFileSystemAPISupported) {
// Handles File selection when showOpenFilePicker is supported and uses the filesystem api
document.getElementById('archiveFiles').addEventListener('click', async function (e) {
e.preventDefault();
- const files = await fileSystem.selectFileFromPickerViaFileSystemApi(e);
+ const files = await abstractFilesystemAccess.selectFileFromPickerViaFileSystemApi(e);
setLocalArchiveFromFileList(files);
});
} else {
@@ -1329,7 +1327,7 @@ function displayFileSelect () {
document.getElementById('archiveFiles').addEventListener('change', async function (e) {
if (isWebkitSupported || isFileSystemAPISupported) {
const activeFilename = e.target.files[0].name;
- await fileSystem.updateZimDropdownOptions([activeFilename], activeFilename);
+ await abstractFilesystemAccess.updateZimDropdownOptions([activeFilename], activeFilename);
}
setLocalArchiveFromFileSelect();
@@ -1372,9 +1370,9 @@ async function handleFileDrop (packet) {
// call the `setLocalArchiveFromFileList`
let loadZim = true;
- if (isFSAPIsupported) loadZim = await fileSystem.handleFolderDropViaFileSystemAPI(packet)
+ if (isFSAPIsupported) loadZim = await abstractFilesystemAccess.handleFolderDropViaFileSystemAPI(packet)
if (isWebkitSupported) {
- const ret = await fileSystem.handleFolderDropViaWebkit(packet)
+ const ret = await abstractFilesystemAccess.handleFolderDropViaWebkit(packet)
loadZim = ret.loadZim
webKitFileList = ret.files
}
diff --git a/www/js/lib/abstractFilesystemAccess.js b/www/js/lib/abstractFilesystemAccess.js
index 03b58ddaf..3e6031992 100644
--- a/www/js/lib/abstractFilesystemAccess.js
+++ b/www/js/lib/abstractFilesystemAccess.js
@@ -1,36 +1,37 @@
/**
* abstractFilesystemAccess.js: Abstraction layer for file access.
- * This is currently only implemented for FirefoxOS, but could be extended to
+ * This is currently only implemented for FirefoxOS and Standard browser (using File System Access API), but could be extended to
* Cordova, Electron or other ways to directly browse and read files from the
* filesystem.
- * It is unfortunately not possible to do that inside a standard browser
- * (even inside an extension).
- *
- * Copyright 2014 Kiwix developers
- * Licence 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 Licence as published by
- * the Free Software Foundation, either version 3 of the Licence, 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
+*
+* Copyright 2014-2023 Kiwix developers and Rishabhg71
+* Licence 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 Licence as published by
+* the Free Software Foundation, either version 3 of the Licence, 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 Licence for more details.
- *
- * You should have received a copy of the GNU General Public Licence
- * along with Kiwix (file LICENSE-GPLv3.txt). If not, see
- */
+*
+* You should have received a copy of the GNU General Public Licence
+* along with Kiwix (file LICENSE-GPLv3.txt). If not, see
+*/
'use strict';
-function StorageFirefoxOS(storage) {
+import cache from './cache.js';
+
+function StorageFirefoxOS (storage) {
this._storage = storage;
this.storageName = storage.storageName;
}
+
/**
* Access the given file.
* @param {String} path absolute path to the file
@@ -88,6 +89,216 @@ StorageFirefoxOS.prototype.enumerate = function (path) {
return this._storage.enumerate();
};
+// refer to this article for easy explanation of File System API https://developer.chrome.com/articles/file-system-access/
+
+/**
+ * @param {Array} files All the File names to be shown in the dropdown
+ * @param {string} selectedFile The name of the file to be selected in the dropdown
+ * @returns {Promise>} Array of unique filenames (if a split zim is considered a single file)
+ */
+async function updateZimDropdownOptions (files, selectedFile) {
+ const select = document.getElementById('archiveList');
+ let options = '';
+ let count = 0;
+ if (files.length !== 0) options += '';
+
+ files.forEach((fileName) => {
+ if (fileName.endsWith('.zim') || fileName.endsWith('.zimaa')) {
+ options += ``;
+ count++;
+ }
+ });
+ select.innerHTML = options;
+ document.getElementById('archiveList').value = selectedFile;
+ document.getElementById('numberOfFilesDisplay').innerText = count;
+ document.getElementById('fileCountDisplay').style.display = '';
+}
+
+/**
+ * Opens the File System API to select a directory
+ */
+async function selectDirectoryFromPickerViaFileSystemApi () {
+ const handle = await window.showDirectoryPicker();
+ const fileNames = [];
+ for await (const entry of handle.values()) {
+ fileNames.push(entry.name);
+ }
+
+ localStorage.setItem('zimFilenames', fileNames.join('|'))
+ updateZimDropdownOptions(fileNames, '');
+ cache.idxDB('zimFiles', handle, function () {
+ // save file in DB
+ });
+}
+
+/**
+ * Opens the File System API to select a file
+ * @returns {Promise>} The selected file from picker
+ */
+async function selectFileFromPickerViaFileSystemApi () {
+ const fileHandles = await window.showOpenFilePicker({ multiple: false });
+ const [selectedFile] = fileHandles;
+ const file = await selectedFile.getFile();
+
+ cache.idxDB('zimFiles', selectedFile, function () {
+ // file saved in DB
+ });
+ updateZimDropdownOptions([selectedFile.name], selectedFile.name);
+ return [file];
+}
+
+/**
+ * Gets the selected zim file from the IndexedDB
+ * @param {string} selectedFilename The name of the file to get back from DB
+ * @returns {Promise>} The selected File Object from cache
+ */
+function getSelectedZimFromCache (selectedFilename) {
+ return new Promise((resolve, _reject) => {
+ cache.idxDB('zimFiles', async function (fileOrDirHandle) {
+ console.log('[DEBUG] current file/dir permission status', await fileOrDirHandle.queryPermission(), fileOrDirHandle);
+ if ((await fileOrDirHandle.queryPermission()) !== 'granted') await fileOrDirHandle.requestPermission();
+
+ if (fileOrDirHandle.kind === 'directory') {
+ const files = [];
+ for await (const entry of fileOrDirHandle.values()) {
+ const filenameWithoutExtension = selectedFilename.replace(/\.zim\w\w$/i, '');
+ const regex = new RegExp(`\\${filenameWithoutExtension}.zim\\w\\w$`, 'i');
+ if (regex.test(entry.name) || entry.name === selectedFilename) {
+ files.push(await entry.getFile());
+ }
+ }
+ resolve(files);
+ } else {
+ const file = await fileOrDirHandle.getFile();
+ resolve([file]);
+ }
+ });
+ });
+}
+
+/**
+ * @typedef {Object.} WebkitFileList
+ */
+
+/**
+ * Gets the selected zim file from the WebkitFileList
+ * @param {WebkitFileList} webKitFileList The WebkitFileList to get the selected file from
+ * @param {string} filename The name of the file to get back from webkitFileList
+ * @returns {Array} The selected Files Object from webkitFileList
+ */
+function getSelectedZimFromWebkitList (webKitFileList, filename) {
+ const filenameWithoutExtension = filename.replace(/\.zim\w\w$/i, '');
+
+ const regex = new RegExp(`\\${filenameWithoutExtension}.zim\\w\\w$`, 'i');
+ const files = [];
+ for (const file of webKitFileList) {
+ if (regex.test(file.name) || file.name === filename) {
+ files.push(file);
+ }
+ }
+ return files;
+}
+
+/**
+ * Loads the Previously selected zim file via IndexedDB
+ */
+function loadPreviousZimFile () {
+ if (typeof window.showOpenFilePicker === 'function') {
+ const filenames = localStorage.getItem('zimFilenames');
+ if (filenames) updateZimDropdownOptions(filenames.split('|'), '');
+ }
+}
+
+/**
+ * Handles the folder drop event via File System API
+ * @param {DragEvent} packet The DragEvent packet
+ * @returns {Promise} Whether the dropped item is a file or directory
+ */
+async function handleFolderDropViaFileSystemAPI (packet) {
+ const isFSAPIsupported = typeof window.showOpenFilePicker === 'function';
+ if (!isFSAPIsupported) return true;
+
+ // Only runs when browser support File System API
+ const fileInfo = packet.dataTransfer.items[0];
+ const fileOrDirHandle = await fileInfo.getAsFileSystemHandle();
+ if (fileOrDirHandle.kind === 'file') {
+ cache.idxDB('zimFiles', fileOrDirHandle, function () {
+ // save file in DB
+ updateZimDropdownOptions([fileOrDirHandle.name], fileOrDirHandle.name);
+ });
+ return true;
+ }
+ if (fileOrDirHandle.kind === 'directory') {
+ const fileNames = [];
+ for await (const entry of fileOrDirHandle.values()) {
+ fileNames.push(entry.name);
+ }
+ localStorage.setItem('zimFilenames', fileNames.join('|'))
+ cache.idxDB('zimFiles', fileOrDirHandle, function () {
+ updateZimDropdownOptions(fileNames, '');
+ // save file in DB
+ });
+ return false;
+ }
+}
+
+/**
+ * Handles the folder drop event via WebkitGetAsEntry
+ * @param {DragEvent} event The DragEvent packet
+ * @returns {Promise<{loadZim: boolean, files: Array} | void>} Whether the dropped item is a file or directory and FileList
+ */
+async function handleFolderDropViaWebkit (event) {
+ var dt = event.dataTransfer;
+
+ var entry = dt.items[0].webkitGetAsEntry();
+ if (entry.isFile) {
+ return { loadZim: true, files: [entry.file] };
+ } else if (entry.isDirectory) {
+ var reader = entry.createReader();
+ const files = await getFilesFromReader(reader);
+ const fileNames = [];
+ files.forEach((file) => fileNames.push(file.name));
+ await updateZimDropdownOptions(fileNames, '');
+ return { loadZim: false, files: files };
+ }
+}
+
+/**
+ * Gets the files from the FileSystemReader
+ * @param {FileSystemDirectoryReader} reader The FileSystemReader to get files from
+ * @returns {Promise>} The files from the reader
+ */
+async function getFilesFromReader (reader) {
+ const files = [];
+ const promise = new Promise(function (resolve, _reject) {
+ reader.readEntries(function (entries) {
+ resolve(entries);
+ });
+ });
+ const entries = await promise;
+
+ for (let index = 0; index < entries.length; index++) {
+ const fileOrDir = entries[index];
+ if (fileOrDir.isFile) {
+ const filePromise = await new Promise(function (resolve, _reject) {
+ fileOrDir.file(function (file) {
+ resolve(file);
+ });
+ });
+ files.push(filePromise);
+ }
+ }
+ return files;
+}
+
export default {
- StorageFirefoxOS: StorageFirefoxOS
-};
\ No newline at end of file
+ StorageFirefoxOS: StorageFirefoxOS,
+ updateZimDropdownOptions,
+ selectDirectoryFromPickerViaFileSystemApi,
+ selectFileFromPickerViaFileSystemApi,
+ getSelectedZimFromCache,
+ loadPreviousZimFile,
+ handleFolderDropViaWebkit,
+ handleFolderDropViaFileSystemAPI,
+ getSelectedZimFromWebkitList
+};
diff --git a/www/js/lib/fileSystem.js b/www/js/lib/fileSystem.js
deleted file mode 100644
index 345911063..000000000
--- a/www/js/lib/fileSystem.js
+++ /dev/null
@@ -1,213 +0,0 @@
-// refer to this article for easy explanation of File System API https://developer.chrome.com/articles/file-system-access/
-
-import cache from './cache.js';
-
-/**
- * @param {Array} files All the File names to be shown in the dropdown
- * @param {string} selectedFile The name of the file to be selected in the dropdown
- * @returns {Promise>} Array of unique filenames (if a split zim is considered a single file)
- */
-async function updateZimDropdownOptions (files, selectedFile) {
- const select = document.getElementById('archiveList');
- let options = '';
- let count = 0;
- if (files.length !== 0) options += '';
-
- files.forEach((fileName) => {
- if (fileName.endsWith('.zim') || fileName.endsWith('.zimaa')) {
- options += ``;
- count++;
- }
- });
- select.innerHTML = options;
- document.getElementById('archiveList').value = selectedFile;
- document.getElementById('numberOfFilesDisplay').innerText = count;
- document.getElementById('fileCountDisplay').style.display = '';
-}
-
-/**
- * Opens the File System API to select a directory
- */
-async function selectDirectoryFromPickerViaFileSystemApi () {
- const handle = await window.showDirectoryPicker();
- const fileNames = [];
- for await (const entry of handle.values()) {
- fileNames.push(entry.name);
- }
-
- localStorage.setItem('zimFilenames', fileNames.join('|'))
- updateZimDropdownOptions(fileNames, '');
- cache.idxDB('zimFiles', handle, function () {
- // save file in DB
- });
-}
-
-/**
- * Opens the File System API to select a file
- * @returns {Promise>} The selected file from picker
- */
-async function selectFileFromPickerViaFileSystemApi () {
- const fileHandles = await window.showOpenFilePicker({ multiple: false });
- const [selectedFile] = fileHandles;
- const file = await selectedFile.getFile();
-
- cache.idxDB('zimFiles', selectedFile, function () {
- // file saved in DB
- });
- updateZimDropdownOptions([selectedFile.name], selectedFile.name);
- return [file];
-}
-
-/**
- * Gets the selected zim file from the IndexedDB
- * @param {string} selectedFilename The name of the file to get back from DB
- * @returns {Promise>} The selected File Object from cache
- */
-function getSelectedZimFromCache (selectedFilename) {
- return new Promise((resolve, _reject) => {
- cache.idxDB('zimFiles', async function (fileOrDirHandle) {
- if ((await fileOrDirHandle.queryPermission()) !== 'granted') await fileOrDirHandle.requestPermission();
-
- if (fileOrDirHandle.kind === 'directory') {
- const files = [];
- for await (const entry of fileOrDirHandle.values()) {
- const filenameWithoutExtension = selectedFilename.replace(/\.zim\w\w$/i, '');
- const regex = new RegExp(`\\${filenameWithoutExtension}.zim\\w\\w$`, 'i');
- if (regex.test(entry.name) || entry.name === selectedFilename) {
- files.push(await entry.getFile());
- }
- }
- resolve(files);
- } else {
- const file = await fileOrDirHandle.getFile();
- resolve([file]);
- }
- });
- });
-}
-
-/**
- * @typedef {Object.} WebkitFileList
- */
-
-/**
- * Gets the selected zim file from the WebkitFileList
- * @param {WebkitFileList} webKitFileList The WebkitFileList to get the selected file from
- * @param {string} filename The name of the file to get back from webkitFileList
- * @returns {Array} The selected Files Object from webkitFileList
- */
-function getSelectedZimFromWebkitList (webKitFileList, filename) {
- const filenameWithoutExtension = filename.replace(/\.zim\w\w$/i, '');
-
- const regex = new RegExp(`\\${filenameWithoutExtension}.zim\\w\\w$`, 'i');
- const files = [];
- for (const file of webKitFileList) {
- if (regex.test(file.name) || file.name === filename) {
- files.push(file);
- }
- }
- return files;
-}
-
-/**
- * Loads the Previously selected zim file via IndexedDB
- */
-function loadPreviousZimFile () {
- if (typeof window.showOpenFilePicker === 'function') {
- const filenames = localStorage.getItem('zimFilenames');
- if (filenames) updateZimDropdownOptions(filenames.split('|'), '');
- }
-}
-
-/**
- * Handles the folder drop event via File System API
- * @param {DragEvent} packet The DragEvent packet
- * @returns {Promise} Whether the dropped item is a file or directory
- */
-async function handleFolderDropViaFileSystemAPI (packet) {
- const isFSAPIsupported = typeof window.showOpenFilePicker === 'function';
- if (!isFSAPIsupported) return true;
-
- // Only runs when browser support File System API
- const fileInfo = packet.dataTransfer.items[0];
- const fileOrDirHandle = await fileInfo.getAsFileSystemHandle();
- if (fileOrDirHandle.kind === 'file') {
- cache.idxDB('zimFiles', fileOrDirHandle, function () {
- // save file in DB
- updateZimDropdownOptions([fileOrDirHandle.name], fileOrDirHandle.name);
- });
- return true;
- }
- if (fileOrDirHandle.kind === 'directory') {
- const fileNames = [];
- for await (const entry of fileOrDirHandle.values()) {
- fileNames.push(entry.name);
- }
- localStorage.setItem('zimFilenames', fileNames.join('|'))
- cache.idxDB('zimFiles', fileOrDirHandle, function () {
- updateZimDropdownOptions(fileNames, '');
- // save file in DB
- });
- return false;
- }
-}
-
-/**
- * Handles the folder drop event via WebkitGetAsEntry
- * @param {DragEvent} event The DragEvent packet
- * @returns {Promise<{loadZim: boolean, files: Array} | void>} Whether the dropped item is a file or directory and FileList
- */
-async function handleFolderDropViaWebkit (event) {
- var dt = event.dataTransfer;
-
- var entry = dt.items[0].webkitGetAsEntry();
- if (entry.isFile) {
- return { loadZim: true, files: [entry.file] };
- } else if (entry.isDirectory) {
- var reader = entry.createReader();
- const files = await getFilesFromReader(reader);
- const fileNames = [];
- files.forEach((file) => fileNames.push(file.name));
- await updateZimDropdownOptions(fileNames, '');
- return { loadZim: false, files: files };
- }
-}
-
-/**
- * Gets the files from the FileSystemReader
- * @param {FileSystemDirectoryReader} reader The FileSystemReader to get files from
- * @returns {Promise>} The files from the reader
- */
-async function getFilesFromReader (reader) {
- const files = [];
- const promise = new Promise(function (resolve, _reject) {
- reader.readEntries(function (entries) {
- resolve(entries);
- });
- });
- const entries = await promise;
-
- for (let index = 0; index < entries.length; index++) {
- const fileOrDir = entries[index];
- if (fileOrDir.isFile) {
- const filePromise = await new Promise(function (resolve, _reject) {
- fileOrDir.file(function (file) {
- resolve(file);
- });
- });
- files.push(filePromise);
- }
- }
- return files;
-}
-
-export default {
- updateZimDropdownOptions,
- selectDirectoryFromPickerViaFileSystemApi,
- selectFileFromPickerViaFileSystemApi,
- getSelectedZimFromCache,
- loadPreviousZimFile,
- handleFolderDropViaWebkit,
- handleFolderDropViaFileSystemAPI,
- getSelectedZimFromWebkitList
-};
From ff3bc2d719d32d8b119e7d2ecb1701cbeb723087 Mon Sep 17 00:00:00 2001
From: RG
Date: Mon, 23 Oct 2023 18:51:01 +0530
Subject: [PATCH 25/45] [REFACTOR] making codefactor happy
---
www/js/lib/abstractFilesystemAccess.js | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/www/js/lib/abstractFilesystemAccess.js b/www/js/lib/abstractFilesystemAccess.js
index 3e6031992..50dad5add 100644
--- a/www/js/lib/abstractFilesystemAccess.js
+++ b/www/js/lib/abstractFilesystemAccess.js
@@ -98,17 +98,17 @@ StorageFirefoxOS.prototype.enumerate = function (path) {
*/
async function updateZimDropdownOptions (files, selectedFile) {
const select = document.getElementById('archiveList');
- let options = '';
+ const options = [];
let count = 0;
- if (files.length !== 0) options += '';
+ if (files.length !== 0) options.push(Option('Select an archive..', ''));
files.forEach((fileName) => {
if (fileName.endsWith('.zim') || fileName.endsWith('.zimaa')) {
- options += ``;
+ options.push(new Option(`${fileName}`, fileName));
count++;
}
});
- select.innerHTML = options;
+ select.replaceChildren(...options);
document.getElementById('archiveList').value = selectedFile;
document.getElementById('numberOfFilesDisplay').innerText = count;
document.getElementById('fileCountDisplay').style.display = '';
@@ -124,7 +124,7 @@ async function selectDirectoryFromPickerViaFileSystemApi () {
fileNames.push(entry.name);
}
- localStorage.setItem('zimFilenames', fileNames.join('|'))
+ localStorage.setItem('zimFilenames', fileNames.join('|'));
updateZimDropdownOptions(fileNames, '');
cache.idxDB('zimFiles', handle, function () {
// save file in DB
@@ -233,7 +233,7 @@ async function handleFolderDropViaFileSystemAPI (packet) {
for await (const entry of fileOrDirHandle.values()) {
fileNames.push(entry.name);
}
- localStorage.setItem('zimFilenames', fileNames.join('|'))
+ localStorage.setItem('zimFilenames', fileNames.join('|'));
cache.idxDB('zimFiles', fileOrDirHandle, function () {
updateZimDropdownOptions(fileNames, '');
// save file in DB
From 88a5088040fabfc9c3796798023a4674a316b56d Mon Sep 17 00:00:00 2001
From: RG
Date: Mon, 23 Oct 2023 20:00:10 +0530
Subject: [PATCH 26/45] [FIX] db name `undefined` changed
---
www/index.html | 2 +-
www/js/init.js | 2 ++
www/js/lib/abstractFilesystemAccess.js | 2 +-
3 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/www/index.html b/www/index.html
index 96c057d44..f6c958232 100644
--- a/www/index.html
+++ b/www/index.html
@@ -473,7 +473,7 @@
Configuration
- Select ZIM file(s)
+ Select ZIM File(s)
diff --git a/www/js/init.js b/www/js/init.js
index bc78891ca..6a5b0a183 100644
--- a/www/js/init.js
+++ b/www/js/init.js
@@ -112,6 +112,8 @@ params['contentInjectionMode'] = getSetting('contentInjectionMode') ||
params['useCanvasElementsForWebpTranscoding'] = null; // Value is determined in uiUtil.determineCanvasElementsWorkaround(), called when setting the content injection mode
params['libraryUrl'] = 'https://library.kiwix.org/'; // Url for iframe that will be loaded to download new zim files
params['altLibraryUrl'] = 'https://download.kiwix.org/zim/'; // Alternative Url for iframe (for use with unsupported browsers) that will be loaded to download new zim files
+params['cacheAPI'] = 'kiwix-js'; // Sets the database name for the IndexedDB cache
+params['cacheIDB'] = 'kiwix-zim'; // Not sure what this does
/**
* Apply any override parameters that might be in the querystring.
diff --git a/www/js/lib/abstractFilesystemAccess.js b/www/js/lib/abstractFilesystemAccess.js
index 50dad5add..757628f0c 100644
--- a/www/js/lib/abstractFilesystemAccess.js
+++ b/www/js/lib/abstractFilesystemAccess.js
@@ -100,7 +100,7 @@ async function updateZimDropdownOptions (files, selectedFile) {
const select = document.getElementById('archiveList');
const options = [];
let count = 0;
- if (files.length !== 0) options.push(Option('Select an archive..', ''));
+ if (files.length !== 0) options.push(new Option('Select an archive..', ''));
files.forEach((fileName) => {
if (fileName.endsWith('.zim') || fileName.endsWith('.zimaa')) {
From b7729b925dbcabddfd0ff96f78302e4d90fa5a3d Mon Sep 17 00:00:00 2001
From: RG
Date: Tue, 24 Oct 2023 20:10:29 +0530
Subject: [PATCH 27/45] [REFACTORS] minor refactors for code review
---
service-worker.js | 1 -
www/css/app.css | 6 ++----
www/index.html | 10 ++++++----
3 files changed, 8 insertions(+), 9 deletions(-)
diff --git a/service-worker.js b/service-worker.js
index 8e8136744..416a7c6e2 100644
--- a/service-worker.js
+++ b/service-worker.js
@@ -126,7 +126,6 @@ const precacheFiles = [
'www/js/lib/arrayFromPolyfill.js',
'www/js/lib/filecache.js',
'www/js/lib/cache.js',
- 'www/js/lib/fileSystem.js',
'www/js/lib/promisePolyfill.js',
'www/js/lib/settingsStore.js',
'www/js/lib/translateUI.js',
diff --git a/www/css/app.css b/www/css/app.css
index a71393c6b..2ea467c75 100644
--- a/www/css/app.css
+++ b/www/css/app.css
@@ -94,10 +94,8 @@
margin: 0 1rem;
}
-#zimSelectDropdown {
- width: 60%;
- font-size: large;
- height: 2em;
+#filesSelectionInfoAndCount p{
+ display: inline;
}
/* Custom file input */
diff --git a/www/index.html b/www/index.html
index f6c958232..a5d04c362 100644
--- a/www/index.html
+++ b/www/index.html
@@ -491,11 +491,13 @@
Configuration
Scanning for archives... Please wait
-
-
0
- archives found in selected location.
+
+
+ 0
+ archives found in selected location.
+
+
Please select the archive you want to use :
-
Please select the archive you want to use :
From 512a99b5459abdfd8d00d95b3cf1da32f32b7553 Mon Sep 17 00:00:00 2001
From: RG
Date: Thu, 26 Oct 2023 19:58:25 +0530
Subject: [PATCH 28/45] [REFACTOR] API support flag moved inside `params`
---
www/js/app.js | 24 +++++++++---------------
www/js/init.js | 6 ++++++
www/js/lib/abstractFilesystemAccess.js | 8 +++-----
3 files changed, 18 insertions(+), 20 deletions(-)
diff --git a/www/js/app.js b/www/js/app.js
index 083bab394..92dd04753 100644
--- a/www/js/app.js
+++ b/www/js/app.js
@@ -1257,14 +1257,11 @@ let webKitFileList = null
* Displays the zone to select files from the archive
*/
function displayFileSelect () {
- const isFileSystemAPISupported = typeof window.showOpenFilePicker === 'function'
- const isWebkitSupported = 'webkitdirectory' in document.createElement('input')
-
- console.debug('File system api supported', isFileSystemAPISupported);
- console.debug('Webkit supported', isWebkitSupported);
+ console.debug('File system api supported', params.isFileSystemApiSupported);
+ console.debug('Webkit supported', params.isWebkitDirApiSupported);
document.getElementById('openLocalFiles').style.display = 'block';
- if (isFileSystemAPISupported || isWebkitSupported) {
+ if (params.isFileSystemApiSupported || params.isWebkitDirApiSupported) {
document.getElementById('chooseArchiveFromLocalStorage').style.display = '';
document.getElementById('folderSelect').style.display = '';
}
@@ -1286,7 +1283,7 @@ function displayFileSelect () {
document.getElementById('archiveList').addEventListener('change', async function (e) {
// handle zim selection from dropdown if multiple files are loaded via webkitdirectory or filesystem api
- if (isFileSystemAPISupported) {
+ if (params.isFileSystemApiSupported) {
const files = await abstractFilesystemAccess.getSelectedZimFromCache(e.target.value)
setLocalArchiveFromFileList(files);
} else {
@@ -1302,7 +1299,7 @@ function displayFileSelect () {
await abstractFilesystemAccess.selectDirectoryFromPickerViaFileSystemApi()
})
}
- if (isWebkitSupported && !isFileSystemAPISupported) {
+ if (params.isWebkitDirApiSupported && !params.isFileSystemApiSupported) {
// Handles Folder selection when webkitdirectory is supported but showDirectoryPicker is not
document.getElementById('folderSelect').addEventListener('change', async function (e) {
e.preventDefault();
@@ -1315,7 +1312,7 @@ function displayFileSelect () {
await abstractFilesystemAccess.updateZimDropdownOptions(filenames, '');
})
}
- if (isFileSystemAPISupported) {
+ if (params.isFileSystemApiSupported) {
// Handles File selection when showOpenFilePicker is supported and uses the filesystem api
document.getElementById('archiveFiles').addEventListener('click', async function (e) {
e.preventDefault();
@@ -1325,7 +1322,7 @@ function displayFileSelect () {
} else {
// Fallbacks to simple file input with multi file selection
document.getElementById('archiveFiles').addEventListener('change', async function (e) {
- if (isWebkitSupported || isFileSystemAPISupported) {
+ if (params.isWebkitDirApiSupported || params.isFileSystemApiSupported) {
const activeFilename = e.target.files[0].name;
await abstractFilesystemAccess.updateZimDropdownOptions([activeFilename], activeFilename);
}
@@ -1363,15 +1360,12 @@ async function handleFileDrop (packet) {
document.getElementById('selectorsDisplay').style.display = 'inline';
document.getElementById('archiveFiles').value = null;
- const isFSAPIsupported = typeof window.showOpenFilePicker === 'function'
- const isWebkitSupported = 'webkitdirectory' in document.createElement('input')
-
// value will be set to true if a folder is dropped then there will be no need to
// call the `setLocalArchiveFromFileList`
let loadZim = true;
- if (isFSAPIsupported) loadZim = await abstractFilesystemAccess.handleFolderDropViaFileSystemAPI(packet)
- if (isWebkitSupported) {
+ if (params.isFileSystemApiSupported) loadZim = await abstractFilesystemAccess.handleFolderDropViaFileSystemAPI(packet)
+ if (params.isWebkitDirApiSupported) {
const ret = await abstractFilesystemAccess.handleFolderDropViaWebkit(packet)
loadZim = ret.loadZim
webKitFileList = ret.files
diff --git a/www/js/init.js b/www/js/init.js
index 6a5b0a183..5afc22522 100644
--- a/www/js/init.js
+++ b/www/js/init.js
@@ -50,6 +50,10 @@
* @property {boolean} useCanvasElementsForWebpTranscoding - 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.
* @property {string} libraryUrl - The URL of the Kiwix library.
* @property {string} altLibraryUrl - The alternative URL of the Kiwix library in non-supported browsers.
+ * @property {string} cacheAPI - Database name for the IndexedDB cache
+ * @property {string} cacheIDB - Not sure what this does
+ * @property {boolean} isFileSystemApiSupported - A boolean indicating whether the FileSystem API is supported.
+ * @property {boolean} isWebkitDirApiSupported - A boolean indicating whether the Webkit Directory API is supported.
* @property {DecompressorAPI} decompressorAPI
/**
@@ -114,6 +118,8 @@ params['libraryUrl'] = 'https://library.kiwix.org/'; // Url for iframe that will
params['altLibraryUrl'] = 'https://download.kiwix.org/zim/'; // Alternative Url for iframe (for use with unsupported browsers) that will be loaded to download new zim files
params['cacheAPI'] = 'kiwix-js'; // Sets the database name for the IndexedDB cache
params['cacheIDB'] = 'kiwix-zim'; // Not sure what this does
+params['isFileSystemApiSupported'] = typeof window.showOpenFilePicker === 'function'; // Not sure what this does
+params['isWebkitDirApiSupported'] = 'webkitdirectory' in document.createElement('input'); // Not sure what this does
/**
* Apply any override parameters that might be in the querystring.
diff --git a/www/js/lib/abstractFilesystemAccess.js b/www/js/lib/abstractFilesystemAccess.js
index 757628f0c..624a1f015 100644
--- a/www/js/lib/abstractFilesystemAccess.js
+++ b/www/js/lib/abstractFilesystemAccess.js
@@ -155,7 +155,6 @@ async function selectFileFromPickerViaFileSystemApi () {
function getSelectedZimFromCache (selectedFilename) {
return new Promise((resolve, _reject) => {
cache.idxDB('zimFiles', async function (fileOrDirHandle) {
- console.log('[DEBUG] current file/dir permission status', await fileOrDirHandle.queryPermission(), fileOrDirHandle);
if ((await fileOrDirHandle.queryPermission()) !== 'granted') await fileOrDirHandle.requestPermission();
if (fileOrDirHandle.kind === 'directory') {
@@ -200,10 +199,10 @@ function getSelectedZimFromWebkitList (webKitFileList, filename) {
}
/**
- * Loads the Previously selected zim file via IndexedDB
+ * Loads the Previously loaded zim filename(s) via local storage
*/
function loadPreviousZimFile () {
- if (typeof window.showOpenFilePicker === 'function') {
+ if (window.params.isFileSystemApiSupported) {
const filenames = localStorage.getItem('zimFilenames');
if (filenames) updateZimDropdownOptions(filenames.split('|'), '');
}
@@ -215,8 +214,7 @@ function loadPreviousZimFile () {
* @returns {Promise} Whether the dropped item is a file or directory
*/
async function handleFolderDropViaFileSystemAPI (packet) {
- const isFSAPIsupported = typeof window.showOpenFilePicker === 'function';
- if (!isFSAPIsupported) return true;
+ if (!window.params.isFileSystemApiSupported) return true;
// Only runs when browser support File System API
const fileInfo = packet.dataTransfer.items[0];
From 804940916d3ef4f7594ddd73c83b39f0f2d0e87c Mon Sep 17 00:00:00 2001
From: RG
Date: Fri, 27 Oct 2023 17:38:35 +0530
Subject: [PATCH 29/45] [FIX] firefox os race condition
---
www/js/app.js | 32 +++++++++++++++-----------
www/js/lib/abstractFilesystemAccess.js | 8 +++++--
2 files changed, 24 insertions(+), 16 deletions(-)
diff --git a/www/js/app.js b/www/js/app.js
index 92dd04753..8972d944b 100644
--- a/www/js/app.js
+++ b/www/js/app.js
@@ -1257,8 +1257,11 @@ let webKitFileList = null
* Displays the zone to select files from the archive
*/
function displayFileSelect () {
+ const isFireFoxOsNativeFileApiAvailable = typeof navigator.getDeviceStorages === 'function';
+
console.debug('File system api supported', params.isFileSystemApiSupported);
console.debug('Webkit supported', params.isWebkitDirApiSupported);
+ console.debug('Firefox os native file support api', isFireFoxOsNativeFileApiAvailable)
document.getElementById('openLocalFiles').style.display = 'block';
if (params.isFileSystemApiSupported || params.isWebkitDirApiSupported) {
@@ -1281,25 +1284,26 @@ function displayFileSelect () {
globalDropZone.addEventListener('drop', handleFileDrop);
}
- document.getElementById('archiveList').addEventListener('change', async function (e) {
- // handle zim selection from dropdown if multiple files are loaded via webkitdirectory or filesystem api
- if (params.isFileSystemApiSupported) {
- const files = await abstractFilesystemAccess.getSelectedZimFromCache(e.target.value)
- setLocalArchiveFromFileList(files);
- } else {
- const files = abstractFilesystemAccess.getSelectedZimFromWebkitList(webKitFileList, e.target.value)
- setLocalArchiveFromFileList(files);
- }
- });
-
- if (typeof window.showDirectoryPicker === 'function') {
+ if (!isFireFoxOsNativeFileApiAvailable) {
+ document.getElementById('archiveList').addEventListener('change', async function (e) {
+ // handle zim selection from dropdown if multiple files are loaded via webkitdirectory or filesystem api
+ if (params.isFileSystemApiSupported) {
+ const files = await abstractFilesystemAccess.getSelectedZimFromCache(e.target.value)
+ setLocalArchiveFromFileList(files);
+ } else {
+ const files = abstractFilesystemAccess.getSelectedZimFromWebkitList(webKitFileList, e.target.value)
+ setLocalArchiveFromFileList(files);
+ }
+ });
+ }
+ if (params.isFileSystemApiSupported && !isFireFoxOsNativeFileApiAvailable) {
// Handles Folder selection when showDirectoryPicker is supported
document.getElementById('folderSelect').addEventListener('click', async function (e) {
e.preventDefault();
await abstractFilesystemAccess.selectDirectoryFromPickerViaFileSystemApi()
})
}
- if (params.isWebkitDirApiSupported && !params.isFileSystemApiSupported) {
+ if (params.isWebkitDirApiSupported && !params.isFileSystemApiSupported && !isFireFoxOsNativeFileApiAvailable) {
// Handles Folder selection when webkitdirectory is supported but showDirectoryPicker is not
document.getElementById('folderSelect').addEventListener('change', async function (e) {
e.preventDefault();
@@ -1312,7 +1316,7 @@ function displayFileSelect () {
await abstractFilesystemAccess.updateZimDropdownOptions(filenames, '');
})
}
- if (params.isFileSystemApiSupported) {
+ if (params.isFileSystemApiSupported && !isFireFoxOsNativeFileApiAvailable) {
// Handles File selection when showOpenFilePicker is supported and uses the filesystem api
document.getElementById('archiveFiles').addEventListener('click', async function (e) {
e.preventDefault();
diff --git a/www/js/lib/abstractFilesystemAccess.js b/www/js/lib/abstractFilesystemAccess.js
index 624a1f015..6493524b7 100644
--- a/www/js/lib/abstractFilesystemAccess.js
+++ b/www/js/lib/abstractFilesystemAccess.js
@@ -139,11 +139,12 @@ async function selectFileFromPickerViaFileSystemApi () {
const fileHandles = await window.showOpenFilePicker({ multiple: false });
const [selectedFile] = fileHandles;
const file = await selectedFile.getFile();
-
+ const filenameList = [selectedFile.name];
+ localStorage.setItem('zimFilenames', filenameList.join('|'));
cache.idxDB('zimFiles', selectedFile, function () {
// file saved in DB
+ updateZimDropdownOptions(filenameList, selectedFile.name);
});
- updateZimDropdownOptions([selectedFile.name], selectedFile.name);
return [file];
}
@@ -155,6 +156,8 @@ async function selectFileFromPickerViaFileSystemApi () {
function getSelectedZimFromCache (selectedFilename) {
return new Promise((resolve, _reject) => {
cache.idxDB('zimFiles', async function (fileOrDirHandle) {
+ // Left it here for debugging purposes as its sometimes asking for permission even when its granted
+ console.debug('FileHandle and Permission', fileOrDirHandle, fileOrDirHandle.queryPermission())
if ((await fileOrDirHandle.queryPermission()) !== 'granted') await fileOrDirHandle.requestPermission();
if (fileOrDirHandle.kind === 'directory') {
@@ -220,6 +223,7 @@ async function handleFolderDropViaFileSystemAPI (packet) {
const fileInfo = packet.dataTransfer.items[0];
const fileOrDirHandle = await fileInfo.getAsFileSystemHandle();
if (fileOrDirHandle.kind === 'file') {
+ localStorage.setItem([fileOrDirHandle.name], [fileOrDirHandle.name].join('|'));
cache.idxDB('zimFiles', fileOrDirHandle, function () {
// save file in DB
updateZimDropdownOptions([fileOrDirHandle.name], fileOrDirHandle.name);
From 16ec687786109c25eec049513e55153f4f26ad57 Mon Sep 17 00:00:00 2001
From: RG
Date: Mon, 30 Oct 2023 15:55:20 +0530
Subject: [PATCH 30/45] [FIX] FFOS UI and race around
---
www/index.html | 2 +-
www/js/lib/abstractFilesystemAccess.js | 3 +++
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/www/index.html b/www/index.html
index a5d04c362..581159baf 100644
--- a/www/index.html
+++ b/www/index.html
@@ -466,7 +466,7 @@
Please select or drag and drop a .zim file (or all the .zimaa, .zimab etc in
case of a split ZIM file):
diff --git a/www/js/lib/abstractFilesystemAccess.js b/www/js/lib/abstractFilesystemAccess.js
index 6493524b7..1c7eb9978 100644
--- a/www/js/lib/abstractFilesystemAccess.js
+++ b/www/js/lib/abstractFilesystemAccess.js
@@ -97,6 +97,9 @@ StorageFirefoxOS.prototype.enumerate = function (path) {
* @returns {Promise>} Array of unique filenames (if a split zim is considered a single file)
*/
async function updateZimDropdownOptions (files, selectedFile) {
+ const isFireFoxOsNativeFileApiAvailable = typeof navigator.getDeviceStorages === 'function';
+ if (isFireFoxOsNativeFileApiAvailable) return // do nothing let other function handle it
+
const select = document.getElementById('archiveList');
const options = [];
let count = 0;
From 96839f735b8b5c50a2baa7c117441b74af32ac03 Mon Sep 17 00:00:00 2001
From: RG
Date: Tue, 31 Oct 2023 18:14:40 +0530
Subject: [PATCH 31/45] [ADD] translations for other languages
---
i18n/en.jsonp.js | 4 ++++
i18n/es.jsonp.js | 4 ++++
i18n/fr.jsonp.js | 4 ++++
www/index.html | 13 ++++++-------
www/js/lib/abstractFilesystemAccess.js | 5 +++--
5 files changed, 21 insertions(+), 9 deletions(-)
diff --git a/i18n/en.jsonp.js b/i18n/en.jsonp.js
index f4233a670..f94b5aea9 100644
--- a/i18n/en.jsonp.js
+++ b/i18n/en.jsonp.js
@@ -19,8 +19,12 @@ document.localeJson = {
"configure-title": "Configuration",
"configure-about-usage-link": "About (Usage)",
"configure-btn-library": "Browse ZIM Library",
+ "configure-btn-folderselect": "Select Folder",
+ "configure-select-file-numbers": "{{numberOfFiles}} archives found in selected location. ",
"configure-download-instructions": "This application needs a ZIM archive to work. For full instructions, please see the section",
"configure-select-instructions": "Please select or drag and drop a .zim file (or all the .zimaa, .zimab etc in case of a split ZIM file):",
+ "configure-select-file-instructions": "Please select the archive you want to use :",
+ "configure-select-file-first-option": "Select an archive..",
"configure-selectordisplay": "Drag and drop a new ZIM file, or",
"configure-selectordisplay-link": "display file selectors",
"configure-static-content": "Only ZIMs with static content (e.g. Wiki-style) are supported in JQuery mode. ",
diff --git a/i18n/es.jsonp.js b/i18n/es.jsonp.js
index 623981f3e..83768dedc 100644
--- a/i18n/es.jsonp.js
+++ b/i18n/es.jsonp.js
@@ -19,8 +19,12 @@ document.localeJson = {
"configure-title": "Configuración",
"configure-about-usage-link": "Información (Uso)",
"configure-btn-library": "Biblioteca ZIM",
+ "configure-btn-folderselect": "Seleccionar carpeta",
+ "configure-select-file-numbers": "{{numberOfFiles}} archivos encontrados en la ubicación seleccionada. ",
"configure-download-instructions": "Esta aplicación necesita un archivo ZIM para funcionar. Para instrucciones completas, vea la sección",
"configure-select-instructions": "Seleccione o arrastre y suelte un archivo .zim (o todos los .zimaa, .zimab etc en caso de un archivo dividido):",
+ "configure-select-file-instructions": "Seleccione el archivo que desea utilizar:",
+ "configure-select-file-first-option": "Selecciona un archivo..",
"configure-selectordisplay": "Arrastre y suelte un nuevo archivo ZIM, o",
"configure-selectordisplay-link": "mostrar selector de archivos",
"configure-static-content": "Sólo funcionan bien los ZIM con contenido estático (tipo Wiki) en el modo JQuery. ",
diff --git a/i18n/fr.jsonp.js b/i18n/fr.jsonp.js
index 2d0c0dff3..72d54589a 100644
--- a/i18n/fr.jsonp.js
+++ b/i18n/fr.jsonp.js
@@ -19,8 +19,12 @@ document.localeJson = {
"configure-title": "Configuration",
"configure-about-usage-link": "Informations (Utilisation)",
"configure-btn-library": "Bibliothèque ZIM",
+ "configure-btn-folderselect": "Sélectionner un dossier",
+ "configure-select-file-numbers": "{{numberOfFiles}} archives trouvées dans le lieu sélectionné. ",
"configure-download-instructions": "Cette application a besoin d'un fichier ZIM pour fonctionner. Pour des instructions complètes, veuillez consulter la section",
"configure-select-instructions": "Veuillez sélectionner ou glisser-déposer un fichier .zim (ou tous les .zimaa, .zimab etc. dans le cas d'un fichier ZIM découpé) :",
+ "configure-select-file-instructions": "Veuillez sélectionner l'archive que vous souhaitez utiliser :",
+ "configure-select-file-first-option": "Sélectionner une archive..",
"configure-selectordisplay": "Glisser-déposer un nouveau fichier ZIM, ou",
"configure-selectordisplay-link": "afficher le sélecteur de fichiers",
"configure-static-content": "Seuls les ZIM avec un contenu statique (de type Wiki) sont pris en charge en mode JQuery. ",
diff --git a/www/index.html b/www/index.html
index 581159baf..a712df559 100644
--- a/www/index.html
+++ b/www/index.html
@@ -475,9 +475,9 @@
diff --git a/www/js/lib/abstractFilesystemAccess.js b/www/js/lib/abstractFilesystemAccess.js
index 1c7eb9978..3f8d64532 100644
--- a/www/js/lib/abstractFilesystemAccess.js
+++ b/www/js/lib/abstractFilesystemAccess.js
@@ -26,6 +26,7 @@
'use strict';
import cache from './cache.js';
+import translateUI from './translateUI.js';
function StorageFirefoxOS (storage) {
this._storage = storage;
@@ -103,7 +104,7 @@ async function updateZimDropdownOptions (files, selectedFile) {
const select = document.getElementById('archiveList');
const options = [];
let count = 0;
- if (files.length !== 0) options.push(new Option('Select an archive..', ''));
+ if (files.length !== 0) options.push(new Option(translateUI.t('configure-select-file-first-option'), ''));
files.forEach((fileName) => {
if (fileName.endsWith('.zim') || fileName.endsWith('.zimaa')) {
@@ -113,8 +114,8 @@ async function updateZimDropdownOptions (files, selectedFile) {
});
select.replaceChildren(...options);
document.getElementById('archiveList').value = selectedFile;
- document.getElementById('numberOfFilesDisplay').innerText = count;
document.getElementById('fileCountDisplay').style.display = '';
+ document.getElementById('fileCountDisplay').innerText = translateUI.t('configure-select-file-numbers').replace('{{numberOfFiles}}', count.toString());
}
/**
From 6e0cc44df43d3bb4043974a061f44c0d37cb9c6e Mon Sep 17 00:00:00 2001
From: RG
Date: Tue, 31 Oct 2023 18:23:48 +0530
Subject: [PATCH 32/45] [ADD] missed elements for translations
---
i18n/en.jsonp.js | 2 ++
i18n/es.jsonp.js | 2 ++
i18n/fr.jsonp.js | 2 ++
www/index.html | 4 ++--
4 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/i18n/en.jsonp.js b/i18n/en.jsonp.js
index f94b5aea9..130cb0bdb 100644
--- a/i18n/en.jsonp.js
+++ b/i18n/en.jsonp.js
@@ -20,6 +20,8 @@ document.localeJson = {
"configure-about-usage-link": "About (Usage)",
"configure-btn-library": "Browse ZIM Library",
"configure-btn-folderselect": "Select Folder",
+ "configure-btn-rescan": "Rescan",
+ "configure-about-rescan-btn": "Rescans your SD Cards and internal memory",
"configure-select-file-numbers": "{{numberOfFiles}} archives found in selected location. ",
"configure-download-instructions": "This application needs a ZIM archive to work. For full instructions, please see the section",
"configure-select-instructions": "Please select or drag and drop a .zim file (or all the .zimaa, .zimab etc in case of a split ZIM file):",
diff --git a/i18n/es.jsonp.js b/i18n/es.jsonp.js
index 83768dedc..30115e778 100644
--- a/i18n/es.jsonp.js
+++ b/i18n/es.jsonp.js
@@ -20,6 +20,8 @@ document.localeJson = {
"configure-about-usage-link": "Información (Uso)",
"configure-btn-library": "Biblioteca ZIM",
"configure-btn-folderselect": "Seleccionar carpeta",
+ "configure-btn-rescan": "Volver a escanear",
+ "configure-about-rescan-btn": "Vuelve a escanear las tarjetas SD y la memoria interna",
"configure-select-file-numbers": "{{numberOfFiles}} archivos encontrados en la ubicación seleccionada. ",
"configure-download-instructions": "Esta aplicación necesita un archivo ZIM para funcionar. Para instrucciones completas, vea la sección",
"configure-select-instructions": "Seleccione o arrastre y suelte un archivo .zim (o todos los .zimaa, .zimab etc en caso de un archivo dividido):",
diff --git a/i18n/fr.jsonp.js b/i18n/fr.jsonp.js
index 72d54589a..8e659faaa 100644
--- a/i18n/fr.jsonp.js
+++ b/i18n/fr.jsonp.js
@@ -20,6 +20,8 @@ document.localeJson = {
"configure-about-usage-link": "Informations (Utilisation)",
"configure-btn-library": "Bibliothèque ZIM",
"configure-btn-folderselect": "Sélectionner un dossier",
+ "configure-btn-rescan": "Rechercher",
+ "configure-about-rescan-btn": "Réanalyse des cartes SD et de la mémoire interne",
"configure-select-file-numbers": "{{numberOfFiles}} archives trouvées dans le lieu sélectionné. ",
"configure-download-instructions": "Cette application a besoin d'un fichier ZIM pour fonctionner. Pour des instructions complètes, veuillez consulter la section",
"configure-select-instructions": "Veuillez sélectionner ou glisser-déposer un fichier .zim (ou tous les .zimaa, .zimab etc. dans le cas d'un fichier ZIM découpé) :",
diff --git a/www/index.html b/www/index.html
index a712df559..dbddfa2f5 100644
--- a/www/index.html
+++ b/www/index.html
@@ -500,8 +500,8 @@
Configuration
-
- Rescans your SD Cards and internal memory
+
+
Rescans your SD Cards and internal memory
From 6c567f039ebd76c0a288ff3aa0c3427d97a763b4 Mon Sep 17 00:00:00 2001
From: RG
Date: Tue, 31 Oct 2023 18:32:41 +0530
Subject: [PATCH 33/45] [REFACTOR] minor fixes and comments
---
www/js/lib/abstractFilesystemAccess.js | 2 ++
www/js/lib/zimfile.js | 9 +--------
2 files changed, 3 insertions(+), 8 deletions(-)
diff --git a/www/js/lib/abstractFilesystemAccess.js b/www/js/lib/abstractFilesystemAccess.js
index 3f8d64532..76ab17180 100644
--- a/www/js/lib/abstractFilesystemAccess.js
+++ b/www/js/lib/abstractFilesystemAccess.js
@@ -99,6 +99,8 @@ StorageFirefoxOS.prototype.enumerate = function (path) {
*/
async function updateZimDropdownOptions (files, selectedFile) {
const isFireFoxOsNativeFileApiAvailable = typeof navigator.getDeviceStorages === 'function';
+ // This will make sure that there is no race around condition when platform is firefox os
+ // as other function will handle the dropdown UI updates
if (isFireFoxOsNativeFileApiAvailable) return // do nothing let other function handle it
const select = document.getElementById('archiveList');
diff --git a/www/js/lib/zimfile.js b/www/js/lib/zimfile.js
index f84651268..54b7284db 100644
--- a/www/js/lib/zimfile.js
+++ b/www/js/lib/zimfile.js
@@ -54,14 +54,7 @@ if (!String.prototype.startsWith) {
});
}
-/**
- * A global variable to track the assembler machine type and the last used decompressor (for reporting to the API panel)
- * This is populated in the Emscripten wrappers
- * @type {Object}
- * @property {String} assemblerMachineType The assembler machine type supported and/or loaded by this app: 'ASM' or 'WASM'
- * @property {String} decompressorLastUsed The decompressor that was last used to decode a compressed cluster (currently 'XZ' or 'ZSTD')
- * @property {String} errorStatus A description of any detected error in loading a decompressor
- */
+// to learn more read init.js:57 or search DecompressorAPI in init.js
params.decompressorAPI = {
assemblerMachineType: null,
decompressorLastUsed: null,
From 1c5e4196ac26dbbe37695346501c79cd69841a5b Mon Sep 17 00:00:00 2001
From: RG
Date: Tue, 31 Oct 2023 23:17:40 +0530
Subject: [PATCH 34/45] requested changes by @jaifroid
---
i18n/en.jsonp.js | 2 +-
i18n/es.jsonp.js | 4 ++--
i18n/fr.jsonp.js | 2 +-
www/css/app.css | 14 ++++++++++++++
www/index.html | 2 +-
www/js/app.js | 7 ++++---
www/js/init.js | 12 ++++++------
www/js/lib/abstractFilesystemAccess.js | 16 ++++++++--------
8 files changed, 37 insertions(+), 22 deletions(-)
diff --git a/i18n/en.jsonp.js b/i18n/en.jsonp.js
index f315593c9..7969f4790 100644
--- a/i18n/en.jsonp.js
+++ b/i18n/en.jsonp.js
@@ -21,7 +21,7 @@ document.localeJson = {
"configure-btn-library": "Browse ZIM Library",
"configure-btn-folderselect": "Select Folder",
"configure-btn-rescan": "Rescan",
- "configure-about-rescan-btn": "Rescans your SD Cards and internal memory",
+ "configure-about-rescan-tip": "Rescans your SD Cards and internal memory",
"configure-select-file-numbers": "{{numberOfFiles}} archives found in selected location. ",
"configure-download-instructions": "This application needs a ZIM archive to work. For full instructions, please see the section",
"configure-select-instructions": "Please select or drag and drop a .zim file (or all the .zimaa, .zimab etc in case of a split ZIM file):",
diff --git a/i18n/es.jsonp.js b/i18n/es.jsonp.js
index e93d188b7..d981bdbf5 100644
--- a/i18n/es.jsonp.js
+++ b/i18n/es.jsonp.js
@@ -19,9 +19,9 @@ document.localeJson = {
"configure-title": "Configuración",
"configure-about-usage-link": "Información (Uso)",
"configure-btn-library": "Biblioteca ZIM",
- "configure-btn-folderselect": "Seleccionar carpeta",
+ "configure-btn-folderselect": "Seleccione carpeta",
"configure-btn-rescan": "Volver a escanear",
- "configure-about-rescan-btn": "Vuelve a escanear las tarjetas SD y la memoria interna",
+ "configure-about-rescan-tip": "Vuelve a escanear las tarjetas SD y la memoria interna",
"configure-select-file-numbers": "{{numberOfFiles}} archivos encontrados en la ubicación seleccionada. ",
"configure-download-instructions": "Esta aplicación necesita un archivo ZIM para funcionar. Para instrucciones completas, vea la sección",
"configure-select-instructions": "Seleccione o arrastre y suelte un archivo .zim (o todos los .zimaa, .zimab etc en caso de un archivo dividido):",
diff --git a/i18n/fr.jsonp.js b/i18n/fr.jsonp.js
index acb61e69f..2d5e135ac 100644
--- a/i18n/fr.jsonp.js
+++ b/i18n/fr.jsonp.js
@@ -21,7 +21,7 @@ document.localeJson = {
"configure-btn-library": "Bibliothèque ZIM",
"configure-btn-folderselect": "Sélectionner un dossier",
"configure-btn-rescan": "Rechercher",
- "configure-about-rescan-btn": "Réanalyse des cartes SD et de la mémoire interne",
+ "configure-about-rescan-tip": "Réanalyser la carte SD et la mérmoire interne",
"configure-select-file-numbers": "{{numberOfFiles}} archives trouvées dans le lieu sélectionné. ",
"configure-download-instructions": "Cette application a besoin d'un fichier ZIM pour fonctionner. Pour des instructions complètes, veuillez consulter la section",
"configure-select-instructions": "Veuillez sélectionner ou glisser-déposer un fichier .zim (ou tous les .zimaa, .zimab etc. dans le cas d'un fichier ZIM découpé) :",
diff --git a/www/css/app.css b/www/css/app.css
index 3480c35d5..eaed4dd0d 100644
--- a/www/css/app.css
+++ b/www/css/app.css
@@ -102,6 +102,14 @@
display: inline;
}
+#archiveList {
+ width: 60%;
+}
+
+#archiveList {
+ width: 60%;
+ min-width: auto;
+}
/* Custom file input */
input[type="file"] {
@@ -334,4 +342,10 @@ button {
padding-bottom: 1px !important;
font-size: 16px !important;
}
+
+ #archiveList {
+ width: 100%;
+ min-width: auto;
+ }
+
}
diff --git a/www/index.html b/www/index.html
index 5104d112a..11b1ca8c6 100644
--- a/www/index.html
+++ b/www/index.html
@@ -501,7 +501,7 @@
Configuration
-
Rescans your SD Cards and internal memory
+
Rescans your SD Cards and internal memory
diff --git a/www/js/app.js b/www/js/app.js
index 337926c4d..e70e45984 100644
--- a/www/js/app.js
+++ b/www/js/app.js
@@ -1342,10 +1342,11 @@ let webKitFileList = null
function displayFileSelect () {
const isFireFoxOsNativeFileApiAvailable = typeof navigator.getDeviceStorages === 'function';
- console.debug('File system api supported', params.isFileSystemApiSupported);
- console.debug('Webkit supported', params.isWebkitDirApiSupported);
- console.debug('Firefox os native file support api', isFireFoxOsNativeFileApiAvailable)
+ console.debug(`File system api is ${params.isFileSystemApiSupported ? '' : 'not '}supported`);
+ console.debug(`Webkit directory api ${params.isWebkitDirApiSupported ? '' : 'not '}supported`);
+ console.debug(`Firefox os native file ${isFireFoxOsNativeFileApiAvailable ? '' : 'not '}support api`)
+ document.getElementById('fileCountDisplay').innerText = translateUI.t('configure-select-file-numbers').replace('{{numberOfFiles}}', '0');
document.getElementById('openLocalFiles').style.display = 'block';
if (params.isFileSystemApiSupported || params.isWebkitDirApiSupported) {
document.getElementById('chooseArchiveFromLocalStorage').style.display = '';
diff --git a/www/js/init.js b/www/js/init.js
index 6c51b28bb..d670f89c7 100644
--- a/www/js/init.js
+++ b/www/js/init.js
@@ -51,8 +51,8 @@
* @property {boolean} useCanvasElementsForWebpTranscoding - 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.
* @property {string} libraryUrl - The URL of the Kiwix library.
* @property {string} altLibraryUrl - The alternative URL of the Kiwix library in non-supported browsers.
- * @property {string} cacheAPI - Database name for the IndexedDB cache
- * @property {string} cacheIDB - Not sure what this does
+ * @property {string} cacheAPI - Name of the prefix used to identify the cache in Cache API
+ * @property {string} cacheIDB - Name of the Indexed DB database
* @property {boolean} isFileSystemApiSupported - A boolean indicating whether the FileSystem API is supported.
* @property {boolean} isWebkitDirApiSupported - A boolean indicating whether the Webkit Directory API is supported.
* @property {DecompressorAPI} decompressorAPI
@@ -119,10 +119,10 @@ params['contentInjectionMode'] = getSetting('contentInjectionMode') ||
params['useCanvasElementsForWebpTranscoding'] = null; // Value is determined in uiUtil.determineCanvasElementsWorkaround(), called when setting the content injection mode
params['libraryUrl'] = 'https://library.kiwix.org/'; // Url for iframe that will be loaded to download new zim files
params['altLibraryUrl'] = 'https://download.kiwix.org/zim/'; // Alternative Url for iframe (for use with unsupported browsers) that will be loaded to download new zim files
-params['cacheAPI'] = 'kiwix-js'; // Sets the database name for the IndexedDB cache
-params['cacheIDB'] = 'kiwix-zim'; // Not sure what this does
-params['isFileSystemApiSupported'] = typeof window.showOpenFilePicker === 'function'; // Not sure what this does
-params['isWebkitDirApiSupported'] = 'webkitdirectory' in document.createElement('input'); // Not sure what this does
+params['cacheAPI'] = 'kiwix-js'; // Sets name of the prefix used to identify the cache in Cache API
+params['cacheIDB'] = 'kiwix-zim'; // Sets name of the Indexed DB database
+params['isFileSystemApiSupported'] = typeof window.showOpenFilePicker === 'function'; // Sets a boolean indicating whether the FileSystem API is supported
+params['isWebkitDirApiSupported'] = 'webkitdirectory' in document.createElement('input'); // Sets a boolean indicating whether the Webkit Directory API is supported
/**
* Apply any override parameters that might be in the querystring.
diff --git a/www/js/lib/abstractFilesystemAccess.js b/www/js/lib/abstractFilesystemAccess.js
index 76ab17180..0366f1e32 100644
--- a/www/js/lib/abstractFilesystemAccess.js
+++ b/www/js/lib/abstractFilesystemAccess.js
@@ -301,12 +301,12 @@ async function getFilesFromReader (reader) {
export default {
StorageFirefoxOS: StorageFirefoxOS,
- updateZimDropdownOptions,
- selectDirectoryFromPickerViaFileSystemApi,
- selectFileFromPickerViaFileSystemApi,
- getSelectedZimFromCache,
- loadPreviousZimFile,
- handleFolderDropViaWebkit,
- handleFolderDropViaFileSystemAPI,
- getSelectedZimFromWebkitList
+ updateZimDropdownOptions: updateZimDropdownOptions,
+ selectDirectoryFromPickerViaFileSystemApi: selectDirectoryFromPickerViaFileSystemApi,
+ selectFileFromPickerViaFileSystemApi: selectFileFromPickerViaFileSystemApi,
+ getSelectedZimFromCache: getSelectedZimFromCache,
+ loadPreviousZimFile: loadPreviousZimFile,
+ handleFolderDropViaWebkit: handleFolderDropViaWebkit,
+ handleFolderDropViaFileSystemAPI: handleFolderDropViaFileSystemAPI,
+ getSelectedZimFromWebkitList: getSelectedZimFromWebkitList
};
From 6ededf1ae04b64488dab9cf1e837b06dc89e9f77 Mon Sep 17 00:00:00 2001
From: RG
Date: Tue, 31 Oct 2023 23:36:05 +0530
Subject: [PATCH 35/45] [REFACTOR] translations updated
---
i18n/en.jsonp.js | 2 +-
i18n/es.jsonp.js | 2 +-
i18n/fr.jsonp.js | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/i18n/en.jsonp.js b/i18n/en.jsonp.js
index 7969f4790..4e8b76d47 100644
--- a/i18n/en.jsonp.js
+++ b/i18n/en.jsonp.js
@@ -22,7 +22,7 @@ document.localeJson = {
"configure-btn-folderselect": "Select Folder",
"configure-btn-rescan": "Rescan",
"configure-about-rescan-tip": "Rescans your SD Cards and internal memory",
- "configure-select-file-numbers": "{{numberOfFiles}} archives found in selected location. ",
+ "configure-select-file-numbers": "{{numberOfFiles}} archive(s) found in selected location. ",
"configure-download-instructions": "This application needs a ZIM archive to work. For full instructions, please see the section",
"configure-select-instructions": "Please select or drag and drop a .zim file (or all the .zimaa, .zimab etc in case of a split ZIM file):",
"configure-select-file-instructions": "Please select the archive you want to use :",
diff --git a/i18n/es.jsonp.js b/i18n/es.jsonp.js
index d981bdbf5..2bb96b3d9 100644
--- a/i18n/es.jsonp.js
+++ b/i18n/es.jsonp.js
@@ -22,7 +22,7 @@ document.localeJson = {
"configure-btn-folderselect": "Seleccione carpeta",
"configure-btn-rescan": "Volver a escanear",
"configure-about-rescan-tip": "Vuelve a escanear las tarjetas SD y la memoria interna",
- "configure-select-file-numbers": "{{numberOfFiles}} archivos encontrados en la ubicación seleccionada. ",
+ "configure-select-file-numbers": "{{numberOfFiles}} archivo(s) encontrados en la ubicación seleccionada. ",
"configure-download-instructions": "Esta aplicación necesita un archivo ZIM para funcionar. Para instrucciones completas, vea la sección",
"configure-select-instructions": "Seleccione o arrastre y suelte un archivo .zim (o todos los .zimaa, .zimab etc en caso de un archivo dividido):",
"configure-select-file-instructions": "Seleccione el archivo que desea utilizar:",
diff --git a/i18n/fr.jsonp.js b/i18n/fr.jsonp.js
index 2d5e135ac..b04c62d67 100644
--- a/i18n/fr.jsonp.js
+++ b/i18n/fr.jsonp.js
@@ -22,7 +22,7 @@ document.localeJson = {
"configure-btn-folderselect": "Sélectionner un dossier",
"configure-btn-rescan": "Rechercher",
"configure-about-rescan-tip": "Réanalyser la carte SD et la mérmoire interne",
- "configure-select-file-numbers": "{{numberOfFiles}} archives trouvées dans le lieu sélectionné. ",
+ "configure-select-file-numbers": "{{numberOfFiles}} archive(s) trouvées dans le lieu sélectionné. ",
"configure-download-instructions": "Cette application a besoin d'un fichier ZIM pour fonctionner. Pour des instructions complètes, veuillez consulter la section",
"configure-select-instructions": "Veuillez sélectionner ou glisser-déposer un fichier .zim (ou tous les .zimaa, .zimab etc. dans le cas d'un fichier ZIM découpé) :",
"configure-select-file-instructions": "Veuillez sélectionner l'archive que vous souhaitez utiliser :",
From 600e83109d6fb8be34297e5c6d2fd33e397655eb Mon Sep 17 00:00:00 2001
From: RG
Date: Wed, 1 Nov 2023 00:56:08 +0530
Subject: [PATCH 36/45] [FIX] edge 18 file picker [FIX] firefox filedrop [FIX]
number of file counter display
---
i18n/en.jsonp.js | 2 +-
i18n/es.jsonp.js | 2 +-
i18n/fr.jsonp.js | 2 +-
www/index.html | 3 ++-
www/js/app.js | 1 -
www/js/lib/abstractFilesystemAccess.js | 15 ++++++++++-----
6 files changed, 15 insertions(+), 10 deletions(-)
diff --git a/i18n/en.jsonp.js b/i18n/en.jsonp.js
index 4e8b76d47..55348999c 100644
--- a/i18n/en.jsonp.js
+++ b/i18n/en.jsonp.js
@@ -22,7 +22,7 @@ document.localeJson = {
"configure-btn-folderselect": "Select Folder",
"configure-btn-rescan": "Rescan",
"configure-about-rescan-tip": "Rescans your SD Cards and internal memory",
- "configure-select-file-numbers": "{{numberOfFiles}} archive(s) found in selected location. ",
+ "configure-select-file-numbers": "archive(s) found in selected location. ",
"configure-download-instructions": "This application needs a ZIM archive to work. For full instructions, please see the section",
"configure-select-instructions": "Please select or drag and drop a .zim file (or all the .zimaa, .zimab etc in case of a split ZIM file):",
"configure-select-file-instructions": "Please select the archive you want to use :",
diff --git a/i18n/es.jsonp.js b/i18n/es.jsonp.js
index 2bb96b3d9..f623d84db 100644
--- a/i18n/es.jsonp.js
+++ b/i18n/es.jsonp.js
@@ -22,7 +22,7 @@ document.localeJson = {
"configure-btn-folderselect": "Seleccione carpeta",
"configure-btn-rescan": "Volver a escanear",
"configure-about-rescan-tip": "Vuelve a escanear las tarjetas SD y la memoria interna",
- "configure-select-file-numbers": "{{numberOfFiles}} archivo(s) encontrados en la ubicación seleccionada. ",
+ "configure-select-file-numbers": "archivo(s) encontrados en la ubicación seleccionada. ",
"configure-download-instructions": "Esta aplicación necesita un archivo ZIM para funcionar. Para instrucciones completas, vea la sección",
"configure-select-instructions": "Seleccione o arrastre y suelte un archivo .zim (o todos los .zimaa, .zimab etc en caso de un archivo dividido):",
"configure-select-file-instructions": "Seleccione el archivo que desea utilizar:",
diff --git a/i18n/fr.jsonp.js b/i18n/fr.jsonp.js
index b04c62d67..b8d71836e 100644
--- a/i18n/fr.jsonp.js
+++ b/i18n/fr.jsonp.js
@@ -22,7 +22,7 @@ document.localeJson = {
"configure-btn-folderselect": "Sélectionner un dossier",
"configure-btn-rescan": "Rechercher",
"configure-about-rescan-tip": "Réanalyser la carte SD et la mérmoire interne",
- "configure-select-file-numbers": "{{numberOfFiles}} archive(s) trouvées dans le lieu sélectionné. ",
+ "configure-select-file-numbers": "archive(s) trouvées dans le lieu sélectionné. ",
"configure-download-instructions": "Cette application a besoin d'un fichier ZIM pour fonctionner. Pour des instructions complètes, veuillez consulter la section",
"configure-select-instructions": "Veuillez sélectionner ou glisser-déposer un fichier .zim (ou tous les .zimaa, .zimab etc. dans le cas d'un fichier ZIM découpé) :",
"configure-select-file-instructions": "Veuillez sélectionner l'archive que vous souhaitez utiliser :",
diff --git a/www/index.html b/www/index.html
index 11b1ca8c6..b7de52d46 100644
--- a/www/index.html
+++ b/www/index.html
@@ -492,8 +492,9 @@
Configuration
+
0
- 0 archives found in selected location.
+ archives found in selected location.
Please select the archive you want to use :
diff --git a/www/js/app.js b/www/js/app.js
index e70e45984..b7ac5db5a 100644
--- a/www/js/app.js
+++ b/www/js/app.js
@@ -1346,7 +1346,6 @@ function displayFileSelect () {
console.debug(`Webkit directory api ${params.isWebkitDirApiSupported ? '' : 'not '}supported`);
console.debug(`Firefox os native file ${isFireFoxOsNativeFileApiAvailable ? '' : 'not '}support api`)
- document.getElementById('fileCountDisplay').innerText = translateUI.t('configure-select-file-numbers').replace('{{numberOfFiles}}', '0');
document.getElementById('openLocalFiles').style.display = 'block';
if (params.isFileSystemApiSupported || params.isWebkitDirApiSupported) {
document.getElementById('chooseArchiveFromLocalStorage').style.display = '';
diff --git a/www/js/lib/abstractFilesystemAccess.js b/www/js/lib/abstractFilesystemAccess.js
index 0366f1e32..6c0b73def 100644
--- a/www/js/lib/abstractFilesystemAccess.js
+++ b/www/js/lib/abstractFilesystemAccess.js
@@ -104,20 +104,23 @@ async function updateZimDropdownOptions (files, selectedFile) {
if (isFireFoxOsNativeFileApiAvailable) return // do nothing let other function handle it
const select = document.getElementById('archiveList');
- const options = [];
+ let options = '';
let count = 0;
- if (files.length !== 0) options.push(new Option(translateUI.t('configure-select-file-first-option'), ''));
+ if (files.length !== 0) options += ``;
files.forEach((fileName) => {
if (fileName.endsWith('.zim') || fileName.endsWith('.zimaa')) {
- options.push(new Option(`${fileName}`, fileName));
+ options += ``;
count++;
}
});
- select.replaceChildren(...options);
+ select.innerHTML = options;
document.getElementById('archiveList').value = selectedFile;
+ document.getElementById('numberOfFilesCount').style.display = '';
document.getElementById('fileCountDisplay').style.display = '';
- document.getElementById('fileCountDisplay').innerText = translateUI.t('configure-select-file-numbers').replace('{{numberOfFiles}}', count.toString());
+
+ document.getElementById('numberOfFilesCount').innerText = count.toString();
+ document.getElementById('fileCountDisplay').innerText = translateUI.t('configure-select-file-numbers');
}
/**
@@ -260,6 +263,8 @@ async function handleFolderDropViaWebkit (event) {
var entry = dt.items[0].webkitGetAsEntry();
if (entry.isFile) {
+ console.log(entry.file);
+ await updateZimDropdownOptions([entry.name], entry.name);
return { loadZim: true, files: [entry.file] };
} else if (entry.isDirectory) {
var reader = entry.createReader();
From c65ab0da2bfa786a4a9ed476535c96ea695e15fe Mon Sep 17 00:00:00 2001
From: RG
Date: Thu, 2 Nov 2023 15:57:52 +0530
Subject: [PATCH 37/45] [FIX] translation errors
---
i18n/en.jsonp.js | 4 ++--
i18n/es.jsonp.js | 4 ++--
i18n/fr.jsonp.js | 8 ++++----
www/css/app.css | 4 ----
www/index.html | 4 ++--
5 files changed, 10 insertions(+), 14 deletions(-)
diff --git a/i18n/en.jsonp.js b/i18n/en.jsonp.js
index 55348999c..cfe699ccd 100644
--- a/i18n/en.jsonp.js
+++ b/i18n/en.jsonp.js
@@ -25,8 +25,8 @@ document.localeJson = {
"configure-select-file-numbers": "archive(s) found in selected location. ",
"configure-download-instructions": "This application needs a ZIM archive to work. For full instructions, please see the section",
"configure-select-instructions": "Please select or drag and drop a .zim file (or all the .zimaa, .zimab etc in case of a split ZIM file):",
- "configure-select-file-instructions": "Please select the archive you want to use :",
- "configure-select-file-first-option": "Select an archive..",
+ "configure-select-file-instructions": "Please select the archive you want to use:",
+ "configure-select-file-first-option": "Select an archive...",
"configure-selectordisplay": "Drag and drop a new ZIM file, or",
"configure-selectordisplay-link": "display file selectors",
"configure-static-content": "Only ZIMs with static content (e.g. Wiki-style) are supported in JQuery mode. ",
diff --git a/i18n/es.jsonp.js b/i18n/es.jsonp.js
index f623d84db..d99dd7a08 100644
--- a/i18n/es.jsonp.js
+++ b/i18n/es.jsonp.js
@@ -22,11 +22,11 @@ document.localeJson = {
"configure-btn-folderselect": "Seleccione carpeta",
"configure-btn-rescan": "Volver a escanear",
"configure-about-rescan-tip": "Vuelve a escanear las tarjetas SD y la memoria interna",
- "configure-select-file-numbers": "archivo(s) encontrados en la ubicación seleccionada. ",
+ "configure-select-file-numbers": "archivo(s) encontrado(s) en la ubicación seleccionada. ",
"configure-download-instructions": "Esta aplicación necesita un archivo ZIM para funcionar. Para instrucciones completas, vea la sección",
"configure-select-instructions": "Seleccione o arrastre y suelte un archivo .zim (o todos los .zimaa, .zimab etc en caso de un archivo dividido):",
"configure-select-file-instructions": "Seleccione el archivo que desea utilizar:",
- "configure-select-file-first-option": "Selecciona un archivo..",
+ "configure-select-file-first-option": "Seleccione un archivo...",
"configure-selectordisplay": "Arrastre y suelte un nuevo archivo ZIM, o",
"configure-selectordisplay-link": "mostrar selector de archivos",
"configure-static-content": "Sólo funcionan bien los ZIM con contenido estático (tipo Wiki) en el modo JQuery. ",
diff --git a/i18n/fr.jsonp.js b/i18n/fr.jsonp.js
index b8d71836e..2b8465fac 100644
--- a/i18n/fr.jsonp.js
+++ b/i18n/fr.jsonp.js
@@ -20,13 +20,13 @@ document.localeJson = {
"configure-about-usage-link": "Informations (Utilisation)",
"configure-btn-library": "Bibliothèque ZIM",
"configure-btn-folderselect": "Sélectionner un dossier",
- "configure-btn-rescan": "Rechercher",
+ "configure-btn-rescan": "Réanalyser",
"configure-about-rescan-tip": "Réanalyser la carte SD et la mérmoire interne",
- "configure-select-file-numbers": "archive(s) trouvées dans le lieu sélectionné. ",
+ "configure-select-file-numbers": "archive(s) trouvée(s) dans le lieu sélectionné. ",
"configure-download-instructions": "Cette application a besoin d'un fichier ZIM pour fonctionner. Pour des instructions complètes, veuillez consulter la section",
"configure-select-instructions": "Veuillez sélectionner ou glisser-déposer un fichier .zim (ou tous les .zimaa, .zimab etc. dans le cas d'un fichier ZIM découpé) :",
- "configure-select-file-instructions": "Veuillez sélectionner l'archive que vous souhaitez utiliser :",
- "configure-select-file-first-option": "Sélectionner une archive..",
+ "configure-select-file-instructions": "Veuillez sélectionner l'archive que vous souhaitez utiliser:",
+ "configure-select-file-first-option": "Sélectionner une archive...",
"configure-selectordisplay": "Glisser-déposer un nouveau fichier ZIM, ou",
"configure-selectordisplay-link": "afficher le sélecteur de fichiers",
"configure-static-content": "Seuls les ZIM avec un contenu statique (de type Wiki) sont pris en charge en mode JQuery. ",
diff --git a/www/css/app.css b/www/css/app.css
index eaed4dd0d..c10178579 100644
--- a/www/css/app.css
+++ b/www/css/app.css
@@ -102,10 +102,6 @@
display: inline;
}
-#archiveList {
- width: 60%;
-}
-
#archiveList {
width: 60%;
min-width: auto;
diff --git a/www/index.html b/www/index.html
index b7de52d46..97a2202ba 100644
--- a/www/index.html
+++ b/www/index.html
@@ -494,9 +494,9 @@
Configuration
0
- archives found in selected location.
+ archive(s) found in selected location.
-
Please select the archive you want to use :
+
Please select the archive you want to use:
From 879db532edeb82510da817fa84f1a4eded09d562 Mon Sep 17 00:00:00 2001
From: RG
Date: Fri, 3 Nov 2023 20:26:32 +0530
Subject: [PATCH 38/45] [FIX] option not loading on refresh
---
i18n/fr.jsonp.js | 2 +-
www/js/app.js | 6 +++---
www/js/lib/abstractFilesystemAccess.js | 21 +++++++++++++--------
3 files changed, 17 insertions(+), 12 deletions(-)
diff --git a/i18n/fr.jsonp.js b/i18n/fr.jsonp.js
index 2b8465fac..1f7d11758 100644
--- a/i18n/fr.jsonp.js
+++ b/i18n/fr.jsonp.js
@@ -25,7 +25,7 @@ document.localeJson = {
"configure-select-file-numbers": "archive(s) trouvée(s) dans le lieu sélectionné. ",
"configure-download-instructions": "Cette application a besoin d'un fichier ZIM pour fonctionner. Pour des instructions complètes, veuillez consulter la section",
"configure-select-instructions": "Veuillez sélectionner ou glisser-déposer un fichier .zim (ou tous les .zimaa, .zimab etc. dans le cas d'un fichier ZIM découpé) :",
- "configure-select-file-instructions": "Veuillez sélectionner l'archive que vous souhaitez utiliser:",
+ "configure-select-file-instructions": "Veuillez sélectionner l'archive que vous souhaitez utiliser :",
"configure-select-file-first-option": "Sélectionner une archive...",
"configure-selectordisplay": "Glisser-déposer un nouveau fichier ZIM, ou",
"configure-selectordisplay-link": "afficher le sélecteur de fichiers",
diff --git a/www/js/app.js b/www/js/app.js
index b7ac5db5a..8f49f41c8 100644
--- a/www/js/app.js
+++ b/www/js/app.js
@@ -1451,9 +1451,9 @@ async function handleFileDrop (packet) {
// call the `setLocalArchiveFromFileList`
let loadZim = true;
- if (params.isFileSystemApiSupported) loadZim = await abstractFilesystemAccess.handleFolderDropViaFileSystemAPI(packet)
- if (params.isWebkitDirApiSupported) {
- const ret = await abstractFilesystemAccess.handleFolderDropViaWebkit(packet)
+ if (params.isFileSystemApiSupported) loadZim = await abstractFilesystemAccess.handleFolderOrFileDropViaFileSystemAPI(packet)
+ else if (params.isWebkitDirApiSupported) {
+ const ret = await abstractFilesystemAccess.handleFolderOrFileDropViaWebkit(packet)
loadZim = ret.loadZim
webKitFileList = ret.files
}
diff --git a/www/js/lib/abstractFilesystemAccess.js b/www/js/lib/abstractFilesystemAccess.js
index 6c0b73def..a7b0dd44e 100644
--- a/www/js/lib/abstractFilesystemAccess.js
+++ b/www/js/lib/abstractFilesystemAccess.js
@@ -214,10 +214,14 @@ function getSelectedZimFromWebkitList (webKitFileList, filename) {
* Loads the Previously loaded zim filename(s) via local storage
*/
function loadPreviousZimFile () {
- if (window.params.isFileSystemApiSupported) {
- const filenames = localStorage.getItem('zimFilenames');
- if (filenames) updateZimDropdownOptions(filenames.split('|'), '');
- }
+ // If we call `updateZimDropdownOptions` without any delay it will run before the internationalization is initialized
+ // It's a bit hacky but it works and I am not sure if there is any other way ATM
+ setTimeout(() => {
+ if (window.params.isFileSystemApiSupported) {
+ const filenames = localStorage.getItem('zimFilenames');
+ if (filenames) updateZimDropdownOptions(filenames.split('|'), '');
+ }
+ }, 200);
}
/**
@@ -225,7 +229,7 @@ function loadPreviousZimFile () {
* @param {DragEvent} packet The DragEvent packet
* @returns {Promise} Whether the dropped item is a file or directory
*/
-async function handleFolderDropViaFileSystemAPI (packet) {
+async function handleFolderOrFileDropViaFileSystemAPI (packet) {
if (!window.params.isFileSystemApiSupported) return true;
// Only runs when browser support File System API
@@ -237,6 +241,7 @@ async function handleFolderDropViaFileSystemAPI (packet) {
// save file in DB
updateZimDropdownOptions([fileOrDirHandle.name], fileOrDirHandle.name);
});
+ localStorage.setItem('zimFilenames', [fileOrDirHandle.name].join('|'));
return true;
}
if (fileOrDirHandle.kind === 'directory') {
@@ -258,7 +263,7 @@ async function handleFolderDropViaFileSystemAPI (packet) {
* @param {DragEvent} event The DragEvent packet
* @returns {Promise<{loadZim: boolean, files: Array} | void>} Whether the dropped item is a file or directory and FileList
*/
-async function handleFolderDropViaWebkit (event) {
+async function handleFolderOrFileDropViaWebkit (event) {
var dt = event.dataTransfer;
var entry = dt.items[0].webkitGetAsEntry();
@@ -311,7 +316,7 @@ export default {
selectFileFromPickerViaFileSystemApi: selectFileFromPickerViaFileSystemApi,
getSelectedZimFromCache: getSelectedZimFromCache,
loadPreviousZimFile: loadPreviousZimFile,
- handleFolderDropViaWebkit: handleFolderDropViaWebkit,
- handleFolderDropViaFileSystemAPI: handleFolderDropViaFileSystemAPI,
+ handleFolderOrFileDropViaWebkit: handleFolderOrFileDropViaWebkit,
+ handleFolderOrFileDropViaFileSystemAPI: handleFolderOrFileDropViaFileSystemAPI,
getSelectedZimFromWebkitList: getSelectedZimFromWebkitList
};
From 40b44cef14e526c5e5bf125c3e462a4a17cec408 Mon Sep 17 00:00:00 2001
From: RG
Date: Fri, 3 Nov 2023 23:02:20 +0530
Subject: [PATCH 39/45] [FIX] UI placement fix
---
www/css/app.css | 7 +++++++
www/index.html | 2 --
2 files changed, 7 insertions(+), 2 deletions(-)
diff --git a/www/css/app.css b/www/css/app.css
index c10178579..233b8d635 100644
--- a/www/css/app.css
+++ b/www/css/app.css
@@ -98,6 +98,9 @@
margin: 0 1rem;
}
+#filesSelectionInfoAndCount {
+ padding-top: 0.5rem;
+}
#filesSelectionInfoAndCount p{
display: inline;
}
@@ -106,6 +109,10 @@
width: 60%;
min-width: auto;
}
+
+#rescanButtonAndText button {
+ margin-top: 0.5rem;
+}
/* Custom file input */
input[type="file"] {
diff --git a/www/index.html b/www/index.html
index 97a2202ba..a206c5e53 100644
--- a/www/index.html
+++ b/www/index.html
@@ -499,14 +499,12 @@
Configuration
Please select the archive you want to use:
-
Rescans your SD Cards and internal memory
-
Display settings
From c74e04d504a2a4ed91c33902e05e06db0c083b8f Mon Sep 17 00:00:00 2001
From: RG
Date: Sun, 5 Nov 2023 02:52:30 +0530
Subject: [PATCH 40/45] [ADD] file list save for webkit dir
---
www/js/app.js | 26 +++++++++++++++++++-------
www/js/lib/abstractFilesystemAccess.js | 9 +++++----
2 files changed, 24 insertions(+), 11 deletions(-)
diff --git a/www/js/app.js b/www/js/app.js
index 8f49f41c8..02062e139 100644
--- a/www/js/app.js
+++ b/www/js/app.js
@@ -1370,10 +1370,15 @@ function displayFileSelect () {
if (!isFireFoxOsNativeFileApiAvailable) {
document.getElementById('archiveList').addEventListener('change', async function (e) {
// handle zim selection from dropdown if multiple files are loaded via webkitdirectory or filesystem api
+ localStorage.setItem('previousZimFileName', e.target.value);
if (params.isFileSystemApiSupported) {
const files = await abstractFilesystemAccess.getSelectedZimFromCache(e.target.value)
setLocalArchiveFromFileList(files);
} else {
+ if (webKitFileList === null) {
+ document.getElementById('folderSelect').click();
+ return;
+ }
const files = abstractFilesystemAccess.getSelectedZimFromWebkitList(webKitFileList, e.target.value)
setLocalArchiveFromFileList(files);
}
@@ -1391,12 +1396,19 @@ function displayFileSelect () {
document.getElementById('folderSelect').addEventListener('change', async function (e) {
e.preventDefault();
const filenames = [];
+ const previousZimFile = []
+ const lastFilename = localStorage.getItem('previousZimFileName');
+ const filenameWithoutExtension = lastFilename.replace(/\.zim\w\w$/i, '');
+ const regex = new RegExp(`\\${filenameWithoutExtension}.zim\\w\\w$`, 'i');
for (const file of e.target.files) {
filenames.push(file.name);
+ if (regex.test(file.name) || file.name === lastFilename) previousZimFile.push(file);
}
webKitFileList = e.target.files;
- // populateDropDownListOfArchives(filenames);
- await abstractFilesystemAccess.updateZimDropdownOptions(filenames, '');
+ localStorage.setItem('zimFilenames', filenames.join('|'));
+ // will load the old file if the selected folder contains the same file
+ if (previousZimFile.length !== 0) setLocalArchiveFromFileList(previousZimFile);
+ await abstractFilesystemAccess.updateZimDropdownOptions(filenames, previousZimFile.length !== 0 ? lastFilename : '');
})
}
if (params.isFileSystemApiSupported && !isFireFoxOsNativeFileApiAvailable) {
@@ -1451,13 +1463,13 @@ async function handleFileDrop (packet) {
// call the `setLocalArchiveFromFileList`
let loadZim = true;
- if (params.isFileSystemApiSupported) loadZim = await abstractFilesystemAccess.handleFolderOrFileDropViaFileSystemAPI(packet)
+ // no previous file will be loaded in case of FileSystemApi
+ if (params.isFileSystemApiSupported) loadZim = await abstractFilesystemAccess.handleFolderOrFileDropViaFileSystemAPI(packet);
else if (params.isWebkitDirApiSupported) {
- const ret = await abstractFilesystemAccess.handleFolderOrFileDropViaWebkit(packet)
- loadZim = ret.loadZim
- webKitFileList = ret.files
+ const ret = await abstractFilesystemAccess.handleFolderOrFileDropViaWebkit(packet);
+ loadZim = ret.loadZim;
+ webKitFileList = ret.files;
}
-
if (loadZim) setLocalArchiveFromFileList(files);
}
diff --git a/www/js/lib/abstractFilesystemAccess.js b/www/js/lib/abstractFilesystemAccess.js
index a7b0dd44e..aff6524a4 100644
--- a/www/js/lib/abstractFilesystemAccess.js
+++ b/www/js/lib/abstractFilesystemAccess.js
@@ -106,7 +106,7 @@ async function updateZimDropdownOptions (files, selectedFile) {
const select = document.getElementById('archiveList');
let options = '';
let count = 0;
- if (files.length !== 0) options += ``;
+ if (files.length !== 0) options += ``;
files.forEach((fileName) => {
if (fileName.endsWith('.zim') || fileName.endsWith('.zimaa')) {
@@ -166,7 +166,7 @@ function getSelectedZimFromCache (selectedFilename) {
return new Promise((resolve, _reject) => {
cache.idxDB('zimFiles', async function (fileOrDirHandle) {
// Left it here for debugging purposes as its sometimes asking for permission even when its granted
- console.debug('FileHandle and Permission', fileOrDirHandle, fileOrDirHandle.queryPermission())
+ console.debug('FileHandle and Permission', fileOrDirHandle, await fileOrDirHandle.queryPermission())
if ((await fileOrDirHandle.queryPermission()) !== 'granted') await fileOrDirHandle.requestPermission();
if (fileOrDirHandle.kind === 'directory') {
@@ -217,7 +217,7 @@ function loadPreviousZimFile () {
// If we call `updateZimDropdownOptions` without any delay it will run before the internationalization is initialized
// It's a bit hacky but it works and I am not sure if there is any other way ATM
setTimeout(() => {
- if (window.params.isFileSystemApiSupported) {
+ if (window.params.isFileSystemApiSupported || window.params.isWebkitDirApiSupported) {
const filenames = localStorage.getItem('zimFilenames');
if (filenames) updateZimDropdownOptions(filenames.split('|'), '');
}
@@ -268,7 +268,7 @@ async function handleFolderOrFileDropViaWebkit (event) {
var entry = dt.items[0].webkitGetAsEntry();
if (entry.isFile) {
- console.log(entry.file);
+ localStorage.setItem('zimFilenames', [entry.name].join('|'));
await updateZimDropdownOptions([entry.name], entry.name);
return { loadZim: true, files: [entry.file] };
} else if (entry.isDirectory) {
@@ -276,6 +276,7 @@ async function handleFolderOrFileDropViaWebkit (event) {
const files = await getFilesFromReader(reader);
const fileNames = [];
files.forEach((file) => fileNames.push(file.name));
+ localStorage.setItem('zimFilenames', fileNames.join('|'));
await updateZimDropdownOptions(fileNames, '');
return { loadZim: false, files: files };
}
From 5f8ce320cde4dd964a715440d677c393a43d4b12 Mon Sep 17 00:00:00 2001
From: RG
Date: Sun, 5 Nov 2023 03:20:09 +0530
Subject: [PATCH 41/45] [FIX] single file picker for webkit
---
www/js/app.js | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/www/js/app.js b/www/js/app.js
index 02062e139..1cb5b3f91 100644
--- a/www/js/app.js
+++ b/www/js/app.js
@@ -1376,7 +1376,9 @@ function displayFileSelect () {
setLocalArchiveFromFileList(files);
} else {
if (webKitFileList === null) {
- document.getElementById('folderSelect').click();
+ const element = localStorage.getItem('zimFilenames').split('|').length === 1 ? 'archiveFiles' : 'folderSelect';
+ // console.log(localStorage.getItem('zimFilenames').split('|'));
+ document.getElementById(element).click();
return;
}
const files = abstractFilesystemAccess.getSelectedZimFromWebkitList(webKitFileList, e.target.value)
@@ -1423,6 +1425,7 @@ function displayFileSelect () {
document.getElementById('archiveFiles').addEventListener('change', async function (e) {
if (params.isWebkitDirApiSupported || params.isFileSystemApiSupported) {
const activeFilename = e.target.files[0].name;
+ localStorage.setItem('zimFilenames', [activeFilename].join('|'));
await abstractFilesystemAccess.updateZimDropdownOptions([activeFilename], activeFilename);
}
From a55d9d29977a0b2737b2543f60d9ea3617f35819 Mon Sep 17 00:00:00 2001
From: RG
Date: Sun, 5 Nov 2023 23:21:09 +0530
Subject: [PATCH 42/45] [FIX] XSS and mobile phone folder picker disabled
---
www/js/app.js | 11 ++++++++---
www/js/lib/abstractFilesystemAccess.js | 13 +++++++++----
2 files changed, 17 insertions(+), 7 deletions(-)
diff --git a/www/js/app.js b/www/js/app.js
index 1cb5b3f91..7ccb946de 100644
--- a/www/js/app.js
+++ b/www/js/app.js
@@ -1341,13 +1341,16 @@ let webKitFileList = null
*/
function displayFileSelect () {
const isFireFoxOsNativeFileApiAvailable = typeof navigator.getDeviceStorages === 'function';
+ let isPlatformMobilePhone = false;
+ if (/Android/i.test(navigator.userAgent)) isPlatformMobilePhone = true;
+ if (/iphone|ipad|ipod/i.test(navigator.userAgent) || navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1) isPlatformMobilePhone = true;
console.debug(`File system api is ${params.isFileSystemApiSupported ? '' : 'not '}supported`);
console.debug(`Webkit directory api ${params.isWebkitDirApiSupported ? '' : 'not '}supported`);
console.debug(`Firefox os native file ${isFireFoxOsNativeFileApiAvailable ? '' : 'not '}support api`)
-
+ console.log('ASSSSS');
document.getElementById('openLocalFiles').style.display = 'block';
- if (params.isFileSystemApiSupported || params.isWebkitDirApiSupported) {
+ if ((params.isFileSystemApiSupported || params.isWebkitDirApiSupported) && !isPlatformMobilePhone) {
document.getElementById('chooseArchiveFromLocalStorage').style.display = '';
document.getElementById('folderSelect').style.display = '';
}
@@ -1398,10 +1401,12 @@ function displayFileSelect () {
document.getElementById('folderSelect').addEventListener('change', async function (e) {
e.preventDefault();
const filenames = [];
+
const previousZimFile = []
- const lastFilename = localStorage.getItem('previousZimFileName');
+ const lastFilename = localStorage.getItem('previousZimFileName') ?? '';
const filenameWithoutExtension = lastFilename.replace(/\.zim\w\w$/i, '');
const regex = new RegExp(`\\${filenameWithoutExtension}.zim\\w\\w$`, 'i');
+
for (const file of e.target.files) {
filenames.push(file.name);
if (regex.test(file.name) || file.name === lastFilename) previousZimFile.push(file);
diff --git a/www/js/lib/abstractFilesystemAccess.js b/www/js/lib/abstractFilesystemAccess.js
index aff6524a4..b3c180ca5 100644
--- a/www/js/lib/abstractFilesystemAccess.js
+++ b/www/js/lib/abstractFilesystemAccess.js
@@ -104,17 +104,22 @@ async function updateZimDropdownOptions (files, selectedFile) {
if (isFireFoxOsNativeFileApiAvailable) return // do nothing let other function handle it
const select = document.getElementById('archiveList');
- let options = '';
+ const options = [];
let count = 0;
- if (files.length !== 0) options += ``;
+ select.innerHTML = '';
+ if (files.length !== 0) {
+ const placeholderOption = new Option(translateUI.t('configure-select-file-first-option'), '');
+ placeholderOption.disabled = true;
+ select.appendChild(placeholderOption);
+ };
files.forEach((fileName) => {
if (fileName.endsWith('.zim') || fileName.endsWith('.zimaa')) {
- options += ``;
+ options.push(new Option(fileName, fileName));
+ select.appendChild(new Option(fileName, fileName));
count++;
}
});
- select.innerHTML = options;
document.getElementById('archiveList').value = selectedFile;
document.getElementById('numberOfFilesCount').style.display = '';
document.getElementById('fileCountDisplay').style.display = '';
From bb1d61b1a5b2a476b2f615e480151aa27996479d Mon Sep 17 00:00:00 2001
From: RG
Date: Sun, 5 Nov 2023 23:44:42 +0530
Subject: [PATCH 43/45] [FIX] auto load zim on Folder select
---
www/js/app.js | 7 ++++---
www/js/lib/abstractFilesystemAccess.js | 10 +++++++++-
2 files changed, 13 insertions(+), 4 deletions(-)
diff --git a/www/js/app.js b/www/js/app.js
index 7ccb946de..c6e7a8e41 100644
--- a/www/js/app.js
+++ b/www/js/app.js
@@ -214,7 +214,7 @@ function resizeIFrame () {
if (iframe.style.display === 'none') {
// We are in About or Configuration, so we only set the region height
region.style.height = window.innerHeight + 'px';
- nestedFrame.style.height = window.innerHeight - 110 + 'px';
+ if (nestedFrame) nestedFrame.style.height = window.innerHeight - 110 + 'px';
} else {
// IE cannot retrieve computed headerStyles till the next paint, so we wait a few ticks
setTimeout(function () {
@@ -1348,7 +1348,7 @@ function displayFileSelect () {
console.debug(`File system api is ${params.isFileSystemApiSupported ? '' : 'not '}supported`);
console.debug(`Webkit directory api ${params.isWebkitDirApiSupported ? '' : 'not '}supported`);
console.debug(`Firefox os native file ${isFireFoxOsNativeFileApiAvailable ? '' : 'not '}support api`)
- console.log('ASSSSS');
+
document.getElementById('openLocalFiles').style.display = 'block';
if ((params.isFileSystemApiSupported || params.isWebkitDirApiSupported) && !isPlatformMobilePhone) {
document.getElementById('chooseArchiveFromLocalStorage').style.display = '';
@@ -1393,7 +1393,8 @@ function displayFileSelect () {
// Handles Folder selection when showDirectoryPicker is supported
document.getElementById('folderSelect').addEventListener('click', async function (e) {
e.preventDefault();
- await abstractFilesystemAccess.selectDirectoryFromPickerViaFileSystemApi()
+ const previousZimFiles = await abstractFilesystemAccess.selectDirectoryFromPickerViaFileSystemApi()
+ if (previousZimFiles.length !== 0) setLocalArchiveFromFileList(previousZimFiles);
})
}
if (params.isWebkitDirApiSupported && !params.isFileSystemApiSupported && !isFireFoxOsNativeFileApiAvailable) {
diff --git a/www/js/lib/abstractFilesystemAccess.js b/www/js/lib/abstractFilesystemAccess.js
index b3c180ca5..63d35752a 100644
--- a/www/js/lib/abstractFilesystemAccess.js
+++ b/www/js/lib/abstractFilesystemAccess.js
@@ -130,19 +130,27 @@ async function updateZimDropdownOptions (files, selectedFile) {
/**
* Opens the File System API to select a directory
+ * @returns {Promise>} Previously selected file if available in selected folder
*/
async function selectDirectoryFromPickerViaFileSystemApi () {
const handle = await window.showDirectoryPicker();
const fileNames = [];
+ const previousZimFile = []
+
+ const lastZimNameWithoutExtension = (localStorage.getItem('previousZimFileName') ?? '').replace(/\.zim\w\w$/i, '');
+ const regex = new RegExp(`\\${lastZimNameWithoutExtension}.zim\\w\\w$`, 'i');
+
for await (const entry of handle.values()) {
fileNames.push(entry.name);
+ if (regex.test(entry.name) || entry.name === (localStorage.getItem('previousZimFileName') ?? '')) previousZimFile.push(await entry.getFile());
}
localStorage.setItem('zimFilenames', fileNames.join('|'));
- updateZimDropdownOptions(fileNames, '');
+ updateZimDropdownOptions(fileNames, previousZimFile.length !== 0 ? localStorage.getItem('previousZimFileName') : '');
cache.idxDB('zimFiles', handle, function () {
// save file in DB
});
+ return previousZimFile;
}
/**
From b18fe52edb71bba683d2becde162f193b529c1eb Mon Sep 17 00:00:00 2001
From: RG
Date: Tue, 7 Nov 2023 23:10:34 +0530
Subject: [PATCH 44/45] Refactor file selection logic for better compatibility
---
www/js/app.js | 71 +++++++++++++++++++++++++++++----------------------
1 file changed, 41 insertions(+), 30 deletions(-)
diff --git a/www/js/app.js b/www/js/app.js
index 3e3bb2b38..d44413639 100644
--- a/www/js/app.js
+++ b/www/js/app.js
@@ -1318,26 +1318,30 @@ function displayFileSelect () {
globalDropZone.addEventListener('drop', handleFileDrop);
}
- if (!isFireFoxOsNativeFileApiAvailable) {
- document.getElementById('archiveList').addEventListener('change', async function (e) {
- // handle zim selection from dropdown if multiple files are loaded via webkitdirectory or filesystem api
- localStorage.setItem('previousZimFileName', e.target.value);
- if (params.isFileSystemApiSupported) {
- const files = await abstractFilesystemAccess.getSelectedZimFromCache(e.target.value)
- setLocalArchiveFromFileList(files);
- } else {
- if (webKitFileList === null) {
- const element = localStorage.getItem('zimFilenames').split('|').length === 1 ? 'archiveFiles' : 'folderSelect';
- // console.log(localStorage.getItem('zimFilenames').split('|'));
- document.getElementById(element).click();
- return;
- }
- const files = abstractFilesystemAccess.getSelectedZimFromWebkitList(webKitFileList, e.target.value)
- setLocalArchiveFromFileList(files);
- }
- });
+ if (isFireFoxOsNativeFileApiAvailable) {
+ useLegacyFilePicker();
+ return;
}
- if (params.isFileSystemApiSupported && !isFireFoxOsNativeFileApiAvailable) {
+
+ document.getElementById('archiveList').addEventListener('change', async function (e) {
+ // handle zim selection from dropdown if multiple files are loaded via webkitdirectory or filesystem api
+ localStorage.setItem('previousZimFileName', e.target.value);
+ if (params.isFileSystemApiSupported) {
+ const files = await abstractFilesystemAccess.getSelectedZimFromCache(e.target.value)
+ setLocalArchiveFromFileList(files);
+ } else {
+ if (webKitFileList === null) {
+ const element = localStorage.getItem('zimFilenames').split('|').length === 1 ? 'archiveFiles' : 'folderSelect';
+ // console.log(localStorage.getItem('zimFilenames').split('|'));
+ document.getElementById(element).click();
+ return;
+ }
+ const files = abstractFilesystemAccess.getSelectedZimFromWebkitList(webKitFileList, e.target.value)
+ setLocalArchiveFromFileList(files);
+ }
+ });
+
+ if (params.isFileSystemApiSupported) {
// Handles Folder selection when showDirectoryPicker is supported
document.getElementById('folderSelect').addEventListener('click', async function (e) {
e.preventDefault();
@@ -1345,7 +1349,7 @@ function displayFileSelect () {
if (previousZimFiles.length !== 0) setLocalArchiveFromFileList(previousZimFiles);
})
}
- if (params.isWebkitDirApiSupported && !params.isFileSystemApiSupported && !isFireFoxOsNativeFileApiAvailable) {
+ if (params.isWebkitDirApiSupported) {
// Handles Folder selection when webkitdirectory is supported but showDirectoryPicker is not
document.getElementById('folderSelect').addEventListener('change', async function (e) {
e.preventDefault();
@@ -1367,7 +1371,7 @@ function displayFileSelect () {
await abstractFilesystemAccess.updateZimDropdownOptions(filenames, previousZimFile.length !== 0 ? lastFilename : '');
})
}
- if (params.isFileSystemApiSupported && !isFireFoxOsNativeFileApiAvailable) {
+ if (params.isFileSystemApiSupported) {
// Handles File selection when showOpenFilePicker is supported and uses the filesystem api
document.getElementById('archiveFiles').addEventListener('click', async function (e) {
e.preventDefault();
@@ -1376,18 +1380,25 @@ function displayFileSelect () {
});
} else {
// Fallbacks to simple file input with multi file selection
- document.getElementById('archiveFiles').addEventListener('change', async function (e) {
- if (params.isWebkitDirApiSupported || params.isFileSystemApiSupported) {
- const activeFilename = e.target.files[0].name;
- localStorage.setItem('zimFilenames', [activeFilename].join('|'));
- await abstractFilesystemAccess.updateZimDropdownOptions([activeFilename], activeFilename);
- }
-
- setLocalArchiveFromFileSelect();
- });
+ useLegacyFilePicker();
}
}
+/**
+ * Adds a event listener to the file input to handle file selection (if no other file picker is supported)
+ */
+function useLegacyFilePicker () {
+ // Fallbacks to simple file input with multi file selection
+ document.getElementById('archiveFiles').addEventListener('change', async function (e) {
+ if (params.isWebkitDirApiSupported || params.isFileSystemApiSupported) {
+ const activeFilename = e.target.files[0].name;
+ localStorage.setItem('zimFilenames', [activeFilename].join('|'));
+ await abstractFilesystemAccess.updateZimDropdownOptions([activeFilename], activeFilename);
+ }
+ setLocalArchiveFromFileSelect();
+ });
+}
+
function handleGlobalDragover (e) {
e.preventDefault();
e.dataTransfer.dropEffect = 'link';
From b23f46dec71d5183cedf2f6c45aaf22726ae0c3d Mon Sep 17 00:00:00 2001
From: RG
Date: Wed, 8 Nov 2023 01:38:33 +0530
Subject: [PATCH 45/45] [FIX] safari file picker not showing
---
www/js/app.js | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/www/js/app.js b/www/js/app.js
index d44413639..9ed4aee50 100644
--- a/www/js/app.js
+++ b/www/js/app.js
@@ -1331,9 +1331,12 @@ function displayFileSelect () {
setLocalArchiveFromFileList(files);
} else {
if (webKitFileList === null) {
- const element = localStorage.getItem('zimFilenames').split('|').length === 1 ? 'archiveFiles' : 'folderSelect';
- // console.log(localStorage.getItem('zimFilenames').split('|'));
- document.getElementById(element).click();
+ const element = localStorage.getItem('zimFilenames').split('|').length === 1 ? 'archiveFiles' : 'archiveFolders';
+ if ('showPicker' in HTMLInputElement.prototype) {
+ document.getElementById(element).showPicker();
+ return;
+ }
+ document.getElementById(element).click()
return;
}
const files = abstractFilesystemAccess.getSelectedZimFromWebkitList(webKitFileList, e.target.value)