/**
* transformStyles.js: Provides transformations in CSS of Wikipedia articles contained in the ZIM file
* This allows the user to choose the presentation style for the page to be viewed.
* Currently available are "mobile" and "desktop" display modes.
*
* Copyright 2017 Kiwix developers
* License GPL v3:
*
* This file is part of Kiwix.
*
* Kiwix is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Kiwix is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kiwix (file LICENSE-GPLv3.txt). If not, see
*/
'use strict';
define(['util', 'uiUtil'], function (util, uiUtil) {
var prefix = (window.location.protocol + '//' + window.location.host + window.location.pathname).replace(/\/[^/]*$/, '/');
/* zl = zimLink; zim = zimType; cc = cssCache; cs = cssSource; i] */
function filterCSS(zl, zim, cc, cs, i) {
var rtnFunction = "injectCSS";
if ((zim != cs) && zl.match(/(-\/(?:s\/css_modules\/|mw\/)?style\.css)|minerva|mobile|parsoid/i)) { //If it's the wrong ZIM type and style matches main styles...
if (zl.match(/-\/(?:s\/|mw\/)?style\.css|minerva|style-mobile\.css/i)) { //If it matches one of the required styles...
zl = (cs == "mobile") ? prefix + "-/s/style-mobile.css" : prefix + "-/s/style.css"; //Take it from cache, because not in the ZIM
console.log("Matched #" + i + " [" + zl + "] from local filesystem because style is not in ZIM" +
"\nbut your display options require a " + cs + " style");
uiUtil.poll("Matched [" + zl.replace(/[^/]+\//g, '').substring(0, 18) + "] from cache" + " because your display options require a " + cs + " style...");
}
if (cs == "desktop" && /minerva|mobile|parsoid|(css_modules|mw)\/style\.css/.test(zl) && !/(css_modules|mw)\/mobile_main_page\.css/.test(zl)) {
//If user selected desktop style and style is one of the mobile styles, but not mobile_main_page for newstyle all image homepages
console.log("Voiding #" + i + " [" + zl + "] from document header \nbecause your display options require a desktop style");
uiUtil.poll("Voiding [" + zl.replace(/[^/]+\//g, '').substring(0, 18) + "] because your display options require a " + cs + " style...");
zl = "#"; //Void these mobile styles
}
// Rename this required mobile style so that we don't trigger reading ZIM as mobile in print intercept
zl = /(css_modules|mw)\/mobile_main_page\.css/.test(zl) && cs == 'desktop' ? prefix + "-/s/css_modules/newstyle_main_page.css" : zl;
} else {
//If this is a standard Wikipedia css use stylesheet cached in the filesystem...
//DEV: Although "." matches any character in regex, there is enough specificity in the patterns below
//DEV: Add any local stylesheets you wish to include here
if (cc &&
(/-\/(s\/|mw\/)?style.css/i.test(zl) ||
/\/mediawiki.toc.css/i.test(zl) ||
/\/ext.cite.styles.css/i.test(zl) ||
/\/ext.timeline.styles.css/i.test(zl) ||
/\/ext.scribunto.logs.css/i.test(zl) ||
/\/mediawiki.page.gallery.styles.css/i.test(zl) ||
/\/ext.cite.a11y.css/i.test(zl) ||
/\/ext.kartographer.style.css/i.test(zl) ||
/\/ext.kartographer.link.css/i.test(zl) ||
/\/ext.kartographer.frame.css/i.test(zl) ||
/\/mw.TMHGalleryHook.js.css/i.test(zl) ||
/\/mw.PopUpMediaTransform.css/i.test(zl) ||
/\/mw.MediaWikiPlayer.loader.css/i.test(zl) ||
/\/ext.tmh.thumbnail.styles.css/i.test(zl) ||
/\/ext.math.styles.css/i.test(zl) ||
/\/ext.math.scripts.css/i.test(zl) ||
/\/ext.inputBox.styles.css/i.test(zl) ||
/\/ext.cite.ux-enhancements.css/i.test(zl) ||
/\/mobile_main_page.css/i.test(zl) ||
/\/mediawiki.ui.input.css/i.test(zl) ||
/\/mediawiki.ui.checkbox.css/i.test(zl) ||
/\/content.parsoid.css/i.test(zl) ||
/\/inserted_style_mobile.css/i.test(zl) ||
/\/inserted_style.css/i.test(zl) ||
/-\/static\/bootstrap\/css\/bootstrap.min.css/i.test(zl) ||
/-\/static\/bootstrap\/css\/bootstrap-theme.min.css/i.test(zl) ||
/-\/static\/main.css/i.test(zl) ||
/\/mobile.css/i.test(zl) ||
/\/style-mobile.css/i.test(zl) ||
/\/skins.minerva.base.reset\|skins.minerva.content.styles\|ext.cite.style\|mediawiki.page.gallery.styles\|mobile.app.pagestyles.android\|mediawiki.skinning.content.parsoid.css/i.test(zl)
)) {
zl = zl.replace(/\|/ig, "_"); //Replace "|" with "_" (legacy for some stylesheets with pipes in filename - but next line renders this redundant in current implementation)
if (/(-\/(s\/|mw\/)?style\.css)|minerva|inserted_style_mobile/i.test(zl)) { //If it matches one of the required styles...
zl = cs == "mobile" ? "-/s/style-mobile.css" : "-/s/style.css";
}
// Rename this required mobile style so that we don't trigger reading ZIM as mobile in print intercept
zl = /\/mobile_main_page\.css/.test(zl) ? "-/s/css_modules/newstyle_main_page.css" : zl;
zl = /\/content\.parsoid\.css/.test(zl) ? "-/s/css_modules/content.parsoid.css" : zl;
// Replace bootstrap with own: DEV: when upgrading to Bootstrap 4, stop doing this!
zl = zl.replace(/.+(bootstrap[^\/]*?\.css)/i, "css/$1");
console.log("Matched #" + i + " [" + zl + "] from local filesystem");
uiUtil.poll("Matched #" + i + " [" + zl.replace(/[^/]+\//g, '').substring(0, 18) + "] from filesystem");
//Make link absolute
zl = zl.replace(/^[/.]*/, prefix);
//injectCSS();
} else if (params.contentInjectionMode == 'jquery') { //Try to get the stylesheet from the ZIM file unless it's the wrong ZIM type
zl = zl.replace(/^[./]+/, ""); //Remove the directory path
console.log("Attempting to resolve CSS link #" + i + " [" + zl + "] from ZIM file..." +
(cc ? "\n(Consider adding file #" + i + " to the local filesystem)" : ""));
rtnFunction = "resolveCSS";
}
}
return { zl: zl, rtnFunction: rtnFunction };
}
function toMobileCSS(html, zim, cc, cs, css) {
//DEV: Careful not to add styles twice...
//NB Can't relocate to filterCSS function above because it filters styles serially and code would be called for every style...
if (zim == "desktop" && zim != cs) { //If ZIM doesn't match user-requested style, add in stylesheets if they're missing
css += /\/content\.parsoid\.css/i.test(css) ? "" : '\r\n';
css += /\/inserted_style_mobile\.css/i.test(css) ? "" : '\r\n';
css += /\/mobile\.css/i.test(css) ? "" : '\r\n';
}
if (cc || (zim == "desktop")) { //If user requested cached styles OR the ZIM does not contain mobile styles
console.log(zim == "desktop" ? "Transforming display style to mobile..." : "Optimizing cached styles for mobile display...");
uiUtil.poll(zim == "desktop" ? "Transforming display style to mobile..." : "Optimizing cached styles for mobile display...");
//Add styling to image captions that is hard-coded in Wikipedia mobile
html = html.replace(/class\s*=\s*["']\s*thumbcaption\s*["']\s*/ig, 'style="margin: 0.5em 0 0.5em; font-size: 0.8em; line-height: 1.5; padding: 0 !important; color: #54595d; width: auto !important;"');
//Wrap
tags in
to control bottom border width if there's an infobox, but not if it's a new-style ZIM with collapsible details tags
if (!/]*class\s*=\s*["'][^"']*(?:mw-stack|infobox|vertical-navbox|qbRight|wv-quickbar))/i.test(html) ? html.replace(/(
)/ig, '
$1
') : html;
}
if (zim == "desktop") {
var infobox = [];
if (/
', 'i');
}
}
if (infobox.length) {
var temphtml = html.replace(infobox[0], "");
var paras = util.matchInner(temphtml, '
]*>', '
', 'gi');
var matched = false;
if (paras.length) {
for (var g = 0; g < 3; g++) {
//Check if the paragraph is a proper sentence, i.e. contains at least 50 non-HTML-delimetered non-full-stop characters, followed by a punctuation character
if (paras[g] && /[^.]{50,}[^.]*[.,;:?!-]/.test(paras[g].replace(/<[^>]*>/g, ""))) { matched = true; break; }
}
if (matched) {
//If there are navboxes below the infobox, hide them in mobile view
temphtml = temphtml.replace(/(
]+navbox))(?:([^>]+?)style\s*=\s*["']([^"']+)["'])?/ig, '$1$2style="display:none;$3"');
//Ensure mobile styling in infobox
infobox[0] = infobox[0].replace(/(<(?:table|div)\b(?=[^>]+?class\s*=\s*["'][^"']*(?:mw-stack|infobox|navbox)))([^>]+?style\s*=\s*["'])(?:([^"']*?)margin\s*:[^;"']*[;"'])?/ig, '$1$2margin:0 auto;$3');
//Clear any fixed width but set it to max-width
infobox[0] = infobox[0].replace(/^(<(?:table|div)\b[^>]+?;\s*width\s*:\s*)([^;"']+)/i, '$1auto;max-width:$2');
//Hide any navboxes inside the infobox
infobox[0] = infobox[0].replace(/(<(?:table|div)\b(?=[^>]+?class\s*=\s*["'][^"']*navbox))(?:([^>]+?)style\s*=\s*["'])(?:([^"']*?)display\s*:[^;"']*[;"'])?/ig, '$1$2style="display:none;$3');
//We already deleted the table above
html = "";
//First try to move the lead paragraph
html = temphtml.replace(/(
', 'i') : tableBox;
if (tableBox && tableBox.length) {
html = html.replace(tableBox, "");
html = html.replace(/(<\/h1>\s*)/i, "$1" + tableBox);
}
}
//Ensure white background colour
html = html.replace(/(class\s*=\s*["']\s*mw-body\s*["'][^>]*?style\s*=\s*["'])/i, "$1background-color:white;");
//Void empty header title
html = html.replace(/
]+titleHeading[^>]+>\s*<\/h1>\s*/ig, "");
}
//Remove hard-coded style on h1
html = html.replace(/(
]+)background-color\s*:\s*white;\s*/i, '$1');
//Remove hard-coded style on infobox
html = html.replace(/(
]*?\bclass=['"][^'"]*infobox)[^>]*\bstyle=['"][^'"]*\b)(?:float:\s*none\s*;?\s*)(?:clear:\s*none\s*;?\s*)?/gi, '$1');
//Remove any residual references to mobile styles (e.g. in data-kiwixhref) because they trigger page reload when printing
css = css.replace(/(]+)(minerva|mobile)/ig, '$1');
return { html : html, css : css };
}
/**
* Functions and classes exposed by this module
*/
return {
toMobileCSS: toMobileCSS,
toDesktopCSS: toDesktopCSS,
filterCSS: filterCSS
};
});