mirror of
https://github.com/kiwix/kiwix-js-pwa.git
synced 2025-08-03 19:38:36 -04:00
parent
1f613e631c
commit
7a072ad6dd
@ -245,7 +245,9 @@
|
||||
<Content Include="www\img\icons\map_marker-30px.png" />
|
||||
<Content Include="www\img\icons\map_marker-18px.png" />
|
||||
<Content Include="www\img\icons\new_window.svg" />
|
||||
<Content Include="www\img\icons\new_window_black.svg" />
|
||||
<Content Include="www\img\icons\new_window_lb.svg" />
|
||||
<Content Include="www\img\icons\new_window_white.svg" />
|
||||
<Content Include="www\img\icons\wikimed-blue-32.png" />
|
||||
<Content Include="www\img\icons\wikimed-lightblue-32.png" />
|
||||
<Content Include="www\img\icons\wikivoyage-black-32.png" />
|
||||
|
@ -246,7 +246,9 @@
|
||||
<Content Include="www\img\icons\map_marker-30px.png" />
|
||||
<Content Include="www\img\icons\map_marker-18px.png" />
|
||||
<Content Include="www\img\icons\new_window.svg" />
|
||||
<Content Include="www\img\icons\new_window_black.svg" />
|
||||
<Content Include="www\img\icons\new_window_lb.svg" />
|
||||
<Content Include="www\img\icons\new_window_white.svg" />
|
||||
<Content Include="www\img\icons\wikimed-blue-32.png" />
|
||||
<Content Include="www\img\icons\wikimed-lightblue-32.png" />
|
||||
<Content Include="www\img\icons\wikivoyage-black-32.png" />
|
||||
|
@ -81,7 +81,7 @@ try switching the app to Restricted mode (see Content injection mode in Configur
|
||||
|
||||
+ <img src="images/googlechrome-color.svg" width="20" /> Google Chrome / Chromium >= 59 (and many browsers based on Chromium, e.g. Opera, Samsung Internet)
|
||||
+ <img src="images/microsoftedge-color.svg" width="20" /> Microsoft Edge (Chromium) >= 79
|
||||
+ <img src="images/firefoxbrowser-color.svg" width="20" /> Mozilla Firefox >= 60 (but see note about Android`*`)
|
||||
+ <img src="images/firefoxbrowser-color.svg" width="20" /> Mozilla Firefox >= 68 (but see note about Android`*`)
|
||||
+ <img src="images/safari-color.svg" width="20" /> Apple Safari >= 11.3 for iOS and macOS (full-text search only works on iOS 15+)
|
||||
+ <img src="images/edgelegacy-color.svg" width="22" /> Microsoft Edge Legacy 18 (Windows only)
|
||||
|
||||
@ -101,7 +101,7 @@ Although deprecated, we will keep support for as long as is practical:
|
||||
|
||||
* Internet Explorer 11 (Restricted mode only, no offline use of PWA)
|
||||
* Edge Legacy <= 17 (Restricted mode only, no offline use of PWA)
|
||||
* Firefox 45-59 (some versions require the user to switch manually to Restricted mode)
|
||||
* Firefox 45-67 (some versions require the user to switch manually to Restricted mode, and some are unable to display WebP images)
|
||||
* Chromium 49-58 (some versions only run in Restricted mode)
|
||||
|
||||
## Reporting bugs and technical support
|
||||
|
@ -182,6 +182,10 @@ const precacheFiles = [
|
||||
'www/img/icons/wikivoyage-white-32.png',
|
||||
'www/img/icons/map_marker-30px.png',
|
||||
'www/img/icons/map_marker-18px.png',
|
||||
'www/img/icons/new_window.svg',
|
||||
'www/img/icons/new_window_black.svg',
|
||||
'www/img/icons/new_window_lb.svg',
|
||||
'www/img/icons/new_window_white.svg',
|
||||
'www/img/spinner.gif',
|
||||
'www/index.html',
|
||||
'www/article.html',
|
||||
|
@ -170,7 +170,7 @@ tr[style*="background: antiquewhite"], tr[style*="background-color:#ee"], tr[sty
|
||||
.mw-ui-button[style*="background"], .mw-ui-button[style*="background"] *, .wikiEditor-ui,
|
||||
table.navbox.collapsible tr:nth-child(2) > td, div.menu, div.NavHead, .oo-ui-popupWidget-popup,
|
||||
.oo-ui-buttonElement-button, .mw-notification, .mwe-popups, .mwe-popups-is-not-tall, .mwe-popups-is-tall,
|
||||
.ui-widget-content, .oo-ui-window-body, #pagehistory li.selected, .tracklist tr, .dataTable tr {
|
||||
.ui-widget-content, .oo-ui-window-body, #pagehistory li.selected, .tracklist tr, .dataTable tr, div.kiwixtooltip {
|
||||
background-color: #222 !important;
|
||||
}
|
||||
|
||||
|
8
www/img/icons/new_window_black.svg
Normal file
8
www/img/icons/new_window_black.svg
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
|
||||
<title>
|
||||
new window
|
||||
</title>
|
||||
<path style="fill:black; opacity:0.7;" d="M17 17H3V3h5V1H3a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-5h-2z"/>
|
||||
<path style="fill:black; opacity:0.7;" d="M19 1h-8l3.29 3.29-5.73 5.73 1.42 1.42 5.73-5.73L19 9V1z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 390 B |
8
www/img/icons/new_window_white.svg
Normal file
8
www/img/icons/new_window_white.svg
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
|
||||
<title>
|
||||
new window
|
||||
</title>
|
||||
<path style="fill:white; opacity:0.7;" d="M17 17H3V3h5V1H3a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-5h-2z"/>
|
||||
<path style="fill:white; opacity:0.7;" d="M19 1h-8l3.29 3.29-5.73 5.73 1.42 1.42 5.73-5.73L19 9V1z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 390 B |
@ -1137,7 +1137,7 @@
|
||||
<div class="row">
|
||||
<h4 class="panel-group-heading">Performance / compatibility</h4>
|
||||
<div class="panel panel-warning" id="performanceSettingsDiv">
|
||||
<div class="panel-heading">Speed up archive access</div>
|
||||
<div class="panel-heading">Caching and preview settings</div>
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
@ -1169,6 +1169,11 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<label class="checkbox" title="Shows a small popup preview of Wikipedia and Wikivoyage articles when the pointer is hovered over an article link. Turn this off if it is too slow or interferes wtih display of articles on small-screen devices.">
|
||||
<input type="checkbox" name="showPopoverPreviews" id="showPopoverPreviewsCheck">
|
||||
<span class="checkmark"></span>
|
||||
<b>Show a popover preview of <i>Wikipedia / Wkivoyage</i> articles when hovering over links</b> (<i>limited functionality in Restricted mode</i>)
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-warning" style="min-width:16em;" id="mathsProcessingMode">
|
||||
|
118
www/js/app.js
118
www/js/app.js
@ -2096,6 +2096,11 @@ function setWindowOpenerUI () {
|
||||
newWin.style.display = 'none';
|
||||
}
|
||||
}
|
||||
document.getElementById('showPopoverPreviewsCheck').addEventListener('change', function (e) {
|
||||
params.showPopoverPreviews = e.target.checked;
|
||||
settingsStore.setItem('showPopoverPreviews', params.showPopoverPreviews, Infinity);
|
||||
params.themeChanged = true;
|
||||
});
|
||||
document.getElementById('allowHTMLExtractionCheck').addEventListener('click', function (e) {
|
||||
params.allowHTMLExtraction = e.target.checked;
|
||||
var alertMessage = '';
|
||||
@ -5205,11 +5210,8 @@ function articleLoader (entry, mimeType) {
|
||||
// Add event listener to iframe window to check for links to external resources
|
||||
function filterClickEvent (event) {
|
||||
// console.debug('filterClickEvent fired');
|
||||
if (params.contentInjectionMode === 'jquery') return;
|
||||
// Ignore click if we are dealing with an image that has not yet been extracted
|
||||
if (event.target.dataset && event.target.dataset.kiwixhidden) return;
|
||||
// Trap clicks in the iframe to restore Fullscreen mode
|
||||
if (params.lockDisplayOrientation) refreshFullScreen(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
|
||||
@ -5217,6 +5219,11 @@ function filterClickEvent (event) {
|
||||
clickedAnchor.passthrough = false;
|
||||
return;
|
||||
}
|
||||
// Remove any Kiwix Popovers that may be hanging around
|
||||
uiUtil.removeKiwixPopoverDivs(event.target.ownerDocument);
|
||||
if (params.contentInjectionMode === 'jquery') return;
|
||||
// Trap clicks in the iframe to restore Fullscreen mode
|
||||
if (params.lockDisplayOrientation) refreshFullScreen(event);
|
||||
if (clickedAnchor) {
|
||||
// Check for Zimit links that would normally be handled by the Replay Worker
|
||||
// DEV: '__WB_pmw' is a function inserted by wombat.js, so this detects links that have been rewritten in zimit2 archives
|
||||
@ -5354,6 +5361,9 @@ var articleLoadedSW = function (dirEntry, container) {
|
||||
anchorParameter = '';
|
||||
}
|
||||
if (dirEntry) uiUtil.makeReturnLink(dirEntry.getTitleOrUrl());
|
||||
if (appstate.wikimediaZimLoaded && params.showPopoverPreviews) {
|
||||
uiUtil.attachKiwixPopoverCss(doc, params.cssTheme === 'darkReader');
|
||||
}
|
||||
params.isLandingPage = false;
|
||||
} else {
|
||||
// If we havent' loaded a text-type document, we probably haven't finished loading
|
||||
@ -6518,7 +6528,9 @@ function displayArticleContentInContainer (dirEntry, htmlArticle) {
|
||||
parseAnchorsJQuery(dirEntry);
|
||||
loadCSSJQuery();
|
||||
images.prepareImagesJQuery(articleWindow);
|
||||
// loadJavascript(); //Disabled for now, since it does nothing - also, would have to load before images, ideally through controlled css loads above
|
||||
if (appstate.wikimediaZimLoaded && params.showPopoverPreviews) {
|
||||
uiUtil.attachKiwixPopoverCss(articleWindow.document);
|
||||
}
|
||||
var determinedTheme = params.cssTheme === 'auto' ? cssUIThemeGetOrSet('auto') : params.cssTheme;
|
||||
if (params.allowHTMLExtraction && appstate.target === 'iframe') {
|
||||
uiUtil.insertBreakoutLink(determinedTheme);
|
||||
@ -6547,6 +6559,8 @@ function displayArticleContentInContainer (dirEntry, htmlArticle) {
|
||||
}
|
||||
anchorParameter = '';
|
||||
}
|
||||
// Trap clicks in the iframe (currently only used for removing popovers in Restricted mode)
|
||||
articleWindow.onclick = filterClickEvent;
|
||||
params.isLandingPage = false;
|
||||
};
|
||||
|
||||
@ -6711,15 +6725,14 @@ function loadCSSJQuery () {
|
||||
// These sections can be opened by clicking on them, but this is done with some javascript.
|
||||
// The code below is a workaround we still need for compatibility with ZIM files generated by mwoffliner in 2018.
|
||||
// A better fix has been made for more recent ZIM files, with the use of noscript tags : see https://github.com/openzim/mwoffliner/issues/324
|
||||
var iframe = articleContainer.contentDocument;
|
||||
var collapsedBlocks = iframe.querySelectorAll('.collapsible-block:not(.open-block), .collapsible-heading:not(.open-block)');
|
||||
var collapsedBlocks = articleDocument.querySelectorAll('.collapsible-block:not(.open-block), .collapsible-heading:not(.open-block)');
|
||||
// Using decrementing loop to optimize performance : see https://stackoverflow.com/questions/3520688
|
||||
for (var i = collapsedBlocks.length; i--;) {
|
||||
collapsedBlocks[i].classList.add('open-block');
|
||||
}
|
||||
var cssCount = 0;
|
||||
var cssFulfilled = 0;
|
||||
Array.prototype.slice.call(iframe.querySelectorAll('link[data-kiwixurl]')).forEach(function (link) {
|
||||
Array.prototype.slice.call(articleDocument.querySelectorAll('link[data-kiwixurl]')).forEach(function (link) {
|
||||
cssCount++;
|
||||
var linkUrl = link.getAttribute('data-kiwixurl');
|
||||
var url = decodeURIComponent(/zimit/.test(appstate.selectedArchive.zimType) ? linkUrl : uiUtil.removeUrlParameters(linkUrl));
|
||||
@ -6804,6 +6817,7 @@ function addListenersToLink (a, href, baseUrl) {
|
||||
a.newcontainer = false;
|
||||
}
|
||||
loadingContainer = false;
|
||||
a.articleloading = false;
|
||||
};
|
||||
var onDetectedClick = function (e) {
|
||||
// Restore original values for this window/tab
|
||||
@ -6875,31 +6889,59 @@ function addListenersToLink (a, href, baseUrl) {
|
||||
setTimeout(reset, 1400);
|
||||
};
|
||||
|
||||
var darkTheme = (params.cssUITheme == 'auto' ? cssUIThemeGetOrSet('auto', true) : params.cssUITheme) !== 'light';
|
||||
|
||||
/* Event processing */
|
||||
a.addEventListener('touchstart', function (e) {
|
||||
// console.debug('a.touchstart');
|
||||
var timeout = 500;
|
||||
if (!appstate.wikimediaZimLoaded || !params.showPopoverPreviews) {
|
||||
if (!params.windowOpener || a.touched) return;
|
||||
e.stopPropagation();
|
||||
// e.preventDefault();
|
||||
a.touched = true;
|
||||
loadingContainer = true;
|
||||
} else {
|
||||
timeout = 200;
|
||||
}
|
||||
a.touched = true;
|
||||
var event = e;
|
||||
// The link will be clicked if the user long-presses for more than 800ms (if the option is enabled)
|
||||
// The link will be clicked if the user long-presses for more than 500ms (if the option is enabled), or 200ms for popover
|
||||
setTimeout(function () {
|
||||
// DEV: appstate.startVector indicates that the app is processing a touch zoom event, so we cancel any new windows
|
||||
// see uiUtil.pointermove_handler
|
||||
if (!a.touched || a.newcontainer || appstate.startVector) return;
|
||||
e.preventDefault();
|
||||
if (appstate.wikimediaZimLoaded && params.showPopoverPreviews) {
|
||||
a.dataset.touchevoked = true;
|
||||
uiUtil.attachKiwixPopoverDiv(event, a, baseUrl, darkTheme);
|
||||
} else {
|
||||
a.newcontainer = true;
|
||||
onDetectedClick(event);
|
||||
}, 800);
|
||||
}
|
||||
event.preventDefault();
|
||||
}, timeout);
|
||||
}, { passive: false });
|
||||
a.addEventListener('touchend', function () {
|
||||
// console.debug('a.touchend');
|
||||
a.touched = false;
|
||||
a.newcontainer = false;
|
||||
loadingContainer = false;
|
||||
// Cancel any popovers because user has clicked
|
||||
a.articleloading = true;
|
||||
setTimeout(reset, 1000);
|
||||
});
|
||||
// This detects right-click in all browsers (only if the option is enabled)
|
||||
a.addEventListener('contextmenu', function (e) {
|
||||
console.debug('contextmenu');
|
||||
// console.debug('contextmenu');
|
||||
if (appstate.wikimediaZimLoaded && params.showPopoverPreviews) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
// console.debug('suppressed contextmenu because processing popovers');
|
||||
var kiwixPopover = e.target.ownerDocument.querySelector('.kiwixtooltip');
|
||||
if (kiwixPopover) {
|
||||
// return;
|
||||
} else if (!a.touched) {
|
||||
a.touched = true;
|
||||
uiUtil.attachKiwixPopoverDiv(e, a, baseUrl, darkTheme);
|
||||
}
|
||||
} else {
|
||||
if (!params.windowOpener) return;
|
||||
if (params.rightClickType === 'double' && !a.touched) {
|
||||
a.touched = true;
|
||||
@ -6914,10 +6956,12 @@ function addListenersToLink (a, href, baseUrl) {
|
||||
a.touched = false;
|
||||
onDetectedClick(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
// This traps the middle-click event before tha auxclick event fires
|
||||
a.addEventListener('mousedown', function (e) {
|
||||
console.debug('mosuedown');
|
||||
// console.debug('a.mousedown');
|
||||
a.dataset.touchevoked = true; // This is needed to simulate touch events in UWP app
|
||||
if (!params.windowOpener) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
@ -6926,22 +6970,49 @@ function addListenersToLink (a, href, baseUrl) {
|
||||
a.newcontainer = true;
|
||||
onDetectedClick(e);
|
||||
} else {
|
||||
console.debug('suppressed mousedown');
|
||||
// console.debug('suppressed mousedown');
|
||||
}
|
||||
});
|
||||
// This detects the middle-click event that opens a new tab in recent Firefox and Chrome
|
||||
// See https://developer.mozilla.org/en-US/docs/Web/API/Element/auxclick_event
|
||||
a.addEventListener('auxclick', function (e) {
|
||||
console.debug('auxclick');
|
||||
// console.debug('a.auxclick');
|
||||
if (!params.windowOpener) return;
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
});
|
||||
// The popover feature requires as a minimum that the browser supports the css matches function
|
||||
// (having this condition prevents very erratic popover placement in IE11, for example, so the feature is disabled)
|
||||
if (appstate.wikimediaZimLoaded && params.showPopoverPreviews && 'matches' in Element.prototype) {
|
||||
a.addEventListener('mouseover', function (e) {
|
||||
// console.debug('a.mouseover');
|
||||
if (a.dataset.touchevoked) return;
|
||||
uiUtil.attachKiwixPopoverDiv(e, a, baseUrl, darkTheme);
|
||||
});
|
||||
a.addEventListener('mouseout', function (e) {
|
||||
if (a.dataset.touchevoked) return;
|
||||
uiUtil.removeKiwixPopoverDivs(e.target.ownerDocument);
|
||||
});
|
||||
a.addEventListener('focus', function (e) {
|
||||
setTimeout(function () { // Delay focus event so touchstart can fire first
|
||||
// console.debug('a.focus');
|
||||
if (a.touched) return;
|
||||
a.focused = true;
|
||||
uiUtil.attachKiwixPopoverDiv(e, a, baseUrl, darkTheme);
|
||||
}, 200);
|
||||
});
|
||||
a.addEventListener('blur', function (e) {
|
||||
// console.debug('a.blur');
|
||||
a.focused = false;
|
||||
});
|
||||
}
|
||||
// The main click routine (called by other events above as well)
|
||||
a.addEventListener('click', function (e) {
|
||||
console.log('Click event', e);
|
||||
console.log('a.click', e);
|
||||
// Cancel any popovers because user has clicked
|
||||
a.articleloading = true;
|
||||
// Prevent opening multiple windows
|
||||
if (loadingContainer || a.touched || a.newcontainer) {
|
||||
if (loadingContainer || a.touched) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
} else {
|
||||
@ -6969,15 +7040,6 @@ function displayHiddenBlockElements (win, doc) {
|
||||
'app to decide when to apply the setting. If you never want to see hidden elements, even in Wikimedia ZIMs, change the ' +
|
||||
'setting to <b>never</b>.</p>';
|
||||
}
|
||||
// else if (params.displayHiddenBlockElements === 'auto') {
|
||||
// message = '<p>There is a new <b>auto</b> setting in Configuration to display hidden elements (navigation boxes, series tables) ' +
|
||||
// "in Wikimedia ZIMs. This is now on by default. If you don't want to see these elements, change the 'Display hidden block elements' " +
|
||||
// "setting to <b>never</b> (under 'Display style').</p>";
|
||||
// if (params.cssSource !== 'desktop') {
|
||||
// message += '<p>Please note that hidden elements are <i>always</i> displayed in Desktop style (regardless of the setting)' +
|
||||
// (params.cssSource !== 'desktop' ? '. You can switch the display style to Desktop in Configuration' : '') + '.</p>';
|
||||
// }
|
||||
// }
|
||||
if (message) {
|
||||
message += '<p><i>This message will not be displayed again, unless you reset the app.</i></p>';
|
||||
params.noHiddenElementsWarning = true;
|
||||
|
@ -91,6 +91,7 @@ params['manipulateImages'] = getSetting('manipulateImages') != null ? getSetting
|
||||
params['linkToWikimediaImageFile'] = getSetting('linkToWikimediaImageFile') == true; // Links images to Wikimedia online version if ZIM archive is a Wikipedia archive
|
||||
params['hideToolbars'] = getSetting('hideToolbars') != null ? getSetting('hideToolbars') : true; // Set default to true (hides both), 'top' (hides top only), or false (no hiding)
|
||||
params['rememberLastPage'] = getSetting('rememberLastPage') != null ? getSetting('rememberLastPage') : true; // Set default option to remember the last visited page between sessions
|
||||
params['showPopoverPreviews'] = getSetting('showPopoverPreviews') !== false; // Allows popover previews of articles for Wikimedia ZIMs (defaults to true)
|
||||
params['assetsCache'] = getSetting('appCache') !== false; // Whether to use cache by default or not
|
||||
params['appCache'] = getSetting('appCache') !== false; // Will be true by default unless explicitly set to false
|
||||
params['useMathJax'] = getSetting('useMathJax') != null ? getSetting('useMathJax') : true; // Set default to true to display math formulae with MathJax, false to use fallback SVG images only
|
||||
@ -263,6 +264,7 @@ document.getElementById('hideToolbarsCheck').indeterminate = params.hideToolbars
|
||||
document.getElementById('hideToolbarsCheck').readOnly = params.hideToolbars === 'top';
|
||||
document.getElementById('hideToolbarsState').innerHTML = (params.hideToolbars === 'top' ? 'top' : params.hideToolbars ? 'both' : 'never');
|
||||
document.getElementById('openExternalLinksInNewTabsCheck').checked = params.openExternalLinksInNewTabs;
|
||||
document.getElementById('showPopoverPreviewsCheck').checked = params.showPopoverPreviews;
|
||||
document.getElementById('disableDragAndDropCheck').checked = params.disableDragAndDrop;
|
||||
document.getElementById('debugLibzimASMDrop').value = params.debugLibzimASM || '';
|
||||
if (params.debugLibzimASM === 'disable') document.getElementById('debugLibzimASMDrop').style.color = 'red';
|
||||
|
@ -538,6 +538,25 @@ function deriveZimUrlFromRelativeUrl (url, base) {
|
||||
return decodeURIComponent(zimUrl.pathname.replace(/^\//, ''));
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a new link element into the document header
|
||||
* @param {Element} doc The document to which to attach the new element
|
||||
* @param {String} cssContent The content to insert as an inline stylesheet
|
||||
* @param {String} id An optional id to add to the style element
|
||||
*/
|
||||
function insertLinkElement (doc, cssContent, id) {
|
||||
var cssElement = document.createElement('style');
|
||||
if (id) {
|
||||
cssElement.id = id;
|
||||
}
|
||||
if (cssElement.styleSheet) {
|
||||
cssElement.styleSheet.cssText = cssContent;
|
||||
} else {
|
||||
cssElement.appendChild(document.createTextNode(cssContent));
|
||||
}
|
||||
doc.head.appendChild(cssElement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Walk up the DOM tree to find the closest element where the tagname matches the supplied regular expression
|
||||
*
|
||||
@ -1396,6 +1415,344 @@ function lockDisplayOrientation (val) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a linked article in a loaded document in order to extract the first main paragraph (the 'lede') and first
|
||||
* main image (if any). This function currently only parses Wikimedia articles. It returns an HTML string, formatted
|
||||
* for display in a popover
|
||||
*
|
||||
* @param {String} href The href of the article link from which to extract the lede
|
||||
* @param {String} baseUrl The base URL of the currently loaded article
|
||||
* @param {Document} articleDocument The DOM of the currently loaded article
|
||||
* @returns {Promise<String>} A Promise for the linked article's lede HTML including first main image URL if any
|
||||
*/
|
||||
function getArticleLede (href, baseUrl, articleDocument) {
|
||||
var uriComponent = removeUrlParameters(href);
|
||||
var zimURL = deriveZimUrlFromRelativeUrl(uriComponent, baseUrl);
|
||||
console.debug('Previewing ' + zimURL);
|
||||
return appstate.selectedArchive.getDirEntryByPath(zimURL).then(function (dirEntry) {
|
||||
var readArticle = function (dirEntry) {
|
||||
return new Promise((resolve, reject) => {
|
||||
appstate.selectedArchive.readUtf8File(dirEntry, function (fileDirEntry, htmlArticle) {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(htmlArticle, 'text/html');
|
||||
// const articleBody = doc.getElementById('mw-content-text');
|
||||
const articleBody = doc.body;
|
||||
if (articleBody) {
|
||||
let balloonString = '';
|
||||
// Remove all standalone style elements, because their content is shown by both innerText and textContent
|
||||
const styleElements = Array.from(articleBody.querySelectorAll('style'));
|
||||
styleElements.forEach(style => {
|
||||
style.parentNode.removeChild(style);
|
||||
});
|
||||
const paragraphs = Array.from(articleBody.querySelectorAll('p'));
|
||||
// Filter out empty paragraphs or those with less than 50 characters
|
||||
const nonEmptyParagraphs = paragraphs.filter(para => {
|
||||
const text = para.innerText.trim();
|
||||
return !/^\s*$/.test(text) && text.length >= 50;
|
||||
});
|
||||
if (nonEmptyParagraphs.length > 0) {
|
||||
// Add two paras (becuase one sometimes isn't enough to fill the box)
|
||||
for (let i = 0; i < 2; i++) {
|
||||
// In Restricted mode, we risk breaking the UI if user clicks on an embedded link, so only use innerText
|
||||
var content = params.contentInjectionMode === 'jquery' ? nonEmptyParagraphs[i].innerText
|
||||
: nonEmptyParagraphs[i].innerHTML;
|
||||
balloonString += '<p>' + content + '</p>';
|
||||
}
|
||||
}
|
||||
const images = articleBody.querySelectorAll('img');
|
||||
let firstImage = null;
|
||||
if (images && params.contentInjectionMode === 'serviceworker') {
|
||||
// Iterate over images until we find one with a width greater than 50 pixels
|
||||
// (this filters out small icons)
|
||||
const imageArray = Array.from(images);
|
||||
for (let j = 0; j < imageArray.length; j++) {
|
||||
if (imageArray[j] && imageArray[j].width > 50) {
|
||||
firstImage = imageArray[j];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (firstImage) {
|
||||
// Calculate absolute URL of image
|
||||
var balloonBaseURL = encodeURI(fileDirEntry.namespace + '/' + fileDirEntry.url.replace(/[^/]+$/, ''));
|
||||
var imageZimURL = encodeURI(deriveZimUrlFromRelativeUrl(firstImage.getAttribute('src'), balloonBaseURL));
|
||||
var absolutePath = articleDocument.location.href.replace(/([^.]\.zim\w?\w?\/).+$/i, '$1');
|
||||
firstImage.src = absolutePath + imageZimURL;
|
||||
balloonString = firstImage.outerHTML + balloonString;
|
||||
}
|
||||
// console.debug(balloonString);
|
||||
if (!balloonString) {
|
||||
reject(new Error('No article lede or image'));
|
||||
} else {
|
||||
resolve(balloonString);
|
||||
}
|
||||
} else {
|
||||
reject(new Error('No article body found'));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
if (dirEntry.redirect) {
|
||||
return new Promise((resolve, reject) => {
|
||||
appstate.selectedArchive.resolveRedirect(dirEntry, function (reDirEntry) {
|
||||
resolve(readArticle(reDirEntry));
|
||||
});
|
||||
}).catch(error => {
|
||||
return Promise.reject(error);
|
||||
});
|
||||
} else {
|
||||
return Promise.resolve(readArticle(dirEntry));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* A function to attach the tooltip CSS for popovers (NB this does not attach the box itself, only the CSS)
|
||||
* @param {Document} doc The document to which to attach the blloon.css styelesheet
|
||||
* @param {Boolean} dark An optional parameter to adjust the background colour for dark themes
|
||||
*/
|
||||
function attachKiwixPopoverCss (doc, dark) {
|
||||
const colour = dark ? '#darkgray' : '#black';
|
||||
const backgroundColour = dark ? '#111' : '#ebf4fb';
|
||||
insertLinkElement(doc, `
|
||||
.kiwixtooltip {
|
||||
position: absolute;
|
||||
bottom: 1em;
|
||||
/* prettify */
|
||||
padding: 0 5px 5px;
|
||||
color: ${colour};
|
||||
background: ${backgroundColour};
|
||||
border: 0.1em solid #b7ddf2;
|
||||
/* round the corners */
|
||||
border-radius: 0.5em;
|
||||
/* handle overflow */
|
||||
overflow: visible;
|
||||
text-overflow: ellipsis;
|
||||
/* handle text wrap */
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
/* add fade-in transition */
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.kiwixtooltip img {
|
||||
float: right;
|
||||
margin-left: 5px;
|
||||
max-width: 40%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
#popcloseicon {
|
||||
padding-top: 1px;
|
||||
padding-right: 2px;
|
||||
font-size: 20px;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
#popcloseicon:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#popbreakouticon {
|
||||
height: 18px;
|
||||
margin-right: 18px;
|
||||
}
|
||||
|
||||
#popbreakouticon:hover {
|
||||
cursor: pointer;
|
||||
}`,
|
||||
// The id of the style element for easy manipulation
|
||||
'kiwixtooltipstylesheet'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches a popover div for the given link to the given document's DOM
|
||||
* @param {Event} ev The event which has fired this popover action
|
||||
* @param {Element} link The link element that is being actioned
|
||||
* @param {String} articleBaseUrl The base URL of the currently loaded document
|
||||
* @param {Boolean} dark An optional value to switch colour theme to dark if true
|
||||
*/
|
||||
function attachKiwixPopoverDiv (ev, link, articleBaseUrl, dark) {
|
||||
// Do not show popover if the user has initiated an article load
|
||||
if (link.articleloading) {
|
||||
// console.debug('Cancelled display of popover because user is loading the underlying article');
|
||||
return;
|
||||
}
|
||||
// Do not disply a popover if one is already showing for the current link
|
||||
var kiwixPopover = ev.target.ownerDocument.querySelector('.kiwixtooltip');
|
||||
var linkHref = link.getAttribute('href');
|
||||
if (kiwixPopover && kiwixPopover.dataset.href === linkHref) return;
|
||||
// console.debug('Attaching popover...');
|
||||
var currentDocument = ev.target.ownerDocument;
|
||||
var articleWindow = currentDocument.defaultView;
|
||||
removeKiwixPopoverDivs(currentDocument);
|
||||
setTimeout(function () {
|
||||
// Check if the link is still being hovered over, and abort display of popover if not
|
||||
if (!linkHref || !link.matches(':hover') && currentDocument.activeElement !== link) return;
|
||||
var div = document.createElement('div');
|
||||
var screenWidth = articleWindow.innerWidth - 40;
|
||||
var screenHeight = document.documentElement.clientHeight;
|
||||
var margin = 40;
|
||||
var divWidth = 512;
|
||||
if (screenWidth <= divWidth) {
|
||||
divWidth = screenWidth;
|
||||
margin = 10;
|
||||
}
|
||||
// Check if we have restricted screen height
|
||||
var divHeight = screenHeight < 512 ? 160 : 256;
|
||||
div.style.width = divWidth + 'px';
|
||||
div.style.height = divHeight + 'px';
|
||||
div.style.display = 'flex';
|
||||
div.style.justifyContent = 'center';
|
||||
div.style.alignItems = 'center';
|
||||
div.className = 'kiwixtooltip';
|
||||
div.innerHTML = '<p>Loading ...</p>';
|
||||
div.dataset.href = linkHref;
|
||||
currentDocument.body.appendChild(div);
|
||||
// Calculate the position of the link that is being hovered
|
||||
var linkRect = link.getBoundingClientRect();
|
||||
// Here's how to position it 40px above the pointer position (DEV: this doesn't work well due to lag)
|
||||
// var divRectY = e.clientY - div.offsetHeight - 20;
|
||||
// Initially position the div 20px above the link
|
||||
var triangleDirection = 'top';
|
||||
var divRectY = (linkRect.top - div.offsetHeight - 20);
|
||||
var triangleY = divHeight + 6;
|
||||
// If we're less than half margin from the top, move the div below the link
|
||||
if (divRectY < margin / 2) {
|
||||
triangleDirection = 'bottom';
|
||||
divRectY = linkRect.bottom + 20;
|
||||
triangleY = -16;
|
||||
}
|
||||
// Position it horizontally in relation to the pointer position
|
||||
var divRectX, triangleX;
|
||||
if (ev.type === 'touchstart') {
|
||||
divRectX = ev.touches[0].clientX - divWidth / 2;
|
||||
triangleX = ev.touches[0].clientX - divRectX - 20;
|
||||
} else if (ev.type === 'focus') {
|
||||
divRectX = linkRect.left + linkRect.width / 2 - divWidth / 2;
|
||||
triangleX = linkRect.left + linkRect.width / 2 - divRectX - 20;
|
||||
} else {
|
||||
divRectX = ev.clientX - divWidth / 2;
|
||||
triangleX = ev.clientX - divRectX - 20;
|
||||
}
|
||||
// If right edge of div is greater than margin from the right side of window, shift it to margin
|
||||
if (divRectX + divWidth > screenWidth - margin) {
|
||||
triangleX += divRectX;
|
||||
divRectX = screenWidth - divWidth - margin;
|
||||
triangleX -= divRectX;
|
||||
}
|
||||
// If we're less than margin to the left, shift it to margin px from left
|
||||
if (divRectX < margin) {
|
||||
triangleX += divRectX;
|
||||
divRectX = margin;
|
||||
triangleX -= divRectX;
|
||||
}
|
||||
// Adjust triangleX if necessary
|
||||
if (triangleX < 10) triangleX = 10;
|
||||
if (triangleX > divWidth - 10) triangleX = divWidth - 10;
|
||||
// Now set the calculated x and y positions
|
||||
div.style.top = divRectY + articleWindow.scrollY + 'px';
|
||||
div.style.left = divRectX + 'px';
|
||||
div.style.opacity = '1';
|
||||
getArticleLede(linkHref, articleBaseUrl, currentDocument).then(function (html) {
|
||||
link.articleloading = false;
|
||||
div.style.justifyContent = '';
|
||||
div.style.alignItems = '';
|
||||
div.style.display = 'block';
|
||||
var breakoutIconFile = window.location.pathname.replace(/\/[^/]*$/, '') + (dark ? '/img/icons/new_window_white.svg' : '/img/icons/new_window_black.svg');
|
||||
var backgroundColour = dark ? '#222' : '#ebf4fb';
|
||||
div.innerHTML = `<div style="position: relative; overflow: hidden; height: ${divHeight}px;">
|
||||
<div style="background: ${backgroundColour} !important; opacity: 70%; position: absolute; top: 0; right: 0; display: flex; align-items: center; padding: 0;">
|
||||
<img id="popbreakouticon" src="${breakoutIconFile}" />
|
||||
<span id="popcloseicon">X</span>
|
||||
</div>
|
||||
<div style="padding-top: 3px">${html}</div>
|
||||
</div>`;
|
||||
// Now insert the arrow
|
||||
var tooltipStyle = articleWindow.document.getElementById('kiwixtooltipstylesheet');
|
||||
var triangleColour = '#b7ddf2'; // Same as border colour of div
|
||||
if (tooltipStyle) {
|
||||
var span = document.createElement('span');
|
||||
span.style.cssText = `
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-${triangleDirection}: 16px solid ${triangleColour};
|
||||
border-left: 8px solid transparent !important;
|
||||
border-right: 8px solid transparent !important;
|
||||
position: absolute;
|
||||
top: ${triangleY}px;
|
||||
left: ${triangleX}px;
|
||||
`;
|
||||
div.appendChild(span);
|
||||
}
|
||||
// Programme the icons
|
||||
var breakout = function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
link.newcontainer = true;
|
||||
link.click();
|
||||
closePopover(div);
|
||||
}
|
||||
var closeIcon = currentDocument.getElementById('popcloseicon');
|
||||
var breakoutIcon = currentDocument.getElementById('popbreakouticon');
|
||||
// Register click event for full support
|
||||
closeIcon.addEventListener('mousedown', function () {
|
||||
closePopover(div);
|
||||
}, true);
|
||||
breakoutIcon.addEventListener('mousedown', breakout, true);
|
||||
// Register either pointerdown or touchstart if supported
|
||||
var eventName = window.PointerEvent ? 'pointerdown' : 'touchstart';
|
||||
closeIcon.addEventListener(eventName, function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
closePopover(div);
|
||||
}, true);
|
||||
breakoutIcon.addEventListener(eventName, breakout, true);
|
||||
}).catch(function (err) {
|
||||
console.warn(err);
|
||||
// Remove the div
|
||||
div.style.opacity = '0';
|
||||
div.parentElement.removeChild(div);
|
||||
link.articleloading = false;
|
||||
});
|
||||
}, 500);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove any preview popover DIVs
|
||||
*
|
||||
* @param {Document} doc The document from which to remove any popovers
|
||||
*/
|
||||
function removeKiwixPopoverDivs (doc) {
|
||||
var divs = doc.getElementsByClassName('kiwixtooltip');
|
||||
setTimeout(function () {
|
||||
Array.prototype.slice.call(divs).forEach(function (div) {
|
||||
var timeoutID;
|
||||
var fadeOutDiv = function () {
|
||||
clearTimeout(timeoutID);
|
||||
if (!div.matches(':hover')) {
|
||||
closePopover(div);
|
||||
} else {
|
||||
timeoutID = setTimeout(fadeOutDiv, 250);
|
||||
}
|
||||
};
|
||||
timeoutID = setTimeout(fadeOutDiv, 250);
|
||||
});
|
||||
}, 300);
|
||||
}
|
||||
|
||||
// Directly close any popovers
|
||||
function closePopover (div) {
|
||||
div.style.opacity = '0';
|
||||
setTimeout(function () {
|
||||
if (div && div.parentElement) {
|
||||
div.parentElement.removeChild(div);
|
||||
}
|
||||
}, 200);
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds the closest <a> or <area> enclosing tag of an element.
|
||||
* Returns undefined if there isn't any.
|
||||
@ -1436,6 +1793,7 @@ export default {
|
||||
feedNodeWithBlob: feedNodeWithBlob,
|
||||
getDataUriFromUint8Array: getDataUriFromUint8Array,
|
||||
deriveZimUrlFromRelativeUrl: deriveZimUrlFromRelativeUrl,
|
||||
insertLinkElement: insertLinkElement,
|
||||
getClosestMatchForTagname: getClosestMatchForTagname,
|
||||
removeUrlParameters: removeUrlParameters,
|
||||
ToC: TableOfContents,
|
||||
@ -1456,6 +1814,9 @@ export default {
|
||||
initTouchZoom: initTouchZoom,
|
||||
appIsFullScreen: appIsFullScreen,
|
||||
lockDisplayOrientation: lockDisplayOrientation,
|
||||
attachKiwixPopoverCss: attachKiwixPopoverCss,
|
||||
attachKiwixPopoverDiv: attachKiwixPopoverDiv,
|
||||
removeKiwixPopoverDivs: removeKiwixPopoverDivs,
|
||||
reportAssemblerErrorToAPIStatusPanel: reportAssemblerErrorToAPIStatusPanel,
|
||||
reportSearchProviderToAPIStatusPanel: reportSearchProviderToAPIStatusPanel,
|
||||
warnAndOpenExternalLinkInNewTab: warnAndOpenExternalLinkInNewTab,
|
||||
|
Loading…
x
Reference in New Issue
Block a user