mirror of
https://github.com/kiwix/kiwix-js.git
synced 2025-08-03 11:16:38 -04:00
parent
2e51b082a7
commit
74a2b54142
@ -14,7 +14,7 @@ const config = {
|
||||
spec: true,
|
||||
useBuiltIns: 'usage',
|
||||
// "forceAllTransforms": true,
|
||||
corejs: '3.30.1'
|
||||
corejs: '3.30.2'
|
||||
// This is necessary for enabling CSS module support
|
||||
// "shippedProposals": true
|
||||
}
|
||||
|
@ -180,7 +180,7 @@ document.localeJson = {
|
||||
"dialog-serviceworker-defaultmodechange-title": "Change of default content injection mode",
|
||||
"dialog-serviceworker-defaultmodechange-message": "<p>We have switched you to ServiceWorker mode (this is now the default). It supports more types of ZIM archives and is much more robust.</p><p>If you experience problems with this mode, you can switch back to the (now deprecated) JQuery mode. In that case, please report the problems you experienced to us (see About section).</p>",
|
||||
"dialog-slideawaycheck-message": "This setting may not work correctly on Firefox OS. If you find that some ZIM links become unresponsive, try turning this setting off.",
|
||||
"dialog-unsupported-archivetype-message": "<p>You are attempting to open a Zimit-style archive, which is currently unsupported in this app.</p><p>A basic view of some static content is shown, but JavaScript and many hyperlinks are non-functional. There is more complete support for this kind of archive in the Kiwix JS PWA. Go to: <a href=\"https://pwa.kiwix.org\" target=\"_blank\">https://pwa.kiwix.org</a>.</p><p>Alternatively, you can use Kiwix Serve to serve this archive to your browser from localhost. Kiwix Serve is included with <a href=\"https://www.kiwix.org/applications/\" target=\"_blank\">Kiwix Desktop</a>.</p>",
|
||||
"dialog-unsupported-archivetype-message": "<p>You are attempting to open a Zimit-style archive, which is not unsupported by your browser version in ServiceWorker mode.</p><p>We have temporarily switched you to JQuery mode so you can view static content, but a lot of content is non-functional. If you can upgrade your browser, you will be able to access dynamic content.</p><p>Alternatively, you can try the Kiwix JS PWA, which supports some dynamic Zimit content in older browsers. Go to: <a href=\"https://pwa.kiwix.org\" target=\"_blank\">https://pwa.kiwix.org</a>.</p>",
|
||||
"dialog-unsupported-archivetype-title": "Unsupported archive type!",
|
||||
"dialog-warning": "Warning",
|
||||
"dialog-welcome-title": "Welcome",
|
||||
@ -213,7 +213,7 @@ document.localeJson = {
|
||||
"about-step1-para1": "Go to <a href=\"https://library.kiwix.org\" target=\"_blank\">https://library.kiwix.org <img src=\"img/Icon_External_Link.png\" /></a> (easy-to-use graphical interface, archive previews) or <a href=\"https://download.kiwix.org/zim/\" target=\"_blank\">https://download.kiwix.org/zim/ <img src=\"img/Icon_External_Link.png\" /></a> (traditional file list), and select the archives that interest you in your favourite language(s). If you don't have Internet access, you can also access/share content from a USB stick.",
|
||||
"about-step1-para2": "For a quick test, we recommend you start with a small archive containing all the <a href=\"https://download.kiwix.org/zim/wikipedia_en_climate_change_maxi.zim\" target=\"_blank\">Wikipedia articles about climate change <img src=\"img/Icon_External_Link.png\" /></a> in English.",
|
||||
"about-step1-para3": "Wikipedia archives with \"<b>mini</b>\" in the filename are the smallest, since they only contain the introduction of each article; \"<b>nopic</b>\" means no pictures; \"<b>maxi</b>\" means everything except video and audio. The largest files without any of these qualifiers are complete. Note that you can only see the filenames easily in the <a href=\"https://download.kiwix.org/zim/\" target=\"_blank\">traditional file list <img src=\"img/Icon_External_Link.png\" /></a>.",
|
||||
"about-step1-para4": "<b>Before downloading non-Wiki ZIM types, check if your browser supports archives with dynamic content</b>: look at the bottom of the Configuration page, in the API panel, and if you see \"ServiceWorker API available and registered\" in green, then the app can read all ZIM types (except Zimit: see <a href=\"#format\">ZIM archive format</a> for more details). Otherwise you should mostly stick to MediaWiki (Wikipedia, Wikivoyage, Wiktionary...) and Stack Exchange archives (see <a href=\"#modes\">modes</a> below for an explanation).",
|
||||
"about-step1-para4": "<b>Before downloading non-Wiki ZIM types, check if your browser supports archives with dynamic content</b>: look at the bottom of the Configuration page, in the API panel, and if you see \"ServiceWorker API available and registered\" in green, then the app can read all ZIM types. Otherwise you should mostly stick to MediaWiki (Wikipedia, Wikivoyage, Wiktionary...) and Stack Exchange archives (see <a href=\"#modes\">modes</a> below for an explanation).",
|
||||
"about-step2": "Step 2: Download or copy the content onto your device",
|
||||
"about-step2-para1": "Download your chosen content and/or copy it into a dedicated ZIM folder on your device's storage. Check the size before you download archives as many file sizes are very large (several Gigabytes). Themed versions such as WikiMed, or versions without images, are much smaller than full Wikipedia archives and can be readily downloaded in your browser.",
|
||||
"about-step2-para2": "If you don't have enough internal storage, consider using a modern microSD card, an external hard drive, or thumb drive. For larger archives (greater than 2GB) on older devices, it is possible that an archive might not be supported if your storage is formatted using a FAT filesystem (NB exFAT is fine). See <a href=\"#FAT\">Downloading large archives</a> below for a solution.",
|
||||
@ -234,12 +234,12 @@ document.localeJson = {
|
||||
"about-privacy-long-para1": "This application only reads the archive files that you explicitly select on your device together with files in its own package. Some ZIM archives contain active content (scripts) which may, in rare circumstances, attempt to contact external servers for incidental files such as fonts. We have a Content Security Policy which attempts to prevent this, but it is not possible to guarantee that all such requests will be blocked, for example if a ZIM were to contain intentionally malicious code that can break the sandbox. Please note that scripts will only run if you enable <a href=\"#modes\">ServiceWorker Mode</a> (the default) in Configuration. Nevertheless, if you believe your Internet access is insecure, or is being observed or censored, we recommend that you completely shut down your Internet (Data or WiFi) access before using the application.",
|
||||
"about-privacy-long-para2": "Additionally, if you obtained this app from a third-party site (including a browser extension store), then the site operator may track your usage of the app (e.g. download, install, uninstall, date and number of sessions) for the purpose of providing anonymous, aggregate usage statistics to developers. If this concerns you, you should check the relevant site's Privacy Policy for further information.",
|
||||
"about-zim-format-para1": "Offline archives use the <a href=\"https://www.openzim.org//wiki/OpenZIM\" target=\"_blank\">OpenZIM format <img src=\"img/Icon_External_Link.png\" /></a>, but note that this app has only been tested extensively on MediaWiki (Wikipedia, Wikivoyage, etc.), Gutenberg, Stackexchage and TED Talks archives. Video content is supported if your device, browser or OS understands the format. Some ZIMs use a proprietary dynamic UI which is only supported in ServiceWorker Mode (<a href=\"#modes\">see below</a>).",
|
||||
"about-zim-format-para2": "This application is not currently compatible with archives that are created by <b>Zimit</b> (<a href=\"https://youzim.it\" target=\"_blank\">https://youzim.it <img src=\"img/Icon_External_Link.png\" /></a>) using the Web Archive (WARC) format. We have plans to enable this format, but there are considerable technical challenges to overcome in order to display these archives with the fidelity that you expect from Kiwix -- see <a href=\"https://github.com/kiwix/kiwix-js/issues/1009\" target=\"_blank\">issue #1009 <img src=\"img/Icon_External_Link.png\" /></a>. <b>However, there is <i>experimental</i> support for Zimit archives in our Progressive Web App (PWA) available from <a href=\"https://pwa.kiwix.org\" target=\"_blank\">https://pwa.kiwix.org <img src=\"img/Icon_External_Link.png\" /></a></b>.",
|
||||
"about-zim-format-para2": "This application is now fully compatible with archives that are created by <b>Zimit</b> (<a href=\"https://youzim.it\" target=\"_blank\">https://youzim.it <img src=\"img/Icon_External_Link.png\" /></a>) using the Web Archive (WARC) format, so long as your browser supports ServiceWorker mode. If you can only use JQuery mode, we do our best to show you the static content of the requested article (no JavaScript can run in this mode). This will work for some basic static sites, but many will look broken. Search for content in the usual way.",
|
||||
"about-large-archives-para1": "Full versions of Wikipedia can be enormous: French, German and Spanish Wikipedia with images are each >25GB; full English Wikipedia with images is >90GB. For these very large archives you can find convenient BitTorrent links provided by <a href=\"https://library.kiwix.org\">library.kiwix.org <img src=\"img/Icon_External_Link.png\" /></a>, which may be more reliable than attempting to use your browser. You can use an open-source BitTorrent client such as <a href=\"https://www.qbittorrent.org/\" target=\"_blank\">QBittorrent <img src=\"img/Icon_External_Link.png\" /></a> with these links (install the software before clicking on the torrent link).",
|
||||
"about-large-archives-para2": "If you need to store a large ZIM archive on an older filesystem formatted as FAT16 or FAT32, you need to be aware of the file size limits of those systems (FAT16 < 2GiB; FAT32 < 4GiB). Most modern microSD cards, thumb drives or hard drives are formatted as exFAT or another modern FS such as NTFS, which do not have this issue. If your ZIM archive is larger than the FS limit, it is possible to split the archive into several 2GiB-1 or 4GiB-1 files (or smaller). You will need to give a file extension to each chunk in the right order following this pattern: <code>*.zimaa</code>, <code>*.zimab</code>, <code>*.zimac</code>, <code>...</code>, etc.). When you pick this archive in the app, be sure to pick <b>all</b> the chunks, or drag-and-drop them all into the app.",
|
||||
"about-large-archives-para3": "For some information on how to split archves, see <a href=\"https://github.com/kiwix/kiwix-js-windows/tree/main/AppPackages#download-a-zim-archive-all-platforms\" target=\"_blank\">Download a ZIM archive <img src=\"img/Icon_External_Link.png\" /></a>.",
|
||||
"about-contentinjection-para1": "Depending on your browser or framework, this app may be capable of running in different modes, which we call \"ServiceWorker Mode\" and \"JQuery Mode\". There is a toggle under Compatibility Settings in Configuration that allows you to select between these. Here is a technical explanation of what these modes do:",
|
||||
"about-contentinjection-para2": "<b>ServiceWorker Mode</b>: This is the default mode. As its name implies, it requires that the browser or framework be capable of installing a Service Worker, which is usually the case in modern browsers. It works by intercepting the browser or framework's Fetch calls (network requests) and supplying the requested content from the ZIM. In this mode, the content is read and supplied as-is from the archive to the browser. Dynamic content (e.g. JavaScript) and proprietary UIs are fully supported in this mode. However, Zimit archives are <b>not</b> currently supported due to a conflict between the Kiwix JS Service Worker and the WARC Service Worker (but you can use the PWA for experimental support, see above). This mode can feel initially a little slower than JQuery mode until commonly used assets are cached, but it soon equals JQuery mode in speed, at least in modern browsers. However, older browsers such as IE11 are incompatible with this mode, and the app must be running in a secure context (<code>https:</code>, <code>localhost</code>, or certain browser extensions). While this mode is not natively supported in Mozilla (Firefox) browser extensions, we provide a functional workaround by re-launching the extension as a Progressive Web App (PWA). Note that this mode cannot run with the <code>file:</code> protocol (but only IE11 and old Edge allow the app to run by launching <code>index.html</code> from the file system).",
|
||||
"about-contentinjection-para2": "<b>ServiceWorker Mode</b>: This is the default mode. As its name implies, it requires that the browser or framework be capable of installing a Service Worker, which is usually the case in modern browsers. It works by intercepting the browser or framework's Fetch calls (network requests) and supplying the requested content from the ZIM. In this mode, the content is read and supplied as-is from the archive to the browser. Dynamic content (e.g. JavaScript) and proprietary UIs are fully supported in this mode. This mode can feel initially a little slower than JQuery mode until commonly used assets are cached, but it soon equals JQuery mode in speed, at least in modern browsers. However, older browsers such as IE11 are incompatible with this mode, and the app must be running in a secure context (<code>https:</code>, <code>localhost</code>, or certain browser extensions). While this mode is not natively supported in Mozilla (Firefox) browser extensions, we provide a functional workaround by re-launching the extension as a Progressive Web App (PWA). Note that this mode cannot run with the <code>file:</code> protocol (but only IE11 and old Edge allow the app to run by launching <code>index.html</code> from the file system).",
|
||||
"about-contentinjection-para3": "<b>ServiceWorkerLocal Mode</b>: This is only available in Chromium extensions. It is the same as above, but due to very tight security restrictions in these extensions, no inline JavaScript or eval statements can run. Unfortunately, modern ZIMs may have such dynamic content, and so functionality may be restricted in this mode. Nevertheless, it is more secure: the content is sandboxed and no one-time server access is required to enable the Service Worker. You are protected from inline code execution at the cost of loss of some features in dynamic ZIMs.",
|
||||
"about-contentinjection-para4": "<b>JQuery Mode</b>: This mode is now deprecated (and does not in fact require JQuery). It is retained for compatibility with older browsers or frameworks that cannot run Service Workers. The mode has limitations which mean that only static content can be displayed, such as that found in Wikipedia / WikiMedia archives and (for now) Stackexchange. It is a way of inserting articles extracted from the ZIM into the DOM (browser document) by injecting the content into an iframe or tab. We then use native DOM methods, or sometimes JQuery, to parse the article's HTML and insert required assets (images, stylesheets, event listeners for hyperlinks, etc.). On old devices, DOM traversal can be slow, but it is compensated for because we do not extract or run JavaScript assets (which would be technically extremely complicated). As a result, for WikiMedia archives this mode is usually quite fast. On the downside, ZIMs that have a proprietary dynamic UI (such as Gutenberg or TED talks) are only partially supported in this mode: the UI does not work, but articles can be searched for and loaded from the search bar.",
|
||||
"about-feedback-para1": "We welcome constructive feedback and suggestions for improvement. We will consider feature requests, but it is even better if you can contribute to improving the app! The source code is on <a href=\"https://github.com/kiwix/kiwix-js\" target=\"_blank\">GitHub <img src=\"img/Icon_External_Link.png\" /></a>, and we welcome Pull Requests (but open an issue and discuss your proposal first).",
|
||||
|
@ -180,7 +180,7 @@ document.localeJson = {
|
||||
"dialog-serviceworker-defaultmodechange-title": "Cambio del modo de inyección de contenido predeterminado",
|
||||
"dialog-serviceworker-defaultmodechange-message": "<p>Le hemos cambiado al modo ServiceWorker (ahora es el modo predeterminado). Soporta más tipos de archivo ZIM y es mucho más robusto.</p><p>Si tiene problemas con este modo, puede volver al modo JQuery (ahora obsoleto). En tal caso, por favor, informe de los problemas que ha experimentado (véase la página Información).</p>",
|
||||
"dialog-slideawaycheck-message": "Esta configuración puede no funcionar correctamente en Firefox OS. Si encuentra que algunos enlaces ZIM no responden, se recomienda desactivar esta configuración.",
|
||||
"dialog-unsupported-archivetype-message": "<p>Está intentando abrir un archivo de estilo Zimit, que actualmente no es compatible con esta aplicación.</p><p>Se muestra una vista básica del contenido estático, pero JavaScript y muchos hipervínculos no son funcionales. Existe un soporte más completo para este tipo de archivo en la PWA de Kiwix JS. Vaya a: <a href=\"https://pwa.kiwix.org\" target=\"_blank\">https://pwa.kiwix.org</a>.</p><p>Alternativamente, puede usar Kiwix Serve para servir este archivo a su navegador desde localhost. Kiwix Serve está incluido con <a href=\"https://www.kiwix.org/applications/\" target=\"_blank\">Kiwix Desktop</a>.</p>",
|
||||
"dialog-unsupported-archivetype-message": "<p>Está intentando abrir un archivo Zimit, lo cual no es compatible con su versión de navegador en modo ServiceWorker.</p><p>Le hemos cambiado temporalmente al modo JQuery para que pueda ver el contenido estático, pero gran parte del contenido no es funcional. Si puede actualizar su navegador, podrá acceder al contenido dinámico.</p><p>Alternativamente, puede probar la PWA de Kiwix JS, que soporta algunos contenidos Zimit dinámicos en navegadores antiguos. Vaya a: <a href=\"https://pwa.kiwix.org\" target=\"_blank\">https://pwa.kiwix.org</a>.</p>",
|
||||
"dialog-unsupported-archivetype-title": "¡Tipo de archivo no compatible!",
|
||||
"dialog-warning": "¡Aviso!",
|
||||
"dialog-welcome-title": "Bienvenide",
|
||||
@ -213,7 +213,7 @@ document.localeJson = {
|
||||
"about-step1-para1": "Vaya a <a href=\"https://library.kiwix.org\" target=\"_blank\">https://library.kiwix.org <img src=\"img/Icon_External_Link.png\" /></a> (interfaz gráfica fácil de usar, vistas previas de archivos) o <a href=\"https://download.kiwix.org/zim/\" target=\"_blank\">https://download.kiwix.org/zim/ <img src=\"img/Icon_External_Link.png\" /></a> (lista tradicional de archivos), y seleccione los archivos que le interesen en su(s) idioma(s) favorito(s). Si no tiene acceso a Internet, también puede acceder/compartir contenido desde una memoria USB.",
|
||||
"about-step1-para2": "Para una prueba rápida, recomendamos que empiece con un pequeño archivo que contiene los <a href=\"https://download.kiwix.org/zim/wikipedia_es_climate_change_maxi.zim\" target=\"_blank\">artículos de Wikipedia sobre el cambio climático <img src=\"img/Icon_External_Link.png\" /></a> en español.",
|
||||
"about-step1-para3": "Los archivos ZIM de Wikipedia con \"<b>mini</b>\" en el nombre son los más pequeños, ya que sólo contienen la introducción de cada artículo; \"<b>nopic</b>\" significa un archivo sin imágenes; \"<b>maxi</b>\" significa que contiene todo excepto video y audio. Los archivos más grandes sin ninguno de estos calificadores son completos. Tenga en cuenta que sólo puede ver los nombres de los archivos fácilmente en la <a href=\"https://download.kiwix.org/zim/\" target=\"_blank\">lista tradicional de archivos <img src=\"img/Icon_External_Link.png\" /></a>.",
|
||||
"about-step1-para4": "<b>Antes de descargar los archivos ZIM que no son de tipo \"Wiki\", compruebe si su navegador soporta los archivos con contenido dinámico</b>: mire en la parte inferior de la página de Configuración, en el panel API, y si ve \"API ServiceWorker: disponible, servicio registrado\" en verde, entonces la aplicación puede leer todos los tipos de ZIM (excepto Zimit: véase <a href=\"#format\">formato de archivo ZIM</a> para más información). De lo contrario, debería limitarse principalmente a los archivos de MediaWiki (Wikipedia, Wikiviajes, Wikcionario...) y Stack Exchange (véase <a href=\"#modes\">modos</a> para una explicación).",
|
||||
"about-step1-para4": "<b>Antes de descargar los archivos ZIM que no son de tipo \"Wiki\", compruebe si su navegador soporta los archivos con contenido dinámico</b>: mire en la parte inferior de la página de Configuración, en el panel API, y si ve \"API ServiceWorker: disponible, servicio registrado\" en verde, entonces la aplicación puede leer todos los tipos de ZIM. De lo contrario, debería limitarse principalmente a los archivos de MediaWiki (Wikipedia, Wikiviajes, Wikcionario...) y Stack Exchange (véase <a href=\"#modes\">modos</a> para una explicación).",
|
||||
"about-step2": "Paso 2: Descargue o copie el contenido en su dispositivo",
|
||||
"about-step2-para1": "Descargue el contenido elegido y/o cópielo en una carpeta ZIM dedicada en el almacenamiento de su dispositivo. Compruebe el tamaño antes de descargar los archivos ya que muchos tamaños de archivo son muy grandes (varios Gigabytes). Las versiones temáticas como WikiMed, o las versiones sin imágenes, son mucho más pequeñas que los archivos ZIM completos y se pueden descargar fácilmente en su navegador.",
|
||||
"about-step2-para2": "Si no tiene suficiente almacenamiento interno, considere la posibilidad de utilizar una tarjeta microSD, un disco duro externo, o una memoria USB moderna. Para archivos más grandes (superiores a 2 GB) en dispositivos antiguos, es posible que un archivo no sea compatible si su almacenamiento está formateado con un sistema de archivos FAT (NB exFAT está bien). Vea <a href=\"#FAT\">Descarga de archivos grandes</a> a continuación para una solución.",
|
||||
@ -234,12 +234,12 @@ document.localeJson = {
|
||||
"about-privacy-long-para1": "Esta aplicación sólo lee los archivos que usted selecciona explícitamente en su dispositivo junto con los archivos del propio paquete del software. Algunos archivos ZIM contienen contenido activo (scripts) que pueden, en circunstancias excepcionales, intentar ponerse en contacto con servidores externos para obtener archivos incidentales como fuentes. Tenemos una Política de Seguridad de Contenido que intenta evitar esto, pero no es posible garantizar que todas estas solicitudes se bloqueen, por ejemplo, si un ZIM contuviera código malicioso intencional que pudiera romper el sandbox. Tenga en cuenta que los scripts sólo se ejecutarán si habilita el <a href=\"#modes\">Modo ServiceWorker</a> (predeterminado) en Configuración. Sin embargo, si cree que su acceso a Internet no es seguro, o está siendo observado o censurado, le recomendamos que cierre completamente su acceso a Internet (datos o WiFi) antes de utilizar la aplicación.",
|
||||
"about-privacy-long-para2": "Además, si obtuvo esta aplicación de un sitio de terceros (incluida una tienda de extensiones de navegador), entonces el operador del sitio puede rastrear su uso de la aplicación (por ejemplo, descarga, instalación, desinstalación, fecha y número de sesiones) con el fin de proporcionar estadísticas de uso anónimas y agregadas a los desarrolladores. Si esto le preocupa, debe consultar la Política de Privacidad del sitio correspondiente para obtener más información.",
|
||||
"about-zim-format-para1": "Los archivos sin conexión utilizan el <a href=\"https://www.openzim.org//wiki/\" target=\"_blank\">formato OpenZIM <img src=\"img/Icon_External_Link.png\" /></a>, pero tenga en cuenta que esta aplicación sólo se ha probado exhaustivamente con los archivos MediaWiki (Wikipedia, Wikivoyage, etc.), Gutenberg, Stackexchage y TED Talks. El contenido de video es compatible si su dispositivo, navegador o sistema operativo entiende el formato. Algunos ZIM utilizan una interfaz de usuario dinámica que sólo es compatible con el <a href=\"#modes\">Modo ServiceWorker</a> (<a href=\"#modes\">ver más abajo</a>).",
|
||||
"about-zim-format-para2": "Esta aplicación no es compatible actualmente con los archivos creados por <b>Zimit</b> (<a href=\"https://youzim.it\" target=\"_blank\">https://youzim.it <img src=\"img/Icon_External_Link.png\" /></a>) utilizando el formato Web Archive (WARC). Tenemos planes para habilitar este formato, pero hay varios desafíos técnicos que superar para mostrar estos archivos con la fidelidad que usted espera de Kiwix -- ver <a href=\"https://github.com/kiwix/kiwix-js/issues/1009\" target=\"_blank\">asunto #1009 <img src=\"img/Icon_External_Link.png\" /></a>. <b>Sin embargo, hay soporte <i>experimental</i> para los archivos Zimit en nuestra Progressive Web App (PWA) disponible en <a href=\"https://pwa.kiwix.org\" target=\"_blank\">https://pwa.kiwix.org <img src=\"img/Icon_External_Link.png\" /></a></b>.",
|
||||
"about-zim-format-para2": "Esta aplicación es ahora totalmente compatible con los archivos creados por <b>Zimit</b> (<a href=\"https://youzim.it\" target=\"_blank\">https://youzim.it <img src=\"img/Icon_External_Link.png\" /></a>) utilizando el formato Archivo de Web (WARC), siempre que su navegador soporte el modo ServiceWorker. Si sólo puede utilizar el modo JQuery, hacemos todo lo posible para mostrarle el contenido estático del artículo solicitado (no se puede ejecutar JavaScript en este modo). Esto funcionará para algunos sitios estáticos básicos, pero muchos se verán rotos. Busque contenido de la forma habitual.",
|
||||
"about-large-archives-para1": "Versiones completas de Wikipedia pueden ser enormes: la Wikipedia francesa, alemana y española con imágenes son cada una >25GB; la Wikipedia inglesa completa con imágenes es >90GB. Para estos archivos muy grandes puede encontrar enlaces convenientes de BitTorrent proporcionados por <a href=\"https://library.kiwix.org\">library.kiwix.org <img src=\"img/Icon_External_Link.png\" /></a>, que pueden ser más fiables que intentar utilizar su navegador para bajar estos archivos. Puede utilizar un cliente BitTorrent de código abierto como <a href=\"https://www.qbittorrent.org/\" target=\"_blank\">QBittorrent <img src=\"img/Icon_External_Link.png\" /></a> con estos enlaces (instale el software antes de hacer clic en el enlace torrent).",
|
||||
"about-large-archives-para2": "Si necesita almacenar un archivo ZIM muy grande en un sistema de archivos más antiguo formateado como FAT16 o FAT32, debe ser consciente de los límites de tamaño de archivo de esos sistemas (FAT16 < 2GiB; FAT32 < 4GiB). La mayoría de las tarjetas microSD, unidades de memoria USB o discos duros modernos están formateados como exFAT u otro sistema de archivos moderno como NTFS, que no tienen este problema. Si su archivo ZIM es más grande que el límite del sistema de archivos, es posible dividir el archivo en varios archivos de 2GiB-1 o 4GiB-1 (o más pequeños). Deberá dar una extensión de archivo a cada fragmento en el orden correcto siguiendo este patrón: <code>*.zimaa</code>, <code>*.zimab</code>, <code>*.zimac</code>, <code>...</code>, etc.). Cuando elija este archivo en la aplicación, asegúrese de elegir <b>todos</b> los fragmentos, o arrástrelos todos a la aplicación.",
|
||||
"about-large-archives-para3": "Para obtener información sobre cómo dividir los archivos, consulte <a href=\"https://github.com/kiwix/kiwix-js-windows/tree/main/AppPackages#download-a-zim-archive-all-platforms\" target=\"_blank\">Bajar un archivo ZIM <img src=\"img/Icon_External_Link.png\" /></a> (en inglés).",
|
||||
"about-contentinjection-para1": "Dependiendo de su navegador o framework, esta aplicación puede ser capaz de ejecutarse en diferentes modos, que llamamos \"Modo ServiceWorker\" y \"Modo JQuery\". Hay una opción en Configuración que le permite seleccionar entre estos modos. A continuación damos una explicación técnica de lo que hacen estos modos:",
|
||||
"about-contentinjection-para2": "<b>Modo ServiceWorker</b>: Este es el modo predeterminado. Como su nombre indica, requiere que el navegador o el framework sea capaz de instalar un Service Worker, lo cual suele ser el caso en los navegadores modernos. Funciona interceptando las llamadas Fetch (solicitudes de red) del navegador o del framework y suministrando el contenido solicitado desde el ZIM. En este modo, el contenido se lee y se suministra tal cual desde el archivo al navegador. El contenido dinámico (por ejemplo, JavaScript) y las interfaces de usuario propietarias son totalmente compatibles con este modo. Sin embargo, los archivos Zimit no son compatibles actualmente debido a un conflicto entre el Service Worker de Kiwix JS y el Service Worker WARC (pero puede utilizar la PWA para obtener soporte experimental, véase más arriba). Este modo puede parecer inicialmente un poco más lento que el modo JQuery hasta que se almacenen en caché los activos de uso común, pero pronto iguala al modo JQuery en velocidad, al menos en los navegadores modernos. Sin embargo, los navegadores antiguos como IE11 son incompatibles con este modo, y la aplicación debe estar funcionando en un contexto seguro (<code>https:</code>, <code>localhost</code>, o ciertas extensiones de navegador). Aunque este modo no es compatible de forma nativa con las extensiones de Mozilla (Firefox), proporcionamos una solución funcional al volver a lanzar la extensión como una Progressive Web App (PWA). Tenga en cuenta que este modo no se puede ejecutar con el protocolo <code>file:</code> (pero sólo IE11 y el antiguo Edge permiten que la aplicación se ejecute al lanzar <code>index.html</code> desde el sistema de archivos).",
|
||||
"about-contentinjection-para2": "<b>Modo ServiceWorker</b>: Este es el modo predeterminado. Como su nombre indica, requiere que el navegador o el framework sea capaz de instalar un Service Worker, lo cual suele ser el caso en los navegadores modernos. Funciona interceptando las llamadas Fetch (solicitudes de red) del navegador o del framework y suministrando el contenido solicitado desde el ZIM. En este modo, el contenido se lee y se suministra tal cual desde el archivo al navegador. El contenido dinámico (por ejemplo, JavaScript) y las interfaces de usuario propietarias son totalmente compatibles con este modo. Este modo puede parecer inicialmente un poco más lento que el modo JQuery hasta que se almacenen en caché los activos de uso común, pero pronto iguala al modo JQuery en velocidad, al menos en los navegadores modernos. Sin embargo, los navegadores antiguos como IE11 son incompatibles con este modo, y la aplicación debe estar funcionando en un contexto seguro (<code>https:</code>, <code>localhost</code>, o ciertas extensiones de navegador). Aunque este modo no es compatible de forma nativa con las extensiones de Mozilla (Firefox), proporcionamos una solución funcional al volver a lanzar la extensión como una Progressive Web App (PWA). Tenga en cuenta que este modo no se puede ejecutar con el protocolo <code>file:</code> (pero sólo IE11 y el antiguo Edge permiten que la aplicación se ejecute al lanzar <code>index.html</code> desde el sistema de archivos).",
|
||||
"about-contentinjection-para3": "<b>Modo ServiceWorkerLocal</b>: Este modo sólo está disponible en las extensiones de Chromium. Es lo mismo que arriba, pero debido a las restricciones de seguridad muy estrictas en estas extensiones, no se puede ejecutar ningún JavaScript en línea o declaraciones eval. Desafortunadamente, los ZIM modernos pueden tener tal contenido dinámico, y por lo tanto la funcionalidad puede estar restringida en este modo. Sin embargo, es más seguro: el contenido está en un sandbox y no se requiere ningún acceso al servidor para habilitar el Service Worker. Está protegido de la ejecución de código en línea a costa de la pérdida de algunas características en los ZIM dinámicos.",
|
||||
"about-contentinjection-para4": "<b>Modo JQuery</b>: Este modo ahora está obsoleto (y de hecho no requiere JQuery). Se mantiene para la compatibilidad con los navegadores antiguos o los frameworks que no pueden ejecutar Service Workers. El modo tiene limitaciones que significan que sólo se puede mostrar contenido estático, como el que se encuentra en los archivos de Wikipedia / WikiMedia y (por ahora) Stackexchange. Es una forma de insertar artículos extraídos del ZIM en el DOM (documento del navegador) inyectando el contenido en un iframe o pestaña. A continuación, utilizamos métodos DOM nativos, o a veces JQuery, para analizar el HTML del artículo e insertar los activos necesarios (imágenes, hojas de estilo, escuchadores de eventos para los hipervínculos, etc.). En los dispositivos antiguos, el recorrido del DOM puede ser lento, pero se compensa porque no extraemos ni ejecutamos activos JavaScript (lo que sería técnicamente extremadamente complicado). Como resultado, para los archivos WikiMedia este modo suele ser bastante rápido. Por otro lado, los ZIM que tienen una interfaz de usuario dinámica propietaria (como Gutenberg o TED Talks) sólo se admiten parcialmente en este modo: la interfaz de usuario no funciona, pero los artículos se pueden buscar y cargar desde la barra de búsqueda.",
|
||||
"about-feedback-para1": "Agradecemos los comentarios constructivos y las sugerencias de mejora. Consideraremos las solicitudes de nuevas funciones, pero es aún mejor si usted mism@ puede contribuir a mejorar la aplicación. El código fuente está en <a href=\"https://github.com/kiwix/kiwix-js\" target=\"_blank\">GitHub <img src=\"img/Icon_External_Link.png\" /></a>, y agradecemos las Pull Requests (pero abra un issue y discuta su propuesta primero).",
|
||||
|
@ -180,8 +180,8 @@ document.localeJson = {
|
||||
"dialog-serviceworker-defaultmodechange-title": "Changement du mode d'injection de contenu par défaut",
|
||||
"dialog-serviceworker-defaultmodechange-message": "<p>Nous vous avons basculé en mode ServiceWorker (c'est maintenant le mode par défaut). Il prend en charge plus de types de fichiers ZIM et est beaucoup plus robuste.</p><p>Si vous rencontrez des problèmes avec ce mode, vous pouvez revenir au mode JQuery (maintenant obsolète). Dans ce cas, veuillez signaler les problèmes que vous avez rencontrés (voir la page Informations).</p>",
|
||||
"dialog-slideawaycheck-message": "Ce paramètre peut ne pas fonctionner correctement chez Firefox OS. Si vous constatez que certains liens ZIM deviennent inactifs, veuillez désactiver ce paramètre.",
|
||||
"dialog-unsupported-archivetype-message": "<p>Vous essayez d'ouvrir un fichier de style Zimit, qui n'est actuellement pas pris en charge par cette application.</p><p>Une vue élémentaire du contenu statique est affichée, mais JavaScript et de nombreux liens hypertextes ne sont pas fonctionnels. La PWA Kiwix JS offre un support plus complet pour ce type d'archives. Allez sur : <a href=\"https://pwa.kiwix.org\" target=\"_blank\">https://pwa.kiwix.org</a>.</p><p>Alternativement, vous pouvez utiliser Kiwix Serve pour servir ce fichier à votre navigateur à partir de localhost. Kiwix Serve est inclus dans <a href=\"https://www.kiwix.org/applications/\" target=\"_blank\">Kiwix Desktop</a>.</p>",
|
||||
"dialog-unsupported-archivetype-title": "Type de fichier non pris en charge !",
|
||||
"dialog-unsupported-archivetype-message": "<p>Vous essayez d'ouvrir une archive de type Zimit, ce qui n'est pas pris en charge par votre version de navigateur en mode ServiceWorker.</p><p>Nous vous avons temporairement basculé en mode JQuery afin que vous puissiez afficher le contenu statique, mais une grande partie du contenu n'est pas fonctionnelle. Si vous pouvez mettre à jour votre navigateur, vous pourrez accéder au contenu dynamique.</p><p>Vous pouvez également essayer la PWA Kiwix JS, qui prend en charge certains contenus Zimit dynamiques dans les anciens navigateurs. Allez sur : <a href=\"https://pwa.kiwix.org\" target=\"_blank\">https://pwa.kiwix.org</a>.</p>",
|
||||
"dialog-unsupported-archivetype-title": "Type d'archive non pris en charge !",
|
||||
"dialog-warning": "Avertissement",
|
||||
"dialog-welcome-title": "Bienvenue",
|
||||
"dialog-welcome-message": "Bienvenue dans Kiwix ! Cette application a besoin d'au moins un fichier ZIM sur votre carte SD (ou stockage interne). Veuillez en télécharger un et le placer sur l'appareil (voir la section Informations). Veuillez également vérifier que votre appareil n'est pas connecté à un ordinateur via le stockage USB (ce qui bloque souvent le contenu de la carte SD).",
|
||||
@ -213,7 +213,7 @@ document.localeJson = {
|
||||
"about-step1-para1": "Allez sur <a href=\"https://library.kiwix.org\" target=\"_blank\">https://library.kiwix.org <img src=\"img/Icon_External_Link.png\" /></a> (interface graphique facile à utiliser, aperçus d'archives) ou sur <a href=\"https://download.kiwix.org/zim/\" target=\"_blank\">https://download.kiwix.org/zim/ <img src=\"img/Icon_External_Link.png\" /></a> (liste de fichiers traditionnelle), et sélectionnez les archives qui vous intéressent dans votre (vos) langue(s) préférée(s). Si vous n'avez pas accès à Internet, vous pouvez également accéder/partager du contenu à partir d'une clé USB.",
|
||||
"about-step1-para2": "Pour un test rapide, nous vous recommandons de commencer par une petite archive contenant les <a href=\"https://download.kiwix.org/zim/wikipedia_fr_climate_change_maxi.zim\" target=\"_blank\">articles Wikipédia sur le changement climatique <img src=\"img/Icon_External_Link.png\" /></a> en français.",
|
||||
"about-step1-para3": "Les archives de Wikipédia dont le nom de fichier contient « <b>mini</b> » sont les plus petites, car elles ne contiennent que l'introduction de chaque article ; « <b>nopic</b> » signifie que les images ont été retirées ; « <b>maxi</b> » signifie que l'archive contient tout sauf la vidéo et l'audio. Les plus grands fichiers sans aucun de ces qualificatifs sont la version complète. Notez que vous ne pouvez voir les noms de fichiers facilement que dans la <a href=\"https://download.kiwix.org/zim/\" target=\"_blank\">liste de fichiers traditionnelle <img src=\"img/Icon_External_Link.png\" /></a>.",
|
||||
"about-step1-para4": "<b>Avant de télécharger les fichiers ZIM qui ne sont pas de type \"Wiki\", vérifiez si votre navigateur prend en charge les archives avec du contenu dynamique</b> : regardez en bas de la page de Configuration, dans le panneau API, et si vous voyez « API ServiceWorker : disponible, service enregistré » en vert, alors l'application peut lire tous les types de fichiers ZIM (sauf Zimit : voir <a href=\"#format\">format d'archive ZIM</a> pour plus d'informations). Sinon, vous devriez vous en tenir aux archives MediaWiki (Wikipédia, Wikivoyage, Wiktionnaire...) et Stack Exchange (voir <a href=\"#modes\">modes</a> ci-dessous pour une explication).",
|
||||
"about-step1-para4": "<b>Avant de télécharger les fichiers ZIM qui ne sont pas de type \"Wiki\", vérifiez si votre navigateur prend en charge les archives avec du contenu dynamique</b> : regardez en bas de la page de Configuration, dans le panneau API, et si vous voyez « API ServiceWorker : disponible, service enregistré » en vert, alors l'application peut lire tous les types de fichiers ZIM. Sinon, vous devriez vous en tenir aux archives MediaWiki (Wikipédia, Wikivoyage, Wiktionnaire...) et Stack Exchange (voir <a href=\"#modes\">modes</a> ci-dessous pour une explication).",
|
||||
"about-step2": "Étape 2 : Téléchargez ou copiez le contenu sur votre appareil",
|
||||
"about-step2-para1": "Téléchargez le contenu choisi et/ou copiez-le dans un dossier ZIM dédié sur le stockage de votre appareil. Vérifiez la taille avant de télécharger des archives car de nombreux fichiers sont très volumineux (plusieurs gigaoctets). Les versions thématiques telles que WikiMed, ou les versions sans images, sont beaucoup plus petites que les archives complètes de Wikipédia et peuvent être facilement téléchargées dans votre navigateur.",
|
||||
"about-step2-para2": "Si vous n'avez pas assez d'espace de stockage interne, envisagez d'utiliser une carte microSD moderne, un disque dur externe ou une clé USB. Pour les archives plus volumineuses (supérieures à 2 Go) sur les anciens appareils, il est possible qu'une archive ne soit pas prise en charge si votre stockage est formaté avec un système de fichiers FAT (NB exFAT est correct). Voir <a href=\"#FAT\">Téléchargement de grandes archives</a> ci-dessous pour une solution.",
|
||||
@ -234,12 +234,12 @@ document.localeJson = {
|
||||
"about-privacy-long-para1": "Cette application ne lit que les fichiers que vous sélectionnez explicitement sur votre appareil, ainsi que les fichiers du propre paquet logiciel. Quelques fichiers ZIM contiennent du contenu actif (scripts) qui peuvent, dans des circonstances exceptionnelles, tenter de contacter des serveurs externes pour obtenir des fichiers accessoires. Nous avons une politique de sécurité du contenu qui tente d'éviter cela, mais il n'est pas possible de garantir que toutes ces demandes seront bloquées, par exemple si un ZIM contenait du code malveillant intentionnel qui pourrait contourner le bac à sable. Veuillez noter que les scripts ne s'exécuteront que si vous activez le <a href=\"#modes\">mode ServiceWorker</a> (réglé par défaut) dans la Configuration. Cependant, si vous pensez que votre accès à Internet n'est pas sûr, ou si vous êtes surveillé ou censuré, nous vous recommandons de fermer complètement votre accès à Internet (données ou WiFi) avant d'utiliser l'application.",
|
||||
"about-privacy-long-para2": "En outre, si vous avez obtenu cette application à partir d'un site tiers (y compris la boutique d'extensions du navigateur), l'opérateur du site peut suivre votre utilisation de l'application (par exemple, téléchargement, installation, désinstallation, date et nombre de sessions) afin de fournir des statistiques d'utilisation anonymes et agrégées aux développeurs. Si cela vous préoccupe, vous devez consulter la politique de confidentialité du site correspondant pour plus d'informations.",
|
||||
"about-zim-format-para1": "Les fichiers hors ligne utilisent le <a href=\"https://www.openzim.org//wiki/\" target=\"_blank\">format OpenZIM <img src=\"img/Icon_External_Link.png\" /></a>, mais veuillez noter que cette application n'a été testée qu'avec les fichiers de MediaWiki (Wikipédia, Wikivoyage, etc.), Gutenberg, Stackexchage et TED Talks. Le contenu vidéo est pris en charge si votre appareil, votre navigateur ou votre système d'exploitation comprend le format. Certains ZIM utilisent une interface utilisateur dynamique qui n'est prise en charge que dans le <a href=\"#modes\">mode ServiceWorker</a> (<a href=\"#modes\">voir ci-dessous</a>).",
|
||||
"about-zim-format-para2": "Cette application ne prend pas en charge actuellement les fichiers créés par <b>Zimit</b> (<a href=\"https://youzim.it\" target=\"_blank\">https://youzim.it <img src=\"img/Icon_External_Link.png\" /></a>) utilisant le format Web Archive (WARC). Nous avons l'intention de prendre en charge ce format, mais il y a plusieurs défis techniques à relever pour afficher ces fichiers avec la fidélité que vous attendez de Kiwix — voir <a href=\"https://github.com/kiwix/kiwix-js/issues/1009\" target=\"_blank\">ticket #1009 <img src=\"img/Icon_External_Link.png\" /></a>. <b>Cependant, il existe une prise en charge <i>expérimentale</i> des fichiers Zimit dans notre Progressive Web App (PWA) disponible au téléchargement sur <a href=\"https://pwa.kiwix.org\" target=\"_blank\">https://pwa.kiwix.org <img src=\"img/Icon_External_Link.png\" /></a></b>.",
|
||||
"about-zim-format-para2": "Cette application est désormais entièrement compatible avec les archives créées par <b>Zimit</b> (<a href=\"https://youzim.it\" target=\"_blank\">https://youzim.it <img src=\"img/Icon_External_Link.png\" /></a>) en utilisant le format Web Archive (WARC), tant que votre navigateur prend en charge le mode ServiceWorker. Si vous ne pouvez utiliser que le mode JQuery, nous faisons de notre mieux pour vous montrer le contenu statique de l'article demandé (aucun JavaScript ne peut s'exécuter dans ce mode). Cela fonctionnera pour certains sites statiques de base, mais beaucoup sembleront cassés. Recherchez le contenu de la manière habituelle.",
|
||||
"about-large-archives-para1": "Les versions complètes de Wikipédia peuvent être énormes : les versions française, allemande et espagnole de Wikipédia avec des images sont chacune >25 Go ; la version anglaise complète de Wikipédia avec des images est >90 Go. Pour ces très gros fichiers, vous pouvez trouver des liens BitTorrent pratiques fournis par <a href=\"https://library.kiwix.org\">library.kiwix.org <img src=\"img/Icon_External_Link.png\" /></a>, qui peuvent être plus fiables que d'essayer d'utiliser votre navigateur pour télécharger ces fichiers. Vous pouvez utiliser un client BitTorrent open source comme <a href=\"https://www.qbittorrent.org/\" target=\"_blank\">QBittorrent <img src=\"img/Icon_External_Link.png\" /></a> avec ces liens (installez le logiciel avant de cliquer sur le lien torrent).",
|
||||
"about-large-archives-para2": "Si vous avez besoin de stocker un très gros fichier ZIM sur un système de fichiers plus ancien formaté comme FAT16 ou FAT32, vous devez être conscient des limites de taille de fichier de ces systèmes (FAT16 < 2GiB ; FAT32 < 4GiB). La plupart des cartes microSD, des clés USB ou des disques durs modernes sont formatés en exFAT ou dans un autre système de fichiers moderne comme NTFS, qui n'ont pas ce problème. Si votre fichier ZIM est plus grand que la limite du système de fichiers, vous pouvez le diviser en plusieurs fichiers de 2GiB-1 ou 4GiB-1 (ou plus petits). Vous devrez donner une extension de fichier à chaque fragment dans l'ordre correct en suivant ce modèle : <code>*.zimaa</code>, <code>*.zimab</code>, <code>*.zimac</code>, <code>...</code>, etc. Lorsque vous sélectionnez ce fichier dans l'application, assurez-vous de sélectionner <b>tous</b> les fragments, ou faites-les tous glisser dans l'application.",
|
||||
"about-large-archives-para3": "Pour plus d'informations sur la façon de diviser les fichiers, voir <a href=\"https://github.com/kiwix/kiwix-js-windows/tree/main/AppPackages#download-a-zim-archive-all-platforms\" target=\"_blank\">Télécharger un fichier ZIM <img src=\"img/Icon_External_Link.png\" /></a> (en anglais).",
|
||||
"about-contentinjection-para1": "En fonction de votre navigateur ou de votre framework, cette application peut être capable de fonctionner dans différents modes, que nous appelons « Mode ServiceWorker » et « Mode JQuery ». Il y a une option dans la Configuration qui vous permet de sélectionner entre ces modes. Nous donnons ci-dessous une explication technique de ce que font ces modes :",
|
||||
"about-contentinjection-para2": "<b>Mode ServiceWorker</b> : C’est le mode par défaut. Comme son nom l'indique, il nécessite que le navigateur ou le framework soit capable d'installer un Service Worker, ce qui est généralement le cas dans les navigateurs modernes. Il fonctionne en interceptant les appels Fetch (requêtes réseau) du navigateur ou du framework et en fournissant le contenu demandé depuis le ZIM. Dans ce mode, le contenu est lu et fourni tel quel depuis le fichier au navigateur. Le contenu dynamique (par exemple, JavaScript) et les interfaces utilisateur propriétaires sont entièrement compatibles avec ce mode. Cependant, les fichiers Zimit ne sont pas actuellement pris en charge en raison d'un conflit entre le Service Worker de Kiwix JS et le Service Worker WARC (mais vous pouvez utiliser la PWA pour obtenir un support expérimental, voir ci-dessus). Ce mode peut sembler initialement un peu plus lent que le mode JQuery jusqu'à ce que les ressources couramment utilisées soient mises en cache, mais il égale rapidement le mode JQuery en vitesse, du moins dans les navigateurs modernes. Cependant, les navigateurs anciens comme IE11 sont incompatibles avec ce mode, et l'application doit fonctionner dans un contexte sécurisé (<code>https:</code>, <code>localhost</code>, ou certaines extensions de navigateur). Bien que ce mode ne soit pas compatible nativement avec les extensions Mozilla (Firefox), nous fournissons une solution fonctionnelle en relançant l'extension comme une Progressive Web App (PWA). Notez que ce mode ne peut pas être exécuté avec le protocole <code>file:</code> (mais seul IE11 et l'ancien Edge permettent à l'application de fonctionner en lançant <code>index.html</code> depuis le système de fichiers).",
|
||||
"about-contentinjection-para2": "<b>Mode ServiceWorker</b> : C’est le mode par défaut. Comme son nom l'indique, il nécessite que le navigateur ou le framework soit capable d'installer un Service Worker, ce qui est généralement le cas dans les navigateurs modernes. Il fonctionne en interceptant les appels Fetch (requêtes réseau) du navigateur ou du framework et en fournissant le contenu demandé depuis le ZIM. Dans ce mode, le contenu est lu et fourni tel quel depuis le fichier au navigateur. Le contenu dynamique (par exemple, JavaScript) et les interfaces utilisateur propriétaires sont entièrement compatibles avec ce mode. Ce mode peut sembler initialement un peu plus lent que le mode JQuery jusqu'à ce que les ressources couramment utilisées soient mises en cache, mais il égale rapidement le mode JQuery en vitesse, du moins dans les navigateurs modernes. Cependant, les navigateurs anciens comme IE11 sont incompatibles avec ce mode, et l'application doit fonctionner dans un contexte sécurisé (<code>https:</code>, <code>localhost</code>, ou certaines extensions de navigateur). Bien que ce mode ne soit pas compatible nativement avec les extensions Mozilla (Firefox), nous fournissons une solution fonctionnelle en relançant l'extension comme une Progressive Web App (PWA). Notez que ce mode ne peut pas être exécuté avec le protocole <code>file:</code> (mais seul IE11 et l'ancien Edge permettent à l'application de fonctionner en lançant <code>index.html</code> depuis le système de fichiers).",
|
||||
"about-contentinjection-para3": "<b>Mode ServiceWorkerLocal</b> : Ce mode n'est disponible que dans les extensions Chromium. Il est identique au mode ci-dessus, mais en raison des restrictions de sécurité très strictes dans ces extensions, aucun JavaScript en ligne ou déclarations eval ne peut être exécuté. Malheureusement, les ZIM modernes peuvent avoir un tel contenu dynamique, et donc la fonctionnalité peut être limitée dans ce mode. Cependant, il est plus sûr : le contenu est dans un bac à sable et aucun accès au serveur n'est requis pour activer le Service Worker. Il est protégé contre l'exécution de code en ligne au détriment de la perte de certaines fonctionnalités dans les ZIM dynamiques.",
|
||||
"about-contentinjection-para4": "<b>Mode JQuery</b> : Ce mode est maintenant obsolète (et en fait ne nécessite pas JQuery). Il est maintenu pour la compatibilité avec les anciens navigateurs ou les frameworks qui ne peuvent pas exécuter de Service Workers. Le mode a des limitations qui signifient qu'il ne peut afficher que du contenu statique, comme celui que l'on trouve dans les fichiers Wikipédia / WikiMédia et (pour l'instant) Stackexchange. C'est une façon d'insérer des articles extraits du ZIM dans le DOM (document du navigateur) en injectant le contenu dans un iframe ou un onglet. Ensuite, nous utilisons des méthodes DOM natives, ou parfois JQuery, pour analyser le HTML de l'article et insérer les ressources nécessaires (images, feuilles de style, écouteurs d'événements pour les hyperliens, etc.). Sur les anciens appareils, le parcours du DOM peut être lent, mais cela est compensé par le fait que nous n'extraions ni n'exécutons les ressources JavaScript (ce qui serait techniquement extrêmement compliqué). En conséquence, pour les fichiers WikiMédia, ce mode est généralement assez rapide. D'autre part, les ZIM qui ont une interface utilisateur dynamique propriétaire (comme Gutenberg ou TED Talks) ne sont pris en charge que partiellement dans ce mode : l'interface utilisateur ne fonctionne pas, mais les articles peuvent être recherchés et chargés à partir de la barre de recherche.",
|
||||
"about-feedback-para1": "Nous apprécions les commentaires constructifs et les suggestions d'amélioration. Nous examinerons les demandes de nouvelles fonctionnalités, mais ce sera encore mieux si vous pouvez contribuer à améliorer l'application vous-même. Le code source est sur <a href=\"https://github.com/kiwix/kiwix-js\" target=\"_blank\">GitHub <img src=\"img/Icon_External_Link.png\" /></a>, et nous apprécions les Pull Requests (mais ouvrez d'abord un ticket pour discuter de votre proposition).",
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "Kiwix",
|
||||
"version": "3.11.1",
|
||||
"version": "3.11.2",
|
||||
|
||||
"description": "Kiwix Offline Browser",
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "Kiwix",
|
||||
"version": "3.11.1",
|
||||
"version": "3.11.2",
|
||||
|
||||
"description": "Kiwix Offline Browser",
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"manifest_version": 2,
|
||||
"name": "Kiwix",
|
||||
"version": "3.11.1",
|
||||
"version": "3.11.2",
|
||||
|
||||
"description": "Kiwix : offline Wikipedia reader",
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "3.11.1",
|
||||
"version": "3.11.2",
|
||||
"name": "Kiwix",
|
||||
"description": "Offline Wikipedia Viewer, and more",
|
||||
"launch_path": "/www/index.html",
|
||||
|
4
package-lock.json
generated
4
package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "kiwix-js",
|
||||
"version": "3.11.1",
|
||||
"version": "3.11.3",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "kiwix-js",
|
||||
"version": "3.11.1",
|
||||
"version": "3.11.3",
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "5.9.0",
|
||||
"@types/fs-extra": "^9.0.11",
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "kiwix-js",
|
||||
"productName": "Kiwix JS",
|
||||
"version": "3.11.1",
|
||||
"version": "3.11.2",
|
||||
"description": "Kiwix JS offline ZIM archive reader",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
60349
replayWorker.js
Normal file
60349
replayWorker.js
Normal file
File diff suppressed because one or more lines are too long
@ -1,10 +1,12 @@
|
||||
// rollup.config.js
|
||||
import resolve from '@rollup/plugin-node-resolve';
|
||||
import babel from '@rollup/plugin-babel';
|
||||
import { transformAsync } from '@babel/core';
|
||||
import commonjs from '@rollup/plugin-commonjs';
|
||||
import replace from '@rollup/plugin-replace';
|
||||
import copy from 'rollup-plugin-copy';
|
||||
import terser from '@rollup/plugin-terser';
|
||||
import { minify } from 'terser';
|
||||
// import styles from "@ironkinoko/rollup-plugin-styles";
|
||||
|
||||
const config = {
|
||||
@ -69,20 +71,54 @@ if (process.env.BUILD === 'production') {
|
||||
src: ['service-worker.js'],
|
||||
dest: 'dist',
|
||||
// Modify the Service Worker precache files
|
||||
transform: (contents, filename) => contents.toString()
|
||||
// Replace the entry point with the bundle
|
||||
.replace(/(www[\\/]js[\\/])app.js/, '$1bundle.min.js')
|
||||
// Remove all the lib files that will be included in the bundle
|
||||
.replace(/,\s+["']www[\\/]js[\\/]lib[\\/]abstractFilesystemAccess[\s\S]+zimfile.js["']\s*/, '')
|
||||
// Replace any references to node_modules
|
||||
.replace(/node_modules\/.*dist\/((?:js|css)\/)?/g, function (m, p1) {
|
||||
p1 = p1 || 'js/';
|
||||
return 'www/' + p1;
|
||||
})
|
||||
// Alter remaining lib references
|
||||
.replace(/([\\/])js[\\/]lib/g, '$1js')
|
||||
// Remove unneeded ASM/WASM binaries
|
||||
.replace(/["']www[\\/]js[\\/].*dec.*js["'],\s*/g, '')
|
||||
transform: async (contents, filename) => {
|
||||
const result = await minify(
|
||||
contents.toString()
|
||||
// Replace the entry point with the bundle
|
||||
.replace(/(www[\\/]js[\\/])app.js/, '$1bundle.min.js')
|
||||
// Remove all the lib files that will be included in the bundle
|
||||
.replace(/,\s+["']www[\\/]js[\\/]lib[\\/]abstractFilesystemAccess[\s\S]+zimfile.js["']\s*/, '')
|
||||
// Replace any references to node_modules
|
||||
.replace(/node_modules\/.*dist\/((?:js|css)\/)?/g, function (m, p1) {
|
||||
p1 = p1 || 'js/';
|
||||
return 'www/' + p1;
|
||||
})
|
||||
// Alter remaining lib references
|
||||
.replace(/([\\/])js[\\/]lib/g, '$1js')
|
||||
// Remove unneeded ASM/WASM binaries
|
||||
.replace(/["']www[\\/]js[\\/].*dec.*js["'],\s*/g, '')
|
||||
);
|
||||
return result.code;
|
||||
}
|
||||
},
|
||||
{
|
||||
src: ['replayWorker.js'],
|
||||
dest: 'dist',
|
||||
transform: async (contents, filename) => {
|
||||
const code = contents.toString();
|
||||
// Now minify the replayWorker
|
||||
const minified = await minify(code);
|
||||
// How to transform with babel (tested to make a difference with Firefox 68+)
|
||||
const result = await transformAsync(minified.code, {
|
||||
filename,
|
||||
presets: [
|
||||
[
|
||||
'@babel/preset-env',
|
||||
{
|
||||
targets: {
|
||||
edge: '18',
|
||||
firefox: '60',
|
||||
chrome: '67',
|
||||
safari: '11.1'
|
||||
},
|
||||
modules: false,
|
||||
spec: true
|
||||
}
|
||||
]
|
||||
]
|
||||
});
|
||||
return result.code;
|
||||
}
|
||||
},
|
||||
{
|
||||
src: 'www/index.html',
|
||||
@ -134,6 +170,10 @@ if (process.env.BUILD === 'production') {
|
||||
// Remove unneeded ASM/WASM binaries
|
||||
.replace(/["']www[\\/]js[\\/].*dec.*js["'],\s*/g, '')
|
||||
},
|
||||
{
|
||||
src: ['replayWorker.js'],
|
||||
dest: 'dist'
|
||||
},
|
||||
{
|
||||
src: 'www/index.html',
|
||||
dest: 'dist/www',
|
||||
|
@ -25,13 +25,15 @@
|
||||
|
||||
/* global chrome */
|
||||
|
||||
/* eslint-disable prefer-const */
|
||||
|
||||
/**
|
||||
* App version number - ENSURE IT MATCHES VALUE IN init.js
|
||||
* DEV: Changing this will cause the browser to recognize that the Service Worker has changed, and it will
|
||||
* download and install a new copy; we have to hard code this here because it is needed before any other file
|
||||
* is cached in APP_CACHE
|
||||
*/
|
||||
const appVersion = '3.11.1';
|
||||
const appVersion = '3.11.2';
|
||||
|
||||
/**
|
||||
* The name of the Cache API cache in which assets defined in regexpCachedContentTypes will be stored
|
||||
@ -64,6 +66,11 @@ var useAssetsCache = true;
|
||||
*/
|
||||
var useAppCache = true;
|
||||
|
||||
/**
|
||||
* A global Boolean that records whether the ReplayWorker is available
|
||||
*/
|
||||
var isReplayWorkerAvailable = false;
|
||||
|
||||
/**
|
||||
* A regular expression that matches the Content-Types of assets that may be stored in ASSETS_CACHE
|
||||
* Add any further Content-Types you wish to cache to the regexp, separated by '|'
|
||||
@ -103,6 +110,7 @@ const regexpByteRangeHeader = /^\s*bytes=(\d+)-/;
|
||||
const precacheFiles = [
|
||||
'.', // This caches the redirect to www/index.html, in case a user launches the app from its root directory
|
||||
'manifest.json',
|
||||
'replayWorker.js',
|
||||
'service-worker.js',
|
||||
'i18n/en.jsonp.js',
|
||||
'i18n/es.jsonp.js',
|
||||
@ -120,6 +128,7 @@ const precacheFiles = [
|
||||
'www/article.html',
|
||||
'www/library.html',
|
||||
'www/main.html',
|
||||
'www/topFrame.html',
|
||||
'www/js/app.js',
|
||||
'www/js/init.js',
|
||||
'www/js/lib/abstractFilesystemAccess.js',
|
||||
@ -223,6 +232,40 @@ self.addEventListener('activate', function (event) {
|
||||
);
|
||||
});
|
||||
|
||||
// Wrapped in try-catch
|
||||
try {
|
||||
// Import ReplayWorker
|
||||
self.importScripts('./replayWorker.js');
|
||||
isReplayWorkerAvailable = true;
|
||||
console.log('[SW] ReplayWorker is available');
|
||||
} catch (err) {
|
||||
console.warn('[SW ReplayWorker is NOT available', err);
|
||||
isReplayWorkerAvailable = false;
|
||||
}
|
||||
|
||||
let replayCollectionsReloaded;
|
||||
|
||||
// Instruct the ReplayWorker to reload all collections, and adjust the root configuration (this is necessary after thw SW has stopped and restarted)
|
||||
if (isReplayWorkerAvailable) {
|
||||
replayCollectionsReloaded = self.sw.collections.listAll().then(function (colls) {
|
||||
if (colls) {
|
||||
console.debug('[SW] Reloading ReplayWorker collections', colls);
|
||||
return Promise.all(colls.map(function (coll) {
|
||||
// console.debug('[SW] Reloading ReplayWorker collection ' + coll.name);
|
||||
return self.sw.collections.reload(coll.name);
|
||||
})).then(function () {
|
||||
// Adjust the root configuration
|
||||
if (self.sw.collections.root) {
|
||||
console.debug('[SW] Adjusting ReplayWorker root configuration to ' + self.sw.collections.root);
|
||||
return setReplayCollectionAsRoot(self.sw.collections.colls[self.sw.collections.root].config.sourceUrl, self.sw.collections.root);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.debug('[SW] No ReplayWorker collections to reload');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// For PWA functionality, this should be true unless explicitly disabled, and in fact currently it is never disabled
|
||||
let fetchCaptureEnabled = true;
|
||||
|
||||
@ -230,6 +273,7 @@ let fetchCaptureEnabled = true;
|
||||
* Intercept selected Fetch requests from the browser window
|
||||
*/
|
||||
self.addEventListener('fetch', function (event) {
|
||||
// console.debug('[SW] Fetch Event processing', event.request.url);
|
||||
// Only handle GET or POST requests (POST is intended to handle video in Zimit ZIMs)
|
||||
if (!/GET|POST/.test(event.request.method)) return;
|
||||
var rqUrl = event.request.url;
|
||||
@ -252,32 +296,36 @@ self.addEventListener('fetch', function (event) {
|
||||
}, function () {
|
||||
// The response was not found in the cache so we look for it in the ZIM
|
||||
// and add it to the cache if it is an asset type (css or js)
|
||||
// YouTube links from Zimit archives are dealt with specially
|
||||
if (/youtubei.*player/.test(strippedUrl) || cache === ASSETS_CACHE && regexpZIMUrlWithNamespace.test(strippedUrl)) {
|
||||
const range = event.request.headers.get('range');
|
||||
return fetchUrlFromZIM(urlObject, range).then(function (response) {
|
||||
// Add css or js assets to ASSETS_CACHE (or update their cache entries) unless the URL schema is not supported
|
||||
if (regexpCachedContentTypes.test(response.headers.get('Content-Type')) &&
|
||||
!regexpExcludedURLSchema.test(event.request.url)) {
|
||||
event.waitUntil(updateCache(ASSETS_CACHE, rqUrl, response.clone()));
|
||||
}
|
||||
return response;
|
||||
}).catch(function (msgPortData) {
|
||||
console.error('Invalid message received from app.js for ' + strippedUrl, msgPortData);
|
||||
return msgPortData;
|
||||
});
|
||||
} else {
|
||||
// It's not an asset, or it doesn't match a ZIM URL pattern, so we should fetch it with Fetch API
|
||||
return fetch(event.request).then(function (response) {
|
||||
// If request was successful, add or update it in the cache, but be careful not to cache the ZIM archive itself!
|
||||
if (!regexpExcludedURLSchema.test(event.request.url) && !/\.zim\w{0,2}$/i.test(strippedUrl)) {
|
||||
event.waitUntil(updateCache(APP_CACHE, rqUrl, response.clone()));
|
||||
}
|
||||
return response;
|
||||
}).catch(function (error) {
|
||||
console.debug('[SW] Network request failed and no cache.', error);
|
||||
});
|
||||
}
|
||||
return zimitResolver(event).then(function (modRequestOrResponse) {
|
||||
if (modRequestOrResponse instanceof Response) {
|
||||
// The request was modified by the ReplayWorker and it returned a modified response, so we return it
|
||||
// console.debug('[SW] Returning modified response from ReplayWorker', modRequest);
|
||||
return cacheAndReturnResponseForAsset(event, modRequestOrResponse);
|
||||
}
|
||||
rqUrl = modRequestOrResponse.url;
|
||||
urlObject = new URL(rqUrl);
|
||||
strippedUrl = urlObject.pathname;
|
||||
if (cache === ASSETS_CACHE && regexpZIMUrlWithNamespace.test(strippedUrl)) {
|
||||
const range = modRequestOrResponse.headers.get('range');
|
||||
return fetchUrlFromZIM(urlObject, range).then(function (response) {
|
||||
return cacheAndReturnResponseForAsset(event, response);
|
||||
}).catch(function (msgPortData) {
|
||||
console.error('Invalid message received from app.js for ' + strippedUrl, msgPortData);
|
||||
return msgPortData;
|
||||
});
|
||||
} else {
|
||||
// It's not an asset, or it doesn't match a ZIM URL pattern, so we should fetch it with Fetch API
|
||||
return fetch(modRequestOrResponse).then(function (response) {
|
||||
// If request was successful, add or update it in the cache, but be careful not to cache the ZIM archive itself!
|
||||
if (!regexpExcludedURLSchema.test(rqUrl) && !/\.zim\w{0,2}$/i.test(strippedUrl)) {
|
||||
event.waitUntil(updateCache(APP_CACHE, rqUrl, response.clone()));
|
||||
}
|
||||
return response;
|
||||
}).catch(function (error) {
|
||||
console.debug('[SW] Network request failed and no cache.', error);
|
||||
});
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
});
|
||||
@ -324,51 +372,218 @@ self.addEventListener('message', function (event) {
|
||||
event.ports[0].postMessage({ type: cacheArr[0], name: cacheArr[1], description: cacheArr[2], count: cacheArr[3] });
|
||||
});
|
||||
}
|
||||
} else if (event.data.msg_type) {
|
||||
// Messages for the ReplayWorker
|
||||
if (event.data.msg_type === 'addColl') {
|
||||
console.debug('[SW] addColl message received from app.js');
|
||||
if (!self.sw) {
|
||||
console.error('[SW] Zimit ZIMs in ServiceWorker mode are not supported in this browser');
|
||||
// Reply to the message port with an error
|
||||
event.ports[0].postMessage({ error: 'ReplayWorker is unsupported!' });
|
||||
} else {
|
||||
event.waitUntil(
|
||||
self.sw.collections._handleMessage(event).then(function () {
|
||||
setReplayCollectionAsRoot(event.data.prefix, event.data.name);
|
||||
// Reply to the message port with a success message
|
||||
event.ports[0].postMessage({ success: 'ReplayWorker is supported!' });
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Handles URLs that need to be extracted from the ZIM archive
|
||||
* Sets a Replay collection as the root configuration, so that the Replay Worker will deal correctly with requests to the collection
|
||||
*
|
||||
* @param {URL} urlObject The URL object to be processed for extraction from the ZIM
|
||||
* @param {String} range Optional byte range string
|
||||
* @param {String} prefix The URL prefix where assets are loaded, consisting of the local path to the ZIM file plus the namespace
|
||||
* @param {String} name The name of the ZIM file (wihtout any extension), used as the Replay root
|
||||
*/
|
||||
function setReplayCollectionAsRoot (prefix, name) {
|
||||
// Guard against prototype pollution attack
|
||||
if (typeof prefix !== 'string' || typeof name !== 'string') {
|
||||
console.error('Invalid prefix or name');
|
||||
return;
|
||||
}
|
||||
const dangerousProps = ['__proto__', 'constructor', 'prototype'];
|
||||
if (dangerousProps.includes(prefix) || dangerousProps.includes(name)) {
|
||||
console.error('Potentially dangerous prefix or name');
|
||||
return;
|
||||
}
|
||||
self.sw.prefix = prefix;
|
||||
self.sw.replayPrefix = prefix;
|
||||
self.sw.distPrefix = prefix + 'dist/';
|
||||
self.sw.apiPrefix = prefix + 'api/';
|
||||
self.sw.staticPrefix = prefix + 'static/';
|
||||
self.sw.api.collections.prefixes = {
|
||||
main: self.sw.prefix,
|
||||
root: self.sw.prefix,
|
||||
static: self.sw.staticPrefix
|
||||
}
|
||||
// If we want to be able to get the static data URL directly from the map, we need to replace the keyes, but as this is quite costly (moving a lot of static)
|
||||
// data around, we're using another way to get the static data URL from the map in zimitResolver()
|
||||
// let newMap = new Map();
|
||||
// for (let [key, value] of self.sw.staticData.entries()) {
|
||||
// const newKey = /wombat\.js/i.test(key) ? self.sw.staticPrefix + 'wombat.js' : /wombatWorkers\.js/i.test(key) ? self.sw.staticPrefix + 'wombatWorkers.js' : key;
|
||||
// newMap.set(newKey, value);
|
||||
// }
|
||||
// self.sw.staticData = newMap;
|
||||
if (self.sw.collections.colls[name]) {
|
||||
self.sw.collections.colls[name].prefix = self.sw.prefix;
|
||||
self.sw.collections.colls[name].rootPrefix = self.sw.prefix;
|
||||
self.sw.collections.colls[name].staticPrefix = self.sw.staticPrefix;
|
||||
self.sw.collections.root = name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles resolving content for Zimit-style ZIM archives
|
||||
*
|
||||
* @param {FetchEvent} event The FetchEvent to be processed
|
||||
* @returns {Promise<Response>} A Promise for the Response, or rejects with the invalid message port data
|
||||
*/
|
||||
function fetchUrlFromZIM (urlObject, range) {
|
||||
function zimitResolver (event) {
|
||||
var rqUrl = event.request.url;
|
||||
var zimStem = rqUrl.replace(/^.*?\/([^/]+?)\.zim\w?\w?\/.*/, '$1');
|
||||
if (/\/A\/load\.js$/.test(rqUrl)) {
|
||||
// If the request is for load.js, we should filter its contents to load the mainUrl, as we don't need the other stuff
|
||||
// concerning registration of the ServiceWorker and postMessage handling
|
||||
console.debug('[SW] Filtering content of load.js', rqUrl);
|
||||
// First we have to get the contents of load.js from the ZIM, because it is a common name, and there is no way to be sure
|
||||
// that the request will be for the Zimit load.js
|
||||
return fetchUrlFromZIM(new URL(rqUrl)).then(function (response) {
|
||||
// The response was found in the ZIM so we respond with it
|
||||
// Clone the response before reading its body
|
||||
var clonedResponse = response.clone();
|
||||
return response.text().then(function (contents) {
|
||||
// We need to replace the entire contents with a single function that loads mainUrl
|
||||
if (/\.register\([^;]+?sw\.js\?replayPrefix/.test(contents)) {
|
||||
var newContents = "window.location.href = window.location.href.replace(/index\\.html/, window.mainUrl.replace('https://', ''));";
|
||||
var responseLoadJS = contsructResponse(newContents, 'text/javascript');
|
||||
return responseLoadJS;
|
||||
} else {
|
||||
// The contents of load.js are not as expected, so we should return the original response
|
||||
return clonedResponse;
|
||||
}
|
||||
});
|
||||
});
|
||||
// Check that the requested URL is for a ZIM that we already have loaded
|
||||
} else if (zimStem !== rqUrl && isReplayWorkerAvailable) {
|
||||
// Wait for the ReplayWorker to initialize and reload all collections
|
||||
return replayCollectionsReloaded.then(function () {
|
||||
if (self.sw.collections.colls && self.sw.collections.colls[zimStem]) {
|
||||
if (self.sw.collections.root !== zimStem) {
|
||||
setReplayCollectionAsRoot(self.sw.collections.colls[zimStem].config.sourceUrl, zimStem);
|
||||
}
|
||||
if (/\/A\/static\//.test(rqUrl)) {
|
||||
// If the request is for static data from the replayWorker, we should get them from the Worker's cache
|
||||
// DEV: This extracts both wombat.js and wombatWorkers.js from the staticData Map
|
||||
var staticDataUrl = rqUrl.replace(/^(.*?\/)[^/]+?\.zim\w?\w?\/[AC/]{2,4}(.*)/, '$1$2')
|
||||
if (self.sw.staticData) {
|
||||
var staticData = self.sw.staticData.get(staticDataUrl);
|
||||
if (staticData) {
|
||||
console.debug('[SW] Returning static data from ReplayWorker', rqUrl);
|
||||
// Construct a new Response with headers to return the static data
|
||||
var responseStaticData = contsructResponse(staticData.content, staticData.type);
|
||||
return Promise.resolve(responseStaticData);
|
||||
} else {
|
||||
// Return a 404 response
|
||||
return Promise.resolve(new Response('', { status: 404, statusText: 'Not Found' }));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// console.debug('[SW] Asking ReplayWorker to handleFetch', rqUrl);
|
||||
return self.sw.handleFetch(event);
|
||||
}
|
||||
} else {
|
||||
// The requested ZIM is not loaded, or it is a regular non-Zimit request
|
||||
return event.request;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// The loaded ZIM archive is not a Zimit archive, or sw-Zimit is unsupported, so we should just return the request
|
||||
return Promise.resolve(event.request);
|
||||
}
|
||||
}
|
||||
|
||||
function contsructResponse (content, contentType) {
|
||||
var headers = new Headers();
|
||||
headers.set('Content-Length', content.length);
|
||||
headers.set('Content-Type', contentType);
|
||||
var responseInit = {
|
||||
status: 200,
|
||||
statusText: 'OK',
|
||||
headers: headers
|
||||
};
|
||||
return new Response(content, responseInit);
|
||||
}
|
||||
|
||||
// Caches and returns the event and response pair for an asset. Do not use this for non-asset requests!
|
||||
function cacheAndReturnResponseForAsset (event, response) {
|
||||
// Add css or js assets to ASSETS_CACHE (or update their cache entries) unless the URL schema is not supported
|
||||
if (regexpCachedContentTypes.test(response.headers.get('Content-Type')) &&
|
||||
!regexpExcludedURLSchema.test(event.request.url)) {
|
||||
event.waitUntil(updateCache(ASSETS_CACHE, event.request.url, response.clone()));
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles URLs that need to be extracted from the ZIM archive. They can be strings or URL objects, and should be URI encoded.
|
||||
*
|
||||
* @param {URL|String} urlObjectOrString The URL object, or a simple string representation, to be processed for extraction from the ZIM
|
||||
* @param {String} range Optional byte range string (mostly used for video or audio streams)
|
||||
* @param {String} expectedHeaders Optional comma-separated list of headers to be expected in the response (for error checking). Note that although
|
||||
* Zimit requests may be for a range of bytes, in fact video (at least) is stored as a blob, so the appropriate response will just be a normal 200.
|
||||
* @returns {Promise<Response>} A Promise for the Response, or rejects with the invalid message port data
|
||||
*/
|
||||
function fetchUrlFromZIM (urlObjectOrString, range, expectedHeaders) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
var pathname = typeof urlObjectOrString === 'string' ? urlObjectOrString : urlObjectOrString.pathname;
|
||||
// Note that titles may contain bare question marks or hashes, so we must use only the pathname without any URL parameters.
|
||||
// Be sure that you haven't encoded any querystring along with the URL.
|
||||
var barePathname = decodeURIComponent(urlObject.pathname);
|
||||
// Be sure that you haven't encoded any querystring along with the URL (Zimit files, however, require encoding of the querystring)
|
||||
var barePathname = decodeURIComponent(pathname);
|
||||
var partsOfZIMUrl = regexpZIMUrlWithNamespace.exec(barePathname);
|
||||
var prefix = partsOfZIMUrl ? partsOfZIMUrl[1] : '';
|
||||
var nameSpace = partsOfZIMUrl ? partsOfZIMUrl[2] : '';
|
||||
var title = partsOfZIMUrl ? partsOfZIMUrl[3] : barePathname;
|
||||
var anchorTarget = urlObject.hash.replace(/^#/, '');
|
||||
var uriComponent = urlObject.search.replace(/\?kiwix-display/, '');
|
||||
var anchorTarget = '';
|
||||
var uriComponent = '';
|
||||
if (typeof urlObjectOrString === 'object') {
|
||||
anchorTarget = urlObjectOrString.hash.replace(/^#/, '');
|
||||
uriComponent = urlObjectOrString.search.replace(/\?kiwix-display/, '');
|
||||
}
|
||||
var titleWithNameSpace = nameSpace + '/' + title;
|
||||
var zimName = prefix.replace(/\/$/, '');
|
||||
|
||||
// console.debug('[SW] Asking app.js for ' + titleWithNameSpace + ' from ' + zimName + '...');
|
||||
|
||||
var messageListener = function (msgPortEvent) {
|
||||
if (msgPortEvent.data.action === 'giveContent') {
|
||||
// Content received from app.js
|
||||
var contentLength = msgPortEvent.data.content ? (msgPortEvent.data.content.byteLength || msgPortEvent.data.content.length) : null;
|
||||
// Content received from app.js (note that null indicates that the content was not found in the ZIM)
|
||||
var contentLength = msgPortEvent.data.content !== null ? (msgPortEvent.data.content.byteLength || msgPortEvent.data.content.length) : null;
|
||||
var contentType = msgPortEvent.data.mimetype;
|
||||
var zimType = msgPortEvent.data.zimType;
|
||||
var headers = new Headers();
|
||||
if (contentLength) headers.set('Content-Length', contentLength);
|
||||
if (contentLength !== null) headers.set('Content-Length', contentLength);
|
||||
// Set Content-Security-Policy to sandbox the content (prevent XSS attacks from malicious ZIMs)
|
||||
headers.set('Content-Security-Policy', "default-src 'self' data: blob: about: chrome-extension: moz-extension: https://browser-extension.kiwix.org https://kiwix.github.io 'unsafe-inline' 'unsafe-eval'; sandbox allow-scripts allow-same-origin allow-modals allow-popups allow-forms allow-downloads;");
|
||||
headers.set('Referrer-Policy', 'no-referrer');
|
||||
if (contentType) headers.set('Content-Type', contentType);
|
||||
|
||||
// Test if the content is a video or audio file. In this case, Chrome & Edge need us to support ranges.
|
||||
// NB, the Replay Worker adds its own Accept-Ranges header, so we don't add it here for such requests.
|
||||
// See kiwix-js #519 and openzim/zimwriterfs #113 for why we test for invalid types like "mp4" or "webm" (without "video/")
|
||||
// The full list of types produced by zimwriterfs is in https://github.com/openzim/zimwriterfs/blob/master/src/tools.cpp
|
||||
if (contentLength >= 1 && /^(video|audio)|(^|\/)(mp4|webm|og[gmv]|mpeg)$/i.test(contentType)) {
|
||||
if (zimType !== 'zimit' && contentLength >= 1 && /^(video|audio)|(^|\/)(mp4|webm|og[gmv]|mpeg)$/i.test(contentType)) {
|
||||
headers.set('Accept-Ranges', 'bytes');
|
||||
}
|
||||
|
||||
var slicedData = msgPortEvent.data.content;
|
||||
if (range) {
|
||||
|
||||
if (range && zimType === 'zimit') {
|
||||
headers.set('Content-Range', range + '/*');
|
||||
} else if (range && slicedData !== null) {
|
||||
// The browser asks for a range of bytes (usually for a video or audio stream)
|
||||
// In this case, we partially honor the request: if it asks for offsets x to y,
|
||||
// we send partial contents starting at x offset, till the end of the data (ignoring y offset)
|
||||
@ -386,17 +601,30 @@ function fetchUrlFromZIM (urlObject, range) {
|
||||
}
|
||||
|
||||
var responseInit = {
|
||||
// HTTP status is usually 200, but has to bee 206 when partial content (range) is sent
|
||||
// HTTP status is usually 200, but has to be 206 when partial content (range) is sent
|
||||
status: range ? 206 : 200,
|
||||
statusText: 'OK',
|
||||
headers: headers
|
||||
};
|
||||
// Deal with a not-found dirEntry
|
||||
if (slicedData === null) {
|
||||
responseInit.status = 404;
|
||||
responseInit.statusText = 'Not Found';
|
||||
}
|
||||
|
||||
if (slicedData === null) slicedData = '';
|
||||
|
||||
// if (expectedHeaders) {
|
||||
// console.debug('[SW] Expected headers were', Object.fromEntries(expectedHeaders));
|
||||
// console.debug('[SW] Constructed headers are', Object.fromEntries(headers));
|
||||
// }
|
||||
|
||||
var httpResponse = new Response(slicedData, responseInit);
|
||||
|
||||
// Let's send the content back from the ServiceWorker
|
||||
resolve(httpResponse);
|
||||
} else if (msgPortEvent.data.action === 'sendRedirect') {
|
||||
console.debug('[SW] Redirecting to ' + msgPortEvent.data.redirectUrl);
|
||||
resolve(Response.redirect(prefix + msgPortEvent.data.redirectUrl));
|
||||
} else {
|
||||
reject(msgPortEvent.data, titleWithNameSpace);
|
||||
|
@ -106,7 +106,7 @@ function runTests (driver, modes) {
|
||||
this.slow(10000);
|
||||
// Run tests twice, once in serviceworker mode and once in jquery mode
|
||||
it('Load Kiwix JS and check title', async function () {
|
||||
await driver.get('http://localhost:' + port + '/dist/www/index.html');
|
||||
await driver.get('http://localhost:' + port + '/dist/www/index.html?noPrompts=true');
|
||||
const title = await driver.getTitle();
|
||||
assert.equal('Kiwix', title);
|
||||
});
|
||||
@ -137,7 +137,7 @@ function runTests (driver, modes) {
|
||||
}
|
||||
if (mode === 'jquery' || serviceWorkerAPI) {
|
||||
// Wait until the mode has switched
|
||||
await driver.sleep(500);
|
||||
await driver.sleep(2000);
|
||||
let serviceWorkerStatus = await driver.findElement(By.id('serviceWorkerStatus')).getText();
|
||||
try {
|
||||
if (mode === 'serviceworker') {
|
||||
@ -152,7 +152,10 @@ function runTests (driver, modes) {
|
||||
}
|
||||
// We failed to switch modes, so let's try switching back and switching to this mode again
|
||||
console.log('\x1b[33m%s\x1b[0m', ' Failed to switch to ' + mode + ' mode, trying again...');
|
||||
const otherModeSelector = await driver.findElement(By.id(mode === 'jquery' ? 'serviceworkerModeRadio' : 'jqueryModeRadio'));
|
||||
let otherModeSelector;
|
||||
await driver.wait(async function () {
|
||||
otherModeSelector = await driver.findElement(By.id(mode === 'jquery' ? 'serviceworkerModeRadio' : 'jqueryModeRadio'));
|
||||
}, 5000);
|
||||
// Click the other mode selector
|
||||
await otherModeSelector.click();
|
||||
// Wait until the mode has switched
|
||||
|
@ -122,7 +122,13 @@ function runTests (driver, modes) {
|
||||
this.slow(10000);
|
||||
// Run tests twice, once in serviceworker mode and once in jquery mode
|
||||
it('Load Kiwix JS and check title', async function () {
|
||||
await driver.get('http://localhost:' + port + '/dist/www/index.html');
|
||||
await driver.get('http://localhost:' + port + '/dist/www/index.html?noPrompts=true');
|
||||
// Pause for 1.3 seconds to allow the app to load
|
||||
await driver.sleep(1300);
|
||||
// Issue a reload to ensure that the app is in the correct mode
|
||||
await driver.navigate().refresh();
|
||||
// Pause for 800 milliseconds to allow the app to reload
|
||||
await driver.sleep(800);
|
||||
const title = await driver.getTitle();
|
||||
assert.equal('Kiwix', title);
|
||||
});
|
||||
@ -231,10 +237,11 @@ function runTests (driver, modes) {
|
||||
await driver.wait(async function () {
|
||||
const contentAvailable = await driver.executeScript('return document.getElementById("mw-content-text");');
|
||||
return contentAvailable;
|
||||
}, 5000);
|
||||
}, 6000);
|
||||
const articleLink = await driver.wait(until.elementLocated(By.xpath('/html/body/div/div/ul/li[77]/a[2]')));
|
||||
const text = await articleLink.getText();
|
||||
// const articleLink = await driver.findElement(By.linkText('This Little Girl of Mine'));
|
||||
assert.equal('This Little Girl of Mine', await articleLink.getText());
|
||||
assert.equal('This Little Girl of Mine', text);
|
||||
// Scroll the element into view and navigate to it
|
||||
await driver.wait(async function () {
|
||||
const elementIsVisible = await driver.executeScript('var el=arguments[0]; el.scrollIntoView(true); setTimeout(function () {el.click();}, 50); return el.offsetParent;', articleLink);
|
||||
|
@ -4,7 +4,7 @@
|
||||
"architecture": "all",
|
||||
"maintainer": "Kiwix team <contact+ubuntutouch@kiwix.org>",
|
||||
"framework": "ubuntu-sdk-16.04",
|
||||
"version": "3.11.1",
|
||||
"version": "3.11.2",
|
||||
"title": "Kiwix",
|
||||
"hooks": {
|
||||
"kiwix": {
|
||||
|
@ -2,7 +2,9 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Placeholder for injecting an article into the iframe</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self' data: file: blob: about: 'unsafe-inline' 'unsafe-eval';">
|
||||
<meta name="description" content="Placeholder for injecting an article into the iframe or window">
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
@ -187,9 +187,8 @@
|
||||
</p>
|
||||
<p data-i18n="about-step1-para4">
|
||||
<b>Before downloading non-Wiki ZIM types, check if your browser supports ZIM archives with dynamic content</b>: look at the bottom of the Configuration
|
||||
page, in the API panel, and if you see "ServiceWorker API available and registered" in green, then the app can read all ZIM types (except Zimit:
|
||||
see <a href="#format">ZIM archive format</a> for more details). Otherwise you should mostly stick to MediaWiki (Wikipedia, Wikivoyage, Wiktionary...)
|
||||
and Stack Exchange archives (see <a href="#modes">modes</a> below for an explanation).
|
||||
page, in the API panel, and if you see "ServiceWorker API available and registered" in green, then the app can read all ZIM types. Otherwise you should
|
||||
mostly stick to MediaWiki (Wikipedia, Wikivoyage, Wiktionary...) and Stack Exchange archives (see <a href="#modes">modes</a> below for an explanation).
|
||||
</p>
|
||||
<h4 data-i18n="about-step2">Step 2: Download or copy the content onto your device</h4>
|
||||
<p data-i18n="about-step2-para1">
|
||||
@ -284,10 +283,10 @@
|
||||
ServiceWorker Mode (<a href="#modes">see below</a>).
|
||||
</p>
|
||||
<p data-i18n="about-zim-format-para2">
|
||||
This application is not currently compatible with archives that are created by <b>Zimit</b> (<a href="https://youzim.it" target="_blank">https://youzim.it <img src="img/Icon_External_Link.png" /></a>)
|
||||
using the Web Archive (WARC) format. We have plans to enable this format, but there are considerable technical challenges to overcome in order to display
|
||||
these archives with the fidelity that you expect from Kiwix -- see <a href="https://github.com/kiwix/kiwix-js/issues/1009" target="_blank">issue #1009 <img src="img/Icon_External_Link.png" /></a>.
|
||||
<b>However, there is <i>experimental</i> support for Zimit archives in our Progressive Web App (PWA) available from <a href="https://pwa.kiwix.org" target="_blank">https://pwa.kiwix.org <img src="img/Icon_External_Link.png" /></a></b>.
|
||||
This application is now fully compatible with archives that are created by <b>Zimit</b> (<a href="https://youzim.it" target="_blank">https://youzim.it <img src="img/Icon_External_Link.png" /></a>)
|
||||
using the Web Archive (WARC) format, so long as your browser supports ServiceWorker mode. If you can only use JQuery mode, we do our best to show you
|
||||
the static content of the requested article (no JavaScript can run in this mode). This will work for some basic static sites, but many will look broken.
|
||||
Search for content in the usual way.
|
||||
</p>
|
||||
|
||||
<h4 id="FAT" data-i18n="about-fat-fs">Downloading and storing large archives</h4>
|
||||
@ -322,8 +321,7 @@
|
||||
be capable of installing a Service Worker, which is usually the case in modern browsers. It works by intercepting
|
||||
the browser or framework's Fetch calls (network requests) and supplying the requested content from the ZIM. In this
|
||||
mode, the content is read and supplied as-is from the archive to the browser. Dynamic content (e.g. JavaScript) and
|
||||
proprietary UIs are fully supported in this mode. However, Zimit archives are <b>not</b> currently supported due to
|
||||
a conflict between the Kiwix JS Service Worker and the WARC Service Worker (but you can use the PWA for experimental support, see above). This mode can feel initially a little
|
||||
proprietary UIs are fully supported in this mode. This mode can feel initially a little
|
||||
slower than JQuery mode until commonly used assets are cached, but it soon equals JQuery mode in speed, at least in
|
||||
modern browsers. However, older browsers such as IE11 are incompatible with this mode, and the app must be running
|
||||
in a secure context (<code>https:</code>, <code>localhost</code>, or certain browser extensions). While this mode is
|
||||
|
552
www/js/app.js
552
www/js/app.js
@ -157,19 +157,25 @@ function resizeIFrame () {
|
||||
// IE cannot retrieve computed headerStyles till the next paint, so we wait a few ticks even if UI animations are disabled
|
||||
}, params.showUIAnimations ? 400 : 100);
|
||||
|
||||
// Get the contentWindow of the iframe to operate on
|
||||
var thisArticleWindow = articleWindow;
|
||||
if (articleWindow.document && articleWindow.document.getElementById('replay_iframe')) {
|
||||
thisArticleWindow = articleContainer.contentWindow.document.getElementById('replay_iframe').contentWindow;
|
||||
}
|
||||
|
||||
// Remove and add the scroll event listener to the new article window
|
||||
// Note that IE11 doesn't support wheel or touch events on the iframe, but it does support keydown and scroll
|
||||
articleWindow.removeEventListener('scroll', uiUtil.scroller);
|
||||
articleWindow.removeEventListener('touchstart', uiUtil.scroller);
|
||||
articleWindow.removeEventListener('touchend', uiUtil.scroller);
|
||||
articleWindow.removeEventListener('wheel', uiUtil.scroller);
|
||||
articleWindow.removeEventListener('keydown', uiUtil.scroller);
|
||||
thisArticleWindow.removeEventListener('scroll', uiUtil.scroller);
|
||||
thisArticleWindow.removeEventListener('touchstart', uiUtil.scroller);
|
||||
thisArticleWindow.removeEventListener('touchend', uiUtil.scroller);
|
||||
thisArticleWindow.removeEventListener('wheel', uiUtil.scroller);
|
||||
thisArticleWindow.removeEventListener('keydown', uiUtil.scroller);
|
||||
if (params.slideAway) {
|
||||
articleWindow.addEventListener('scroll', uiUtil.scroller);
|
||||
articleWindow.addEventListener('touchstart', uiUtil.scroller);
|
||||
articleWindow.addEventListener('touchend', uiUtil.scroller);
|
||||
articleWindow.addEventListener('wheel', uiUtil.scroller);
|
||||
articleWindow.addEventListener('keydown', uiUtil.scroller);
|
||||
thisArticleWindow.addEventListener('scroll', uiUtil.scroller);
|
||||
thisArticleWindow.addEventListener('touchstart', uiUtil.scroller);
|
||||
thisArticleWindow.addEventListener('touchend', uiUtil.scroller);
|
||||
thisArticleWindow.addEventListener('wheel', uiUtil.scroller);
|
||||
thisArticleWindow.addEventListener('keydown', uiUtil.scroller);
|
||||
}
|
||||
}
|
||||
|
||||
@ -338,7 +344,7 @@ prefixElement.addEventListener('blur', function () {
|
||||
appstate.search.status = 'cancelled';
|
||||
region.style.overflowY = 'hidden';
|
||||
region.style.height = 'auto';
|
||||
document.getElementById('searchingArticles').style.display = 'none';
|
||||
uiUtil.spinnerDisplay(false);
|
||||
document.getElementById('articleListWithHeader').style.display = 'none';
|
||||
}
|
||||
});
|
||||
@ -391,8 +397,8 @@ document.getElementById('btnHome').addEventListener('click', function (event) {
|
||||
var articleListHeaderMessage = document.getElementById('articleListHeaderMessage');
|
||||
while (articleList.firstChild) articleList.removeChild(articleList.firstChild);
|
||||
while (articleListHeaderMessage.firstChild) articleListHeaderMessage.removeChild(articleListHeaderMessage.firstChild);
|
||||
document.getElementById('searchingArticles').style.display = 'none';
|
||||
document.getElementById('articleContent').style.display = 'none';
|
||||
uiUtil.spinnerDisplay(false);
|
||||
// document.getElementById('articleContent').style.display = 'none';
|
||||
// Empty and purge the article contents
|
||||
var articleContent = document.getElementById('articleContent');
|
||||
var articleContentDoc = articleContent ? articleContent.contentDocument : null;
|
||||
@ -728,8 +734,16 @@ function getAssetsCacheAttributes () {
|
||||
// Handler for recieving message reply from service worker
|
||||
channel.port1.onmessage = function (event) {
|
||||
var cache = event.data;
|
||||
if (cache.error) reject(cache.error);
|
||||
else resolve(cache);
|
||||
if (cache.error) {
|
||||
reject(cache.error);
|
||||
} else {
|
||||
if (cache.type === 'cacheAPI' && selectedArchive && selectedArchive.zimType === 'zimit' && appstate.isReplayWorkerAvailable) {
|
||||
cache.type = 'replayWorker';
|
||||
cache.description = 'ReplayWorker';
|
||||
cache.count = '-';
|
||||
}
|
||||
resolve(cache);
|
||||
}
|
||||
};
|
||||
// Ask Service Worker for its cache status and asset count
|
||||
navigator.serviceWorker.controller.postMessage({
|
||||
@ -829,6 +843,11 @@ function initServiceWorkerMessaging () {
|
||||
}
|
||||
handleMessageChannelMessage(event);
|
||||
}
|
||||
} else if (event.data.msg_type) {
|
||||
// Messages received from the ReplayWorker
|
||||
if (event.data.msg_type === 'colAdded') {
|
||||
console.debug('ReplayWorker added a collection');
|
||||
}
|
||||
} else {
|
||||
console.error('Invalid message received', event.data);
|
||||
}
|
||||
@ -848,20 +867,22 @@ function initServiceWorkerMessaging () {
|
||||
console.error('The Service Worker is not controlling the current page! We have to reload.');
|
||||
// Turn off failsafe, as this is a controlled reboot
|
||||
settingsStore.setItem('lastPageLoad', 'rebooting', Infinity);
|
||||
window.location.reload();
|
||||
if (!appstate.preventAutoReboot) window.location.reload();
|
||||
} else if (navigator && navigator.serviceWorker && !navigator.serviceWorker.controller) {
|
||||
uiUtil.systemAlert('<p>No Service Worker is registered, meaning this app will not currently work offline!</p><p>Would you like to switch to ServiceWorker mode?</p>',
|
||||
'Offline use is disabled!', true).then(function (response) {
|
||||
if (response) {
|
||||
setContentInjectionMode('serviceworker');
|
||||
if (selectedArchive) {
|
||||
setTimeout(function () {
|
||||
params.themeChanged = true;
|
||||
document.getElementById('btnHome').click();
|
||||
}, 750);
|
||||
if (!params.noPrompts) {
|
||||
uiUtil.systemAlert('<p>No Service Worker is registered, meaning this app will not currently work offline!</p><p>Would you like to switch to ServiceWorker mode?</p>',
|
||||
'Offline use is disabled!', true).then(function (response) {
|
||||
if (response) {
|
||||
setContentInjectionMode('serviceworker');
|
||||
if (selectedArchive) {
|
||||
setTimeout(function () {
|
||||
params.themeChanged = true;
|
||||
document.getElementById('btnHome').click();
|
||||
}, 750);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -881,6 +902,7 @@ function setContentInjectionMode (value) {
|
||||
params.serviceWorkerLocal = true;
|
||||
}
|
||||
params.contentInjectionMode = value;
|
||||
params.originalContentInjectionMode = null;
|
||||
var message = '';
|
||||
if (value === 'jquery') {
|
||||
if (!params.appCache) {
|
||||
@ -943,15 +965,19 @@ function setContentInjectionMode (value) {
|
||||
'<p>You can continue to use the app in the (now deprecated) JQuery mode, but note that this mode only works well with ' +
|
||||
'ZIM archives that have static content, such as Wikipedia / Wikimedia ZIMs or Stackexchange.</p>' +
|
||||
'<p>If you can, we recommend that you update your browser to a version that supports ServiceWorker mode.</p>';
|
||||
uiUtil.systemAlert(message, (translateUI.t('dialog-launchpwa-unsupported-title') || 'ServiceWorker API not available'), true, null,
|
||||
(translateUI.t('dialog-serviceworker-unsupported-fallback') || 'Use JQuery mode')).then(function (response) {
|
||||
if (params.referrerExtensionURL && response) {
|
||||
var uriParams = '?allowInternetAccess=false&contentInjectionMode=jquery&defaultModeChangeAlertDisplayed=true';
|
||||
window.location.href = params.referrerExtensionURL + '/www/index.html' + uriParams;
|
||||
} else {
|
||||
setContentInjectionMode(params.oldInjectionMode || 'jquery');
|
||||
}
|
||||
});
|
||||
if (!params.noPrompts) {
|
||||
uiUtil.systemAlert(message, (translateUI.t('dialog-launchpwa-unsupported-title') || 'ServiceWorker API not available'), true, null,
|
||||
(translateUI.t('dialog-serviceworker-unsupported-fallback') || 'Use JQuery mode')).then(function (response) {
|
||||
if (params.referrerExtensionURL && response) {
|
||||
var uriParams = '?allowInternetAccess=false&contentInjectionMode=jquery&defaultModeChangeAlertDisplayed=true';
|
||||
window.location.href = params.referrerExtensionURL + '/www/index.html' + uriParams;
|
||||
} else {
|
||||
setContentInjectionMode(params.oldInjectionMode || 'jquery');
|
||||
}
|
||||
});
|
||||
} else {
|
||||
setContentInjectionMode(params.oldInjectionMode || 'jquery');
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!isMessageChannelAvailable()) {
|
||||
@ -1002,15 +1028,18 @@ function setContentInjectionMode (value) {
|
||||
message += (translateUI.t('dialog-serviceworker-registration-failure-fileprotocol') ||
|
||||
'<br/><br/>You seem to be opening kiwix-js with the file:// protocol. You should open it through a web server: either through a local one (http://localhost/...) or through a remote one (but you need a secure connection: https://webserver.org/...)');
|
||||
}
|
||||
uiUtil.systemAlert(message, (translateUI.t('dialog-serviceworker-registration-failure-title') || 'Failed to register Service Worker')).then(function () {
|
||||
setContentInjectionMode('jquery');
|
||||
// We need to wait for the previous dialogue box to unload fully before attempting to display another
|
||||
setTimeout(function () {
|
||||
params.defaultModeChangeAlertDisplayed = false;
|
||||
settingsStore.removeItem('defaultModeChangeAlertDisplayed');
|
||||
checkAndDisplayInjectionModeChangeAlert();
|
||||
}, 1200);
|
||||
});
|
||||
appstate.preventAutoReboot = true;
|
||||
if (!params.noPrompts) {
|
||||
uiUtil.systemAlert(message, (translateUI.t('dialog-serviceworker-registration-failure-title') || 'Failed to register Service Worker')).then(function () {
|
||||
setContentInjectionMode('jquery');
|
||||
// We need to wait for the previous dialogue box to unload fully before attempting to display another
|
||||
setTimeout(function () {
|
||||
params.defaultModeChangeAlertDisplayed = false;
|
||||
settingsStore.removeItem('defaultModeChangeAlertDisplayed');
|
||||
checkAndDisplayInjectionModeChangeAlert();
|
||||
}, 1200);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -1200,7 +1229,7 @@ window.onpopstate = function (event) {
|
||||
var titleSearch = event.state.titleSearch;
|
||||
document.getElementById('prefix').value = '';
|
||||
document.getElementById('welcomeText').style.display = 'none';
|
||||
document.getElementById('searchingArticles').style.display = 'none';
|
||||
uiUtil.spinnerDisplay(false);
|
||||
$('.navbar-collapse').collapse('hide');
|
||||
document.getElementById('configuration').style.display = 'none';
|
||||
document.getElementById('articleListWithHeader').style.display = 'none';
|
||||
@ -1561,6 +1590,13 @@ function archiveReadyCallback (archive) {
|
||||
// A css cache significantly speeds up the loading of CSS files (used by default in jQuery mode)
|
||||
selectedArchive.cssCache = new Map();
|
||||
|
||||
if (selectedArchive.zimType !== 'zimit') {
|
||||
if (params.originalContentInjectionMode) {
|
||||
params.contentInjectionMode = params.originalContentInjectionMode;
|
||||
params.originalContentInjectionMode = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the Service Worker
|
||||
if (params.contentInjectionMode === 'serviceworker') {
|
||||
initServiceWorkerMessaging();
|
||||
@ -1658,7 +1694,7 @@ function searchDirEntriesFromPrefix (prefix) {
|
||||
if (activeContent) activeContent.style.display = 'none';
|
||||
selectedArchive.findDirEntriesWithPrefix(appstate.search, populateListOfArticles);
|
||||
} else {
|
||||
document.getElementById('searchingArticles').style.display = 'none';
|
||||
uiUtil.spinnerDisplay(false);
|
||||
// We have to remove the focus from the search field,
|
||||
// so that the keyboard does not stay above the message
|
||||
document.getElementById('searchArticles').focus();
|
||||
@ -1720,7 +1756,7 @@ function populateListOfArticles (dirEntryArray, reportingSearch) {
|
||||
return false;
|
||||
});
|
||||
});
|
||||
if (!stillSearching) document.getElementById('searchingArticles').style.display = 'none';
|
||||
if (!stillSearching) uiUtil.spinnerDisplay(false);
|
||||
document.getElementById('articleListWithHeader').style.display = '';
|
||||
}
|
||||
|
||||
@ -1789,7 +1825,7 @@ function readArticle (dirEntry) {
|
||||
expectedArticleURLToBeDisplayed = dirEntry.namespace + '/' + dirEntry.url;
|
||||
// We must remove focus from UI elements in order to deselect whichever one was clicked (in both jQuery and SW modes),
|
||||
// but we should not do this when opening the landing page (or else one of the Unit Tests fails, at least on Chrome 58)
|
||||
if (!params.isLandingPage) document.getElementById('articleContent').contentWindow.focus();
|
||||
if (!params.isLandingPage) articleContainer.contentWindow.focus();
|
||||
// Show the spinner with a loading message
|
||||
var message = dirEntry.url.match(/(?:^|\/)([^/]{1,13})[^/]*?$/);
|
||||
message = message ? message[1] + '...' : '...';
|
||||
@ -1804,84 +1840,55 @@ function readArticle (dirEntry) {
|
||||
var encodedUrl = dirEntry.url.replace(/[^/]+/g, function (matchedSubstring) {
|
||||
return encodeURIComponent(matchedSubstring);
|
||||
});
|
||||
var iframeArticleContent = document.getElementById('articleContent');
|
||||
iframeArticleContent.onload = function () {
|
||||
// The content is fully loaded by the browser : we can hide the spinner
|
||||
document.getElementById('cachingAssets').textContent = translateUI.t('spinner-caching-assets') || 'Caching assets...';
|
||||
document.getElementById('cachingAssets').style.display = 'none';
|
||||
document.getElementById('searchingArticles').style.display = 'none';
|
||||
// Set the requested appTheme
|
||||
uiUtil.applyAppTheme(params.appTheme);
|
||||
// Display the iframe content
|
||||
document.getElementById('articleContent').style.display = '';
|
||||
// Deflect drag-and-drop of ZIM file on the iframe to Config
|
||||
if (!params.disableDragAndDrop) {
|
||||
var doc = iframeArticleContent.contentDocument ? iframeArticleContent.contentDocument.documentElement : null;
|
||||
var docBody = doc ? doc.getElementsByTagName('body') : null;
|
||||
docBody = docBody ? docBody[0] : null;
|
||||
if (docBody) {
|
||||
docBody.addEventListener('dragover', handleIframeDragover);
|
||||
docBody.addEventListener('drop', handleIframeDrop);
|
||||
}
|
||||
}
|
||||
resizeIFrame();
|
||||
|
||||
if (iframeArticleContent.contentWindow) {
|
||||
// Configure home key press to focus #prefix only if the feature is in active state
|
||||
if (params.useHomeKeyToFocusSearchBar) { iframeArticleContent.contentWindow.addEventListener('keydown', focusPrefixOnHomeKey); }
|
||||
if (params.openExternalLinksInNewTabs) {
|
||||
// Add event listener to iframe window to check for links to external resources
|
||||
iframeArticleContent.contentWindow.addEventListener('click', function (event) {
|
||||
// Find the closest enclosing A tag (if any)
|
||||
var clickedAnchor = uiUtil.closestAnchorEnclosingElement(event.target);
|
||||
if (clickedAnchor) {
|
||||
var href = clickedAnchor.getAttribute('href');
|
||||
// We assume that, if an absolute http(s) link is hardcoded inside an HTML string,
|
||||
// it means it's a link to an external website.
|
||||
// We also do it for ftp even if it's not supported any more by recent browsers...
|
||||
if (/^(?:http|ftp)/i.test(href)) {
|
||||
uiUtil.warnAndOpenExternalLinkInNewTab(event, clickedAnchor);
|
||||
} else if (/\.pdf([?#]|$)/i.test(href)) {
|
||||
// Due to the iframe sandbox, we have to prevent the PDF viewer from opening in the iframe and instead open it in a new tab
|
||||
event.preventDefault();
|
||||
window.open(clickedAnchor.href, '_blank');
|
||||
} else if (/\/[-ABCIJMUVWX]\/.+$/.test(clickedAnchor.href)) {
|
||||
// Show the spinner if it's a ZIM link, but not an anchor
|
||||
if (!~href.indexOf('#')) {
|
||||
var message = href.match(/(?:^|\/)([^/]{1,13})[^/]*?$/);
|
||||
message = message ? message[1] + '...' : '...';
|
||||
uiUtil.spinnerDisplay(true, (translateUI.t('spinner-loading') || 'Loading') + ' ' + message);
|
||||
// In case of false positive, ensure spinner is eventually hidden
|
||||
setTimeout(function () {
|
||||
uiUtil.spinnerDisplay(false);
|
||||
}, 4000);
|
||||
uiUtil.showSlidingUIElements();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// Reset UI when the article is unloaded
|
||||
iframeArticleContent.contentWindow.onunload = function () {
|
||||
// remove eventListener to avoid memory leaks
|
||||
iframeArticleContent.contentWindow.removeEventListener('keydown', focusPrefixOnHomeKey);
|
||||
var articleList = document.getElementById('articleList');
|
||||
var articleListHeaderMessage = document.getElementById('articleListHeaderMessage');
|
||||
while (articleList.firstChild) articleList.removeChild(articleList.firstChild);
|
||||
while (articleListHeaderMessage.firstChild) articleListHeaderMessage.removeChild(articleListHeaderMessage.firstChild);
|
||||
document.getElementById('articleListWithHeader').style.display = 'none';
|
||||
document.getElementById('prefix').value = '';
|
||||
document.getElementById('searchingArticles').style.display = '';
|
||||
};
|
||||
}
|
||||
};
|
||||
// Set up article onload handler
|
||||
articleLoader();
|
||||
|
||||
if (!isDirEntryExpectedToBeDisplayed(dirEntry)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// We put the ZIM filename as a prefix in the URL, so that browser caches are separate for each ZIM file
|
||||
iframeArticleContent.src = '../' + selectedArchive.file.name + '/' + dirEntry.namespace + '/' + encodedUrl;
|
||||
if (selectedArchive.zimType === 'zimit' && params.isLandingPage) {
|
||||
var archiveName = selectedArchive.file.name.replace(/\.zim\w{0,2}$/i, '');
|
||||
var cns = selectedArchive.getContentNamespace();
|
||||
// Support type 0 and type 1 Zimit archives
|
||||
var replayCns = cns === 'C' ? '/C/A/' : '/A/';
|
||||
var base = window.location.href.replace(/^(.*?\/)www\/.*$/, '$1');
|
||||
var prefix = base + selectedArchive.file.name + replayCns;
|
||||
// Open a new message channel to the ServiceWorker
|
||||
var zimitMessageChannel = new MessageChannel();
|
||||
zimitMessageChannel.port1.onmessage = function (event) {
|
||||
if (event.data.error) {
|
||||
console.error('Reading Zimit archives in ServiceWorker mode is not supported in this browser', event.data.error);
|
||||
handleUnsupportedReplayWorker(dirEntry);
|
||||
} else if (event.data.success) {
|
||||
// console.debug(event.data.success);
|
||||
appstate.isReplayWorkerAvailable = true;
|
||||
// We put the ZIM filename as a prefix in the URL, so that browser caches are separate for each ZIM file
|
||||
articleContainer.src = '../' + selectedArchive.file.name + '/' + dirEntry.namespace + '/' + encodedUrl;
|
||||
}
|
||||
};
|
||||
// If we are dealing with a Zimit ZIM, we need to instruct Replay to add the file as a new collection
|
||||
navigator.serviceWorker.controller.postMessage({
|
||||
msg_type: 'addColl',
|
||||
name: archiveName,
|
||||
prefix: prefix,
|
||||
file: { sourceUrl: 'proxy:' + prefix },
|
||||
root: true,
|
||||
skipExisting: false,
|
||||
extraConfig: {
|
||||
// prefix: prefix, // If not set, Replay will use the proxy URL (without the 'proxy:' prefix)
|
||||
sourceType: 'kiwix',
|
||||
notFoundPageUrl: './404.html'/*,
|
||||
baseUrl: base + selectedArchive.file.name + '/',
|
||||
baseUrlHashReplay: false */
|
||||
},
|
||||
topTemplateUrl: './www/topFrame.html'
|
||||
}, [zimitMessageChannel.port2]);
|
||||
} else {
|
||||
// We put the ZIM filename as a prefix in the URL, so that browser caches are separate for each ZIM file
|
||||
articleContainer.src = '../' + selectedArchive.file.name + '/' + dirEntry.namespace + '/' + encodedUrl;
|
||||
}
|
||||
} else {
|
||||
// In jQuery mode, we read the article content in the backend and manually insert it in the iframe
|
||||
if (dirEntry.isRedirect()) {
|
||||
@ -1902,6 +1909,166 @@ function readArticle (dirEntry) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the iframe to which to attach the onload event, and attaches it
|
||||
*/
|
||||
function articleLoader () {
|
||||
if (selectedArchive.zimType === 'zimit') {
|
||||
var doc = articleContainer.contentDocument || null;
|
||||
if (doc) {
|
||||
var replayIframe = doc.getElementById('replay_iframe');
|
||||
if (replayIframe) {
|
||||
replayIframe.onload = function () {
|
||||
articleLoadedSW(replayIframe);
|
||||
};
|
||||
}
|
||||
}
|
||||
} else {
|
||||
articleContainer.onload = function () {
|
||||
articleLoadedSW(articleContainer);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Postprocessing required after the article contents are loaded
|
||||
* @param {HTMLIFrameElement} iframeArticleContent The iframe containing the article content
|
||||
*/
|
||||
function articleLoadedSW (iframeArticleContent) {
|
||||
// The content is fully loaded by the browser : we can hide the spinner
|
||||
document.getElementById('cachingAssets').textContent = translateUI.t('spinner-caching-assets') || 'Caching assets...';
|
||||
document.getElementById('cachingAssets').style.display = 'none';
|
||||
uiUtil.spinnerDisplay(false);
|
||||
// Set the requested appTheme
|
||||
uiUtil.applyAppTheme(params.appTheme);
|
||||
// Display the iframe content
|
||||
iframeArticleContent.style.display = '';
|
||||
articleContainer.style.display = '';
|
||||
// Deflect drag-and-drop of ZIM file on the iframe to Config
|
||||
if (!params.disableDragAndDrop) {
|
||||
var doc = iframeArticleContent.contentDocument ? iframeArticleContent.contentDocument.documentElement : null;
|
||||
var docBody = doc ? doc.getElementsByTagName('body') : null;
|
||||
docBody = docBody ? docBody[0] : null;
|
||||
if (docBody) {
|
||||
docBody.ondragover = handleIframeDragover;
|
||||
docBody.ondrop = handleIframeDrop;
|
||||
}
|
||||
}
|
||||
resizeIFrame();
|
||||
|
||||
if (iframeArticleContent.contentWindow) {
|
||||
// Configure home key press to focus #prefix only if the feature is in active state
|
||||
if (params.useHomeKeyToFocusSearchBar) { iframeArticleContent.contentWindow.onkeydown = focusPrefixOnHomeKey; }
|
||||
if (params.openExternalLinksInNewTabs) {
|
||||
// Add event listener to iframe window to check for links to external resources
|
||||
iframeArticleContent.contentWindow.onclick = function (event) {
|
||||
// Find the closest enclosing A tag (if any)
|
||||
var clickedAnchor = uiUtil.closestAnchorEnclosingElement(event.target);
|
||||
// If the anchor has a passthrough property, then we have already checked it is safe, so we can return
|
||||
if (clickedAnchor && clickedAnchor.passthrough) return;
|
||||
if (clickedAnchor) {
|
||||
// Check for Zimit links that would normally be handled by the Replay Worker
|
||||
if (appstate.isReplayWorkerAvailable) {
|
||||
handleClickOnReplayLink(event, clickedAnchor);
|
||||
return;
|
||||
}
|
||||
// We assume that, if an absolute http(s) link is hardcoded inside an HTML string,
|
||||
// it means it's a link to an external website.
|
||||
// We also do it for ftp even if it's not supported any more by recent browsers...
|
||||
var href = clickedAnchor.getAttribute('href');
|
||||
if (/^(?:http|ftp)/i.test(href)) {
|
||||
uiUtil.warnAndOpenExternalLinkInNewTab(event, clickedAnchor);
|
||||
} else if (/\.pdf([?#]|$)/i.test(href) && selectedArchive.zimType !== 'zimit') {
|
||||
// Due to the iframe sandbox, we have to prevent the PDF viewer from opening in the iframe and instead open it in a new tab
|
||||
event.preventDefault();
|
||||
window.open(clickedAnchor.href, '_blank');
|
||||
} else if (/\/[-ABCIJMUVWX]\/.+$/.test(clickedAnchor.href)) {
|
||||
// Show the spinner if it's a ZIM link, but not an anchor
|
||||
if (!~href.indexOf('#')) {
|
||||
var message = href.match(/(?:^|\/)([^/]{1,13})[^/]*?$/);
|
||||
message = message ? message[1] + '...' : '...';
|
||||
uiUtil.spinnerDisplay(true, (translateUI.t('spinner-loading') || 'Loading') + ' ' + message);
|
||||
// In case of false positive, ensure spinner is eventually hidden
|
||||
setTimeout(function () {
|
||||
uiUtil.spinnerDisplay(false);
|
||||
}, 4000);
|
||||
uiUtil.showSlidingUIElements();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
// Reset UI when the article is unloaded
|
||||
iframeArticleContent.contentWindow.onunload = function () {
|
||||
iframeArticleContent.loader = false;
|
||||
// remove eventListener to avoid memory leaks
|
||||
// iframeArticleContent.contentWindow.removeEventListener('keydown', focusPrefixOnHomeKey);
|
||||
var articleList = document.getElementById('articleList');
|
||||
var articleListHeaderMessage = document.getElementById('articleListHeaderMessage');
|
||||
while (articleList.firstChild) articleList.removeChild(articleList.firstChild);
|
||||
while (articleListHeaderMessage.firstChild) articleListHeaderMessage.removeChild(articleListHeaderMessage.firstChild);
|
||||
document.getElementById('articleListWithHeader').style.display = 'none';
|
||||
document.getElementById('prefix').value = '';
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
// Handles a click on a Zimit link that has been processed by Wombat
|
||||
function handleClickOnReplayLink (ev, anchor) {
|
||||
var pseudoNamespace = selectedArchive.zimitPrefix.replace(/^(.*\/)[^/]{2,}\/$/, '$1');
|
||||
var pseudoDomainPath = anchor.hostname + anchor.pathname;
|
||||
var containingDocDomainPath = anchor.ownerDocument.location.hostname + anchor.ownerDocument.location.pathname;
|
||||
// If the paths are identical, then we are dealing with a link to an anchor in the same document, so we can return
|
||||
if (anchor.protocol !== document.location.protocol || pseudoDomainPath === containingDocDomainPath) return;
|
||||
var zimUrl = pseudoNamespace + pseudoDomainPath + anchor.search;
|
||||
// We are dealing with a ZIM link transformed by Wombat, so we need to reconstruct the ZIM link
|
||||
if (zimUrl) {
|
||||
ev.preventDefault();
|
||||
ev.stopPropagation();
|
||||
selectedArchive.getDirEntryByPath(zimUrl).then(function (dirEntry) {
|
||||
if (dirEntry) {
|
||||
var mimetype = dirEntry.getMimetype();
|
||||
// Due to the iframe sandbox, we have to prevent the PDF viewer from opening in the iframe and instead open it in a new tab
|
||||
// Note that some Replay PDFs have html mimetypes, or can be redirects to PDFs, we need to check the URL as well
|
||||
if (/pdf/i.test(mimetype) || /\.pdf(?:[#?]|$)/i.test(anchor.href)) {
|
||||
window.open(anchor.href, '_blank');
|
||||
/*
|
||||
} else if (/\bx?html\b/i.test(mimetype)) {
|
||||
// If the SW has gone to sleep, loading this way gives it a chance to reload configuration
|
||||
params.isLandingPage = false;
|
||||
readArticle(dirEntry); */
|
||||
} else {
|
||||
// Fingers crossed, let Replay handle this link
|
||||
anchor.passthrough = true;
|
||||
// Handle middle-clicks and ctrl-clicks
|
||||
if (ev.ctrlKey || ev.metaKey || ev.button === 1) {
|
||||
window.open(anchor.href, '_blank');
|
||||
} else {
|
||||
anchor.click();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If dirEntry was not-found, it's probably an external link, so warn user before opening a new tab/window
|
||||
uiUtil.warnAndOpenExternalLinkInNewTab(null, anchor);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function handleUnsupportedReplayWorker (unhandledDirEntry) {
|
||||
appstate.isReplayWorkerAvailable = false;
|
||||
params.originalContentInjectionMode = params.contentInjectionMode;
|
||||
params.contentInjectionMode = 'jquery';
|
||||
readArticle(unhandledDirEntry);
|
||||
// if (!params.hideActiveContentWarning) uiUtil.displayActiveContentWarning();
|
||||
uiUtil.systemAlert(translateUI.t('dialog-unsupported-archivetype-message') || '<p>You are attempting to open a Zimit-style archive, ' +
|
||||
'which is not unsupported by your browser version in ServiceWorker mode.</p><p>We have temporarily switched you to JQuery mode ' +
|
||||
'so you can view static content, but a lot of content is non-functional. If you can upgrade your browser, you will ' +
|
||||
'be able to access dynamic content.</p><p>Alternatively, you can try the Kiwix JS PWA, which supports some dynamic Zimit content ' +
|
||||
'in older browsers. Go to: <a href="https://pwa.kiwix.org" target="_blank">https://pwa.kiwix.org</a>.</p>',
|
||||
translateUI.t('dialog-unsupported-archivetype-title') || 'Unsupported archive type!');
|
||||
}
|
||||
|
||||
/**
|
||||
* Function that handles a message of the messageChannel.
|
||||
* It tries to read the content in the backend, and sends it back to the ServiceWorker
|
||||
@ -1912,11 +2079,16 @@ function handleMessageChannelMessage (event) {
|
||||
// We received a message from the ServiceWorker
|
||||
// The ServiceWorker asks for some content
|
||||
var title = event.data.title;
|
||||
if (selectedArchive.zimType === 'zimit') {
|
||||
// Zimit ZIMs store assets with the querystring, so we need to add it!
|
||||
title = title + event.data.search;
|
||||
}
|
||||
var messagePort = event.ports[0];
|
||||
var readFile = function (dirEntry) {
|
||||
if (dirEntry === null) {
|
||||
console.error('Title ' + title + ' not found in archive.');
|
||||
messagePort.postMessage({ action: 'giveContent', title: title, content: '' });
|
||||
console.warn('Title ' + title.replace(/^(.{1,160}).*/, '$1...') + ' not found in archive.');
|
||||
// DEV: We send null for the content, so that the ServiceWorker knows that the article was not found (as opposed to being merely empty)
|
||||
messagePort.postMessage({ action: 'giveContent', title: title, content: null, zimType: selectedArchive.zimType });
|
||||
} else if (dirEntry.isRedirect()) {
|
||||
selectedArchive.resolveRedirect(dirEntry, function (resolvedDirEntry) {
|
||||
var redirectURL = resolvedDirEntry.namespace + '/' + resolvedDirEntry.url;
|
||||
@ -1930,14 +2102,26 @@ function handleMessageChannelMessage (event) {
|
||||
// Let's read the content in the ZIM file
|
||||
selectedArchive.readBinaryFile(dirEntry, function (fileDirEntry, content) {
|
||||
var mimetype = fileDirEntry.getMimetype();
|
||||
// Show the spinner
|
||||
var shortTitle = dirEntry.getTitleOrUrl().replace(/^.*?([^/]{3,18})[^/]*\/?$/, '$1 ...');
|
||||
if (!/moved/i.test(shortTitle) && !/image|javascript|warc-headers|jsonp?/.test(mimetype)) {
|
||||
uiUtil.spinnerDisplay(true, (translateUI.t('spinner-loading') || 'Loading') + ' ' + shortTitle);
|
||||
clearTimeout(window.timeout);
|
||||
window.timeout = setTimeout(function () {
|
||||
uiUtil.spinnerDisplay(false);
|
||||
}, 1000);
|
||||
// Ensure the article onload event gets attached to the right iframe
|
||||
articleLoader();
|
||||
}
|
||||
// Let's send the content to the ServiceWorker
|
||||
var message = { action: 'giveContent', title: title, content: content.buffer, mimetype: mimetype };
|
||||
messagePort.postMessage(message, [content.buffer]);
|
||||
var buffer = content.buffer ? content.buffer : content;
|
||||
var message = { action: 'giveContent', title: title, content: buffer, mimetype: mimetype, zimType: selectedArchive.zimType };
|
||||
messagePort.postMessage(message);
|
||||
});
|
||||
}
|
||||
};
|
||||
selectedArchive.getDirEntryByPath(title).then(readFile).catch(function () {
|
||||
messagePort.postMessage({ action: 'giveContent', title: title, content: new Uint8Array() });
|
||||
messagePort.postMessage({ action: 'giveContent', title: title, content: new Uint8Array(), zimType: selectedArchive.zimType });
|
||||
});
|
||||
}
|
||||
|
||||
@ -1947,11 +2131,11 @@ var regexpZIMUrlWithNamespace = /^[./]*([-ABCIJMUVWX]\/.+)$/;
|
||||
// The case-insensitive regex below finds images, scripts, stylesheets and tracks with ZIM-type metadata and image namespaces.
|
||||
// It first searches for <img, <script, <link, etc., then scans forward to find, on a word boundary, either src=["'] or href=["']
|
||||
// (ignoring any extra whitespace), and it then tests the path of the URL with a non-capturing negative lookahead (?!...) that excludes
|
||||
// absolute URIs with protocols that conform to RFC 3986 (e.g. 'http:', 'data:'). It then captures the whole of the URL up until either
|
||||
// the opening delimiter (" or ', which is capture group \3) or a querystring or hash character (? or #). When the regex is used
|
||||
// below, it will be further processed to calculate the ZIM URL from the relative path. This regex can cope with legitimate single
|
||||
// quote marks (') in the URL.
|
||||
var regexpTagsWithZimUrl = /(<(?:img|script|link|track)\b[^>]*?\s)(?:src|href)(\s*=\s*(["']))(?![a-z][a-z0-9+.-]+:)(.+?)(?=\3|\?|#)/ig;
|
||||
// absolute URIs with protocols that conform to RFC 3986 (e.g. 'http:', 'data:'). It then captures the whole of the URL up until any
|
||||
// querystring (? character) which (if it is exists) is captured with its contents in another gourp. The regex then tests for the end
|
||||
// of the URL with the opening delimiter (" or ', which is capture group \3) or a hash character (#). When the regex is used below, it
|
||||
// will be further processed to calculate the ZIM URL from the relative path. This regex can cope with legitimate single quote marks (') in the URL.
|
||||
var regexpTagsWithZimUrl = /(<(?:img|script|link|track)\b[^>]*?\s)(?:src|href)(\s*=\s*(["']))(?![a-z][a-z0-9+.-]+:)(.+?)(\?.*?)?(?=\3|#)/ig;
|
||||
// Regex below tests the html of an article for active content [kiwix-js #466]
|
||||
// It inspects every <script> block in the html and matches in the following cases: 1) the script is of type "module"; 2) the script
|
||||
// loads a UI application called app.js, init.js, or other common scripts found in unsupported ZIMs; 3) the script block has inline
|
||||
@ -1964,6 +2148,10 @@ var regexpActiveContent = /<script\b(?:(?![^>]+src\b)|(?=[^>]*type=["']module["'
|
||||
// the link. This is currently the case for epub and pdf files in Project Gutenberg ZIMs -- add any further types you need
|
||||
// to support to this regex. The "zip" has been added here as an example of how to support further filetypes
|
||||
var regexpDownloadLinks = /^.*?\.epub([?#]|$)|^.*?\.pdf([?#]|$)|^.*?\.odt([?#]|$)|^.*?\.zip([?#]|$)/i;
|
||||
// A regex to find the Zimit prefix in a Zimit-based article
|
||||
var regexpGetZimitPrefix = /link\s+rel=["']canonical["']\s+href="https?:\/\/([^/"]+)/i;
|
||||
// A regex to find and help transform assets in an article in a Zimit-based archive
|
||||
var regexpZimitHtmlLinks = /(<(?:a|img|script|link|track|meta|iframe)\b[^>]*?[\s;])(?:src\b|href|url)\s*(=\s*(["']))(?=[./]+|https?)((?:[^>](?!\3|\?|#))+[^>])([^>]*>)/ig;
|
||||
|
||||
// A string to hold any anchor parameter in clicked ZIM URLs (as we must strip these to find the article in the ZIM)
|
||||
var anchorParameter;
|
||||
@ -1980,7 +2168,7 @@ function displayArticleContentInIframe (dirEntry, htmlArticle) {
|
||||
return;
|
||||
}
|
||||
// Display Bootstrap warning alert if the landing page contains active content
|
||||
if (!params.hideActiveContentWarning && !selectedArchive.zimType === 'zimit' && params.isLandingPage) {
|
||||
if (!params.hideActiveContentWarning && params.isLandingPage) {
|
||||
if (regexpActiveContent.test(htmlArticle)) {
|
||||
// Exempted scripts: active content warning will not be displayed if any listed script is in the html [kiwix-js #889]
|
||||
if (!/<script\b[^'"]+['"][^'"]*?mooc\.js/i.test(htmlArticle)) {
|
||||
@ -1992,20 +2180,69 @@ function displayArticleContentInIframe (dirEntry, htmlArticle) {
|
||||
// Calculate the current article's ZIM baseUrl to use when processing relative links
|
||||
var baseUrl = dirEntry.namespace + '/' + dirEntry.url.replace(/[^/]+$/, '');
|
||||
|
||||
// Add CSP to prevent external scripts and content - note that any existing CSP can only be hardened, not loosened
|
||||
htmlArticle = htmlArticle.replace(/(<head\b[^>]*>)\s*/, '$1\n <meta http-equiv="Content-Security-Policy" content="default-src \'self\' data: file: blob: about: \'unsafe-inline\' \'unsafe-eval\';"></meta>\n ');
|
||||
|
||||
// Transform as many Zimit-style URLs as possible to their ZIM equivalents
|
||||
if (selectedArchive.zimType === 'zimit') {
|
||||
var rootDirectory = dirEntry.url === dirEntry.url.replace(/^((?:A\/)?[^/]+\/?).*/, '$1');
|
||||
// Try to get the Zimit prefix from any canonical URL in the article
|
||||
var zimitPrefix = htmlArticle.match(regexpGetZimitPrefix);
|
||||
// If we couldn't get it, reconstruct it from the archive's zimitPrefix
|
||||
zimitPrefix = zimitPrefix ? zimitPrefix[1] : selectedArchive.zimitPrefix.replace(/^[CA]\/(?:A\/)?([^/]+).*/, '$1');
|
||||
zimitPrefix = (dirEntry.namespace === 'C' ? 'A/' : '') + zimitPrefix;
|
||||
htmlArticle = htmlArticle.replace(regexpZimitHtmlLinks, function (match, blockStart, equals, quote, relAssetUrl, blockClose) {
|
||||
var newBlock = match;
|
||||
var assetUrl = relAssetUrl;
|
||||
// console.log('Asset URL: ' + assetUrl);
|
||||
// Remove google analytics and other analytics files that cause stall
|
||||
if (/analytics|typepad.*stats|googleads|doubleclick|syndication/i.test(assetUrl)) return '';
|
||||
// For root-relative links, we need to add the zimitPrefix
|
||||
assetUrl = assetUrl.replace(/^\/(?!\/)/, dirEntry.namespace + '/' + zimitPrefix + '/');
|
||||
// For Zimit assets that begin with https: or // the zimitPrefix is derived from the URL
|
||||
assetUrl = assetUrl.replace(/^(?:https?:)?\/\//i, dirEntry.namespace + '/' + (dirEntry.namespace === 'C' ? 'A/' : ''));
|
||||
// For fully relative links, we have to remove any '..' if we are in root directory
|
||||
if (rootDirectory) assetUrl = assetUrl.replace(/^(\.\.\/?)+/, dirEntry.namespace + '/' + zimitPrefix + '/');
|
||||
newBlock = newBlock.replace(relAssetUrl, assetUrl);
|
||||
// console.debug('Transform: \n' + match + '\n -> ' + newBlock);
|
||||
return newBlock;
|
||||
});
|
||||
// Deal with image srcsets
|
||||
htmlArticle = htmlArticle.replace(/<img\b[^>]+srcset=["']([^"']+)/ig, function (match, srcset) {
|
||||
var srcsetArr = srcset.split(',');
|
||||
for (var i = 0; i < srcsetArr.length; i++) {
|
||||
// For root-relative links, we need to add the zimitPrefix
|
||||
srcsetArr[i] = srcsetArr[i].replace(/^\s?\/(?!\/)/, dirEntry.namespace + '/' + zimitPrefix + '/');
|
||||
// Zimit prefix is in the URL for absolute URLs
|
||||
srcsetArr[i] = srcsetArr[i].replace(/^(?:\s?https?:)?\/\//i, dirEntry.namespace + '/' + (dirEntry.namespace === 'C' ? 'A/' : ''));
|
||||
if (rootDirectory) srcsetArr[i] = srcsetArr[i].replace(/^(\.\.\/?)+/, dirEntry.namespace + '/' + zimitPrefix + '/');
|
||||
}
|
||||
match = match.replace(srcset, srcsetArr.join(', '));
|
||||
return match;
|
||||
});
|
||||
}
|
||||
|
||||
// Replaces ZIM-style URLs of img, script, link and media tags with a data-kiwixurl to prevent 404 errors [kiwix-js #272 #376]
|
||||
// This replacement also processes the URL relative to the page's ZIM URL so that we can find the ZIM URL of the asset
|
||||
// with the correct namespace (this works for old-style -,I,J namespaces and for new-style C namespace)
|
||||
htmlArticle = htmlArticle.replace(regexpTagsWithZimUrl, function (match, blockStart, equals, quote, relAssetUrl) {
|
||||
if (selectedArchive.zimitPrefix && baseUrl === selectedArchive.zimitPrefix) {
|
||||
// We are at the root of a Zimit archive, so we shouldn't allow any navigation higher than root
|
||||
relAssetUrl = relAssetUrl.replace(/^[./]+/, '');
|
||||
htmlArticle = htmlArticle.replace(regexpTagsWithZimUrl, function (match, blockStart, equals, quote, relAssetUrl, querystring) {
|
||||
// We need to save the query string if any for Zimit-style archives
|
||||
querystring = querystring || '';
|
||||
if (selectedArchive.zimType !== 'zimit') {
|
||||
var assetZIMUrl = uiUtil.deriveZimUrlFromRelativeUrl(relAssetUrl, baseUrl);
|
||||
// DEV: Note that deriveZimUrlFromRelativeUrl produces a *decoded* URL (and incidentally would remove any URI component)
|
||||
// We therefore re-encode the URI with encodeURI (which does not encode forward slashes) instead
|
||||
// of encodeURIComponent
|
||||
assetZIMUrl = encodeURI(assetZIMUrl);
|
||||
} else {
|
||||
// For Zimit-style ZIMs, we we have to remove any root path for jQuery mode to detect the asset
|
||||
// var rootPathToAsset = document.location.pathname.replace(/\/index.html.*/, '/') + selectedArchive.file.name + '/';
|
||||
// relAssetUrl = relAssetUrl.replace(rootPathToAsset, '');
|
||||
assetZIMUrl = relAssetUrl + querystring;
|
||||
}
|
||||
var assetZIMUrl = uiUtil.deriveZimUrlFromRelativeUrl(relAssetUrl, baseUrl);
|
||||
// DEV: Note that deriveZimUrlFromRelativeUrl produces a *decoded* URL (and incidentally would remove any URI component
|
||||
// if we had captured it). We therefore re-encode the URI with encodeURI (which does not encode forward slashes) instead
|
||||
// of encodeURIComponent.
|
||||
return blockStart + 'data-kiwixurl' + equals + encodeURI(assetZIMUrl);
|
||||
return blockStart + 'data-kiwixurl' + equals + assetZIMUrl;
|
||||
});
|
||||
|
||||
// We also need to process data:image/webp if the browser needs the WebPMachine
|
||||
if (webpMachine) htmlArticle = htmlArticle.replace(/(<img\b[^>]*?\s)src(\s*=\s*["'])(?=data:image\/webp)([^"']+)/ig, '$1data-kiwixurl$2$3');
|
||||
|
||||
@ -2168,8 +2405,8 @@ function displayArticleContentInIframe (dirEntry, htmlArticle) {
|
||||
anchorParameter = anchorParameter ? anchorParameter[1] : '';
|
||||
var zimUrl;
|
||||
if (selectedArchive.zimitPrefix && ~href.indexOf(selectedArchive.zimitPrefix)) {
|
||||
// It's already a full ZIM URL, so we can use it directly
|
||||
zimUrl = href;
|
||||
// It's already a full ZIM URL, so we can use it after stripping any anchor
|
||||
zimUrl = decodeURIComponent(href.replace(/#.*/, ''));
|
||||
} else {
|
||||
// It's a relative URL, so we need to calculate the full ZIM URL
|
||||
zimUrl = uiUtil.deriveZimUrlFromRelativeUrl(uriComponent, baseUrl);
|
||||
@ -2245,7 +2482,7 @@ function displayArticleContentInIframe (dirEntry, htmlArticle) {
|
||||
Array.prototype.slice.call(iframe.querySelectorAll('link[data-kiwixurl]')).forEach(function (link) {
|
||||
cssCount++;
|
||||
var linkUrl = link.getAttribute('data-kiwixurl');
|
||||
var url = decodeURIComponent(uiUtil.removeUrlParameters(linkUrl));
|
||||
var url = decodeURIComponent(selectedArchive.zimType === 'zimit' ? linkUrl : uiUtil.removeUrlParameters(linkUrl));
|
||||
if (selectedArchive.cssCache.has(url)) {
|
||||
var nodeContent = selectedArchive.cssCache.get(url);
|
||||
if (/stylesheet/i.test(link.rel)) uiUtil.replaceCSSLinkWithInlineCSS(link, nodeContent);
|
||||
@ -2283,7 +2520,7 @@ function displayArticleContentInIframe (dirEntry, htmlArticle) {
|
||||
if (cssFulfilled >= cssCount) {
|
||||
document.getElementById('cachingAssets').textContent = translateUI.t('spinner-caching-assets') || 'Caching assets...';
|
||||
document.getElementById('cachingAssets').style.display = 'none';
|
||||
document.getElementById('searchingArticles').style.display = 'none';
|
||||
uiUtil.spinnerDisplay(false);
|
||||
document.getElementById('articleContent').style.display = '';
|
||||
// We have to resize here for devices with On Screen Keyboards when loading from the article search list
|
||||
resizeIFrame();
|
||||
@ -2427,7 +2664,7 @@ function goToRandomArticle () {
|
||||
document.getElementById('searchingArticles').style.display = '';
|
||||
selectedArchive.getRandomDirEntry(function (dirEntry) {
|
||||
if (dirEntry === null || dirEntry === undefined) {
|
||||
document.getElementById('searchingArticles').style.display = 'none';
|
||||
uiUtil.spinnerDisplay(false);
|
||||
uiUtil.systemAlert(translateUI.t('dialog-randomarticle-error-message') || 'Error finding random article',
|
||||
translateUI.t('dialog-article-notfound-title') || 'Error: article not found');
|
||||
} else {
|
||||
@ -2461,35 +2698,16 @@ function goToMainArticle () {
|
||||
selectedArchive.getMainPageDirEntry(function (dirEntry) {
|
||||
if (dirEntry === null || dirEntry === undefined) {
|
||||
console.error('Error finding main article.');
|
||||
document.getElementById('searchingArticles').style.display = 'none';
|
||||
uiUtil.spinnerDisplay(false);
|
||||
document.getElementById('welcomeText').style.display = '';
|
||||
} else {
|
||||
// For now, this code doesn't support reading Zimit archives without error, so we warn the user and suggest some solutions
|
||||
if (selectedArchive.zimType === 'zimit') {
|
||||
uiUtil.systemAlert(translateUI.t('dialog-unsupported-archivetype-message') || '<p>You are attempting to open a Zimit-style archive, which is currently unsupported in this app.</p>' +
|
||||
'<p>A basic view of some static content is shown, but JavaScript and many hyperlinks are non-functional. ' +
|
||||
'There is more complete support for this kind of archive in the Kiwix JS PWA. Go to: ' +
|
||||
'<a href="https://pwa.kiwix.org" target="_blank">https://pwa.kiwix.org</a>.</p>' +
|
||||
'<p>Alternatively, you can use Kiwix Serve to serve this archive to your browser from localhost. ' +
|
||||
'Kiwix Serve is included with <a href="https://www.kiwix.org/applications/" target="_blank">Kiwix Desktop</a>.</p>',
|
||||
translateUI.t('dialog-unsupported-archivetype-title') || 'Unsupported archive type!');
|
||||
document.getElementById('searchingArticles').style.display = 'none';
|
||||
// document.getElementById('welcomeText').style.display = '';
|
||||
// Some basic support for displaying Zimit content is available if we set the contentInjectionMode to jquery, storing the original value
|
||||
params.originalContentInjectionMode = params.originalContentInjectionMode || params.contentInjectionMode;
|
||||
params.contentInjectionMode = 'jquery';
|
||||
} else {
|
||||
// Restore the contentInjectionMode to its original value if it was changed above
|
||||
params.contentInjectionMode = params.originalContentInjectionMode || params.contentInjectionMode;
|
||||
params.originalContentInjectionMode = null;
|
||||
}
|
||||
// DEV: see comment above under goToRandomArticle()
|
||||
if (dirEntry.redirect || dirEntry.getMimetype() === 'text/html' || dirEntry.namespace === 'A') {
|
||||
params.isLandingPage = true;
|
||||
readArticle(dirEntry);
|
||||
} else {
|
||||
console.error('The main page of this archive does not seem to be an article');
|
||||
document.getElementById('searchingArticles').style.display = 'none';
|
||||
uiUtil.spinnerDisplay(false);
|
||||
document.getElementById('welcomeText').style.display = '';
|
||||
}
|
||||
}
|
||||
|
@ -77,7 +77,7 @@ var params = {};
|
||||
* WARNING: Only change these parameters if you know what you are doing
|
||||
*/
|
||||
// The current version number of this app
|
||||
params['appVersion'] = '3.11.1'; // **IMPORTANT** Ensure this is the same as the version number in service-worker.js
|
||||
params['appVersion'] = '3.11.2'; // **IMPORTANT** Ensure this is the same as the version number in service-worker.js
|
||||
// The PWA server (for use with the browser extensions in ServiceWorker mode)
|
||||
params['PWAServer'] = 'https://browser-extension.kiwix.org/current/'; // Include final slash!
|
||||
// params['PWAServer'] = 'https://kiwix.github.io/kiwix-js/'; // DEV: Uncomment this line for testing code on GitHub Pages
|
||||
|
@ -165,7 +165,7 @@ function idxDB (keyOrCommand, valueOrCallback, callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete all non-curren IdxDB databases (only works in Chromium currently)
|
||||
// Delete all non-current IdxDB databases (only works in Chromium currently)
|
||||
if (keyOrCommand === 'deleteNonCurrent') {
|
||||
if (indexedDB.databases) {
|
||||
var result = 0;
|
||||
|
@ -65,7 +65,7 @@ function getBestAvailableStorageAPI () {
|
||||
/**
|
||||
* Performs a full app reset, deleting all caches and settings
|
||||
* Or, if a parameter is supplied, deletes or disables the object
|
||||
* @param {String} object Optional name of the object to disable or delete ('cookie', 'localStorage', 'cacheAPI')
|
||||
* @param {String} object Optional name of the object to disable or delete ('cookie', 'localStorage', 'indexedDB', 'cacheAPI')
|
||||
*/
|
||||
function reset (object) {
|
||||
// 1. Clear any cookie entries
|
||||
@ -92,7 +92,40 @@ function reset (object) {
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Clear any Cache API caches
|
||||
// 3. Clear any IndexedDB databases
|
||||
if (!object || object === 'indexedDB') {
|
||||
if (window.indexedDB) {
|
||||
// Attempt to delete all databases (only works in Chromium-based browsers)
|
||||
if (indexedDB.databases) {
|
||||
var result = 0;
|
||||
indexedDB.databases().then(function (dbs) {
|
||||
dbs.forEach(function (db) {
|
||||
result++;
|
||||
indexedDB.deleteDatabase(db.name);
|
||||
console.debug('Deleting ' + db.name + '...');
|
||||
});
|
||||
}).then(function () {
|
||||
console.debug('Deleted ' + result + ' indexedDB databases...');
|
||||
}).catch(function (err) {
|
||||
console.error('Error deleting indexedDB databases', err);
|
||||
});
|
||||
} else {
|
||||
// For Firefox, we can only delete databases we know the names of
|
||||
var dbNames = [params.cacheIDB, 'collDB'];
|
||||
dbNames.forEach(function (dbName) {
|
||||
var deleteRequest = indexedDB.deleteDatabase(dbName);
|
||||
deleteRequest.onsuccess = function () {
|
||||
console.debug('Deleted ' + dbName + '...');
|
||||
}
|
||||
deleteRequest.onerror = function (ev) {
|
||||
console.error('Error deleting ' + dbName + '...', ev);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Clear any Cache API caches
|
||||
if (!object || object === 'cacheAPI') {
|
||||
getCacheNames(function (cacheNames) {
|
||||
if (cacheNames && !cacheNames.error) {
|
||||
|
@ -33,6 +33,7 @@ import translateUI from './translateUI.js';
|
||||
const header = document.getElementById('top');
|
||||
const footer = document.getElementById('footer');
|
||||
const activeContent = document.getElementById('activeContent');
|
||||
let articleContainer = document.getElementById('articleContent');
|
||||
|
||||
/**
|
||||
* Hides slide-away UI elements
|
||||
@ -43,7 +44,7 @@ function hideSlidingUIElements () {
|
||||
const footerStyles = getComputedStyle(footer);
|
||||
const footerHeight = parseFloat(footerStyles.height) + parseFloat(footerStyles.marginTop) - 2;
|
||||
const headerStyles = getComputedStyle(header);
|
||||
const headerHeight = parseFloat(headerStyles.height) + parseFloat(headerStyles.marginBottom) - 2;
|
||||
const headerHeight = parseFloat(headerStyles.height) + parseFloat(headerStyles.marginBottom) + 8;
|
||||
const iframeHeight = parseFloat(articleElement.style.height.replace('px', ''));
|
||||
footer.style.transform = 'translateY(' + footerHeight + 'px)';
|
||||
articleContainer.style.height = iframeHeight + headerHeight + 'px';
|
||||
@ -59,7 +60,7 @@ function showSlidingUIElements () {
|
||||
const articleContainer = document.getElementById('articleContent');
|
||||
const articleElement = document.querySelector('article');
|
||||
const headerStyles = getComputedStyle(document.getElementById('top'));
|
||||
const headerHeight = parseFloat(headerStyles.height) + parseFloat(headerStyles.marginBottom);
|
||||
const headerHeight = parseFloat(headerStyles.height) + parseFloat(headerStyles.marginBottom) - 2;
|
||||
header.style.transform = 'translateY(0)';
|
||||
// Needed for Windows Mobile to prevent header disappearing beneath iframe
|
||||
articleElement.style.transform = 'translateY(-1px)';
|
||||
@ -74,7 +75,12 @@ let scrollThrottle = false;
|
||||
* Luuncher for the slide-away function, including a throttle to prevent it being called too often
|
||||
*/
|
||||
function scroller (e) {
|
||||
const articleContainer = document.getElementById('articleContent');
|
||||
// We have to refresh the articleContainer when the window changes
|
||||
articleContainer = document.getElementById('articleContent');
|
||||
// Get the replay_iframe if it exists
|
||||
if (articleContainer.contentWindow && articleContainer.contentWindow.document && articleContainer.contentWindow.document.getElementById('replay_iframe')) {
|
||||
articleContainer = articleContainer.contentWindow.document.getElementById('replay_iframe');
|
||||
}
|
||||
if (scrollThrottle) return;
|
||||
// windowIsScrollable gets set and reset in slideAway()
|
||||
if (windowIsScrollable && e.type === 'wheel') return;
|
||||
@ -117,7 +123,6 @@ let windowIsScrollable = false;
|
||||
|
||||
// Slides away or restores the header and footer
|
||||
function slideAway (e) {
|
||||
const articleContainer = document.getElementById('articleContent');
|
||||
const newScrollY = articleContainer.contentWindow.pageYOffset;
|
||||
let delta;
|
||||
const visibleState = /\(0p?x?\)/.test(header.style.transform);
|
||||
@ -531,7 +536,7 @@ function displayFileDownloadAlert (title, download, contentType, content) {
|
||||
});
|
||||
}
|
||||
}
|
||||
document.getElementById('searchingArticles').style.display = 'none';
|
||||
spinnerDisplay(false);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -873,7 +878,8 @@ function applyAppTheme (theme) {
|
||||
// If we are in Config and a real document has been loaded already, expose return link so user can see the result of the change
|
||||
// DEV: The Placeholder string below matches the dummy article.html that is loaded before any articles are loaded
|
||||
if (document.getElementById('liConfigureNav').classList.contains('active') && doc &&
|
||||
doc.title !== 'Placeholder for injecting an article into the iframe') {
|
||||
// Check if the document contains a meta element with name="description"
|
||||
!(doc.querySelector('meta[content="Placeholder for injecting an article into the iframe or window"]'))) {
|
||||
showReturnLink();
|
||||
}
|
||||
}
|
||||
@ -921,12 +927,14 @@ function reportSearchProviderToAPIStatusPanel (provider) {
|
||||
/**
|
||||
* Warn the user that he/she clicked on an external link, and open it in a new tab
|
||||
*
|
||||
* @param {Event} event the click event (on an anchor) to handle
|
||||
* @param {Element} clickedAnchor the DOM anchor that has been clicked (optional, defaults to event.target)
|
||||
* @param {Event} event The click event (on an anchor) to handle. If not provided, then clickedAnchor must be provided.
|
||||
* @param {Element} clickedAnchor The DOM anchor that has been clicked (optional, defaults to event.target).
|
||||
*/
|
||||
function warnAndOpenExternalLinkInNewTab (event, clickedAnchor) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
if (!clickedAnchor) clickedAnchor = event.target;
|
||||
var target = clickedAnchor.target;
|
||||
var message = translateUI.t('dialog-open-externalurl-message') || '<p>Do you want to open this external link?';
|
||||
|
@ -547,7 +547,7 @@ ZIMArchive.prototype.resolveRedirect = function (dirEntry, callback) {
|
||||
*/
|
||||
ZIMArchive.prototype.readUtf8File = function (dirEntry, callback) {
|
||||
var that = this;
|
||||
dirEntry.readData().then(function (data) {
|
||||
return dirEntry.readData().then(function (data) {
|
||||
var html = utf8.parse(data);
|
||||
if (that && that.zimType === 'zimit' && params.isLandingPage) {
|
||||
// We are dealing with a Zimit ZIM, so we need to extract the redirect from the landing page
|
||||
@ -559,7 +559,7 @@ ZIMArchive.prototype.readUtf8File = function (dirEntry, callback) {
|
||||
// Store a full Zimit prefix in the archive object
|
||||
that.zimitPrefix = dirEntry.namespace + '/' + relativeZimitPrefix + '/';
|
||||
// Mark the directory entry as a redirect
|
||||
dirEntry.isredirect = true;
|
||||
// dirEntry.isredirect = true;
|
||||
dirEntry.zimitRedirect = zimitStartPage;
|
||||
}
|
||||
}
|
||||
@ -578,7 +578,23 @@ ZIMArchive.prototype.readUtf8File = function (dirEntry, callback) {
|
||||
* @param {callbackBinaryContent} callback
|
||||
*/
|
||||
ZIMArchive.prototype.readBinaryFile = function (dirEntry, callback) {
|
||||
var that = this;
|
||||
return dirEntry.readData().then(function (data) {
|
||||
if (that && that.zimType === 'zimit' && params.isLandingPage && /\bx?html/i.test(dirEntry.getMimetype())) {
|
||||
// We are dealing with a Zimit ZIM, so we need to extract the redirect from the landing page
|
||||
var contents = utf8.parse(data);
|
||||
var redirect = contents.match(/window\.mainUrl\s*=\s*(['"])https?:\/\/([^/]+)(.+?)\1/);
|
||||
if (redirect && redirect[2] && redirect[3]) {
|
||||
// Logic added to distinguish between Type 0 and Type 1 Zimit ZIMs
|
||||
var relativeZimitPrefix = (dirEntry.namespace === 'C' ? 'A/' : '') + redirect[2];
|
||||
var zimitStartPage = dirEntry.namespace + '/' + relativeZimitPrefix + redirect[3];
|
||||
// Store a full Zimit prefix in the archive object
|
||||
that.zimitPrefix = dirEntry.namespace + '/' + relativeZimitPrefix + '/';
|
||||
// Mark the directory entry as a redirect
|
||||
// dirEntry.isredirect = true;
|
||||
dirEntry.zimitRedirect = zimitStartPage;
|
||||
}
|
||||
}
|
||||
callback(dirEntry, data);
|
||||
});
|
||||
};
|
||||
|
96
www/topFrame.html
Normal file
96
www/topFrame.html
Normal file
@ -0,0 +1,96 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self' data: file: blob: about: 'unsafe-inline' 'unsafe-eval'; frame-src 'self' moz-extension: chrome-extension:; object-src 'none';">
|
||||
<meta charset="utf-8"/>
|
||||
<style>
|
||||
html, body
|
||||
{
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: scroll;
|
||||
}
|
||||
|
||||
</style>
|
||||
</head>
|
||||
<body style="margin: 0; padding: 0;">
|
||||
<iframe id="replay_iframe" frameborder="0" seamless="seamless" scrolling="yes" class="wb_iframe" allow="autoplay; fullscreen"></iframe>
|
||||
<script>
|
||||
const prefix = "$PREFIX";
|
||||
const startUrl = "$URL";
|
||||
|
||||
const iframe = document.querySelector("iframe");
|
||||
|
||||
// update URL when iframe changes
|
||||
window.addEventListener("message", function() {
|
||||
if (event.data.wb_type === "load" || event.data.wb_type === "replace-url") {
|
||||
|
||||
if (event.data.title) {
|
||||
document.title = event.data.title;
|
||||
}
|
||||
|
||||
// remove scheme to be consistent with current canonicalization
|
||||
const urlNoScheme = event.data.url.slice(event.data.url.indexOf("//") + 2);
|
||||
window.history.replaceState(null, "", prefix + urlNoScheme);
|
||||
|
||||
// if icons received, replace any existing icons with new ones
|
||||
if (event.data.icons) {
|
||||
const head = document.querySelector('head');
|
||||
const oldLinks = document.querySelectorAll("link[rel*='icon']");
|
||||
|
||||
for (const link of oldLinks) {
|
||||
head.removeChild(link);
|
||||
}
|
||||
|
||||
// attempt to load the default "<origin>/favicon.ico" if no other favicon is specified
|
||||
if (!event.data.icons.length) {
|
||||
event.data.icons = [{
|
||||
"href": prefix + "mp_/" + new URL("/favicon.ico", event.data.url),
|
||||
"rel": "icon"
|
||||
}];
|
||||
}
|
||||
|
||||
for (const icon of event.data.icons) {
|
||||
const link = document.createElement('link');
|
||||
link.rel = icon.rel;
|
||||
|
||||
const parts = icon.href.split("/mp_/", 2);
|
||||
|
||||
// probably an invalid URL
|
||||
if (parts.length < 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const url = parts[1];
|
||||
|
||||
const urlNoScheme = url.slice(url.indexOf("//") + 2);
|
||||
|
||||
// need to escape utf-8, then % encode the entire string
|
||||
let encodedUrl = encodeURI(urlNoScheme);
|
||||
encodedUrl = encodeURIComponent(urlNoScheme)
|
||||
|
||||
link.href = parts[0] + "/" + encodedUrl;
|
||||
|
||||
head.appendChild(link);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
iframe.src = prefix + "mp_/" + startUrl;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Loading…
x
Reference in New Issue
Block a user