Викиучебник ruwikibooks https://ru.wikibooks.org/wiki/%D0%97%D0%B0%D0%B3%D0%BB%D0%B0%D0%B2%D0%BD%D0%B0%D1%8F_%D1%81%D1%82%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D0%B0 MediaWiki 1.47.0-wmf.18 first-letter Медиа Служебная Обсуждение Участник Обсуждение участника Викиучебник Обсуждение Викиучебника Файл Обсуждение файла MediaWiki Обсуждение MediaWiki Шаблон Обсуждение шаблона Справка Обсуждение справки Категория Обсуждение категории Полка Обсуждение полки Импортировано Обсуждение импортированного Рецепт Обсуждение рецепта Задача Обсуждение задачи TimedText TimedText talk Модуль Обсуждение модуля Event Event talk MediaWiki:LAPI.js 8 18579 269959 123615 2026-09-06T20:27:26Z ASarabadani (WMF) 76726 Allow thumb.wikimedia.org alongside upload.wikimedia.org ([[phab:T427465]]) 269959 javascript text/javascript // <source lang=javascript"> /* Small JS library containing stuff I use often. Author: [[User:Lupo]], June 2009 License: Quadruple licensed GFDL, GPL, LGPL and Creative Commons Attribution 3.0 (CC-BY-3.0) Choose whichever license of these you like best :-) Includes the following components: - Object enhancements (clone, merge) - String enhancements (trim, ...) - Array enhancements (JS 1.6) - Function enhancements (bind) - LAPI Most basic DOM functions: $ (getElementById), make - LAPI.Ajax Ajax request implementation, tailored for MediaWiki/WMF sites - LAPI.Browser Browser detection (general) - LAPI.DOM DOM helpers, including a cross-browser DOM parser - LAPI.WP MediaWiki/WMF-specific DOM routines - LAPI.Edit Simple editor implementation with save, cancel, preview (for WMF sites) - LAPI.Evt Event handler routines (general) - LAPI.Pos Position calculations (general) */ // Global: importScript (from wiki.js, for MediaWiki:AjaxSubmit.js) // Configuration: set this to the URL of your image server. The value is a string representation // of a regular expression. For instance, for Wikia, use "http://images\\d\\.wikia\\.nocookie\\.net". // Remember to double-escape the backslash. if (typeof (LAPI_file_store) == 'undefined') var LAPI_file_store = "(https?:)?//(?:upload|thumb)\.wikimedia\.org/"; // Some basic routines, mainly enhancements of the String, Array, and Function objects. // Some taken from Javascript 1.6, some own. /** Object enhancements ************/ // Note: adding these to the prototype may break other code that assumes that // {} has no properties at all. Object.clone = function (source, includeInherited) { if (!source) return null; var result = {}; for (var key in source) { if (includeInherited || source.hasOwnProperty (key)) result[key] = source[key]; } return result; }; Object.merge = function (from, into, includeInherited) { if (!from) return into; for (var key in from) { if (includeInherited || from.hasOwnProperty (key)) into[key] = from[key]; } return into; }; Object.mergeSome = function (from, into, includeInherited, predicate) { if (!from) return into; if (typeof (predicate) == 'undefined') return Object.merge (from, into, includeInherited); for (var key in from) { if ((includeInherited || from.hasOwnProperty (key)) && predicate (from, into, key)) into[key] = from[key]; } return into; }; Object.mergeSet = function (from, into, includeInherited) { return Object.mergeSome (from, into, includeInherited, function (src, tgt, key) {return src[key] != null;}); } /** String enhancements (Javascript 1.6) ************/ // Removes given characters from both ends of the string. // If no characters are given, defaults to removing whitespace. if (!String.prototype.trim) { String.prototype.trim = function (chars) { if (!chars) return this.replace (/^\s+|\s+$/g, ""); return this.trimRight (chars).trimLeft (chars); }; } // Removes given characters from the beginning of the string. // If no characters are given, defaults to removing whitespace. if (!String.prototype.trimLeft) { String.prototype.trimLeft = function (chars) { if (!chars) return this.replace (/^\s\s*/, ""); return this.replace (new RegExp ('^[' + chars.escapeRE () + ']+'), ""); }; } String.prototype.trimFront = String.prototype.trimLeft; // Synonym // Removes given characters from the end of the string. // If no characters are given, defaults to removing whitespace. if (!String.prototype.trimRight) { String.prototype.trimRight = function (chars) { if (!chars) return this.replace (/\s\s*$/, ""); return this.replace (new RegExp ('[' + chars.escapeRE () + ']+$'), ""); }; } String.prototype.trimEnd = String.prototype.trimRight; // Synonym /** Further String enhancements ************/ // Returns true if the string begins with prefix. String.prototype.startsWith = function (prefix) { return this.indexOf (prefix) == 0; }; // Returns true if the string ends in suffix String.prototype.endsWith = function (suffix) { return this.lastIndexOf (suffix) + suffix.length == this.length; }; // Returns true if the string contains s. String.prototype.contains = function (s) { return this.indexOf (s) >= 0; }; // Replace all occurrences of a string pattern by replacement. String.prototype.replaceAll = function (pattern, replacement) { return this.split (pattern).join (replacement); }; // Escape all backslashes and single or double quotes such that the result can // be used in Javascript inside quotes or double quotes. String.prototype.stringifyJS = function () { return this.replace (/([\\\'\"]|%5C|%27|%22)/g, '\\$1') // ' // Fix syntax coloring .replace (/\n/g, '\\n'); } // Escape all RegExp special characters such that the result can be safely used // in a RegExp as a literal. String.prototype.escapeRE = function () { return this.replace (/([\\{}()|.?*+^$\[\]])/g, "\\$1"); }; String.prototype.escapeXML = function (quot, apos) { var s = this.replace (/&/g, '&amp;') .replace (/\xa0/g, '&nbsp;') .replace (/</g, '&lt;') .replace (/>/g, '&gt;'); if (quot) s = s.replace (/\"/g, '&quot;'); // " // Fix syntax coloring if (apos) s = s.replace (/\'/g, '&apos;'); // ' // Fix syntax coloring return s; }; String.prototype.decodeXML = function () { return this.replace(/&quot;/g, '"') .replace(/&apos;/g, "'") .replace(/&gt;/g, '>') .replace(/&lt;/g, '<') .replace(/&nbsp;/g, '\xa0') .replace(/&amp;/g, '&'); }; String.prototype.capitalizeFirst = function () { return this.substring (0, 1).toUpperCase() + this.substring (1); }; String.prototype.lowercaseFirst = function () { return this.substring (0, 1).toLowerCase() + this.substring (1); }; // This is actually a function on URLs, but since URLs typically are strings in // Javascript, let's include this one here, too. String.prototype.getParamValue = function (param) { var re = new RegExp ('[&?]' + param.escapeRE () + '=([^&#]*)'); var m = re.exec (this); if (m && m.length >= 2) return decodeURIComponent (m[1]); return null; }; String.getParamValue = function (param, url) { if (typeof (url) == 'undefined' || url === null) url = document.location.href; try { return url.getParamValue (param); } catch (e) { return null; } }; /** Function enhancements ************/ if (!Function.prototype.bind) { // Return a function that calls the function with 'this' bound to 'thisObject' Function.prototype.bind = function (thisObject) { var f = this, obj = thisObject, slice = Array.prototype.slice, prefixedArgs = slice.call (arguments, 1); return function () { return f.apply (obj, prefixedArgs.concat (slice.call (arguments))); }; }; } /** Array enhancements (Javascript 1.6) ************/ // Note that contrary to JS 1.6, we treat the thisObject as optional. // Don't add to the prototype, that would break for (var key in array) loops! // Returns a new array containing only those elements for which predicate // is true. if (!Array.filter) { Array.filter = function (target, predicate, thisObject) { if (target === null) return null; if (typeof (target.filter) == 'function') return target.filter (predicate, thisObject); if (typeof (predicate) != 'function') throw new Error ('Array.filter: predicate must be a function'); var l = target.length; var result = []; if (thisObject) predicate = predicate.bind (thisObject); for (var i=0; l && i < l; i++) { if (i in target) { var curr = target[i]; if (predicate (curr, i, target)) result[result.length] = curr; } } return result; }; } Array.select = Array.filter; // Synonym // Calls iterator on all elements of the array if (!Array.forEach) { Array.forEach = function (target, iterator, thisObject) { if (target === null) return; if (typeof (target.forEach) == 'function') { target.forEach (iterator, thisObject); return; } if (typeof (iterator) != 'function') throw new Error ('Array.forEach: iterator must be a function'); var l = target.length; if (thisObject) iterator = iterator.bind (thisObject); for (var i=0; l && i < l; i++) { if (i in target) iterator (target[i], i, target); } }; } // Returns true if predicate is true for every element of the array, false otherwise if (!Array.every) { Array.every = function (target, predicate, thisObject) { if (target === null) return true; if (typeof (target.every) == 'function') return target.every (predicate, thisObject); if (typeof (predicate) != 'function') throw new Error ('Array.every: predicate must be a function'); var l = target.length; if (thisObject) predicate = predicate.bind (thisObject); for (var i=0; l && i < l; i++) { if (i in target && !predicate (target[i], i, target)) return false; } return true; }; } Array.forAll = Array.every; // Synonym // Returns true if predicate is true for at least one element of the array, false otherwise. if (!Array.some) { Array.some = function (target, predicate, thisObject) { if (target === null) return false; if (typeof (target.some) == 'function') return target.some (predicate, thisObject); if (typeof (predicate) != 'function') throw new Error ('Array.some: predicate must be a function'); var l = target.length; if (thisObject) predicate = predicate.bind (thisObject); for (var i=0; l && i < l; i++) { if (i in target && predicate (target[i], i, target)) return true; } return false; }; } Array.exists = Array.some; // Synonym // Returns a new array built by applying mapper to all elements. if (!Array.map) { Array.map = function (target, mapper, thisObject) { if (target === null) return null; if (typeof (target.map) == 'function') return target.map (mapper, thisObject); if (typeof (mapper) != 'function') throw new Error ('Array.map: mapper must be a function'); var l = target.length; var result = []; if (thisObject) mapper = mapper.bind (thisObject); for (var i=0; l && i < l; i++) { if (i in target) result[i] = mapper (target[i], i, target); } return result; }; } if (!Array.indexOf) { Array.indexOf = function (target, elem, from) { if (target === null) return -1; if (typeof (target.indexOf) == 'function') return target.indexOf (elem, from); if (typeof (target.length) == 'undefined') return -1; var l = target.length; if (isNaN (from)) from = 0; else from = from || 0; from = (from < 0) ? Math.ceil (from) : Math.floor (from); if (from < 0) from += l; if (from < 0) from = 0; while (from < l) { if (from in target && target[from] === elem) return from; from += 1; } return -1; }; } if (!Array.lastIndexOf) { Array.lastIndexOf = function (target, elem, from) { if (target === null) return -1; if (typeof (target.lastIndexOf) == 'function') return target.lastIndexOf (elem, from); if (typeof (target.length) == 'undefined') return -1; var l = target.length; if (isNaN (from)) from = l-1; else from = from || (l-1); from = (from < 0) ? Math.ceil (from) : Math.floor (from); if (from < 0) from += l; else if (from >= l) from = l-1; while (from >= 0) { if (from in target && target[from] === elem) return from; from -= 1; } return -1; }; } /** Additional Array enhancements ************/ Array.remove = function (target, elem) { var i = Array.indexOf (target, elem); if (i >= 0) target.splice (i, 1); }; Array.contains = function (target, elem) { return Array.indexOf (target, elem) >= 0; }; Array.flatten = function (target) { var result = []; Array.forEach (target, function (elem) {result = result.concat (elem);}); return result; }; // Calls selector on the array elements until it returns a non-null object // and then returns that object. If selector always returns null, any also // returns null. See also Array.map. Array.any = function (target, selector, thisObject) { if (target === null) return null; if (typeof (selector) != 'function') throw new Error ('Array.any: selector must be a function'); var l = target.length; var result = null; if (thisObject) selector = selector.bind (thisObject); for (var i=0; l && i < l; i++) { if (i in target) { result = selector (target[i], i, target); if (result != null) return result; } } return null; }; // Return a contiguous array of the contents of source, which may be an array or pseudo-array, // basically anything that has a length and can be indexed. (E.g. live HTMLCollections, but also // Strings, or objects, or the arguments "variable". Array.make = function (source) { if (!source || typeof (source.length) == 'undefined') return null; var result = []; var l = source.length; for (var i=0; i < l; i++) { if (i in source) result[result.length] = source[i]; } return result; }; if (typeof (window.LAPI) == 'undefined') { window.LAPI = { Ajax : { getRequest : function () { var request = null; try { request = new XMLHttpRequest(); } catch (anything) { request = null; if (!!window.ActiveXObject) { if (typeof (LAPI.Ajax.getRequest.msXMLHttpID) == 'undefined') { var XHR_ids = [ 'MSXML2.XMLHTTP.6.0', 'MSXML2.XMLHTTP.3.0' , 'MSXML2.XMLHTTP', 'Microsoft.XMLHTTP' ]; for (var i=0; i < XHR_ids.length && !request; i++) { try { request = new ActiveXObject (XHR_ids[i]); if (request) LAPI.Ajax.getRequest.msXMLHttpID = XHR_ids[i]; } catch (ex) { request = null; } } if (!request) LAPI.Ajax.getRequest.msXMLHttpID = null; } else if (LAPI.Ajax.getRequest.msXMLHttpID) { request = new ActiveXObject (LAPI.Ajax.getRequest.msXMLHttpID); } } // end if IE } // end try-catch return request; } }, $ : function (selector, doc, multi) { if (!selector || selector.length == 0) return null; doc = doc || document; if (typeof (selector) == 'string') { if (selector.charAt (0) == '#') selector = selector.substring (1); if (selector.length > 0) return doc.getElementById (selector); return null; } else { if (multi) return Array.map (selector, function (id) {return LAPI.$ (id, doc);}); return Array.any (selector, function (id) {return LAPI.$ (id, doc);}); } }, make : function (tag, attribs, css, doc) { doc = doc || document; if (!tag || tag.length == 0) throw new Error ('No tag for LAPI.make'); var result = doc.createElement (tag); Object.mergeSet (attribs, result); Object.mergeSet (css, result.style); if (/^(form|input|button|select|textarea)$/.test (tag) && result.id && result.id.length > 0 && !result.name ) { result.name = result.id; } return result; }, formatException : function (ex, asDOM) { var name = ex.name || ""; var msg = ex.message || ""; var file = null; var line = null; if (msg && msg.length > 0 && msg.charAt (0) == '#') { // User msg: don't confuse users with error locations. (Note: could also use // custom exception types, but that doesn't work right on IE6.) msg = msg.substring (1); } else { file = ex.fileName || ex.sourceURL || null; // Gecko, Webkit, others line = ex.lineNumber || ex.line || null; // Gecko, Webkit, others } if (name || msg) { if (!asDOM) { return 'Exception ' + name + ': ' + msg + (file ? '\nFile ' + file + (line ? ' (' + line + ')' : "") : "") ; } else { var ex_msg = LAPI.make ('div'); ex_msg.appendChild (document.createTextNode ('Exception ' + name + ': ' + msg)); if (file) { ex_msg.appendChild (LAPI.make ('br')); ex_msg.appendChild (document.createTextNode ('File ' + file + (line ? ' (' + line + ')' : ""))); } return ex_msg; } } else { return null; } } }; } // end if (guard) if (typeof (LAPI.Browser) == 'undefined') { // Yes, usually it's better to test for available features. But sometimes there's no // way around testing for specific browsers (differences in dimensions, layout errors, // etc.) LAPI.Browser = (function (agent) { var result = {}; result.client = agent; var m = agent.match(/applewebkit\/(\d+)/); result.is_webkit = (m != null); result.is_safari = result.is_webkit && !agent.contains ('spoofer'); result.webkit_version = (m ? parseInt (m[1]) : 0); result.is_khtml = navigator.vendor == 'KDE' || (document.childNodes && !document.all && !navigator.taintEnabled && navigator.accentColorName); result.is_gecko = agent.contains ('gecko') && !/khtml|spoofer|netscape\/7\.0/.test (agent); result.is_ff_1 = agent.contains ('firefox/1'); result.is_ff_2 = agent.contains ('firefox/2'); result.is_ff_ge_2 = /firefox\/[2-9]|minefield\/3/.test (agent); result.is_ie = agent.contains ('msie') || !!window.ActiveXObject; result.is_ie_lt_7 = false; if (result.is_ie) { var version = /msie ((\d|\.)+)/.exec (agent); result.is_ie_lt_7 = (version != null && (parseFloat(version[1]) < 7)); } result.is_opera = agent.contains ('opera'); result.is_opera_ge_9 = false; result.is_opera_95 = false; if (result.is_opera) { m = /opera\/((\d|\.)+)/.exec (agent); result.is_opera_95 = m && (parseFloat (m[1]) >= 9.5); result.is_opera_ge_9 = m && (parseFloat (m[1]) >= 9.0); } result.is_mac = agent.contains ('mac'); return result; })(navigator.userAgent.toLowerCase ()); } // end if (guard) if (typeof (LAPI.DOM) == 'undefined') { LAPI.DOM = { // IE6 doesn't have these Node constants in Node, so put them here ELEMENT_NODE : 1, ATTRIBUTE_NODE : 2, TEXT_NODE : 3, CDATA_SECTION_NODE : 4, ENTITY_REFERENCE_NODE : 5, ENTITY_NODE : 6, PROCESSING_INSTRUCTION_NODE : 7, COMMENT_NODE : 8, DOCUMENT_NODE : 9, DOCUMENT_TYPE_NODE : 10, DOCUMENT_FRAGMENT_NODE : 11, NOTATION_NODE : 12, cleanAttributeName : function (attr_name) { if (!LAPI.Browser.is_ie) return attr_name; if (!LAPI.DOM.cleanAttributeName._names) { LAPI.DOM.cleanAttributeName._names = { 'class' : 'className' ,'cellspacing' : 'cellSpacing' ,'cellpadding' : 'cellPadding' ,'colspan' : 'colSpan' ,'maxlength' : 'maxLength' ,'readonly' : 'readOnly' ,'rowspan' : 'rowSpan' ,'tabindex' : 'tabIndex' ,'valign' : 'vAlign' }; } var cleaned = attr_name.toLowerCase (); return LAPI.DOM.cleanAttributeName._names[cleaned] || cleaned; }, importNode : function (into, node, deep) { if (!node) return null; if (into.importNode) return into.importNode (node, deep); if (node.ownerDocument == into) return node.cloneNode (deep); var new_node = null; switch (node.nodeType) { case LAPI.DOM.ELEMENT_NODE : new_node = into.createElement (node.nodeName); Array.forEach ( node.attributes , function (attr) { if (attr && attr.nodeValue && attr.nodeValue.length > 0) new_node.setAttribute (LAPI.DOM.cleanAttributeName (attr.name), attr.nodeValue); } ); new_node.style.cssText = node.style.cssText; if (deep) { Array.forEach ( node.childNodes , function (child) { var copy = LAPI.DOM.importNode (into, child, true); if (copy) new_node.appendChild (copy); } ); } return new_node; case LAPI.DOM.TEXT_NODE : return into.createTextNode (node.nodeValue); case LAPI.DOM.CDATA_SECTION_NODE : return (into.createCDATASection ? into.createCDATASection (node.nodeValue) : into.createTextNode (node.nodeValue) ); case LAPI.DOM.COMMENT_NODE : return into.createComment (node.nodeValue); default : return null; } // end switch }, parse : function (str, content_type) { function getDocument (str, content_type) { if (typeof (DOMParser) != 'undefined') { var parser = new DOMParser (); if (parser && parser.parseFromString) return parser.parseFromString (str, content_type); } // We don't have DOMParser if (LAPI.Browser.is_ie) { var doc = null; // Apparently, these can be installed side-by-side. Try to get the newest one available. // Unfortunately, one finds a variety of version strings on the net. I have no idea which // ones are correct. if (typeof (LAPI.DOM.parse.msDOMDocumentID) == 'undefined') { // If we find a parser, we cache it. If we cannot find one, we also remember that. var parsers = [ 'MSXML6.DOMDocument','MSXML5.DOMDocument','MSXML4.DOMDocument','MSXML3.DOMDocument' ,'MSXML2.DOMDocument.5.0','MSXML2.DOMDocument.4.0','MSXML2.DOMDocument.3.0' ,'MSXML2.DOMDocument','MSXML.DomDocument','Microsoft.XmlDom']; for (var i=0; i < parsers.length && !doc; i++) { try { doc = new ActiveXObject (parsers[i]); if (doc) LAPI.DOM.parse.msDOMDocumentID = parsers[i]; } catch (ex) { doc = null; } } if (!doc) LAPI.DOM.parse.msDOMDocumentID = null; } else if (LAPI.DOM.parse.msDOMDocumentID) { doc = new ActiveXObject (LAPI.DOM.parse.msDOMDocumentID); } if (doc) { doc.async = false; doc.loadXML (str); return doc; } } // Try using a "data" URI (http://www.ietf.org/rfc/rfc2397). Reported to work on // older Safaris. content_type = content_type || 'application/xml'; var req = LAPI.Ajax.getRequest (); if (req) { // Synchronous is OK, since "data" URIs are local req.open ('GET', 'data:' + content_type + ';charset=utf-8,' + encodeURIComponent (str), false); if (req.overrideMimeType) req.overrideMimeType (content_type); req.send (null); return req.responseXML; } return null; } // end getDocument var doc = null; try { doc = getDocument (str, content_type); } catch (ex) { doc = null; } if ( ( (!doc || !doc.documentElement) && ( str.search (/^\s*(<xml[^>]*>\s*)?<!doctype\s+html/i) >= 0 || str.search (/^\s*<html/i) >= 0 ) ) || (doc && ( LAPI.Browser.is_ie && (!doc.documentElement && doc.parseError && doc.parseError.errorCode != 0 && doc.parseError.reason.contains ('Error processing resource') && doc.parseError.reason.contains ('http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd') ) ) ) ) { // Either the text specified an (X)HTML document, but we failed to get a Document, or we // hit the walls of the single-origin policy on IE which tries to get the DTD from the // URI specified... Let's fake a document: doc = LAPI.DOM.fakeHTMLDocument (str); } return doc; }, parseHTML : function (str, sanity_check) { // Always use a faked document; parsing as XML and then treating the result as HTML doesn't work right with HTML5. return LAPI.DOM.fakeHTMLDocument (str); }, fakeHTMLDocument : function (str) { var body_tag = /<body.*?>/.exec (str); if (!body_tag || body_tag.length == 0) return null; body_tag = body_tag.index + body_tag[0].length; // Index after the opening body tag var body_end = str.lastIndexOf ('</body>'); if (body_end < 0) return null; var content = str.substring (body_tag, body_end); // Anything in between content = content.replace(/<script(.|\s)*?\/script>/g, ""); // Sanitize: strip scripts return new LAPI.DOM.DocumentFacade (content); }, isValid : function (doc) { if (!doc) return doc; if (typeof (doc.parseError) != 'undefined') { // IE if (doc.parseError.errorCode != 0) { throw new Error ( 'XML parse error: ' + doc.parseError.reason + ' line ' + doc.parseError.line + ' col ' + doc.parseError.linepos + '\nsrc = ' + doc.parseError.srcText); } } else { // FF... others? var root = doc.documentElement; if (/^parsererror$/i.test (root.tagName)) { throw new Error ('XML parse error: ' + root.getInnerText ()); } } return doc; }, hasClass : function (node, className) { if (!node) return false; return (' ' + node.className + ' ').contains (' ' + className + ' '); }, setContent : function (node, content) { if (content == null) return node; LAPI.DOM.removeChildren (node); if (content.nodeName) { // presumably a DOM tree, like a span or a document fragment node.appendChild (content); } else if (typeof (node.innerHTML) != 'undefined') { node.innerHTML = content.toString (); } else { node.appendChild (document.createTextNode (content.toString ())); } return node; }, makeImage : function (src, width, height, title, doc) { return LAPI.make ( 'img' , {src : src, width: "" + width, height : "" + height, title : title} , doc ); }, makeButton : function (id, text, f, submit, doc) { return LAPI.make ( 'input' , {id : id || "", type: (submit ? 'submit' : 'button'), value: text, onclick: f} , doc ); }, makeLabel : function (id, text, for_elem, doc) { var label = LAPI.make ('label', {id: id || "", htmlFor: for_elem}, null, doc); return LAPI.DOM.setContent (label, text); }, makeLink : function (url, text, tooltip, onclick, doc) { var lk = LAPI.make ('a', {href: url, title: tooltip, onclick: onclick}, null, doc); return LAPI.DOM.setContent (lk, text || url); }, // Unfortunately, extending Node.prototype may not work on some browsers, // most notably (you've guessed it) IE... getInnerText : function (node) { if (node.textContent) return node.textContent; if (node.innerText) return node.innerText; var result = ""; if (node.nodeType == LAPI.DOM.TEXT_NODE) { result = node.nodeValue; } else { Array.forEach (node.childNodes, function (elem) { switch (elem.nodeType) { case LAPI.DOM.ELEMENT_NODE: result += LAPI.DOM.getInnerText (elem); break; case LAPI.DOM.TEXT_NODE: result += elem.nodeValue; break; } } ); } return result; }, removeNode : function (node) { if (node.parentNode) node.parentNode.removeChild (node); return node; }, removeChildren : function (node) { // if (typeof (node.innerHTML) != 'undefined') node.innerHTML = ""; // Not a good idea. On IE this destroys all contained nodes, even if they're still referenced // from JavaScript! Can't have that... while (node.firstChild) node.removeChild (node.firstChild); return node; }, insertNode : function (node, before) { before.parentNode.insertBefore (node, before); return node; }, insertAfter : function (node, after) { var next = after.nextSibling; after.parentNode.insertBefore (node, next); return node; }, replaceNode : function (node, newNode) { node.parentNode.replaceChild (node, newNode); return newNode; }, isParentOf : function (parent, child) { while (child && child != parent && child.parentNode) child = child.parentNode; return child == parent; }, // Property is to be in CSS style, e.g. 'background-color', not in JS style ('backgroundColor')! // Use standard 'cssFloat' for float property. currentStyle : function (element, property) { function normalize (prop) { // Don't use a regexp with a lambda function (available only in JS 1.3)... and I once had a // case where IE6 goofed grossly with a lambda function. Since then I try to avoid those // (though they're neat). if (prop == 'cssFloat') return 'styleFloat'; // We'll try both variants below, standard first... var result = prop.split ('-'); result = Array.map (result, function (s) { if (s) return s.capitalizeFirst (); else return s;}); result = result.join (""); return result.lowercaseFirst (); } if (element.ownerDocument.defaultView && element.ownerDocument.defaultView.getComputedStyle) { // Gecko etc. if (property == 'cssFloat') property = 'float'; return element.ownerDocument.defaultView.getComputedStyle (element, null).getPropertyValue (property); } else { var result; if (element.currentStyle) { // IE, has subtle differences to getComputedStyle result = element.currentStyle[property] || element.currentStyle[normalize (property)]; } else // Not exactly right, but best effort result = element.style[property] || element.style[normalize (property)]; // Convert em etc. to pixels. Kudos to Dean Edwards; see // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 if (!/^\d+(px)?$/i.test (result) && /^\d/.test (result) && element.runtimeStyle) { var style = element.style.left; var runtimeStyle = element.runtimeStyle.left; element.runtimeStyle.left = element.currentStyle.left; element.style.left = result || 0; result = elem.style.pixelLeft + "px"; element.style.left = style; element.runtimeStyle.left = runtimeStyle; } } }, // Load a given image in a given size. Parameters: // title // Full title of the image, including the "File:" namespace // url // If != null, URL of an existing thumb for that image. If width is null, may contain the url // of the full image. // width // If != null, desired width of the image, otherwise load the full image // height // If width != null, height should also be set. // auto_thumbs // True if missing thumbnails are generated automatically. // success // Function to be called once the image is loaded. Takes one parameter: the IMG-tag of // the loaded image // failure // Function to be called if the image cannot be loaded. Takes one parameter: a string // containing an error message. loadImage : function (title, url, width, height, auto_thumbs, success, failure) { if (auto_thumbs && url) { // MediaWiki-style with 404 handler. Set condition to false if your wiki does not have such a // setup. var img_src = null; if (width) { var i = url.lastIndexOf ('/'); if (i >= 0) { img_src = url.substring (0, i) + url.substring (i).replace (/^\/\d+px-/, '/' + width + 'px-'); } } else if (url) { img_src = url; } if (!img_src) { failure ("Cannot load image from url " + url); return; } var img_loader = LAPI.make ( 'img' , {src: img_src} , {position: 'absolute', top: '0px', left: '0px', display: 'none'} ); if (width) img_loader.width = "" + width; if (height) img_loader.height = "" + height; LAPI.Evt.attach (img_loader, 'load', function () {success (img_loader);}); document.body.appendChild (img_loader); // Now the browser goes loading the image } else { // No url to work with. Use parseWikitext to have a thumb generated an to get its URL. LAPI.Ajax.parseWikitext ( '[[' + title + (width ? '|' + width + 'px' : "") + ']]' , function (html, failureFunc) { var dummy = LAPI.make ( 'div' , null , {position: 'absolute', top: '0px', left: '0px', display: 'none'} ); document.body.appendChild (dummy); // Now start loading the image dummy.innerHTML = html; var imgs = dummy.getElementsByTagName ('img'); LAPI.Evt.attach ( imgs[0], 'load' , function () { success (imgs[0]); LAPI.DOM.removeNode (dummy); } ); } , function (request, json_result) { failure ("Image loading failed: " + request.status + ' ' + request.statusText); } , false // Not as preview , null // user language: don't care , null // on page: don't care , 3600 // Cache for an hour ); } } }; // end LAPI.DOM LAPI.DOM.DocumentFacade = function () {this.initialize.apply (this, arguments);}; LAPI.DOM.DocumentFacade.prototype = { initialize : function (text) { // It's not a real document, but it will behave like one for our purposes. this.documentElement = LAPI.make ('div', null, {display: 'none', position: 'absolute'}); this.body = LAPI.make ('div', null, {position: 'relative'}); this.documentElement.appendChild (this.body); document.body.appendChild (this.documentElement); this.body.innerHTML = text; // Find all forms var forms = document.getElementsByTagName ('form'); var self = this; this.forms = Array.select (forms, function (f) {return LAPI.DOM.isParentOf (self.body, f);}); // Konqueror 4.2.3/4.2.4 clears form.elements when the containing div is removed from the // parent document?! if (!LAPI.Browser.is_khtml) { LAPI.DOM.removeNode (this.documentElement); } else { this.dispose = function () {LAPI.DOM.removeNode (this.documentElement);}; // Since we must leave the stuff *in* the original document on Konqueror, we'll also need a // dispose routine... what an ugly hack. } this.allIDs = {}; this.isFake = true; }, createElement : function (tag) { return document.createElement (tag); }, createDocumentFragment : function () { return document.createDocumentFragment (); }, createTextNode : function (text) { return document.createTextNode (text); }, createComment : function (text) { return document.createComment (text); }, createCDATASection : function (text) { return document.createCDATASection (text); }, createAttribute : function (name) { return document.createAttribute (name); }, createEntityReference : function (name) { return document.createEntityReference (name); }, createProcessingInstruction : function (target, data) { return document.createProcessingInstruction (target, data); }, getElementsByTagName : function (tag) { // Grossly inefficient, but deprecated anyway var res = []; function traverse (node, tag) { if (node.nodeName.toLowerCase () == tag) res[res.length] = node; var curr = node.firstChild; while (curr) { traverse (curr, tag); curr = curr.nextSibling; } } traverse (this.body, tag.toLowerCase ()); return res; }, getElementById : function (id) { function traverse (elem, id) { if (elem.id == id) return elem; var res = null; var curr = elem.firstChild; while (curr && !res) { res = traverse (curr, id); curr = curr.nextSibling; } return res; } if (!this.allIDs[id]) this.allIDs[id] = traverse (this.body, id); return this.allIDs[id]; } // ...NS operations omitted }; // end DocumentFacade if (document.importNode) { LAPI.DOM.DocumentFacade.prototype.importNode = function (node, deep) { document.importNode (node, deep); }; } } // end if (guard) if (typeof (LAPI.WP) == 'undefined') { LAPI.WP = { getContentDiv : function (doc) { // Monobook, modern, classic skins return LAPI.$ (['bodyContent', 'mw_contentholder', 'article'], doc); }, fullImageSizeFromPage : function (doc) { // Get the full img size. This is screenscraping :-( but there are times where you don't // want to get this info from the server using an Ajax call. // Note: we get the size from the file history table because the text just below the image // is all scrambled on RTL wikis. For instance, on ar-WP, it is // "‏ (1,806 × 1,341 بكسل، حجم الملف: 996 كيلوبايت، نوع الملف: image/jpeg) and with uselang=en, // it is at ar-WP "‏ (1,806 × 1,341 pixels, file size: 996 KB, MIME type: image/jpeg)" // However, in the file history table, it looks good no matter the language and writing // direction. // Update: this fails on e.g. ar-WP because someone had the great idea to use localized // numerals, but the digit transform table is empty! var result = {width : 0, height : 0}; var file_hist = LAPI.$ ('mw-imagepage-section-filehistory', doc); if (!file_hist) return result; try { var $file_curr = window.jQuery ? $(file_hist).find('td.filehistory-selected') : getElementsByClassName(file_hist, 'td', 'filehistory-selected'); // Did they change the column order here? It once was nextSibling.nextSibling... but somehow // the thumbnails seem to be gone... Right: // http://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/includes/ImagePage.php?r1=52385&r2=53130 file_hist = LAPI.DOM.getInnerText ($file_curr[0].nextSibling); if (!file_hist.contains ('×')) { file_hist = LAPI.DOM.getInnerText ($file_curr[0].nextSibling.nextSibling); if (!file_hist.contains ('×')) file_hist = null; } } catch (ex) { return result; } // Now we have "number×number" followed by something arbitrary if (file_hist) { file_hist = file_hist.split ('×', 2); result.width = parseInt (file_hist.shift ().replace (/[^0-9]/g, ""), 10); // Height is a bit more difficult because e.g. uselang=eo uses a space as the thousands // separator. Hence we have to extract this more carefully file_hist = file_hist.pop(); // Everything after the "×" // Remove any white space embedded between digits file_hist = file_hist.replace (/(\d)\s*(\d)/g, '$1$2'); file_hist = file_hist.split (" ",2).shift ().replace (/[^0-9]/g, ""); result.height = parseInt (file_hist, 10); if (isNaN (result.width) || isNaN (result.height)) result = {width : 0, height : 0}; } return result; }, getPreviewImage : function (title, doc) { var file_div = LAPI.$ ('file', doc); if (!file_div) return null; // Catch page without file... var imgs = file_div.getElementsByTagName ('img'); title = title || mw.config.get('wgTitle'); for (var i = 0; i < imgs.length; i++) { var src = decodeURIComponent (imgs[i].getAttribute ('src', 2)).replace ('%26', '&'); if (src.search (new RegExp ('^' + LAPI_file_store + '.*/' + title.replace (/ /g, '_').escapeRE () + '(/.*)?$')) == 0) return imgs[i]; } return null; }, pageFromLink : function (lk) { if (!lk) return null; var href = lk.getAttribute ('href', 2); if (!href) return null; // This is a bit tricky to get right, because wgScript can be a substring prefix of // wgArticlePath, or vice versa. var script = mw.config.get('wgScript') + '?'; if (href.startsWith (script) || href.startsWith (mw.config.get('wgServer') + script) || mw.config.get('wgServer').startsWith('//') && href.startsWith (document.location.protocol + mw.config.get('wgServer') + script)) { // href="/w/index.php?title=..." return href.getParamValue ('title'); } // Now try wgArticlePath: href="/wiki/..." var prefix = mw.config.get('wgArticlePath').replace ('$1', ""); if (!href.startsWith (prefix)) prefix = mw.config.get('wgServer') + prefix; // Fully expanded URL? if (!href.startsWith (prefix) && prefix.startsWith ('//')) prefix = document.location.protocol + prefix; // Protocol-relative wgServer? if (href.startsWith (prefix)) return decodeURIComponent (href.substring (prefix.length)); // Do we have variants? var variants = mw.config.get('wgVariantArticlePath'); if (variants && variants.length > 0) { var re = new RegExp (variants.escapeRE().replace ('\\$2', "[^\\/]*").replace ('\\$1', "(.*)")); var m = re.exec (href); if (m && m.length > 1) return decodeURIComponent (m[m.length-1]); } // Finally alternative action paths var actions = mw.config.get('wgActionPaths'); if (actions) { for (var i=0; i < actions.length; i++) { var p = actions[i]; if (p && p.length > 0) { p = p.replace('$1', ""); if (!href.startsWith (p)) p = mw.config.get('wgServer') + p; if (!href.startsWith (p) && p.startsWith('//')) p = document.location.protocol + p; if (href.startsWith (p)) return decodeURIComponent (href.substring (p.length)); } } } return null; }, revisionFromHtml : function (htmlOfPage) { var revision_id = null; if (window.mediaWiki) { // MW 1.17+ revision_id = htmlOfPage.match (/(mediaWiki|mw).config.set\(\{.*"wgCurRevisionId"\s*:\s*(\d+),/); if (revision_id) revision_id = parseInt (revision_id[2], 10); } else { // MW < 1.17 revision_id = htmlOfPage.match (/wgCurRevisionId\s*=\s*(\d+)[;,]/); if (revision_id) revision_id = parseInt (revision_id[1], 10); } return revision_id; } }; // end LAPI.WP } // end if (guard) if (typeof (LAPI.Ajax.doAction) == 'undefined') { importScript ('MediaWiki:AjaxSubmit.js'); // Legacy code: ajaxSubmit LAPI.Ajax.getXML = function (request, failureFunc) { var doc = null; if (request.responseXML && request.responseXML.documentElement) { doc = request.responseXML; } else { try { doc = LAPI.DOM.parse (request.responseText, 'text/xml'); } catch (ex) { if (typeof (failureFunc) == 'function') failureFunc (request, ex); doc = null; } } if (doc) { try { doc = LAPI.DOM.isValid (doc); } catch (ex) { if (typeof (failureFunc) == 'function') failureFunc (request, ex); doc = null; } } return doc; }; LAPI.Ajax.getHTML = function (request, failureFunc, sanity_check) { // Konqueror sometimes has severe problems with responseXML. It does set it, but getElementById // may fail to find elements known to exist. var doc = null; // Always use our own parser instead of responseXML; that doesn't work right with HTML5. (It did work with XHTML, though.) // if ( request.responseXML && request.responseXML.documentElement // && request.responseXML.documentElement.tagName == 'HTML' // && (!sanity_check || request.responseXML.getElementById (sanity_check) != null) // ) // { // doc = request.responseXML; // } else { try { doc = LAPI.DOM.parseHTML (request.responseText, sanity_check); if (!doc) throw new Error ('#Could not understand request result'); } catch (ex) { if (typeof (failureFunc) == 'function') failureFunc (request, ex); doc = null; } // } if (doc) { try { doc = LAPI.DOM.isValid (doc); } catch (ex) { if (typeof (failureFunc) == 'function') failureFunc (request, ex); doc = null; } } if (doc === null) return doc; // We've gotten XML. There is a subtle difference between XML and (X)HTML concerning leading newlines in textareas: // XML is required to pass through any whitespace (http://www.w3.org/TR/2004/REC-xml-20040204/#sec-white-space), whereas // HTML may or must not (e.g. http://www.w3.org/TR/html4/appendix/notes.html#h-B.3.1, though it is unclear whether that // really applies to the content of a textarea, but the draft HTML 5 spec explicitly says that the first newline in a // <textarea> is swallowed in HTML: // http://www.whatwg.org/specs/web-apps/current-work/multipage/syntax.html#element-restrictions). // Because of the latter MW1.18+ adds a newline after the <textarea> start tag if the value starts with a newline. That // solves bug 12130 (leading newlines swallowed), but since XML passes us this extra newline, we might end up adding a // leading newline upon each edit. // Let's try to make sure that all textarea's values are as they should be in HTML. // Note: since the above change to always use our own parser, which always returns a faked HTML document, this should be // unnecessary since doc.isFake should always be true. if (typeof (LAPI.Ajax.getHTML.extraNewlineRE) == 'undefined') { // Feature detection. Compare value after parsing with value after .innerHTML. LAPI.Ajax.getHTML.extraNewlineRE = null; // Don't know; hence do nothing try { var testTA = '<textarea id="test">\nTest</textarea>'; var testString = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n' + '<html xmlns="http://www.w3.org/1999/xhtml" lang="en" dir="ltr">\n' + '<head><title>Test</title></head><body><form>' + testTA + '</form></body>\n' + '</html>'; var testDoc = LAPI.DOM.parseHTML (testString, 'test'); var testVal = "" + testDoc.getElementById ('test').value; if (testDoc.dispose) testDoc.dispose(); var testDiv = LAPI.make ('div', null, {display: 'none'}); document.body.appendChild (testDiv); testDiv.innerHTML = testTA; if (testDiv.firstChild.value != testVal) { LAPI.Ajax.getHTML.extraNewlineRE = /^\r?\n/; if (testDiv.firstChild.value != testVal.replace(LAPI.Ajax.getHTML.extraNewlineRE, "")) { // Huh? Not the expected difference: go back to "don't know" mode LAPI.Ajax.getHTML.extraNewlineRE = null; } } LAPI.DOM.removeNode (testDiv); } catch (any) { LAPI.Ajax.getHTML.extraNewlineRE = null; } } if (!doc.isFake && LAPI.Ajax.getHTML.extraNewlineRE !== null) { // If have a "fake" doc, then we did parse through .innerHTML anyway. No need to fix anything. // (Hm. Maybe we should just always use a fake doc?) var tas = doc.getElementsByTagName ('textarea'); for (var i = 0, l = tas.length; i < l; i++) { tas[i].value = tas[i].value.replace(LAPI.Ajax.getHTML.extraNewlineRE, ""); } } return doc; }; LAPI.Ajax.get = function (uri, params, success, failure, config) { var original_failure = failure; if (!failure || typeof (failure) != 'function') failure = function () {}; if (!success || typeof (success) != 'function') throw new Error ('No success function supplied for LAPI.Ajax.get ' + uri + ' with arguments ' + params.toString ()); var request = LAPI.Ajax.getRequest (); if (!request) { failure (request); return; } var args = ""; var question_mark = uri.indexOf ('?'); if (question_mark) { args = uri.substring (question_mark + 1); uri = uri.substring (0, question_mark); } if (params != null) { if (typeof (params) == 'string' && params.length > 0) { args += (args.length > 0 ? '&' : "") + ((params.charAt (0) == '&' || params.charAt (0) == '?') ? params.substring (1) : params ); // Must already be encoded! } else { for (var param in params) { args += (args.length > 0 ? '&' : "") + param; if (params[param] != null) args += '=' + encodeURIComponent (params[param]); } } } var method; if (uri.startsWith ('//')) uri = document.location.protocol + uri; // Avoid protocol-relative URIs (IE7 bug) if (uri.length + args.length + 1 < (LAPI.Browser.is_ie ? 2040 : 4080)) { // Both browsers and web servers may have limits on URL length. IE has a limit of 2083 characters // (2048 in the path part), and the WMF servers seem to impose a limit of 4kB. method = 'GET'; uri += '?' + args; args = null; } else { method = 'POST'; // We'll lose caching, but at least we can make the request. } request.open (method, uri, true); request.setRequestHeader ('Pragma', 'cache=yes'); request.setRequestHeader ( 'Cache-Control' , 'no-transform' + (params && params.maxage ? ', max-age=' + params.maxage : "") + (params && params.smaxage ? ', s-maxage=' + params.smaxage : "") ); if (config) { for (var conf in config) { if (conf == 'overrideMimeType') { if (config[conf] && config[conf].length > 0 && request.overrideMimeType) request.overrideMimeType (config[conf]); } else { request.setRequestHeader (conf, config[conf]); } } } if (args) request.setRequestHeader ('Content-Type', 'application/x-www-form-urlencoded'); request.onreadystatechange = function () { if (request.readyState != 4) return; // Wait until the request has completed. try { if (request.status != 200) throw new Error ('#Request to server failed. Status: ' + request.status + ' ' + request.statusText + ' URI: ' + uri); if (!request.responseText) throw new Error ('#Empty response from server for request ' + uri); } catch (ex) { failure (request, ex); return; } success (request, original_failure); }; request.send (args); }; LAPI.Ajax.getPage = function (page, action, params, success, failure) { var uri = mw.config.get('wgServer') + mw.config.get('wgScript') + '?title=' + encodeURIComponent (page) + (action ? '&action=' + action : ""); LAPI.Ajax.get (uri, params, success, failure, {overrideMimeType : 'application/xml'}); }; // modify is supposed to save the changes at the end, e.g. using LAPI.Ajax.submit. // modify is called with three parameters: the document, possibly the form, and the optional // failure function. The failure function is called with the request as the first parameter, // and possibly an exception as the second parameter. LAPI.Ajax.doAction = function (page, action, form, modify, failure) { if (!page || !action || !modify || typeof (modify) != 'function') throw new Error ('Parameter inconsistency in LAPI.Ajax.doAction.'); var original_failure = failure; if (!failure || typeof (failure) != 'function') failure = function () {}; LAPI.Ajax.getPage ( page, action, null // No additional parameters , function (request, failureFunc) { var doc = null; var the_form = null; var revision_id = null; try { // Convert responseText into DOM tree. doc = LAPI.Ajax.getHTML (request, failureFunc, form); if (!doc) return; var err_msg = LAPI.$ ('permissions-errors', doc); if (err_msg) throw new Error ('#' + LAPI.DOM.getInnerText (err_msg)); if (form) { the_form = LAPI.$ (form, doc); if (!the_form) throw new Error ('#Server reply does not contain mandatory form.'); } revision_id = LAPI.WP.revisionFromHtml (request.responseText); } catch (ex) { failureFunc (request, ex); return; } modify (doc, the_form, original_failure, revision_id) } , failure ); }; // end LAPI.Ajax.doAction LAPI.Ajax.submit = function (form, after_submit) { try { ajaxSubmit (form, null, after_submit, true); // Legacy code from MediaWiki:AjaxSubmit } catch (ex) { after_submit (null, ex); } }; // end LAPI.Ajax.submit LAPI.Ajax.editPage = function (page, modify, failure) { LAPI.Ajax.doAction (page, 'edit', 'editform', modify, failure); }; // end LAPI.Ajax.editPage LAPI.Ajax.checkEdit = function (request) { if (!request) return true; // Check for previews (session token lost?) or edit forms (edit conflict). try { var doc = LAPI.Ajax.getHTML (request, function () {throw new Error ('Cannot check HTML');}); if (!doc) return false; return LAPI.$ (['wikiPreview', 'editform'], doc) == null; } catch (anything) { return false; } }; // end LAPI.Ajax.checkEdit LAPI.Ajax.submitEdit = function (form, success, failure) { if (!success || typeof (success) != 'function') success = function () {}; if (!failure || typeof (failure) != 'function') failure = function () {}; LAPI.Ajax.submit ( form , function (request, ex) { if (ex) { failure (request, ex); } else { var successful = false; try { successful = request && request.status == 200 && LAPI.Ajax.checkEdit (request); } catch (some_error) { failure (request, some_error); return; } if (successful) success (request); else failure (request); } } ); }; // end LAPI.Ajax.submitEdit LAPI.Ajax.apiGet = function (action, params, success, failure) { var original_failure = failure; if (!failure || typeof (failure) != 'function') failure = function () {}; if (!success || typeof (success) != 'function') throw new Error ('No success function supplied for LAPI.Ajax.apiGet ' + action + ' with arguments ' + params.toString ()); var is_json = false; if (params != null) { if (typeof (params) == 'string') { if (!/format=[^&]+/.test (params)) params += '&format=json'; is_json = /format=json(&|$)/.test (params); // Exclude jsonfm, which actually serves XHTML } else { if (typeof (params['format']) != 'string' || params.format.length == 0) params.format = 'json'; is_json = params.format == 'json'; } } var uri = mw.config.get('wgServer') + mw.config.get('wgScriptPath') + '/api.php' + (action ? '?action=' + action : ""); LAPI.Ajax.get ( uri, params , function (request, failureFunc) { if (is_json && request.responseText.trimLeft().charAt (0) != '{') { failureFunc (request); } else { success ( request , (is_json ? eval ('(' + request.responseText.trimLeft() + ')') : null) , original_failure ); } } , failure ); }; // end LAPI.Ajax.apiGet LAPI.Ajax.parseWikitext = function (wikitext, success, failure, as_preview, user_language, on_page, cache) { if (!failure || typeof (failure) != 'function') failure = function () {}; if (!success || typeof (success) != 'function') throw new Error ('No success function supplied for parseWikitext'); if (!wikitext && !on_page) throw new Error ('No wikitext or page supplied for parseWikitext'); var params = null; if (!wikitext) { params = {pst: null, page: on_page}; } else { params = { pst : null // Do the pre-save-transform: Pipe magic, tilde expansion, etc. ,text : (as_preview ? '\<div style="border:1px solid red; padding:0.5em;"\>' + '\<div class="previewnote"\>' + '\{\{MediaWiki:Previewnote/' + (user_language || mw.config.get('wgUserLanguage')) +'\}\}' + '\<\/div>\<div\>\n' : "") + wikitext + (as_preview ? '\<\/div\>\<div style="clear:both;"\>\<\/div\>\<\/div\>' : "") ,title: on_page || mw.config.get('wgPageName') || "API" }; } params.prop = 'text'; params.uselang = user_language || mw.config.get('wgUserLanguage'); // see bugzilla 22764 if (cache && /^\d+$/.test(cache=cache.toString())) { params.maxage = cache; params.smaxage = cache; } LAPI.Ajax.apiGet ( 'parse' , params , function (req, json_result, failureFunc) { // Success. if (!json_result || !json_result.parse || !json_result.parse.text) { failureFunc (req, json_result); return; } success (json_result.parse.text['*'], failureFunc); } , failure ); }; // end LAPI.Ajax.parseWikitext // Throbber backward-compatibility LAPI.Ajax.injectSpinner = function (elementBefore, id) {}; // No-op, replaced as appropriate below. LAPI.Ajax.removeSpinner = function (id) {}; // No-op, replaced as appropriate below. if (typeof window.jQuery == 'undefined' || typeof window.mediaWiki == 'undefined' || typeof window.mediaWiki.loader == 'undefined') { // Assume old-stlye if (typeof window.injectSpinner != 'undefined') { LAPI.Ajax.injectSpinner = window.injectSpinner; } if (typeof window.removeSpinner != 'undefined') { LAPI.Ajax.removeSpinner = window.removeSpinner; } } else { window.mediaWiki.loader.using('jquery.spinner', function () { LAPI.Ajax.injectSpinner = function (elementBefore, id) { window.jQuery(elementBefore).injectSpinner(id); } LAPI.Ajax.removeSpinner = function (id) { window.jQuery.removeSpinner(id); } }); } } // end if (guard) if (typeof (LAPI.Pos) == 'undefined') { LAPI.Pos = { // Returns the global coordinates of the mouse pointer within the document. mousePosition : function (evt) { if (!evt || (typeof (evt.pageX) == 'undefined' && typeof (evt.clientX) == 'undefined')) // No way to calculate a mouse pointer position return null; if (typeof (evt.pageX) != 'undefined') return { x : evt.pageX, y : evt.pageY }; var offset = LAPI.Pos.scrollOffset (); var mouse_delta = LAPI.Pos.mouse_offset (); var coor_x = evt.clientX + offset.x - mouse_delta.x; var coor_y = evt.clientY + offset.y - mouse_delta.y; return { x : coor_x, y : coor_y }; }, // Operations on document level: // Returns the scroll offset of the whole document (in other words, the coordinates // of the top left corner of the viewport). scrollOffset : function () { return {x : LAPI.Pos.getScroll ('Left'), y : LAPI.Pos.getScroll ('Top') }; }, getScroll : function (what) { var s = 'scroll' + what; return (document.documentElement ? document.documentElement[s] : 0) || document.body[s] || 0; }, // Returns the size of the viewport (result.x is the width, result.y the height). viewport : function () { return {x : LAPI.Pos.getViewport ('Width'), y : LAPI.Pos.getViewport ('Height') }; }, getViewport : function (what) { if ( LAPI.Browser.is_opera_95 && what == 'Height' || LAPI.Browser.is_safari && !document.evaluate) return window['inner' + what]; var s = 'client' + what; if (LAPI.Browser.is_opera) return document.body[s]; return (document.documentElement ? document.documentElement[s] : 0) || document.body[s] || 0; }, // Operations on DOM nodes position : (function () { // The following is the jQuery.offset implementation. We cannot use jQuery yet in globally // activated scripts (it has strange side effects for Opera 8 users who can't log in anymore, // and it breaks the search box for some users). Note that jQuery does not support Opera 8. // Until the WMF servers serve jQuery by default, this copy from the jQuery 1.3.2 sources is // needed here. If and when we have jQuery available officially, the whole thing here can be // replaced by "var tmp = jQuery (node).offset(); return {x:tmp.left, y:tmp.top};" // Kudos to the jQuery development team. Any errors in this adaptation are my own. (Lupo, // 2009-08-24). var data = null; function jQuery_init () { data = {}; // Capability check from jQuery. var body = document.body; var container = document.createElement('div'); var html = '<div style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;' + 'padding:0;width:1px;height:1px;"><div></div></div><table style="position:absolute;' + 'top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;" ' + 'cellpadding="0" cellspacing="0"><tr><td></td></tr></table>'; var rules = { position: 'absolute', visibility: 'hidden' ,top: 0, left: 0 ,margin: 0, border: 0 ,width: '1px', height: '1px' }; Object.merge (rules, container.style); container.innerHTML = html; body.insertBefore(container, body.firstChild); var innerDiv = container.firstChild; var checkDiv = innerDiv.firstChild; var td = innerDiv.nextSibling.firstChild.firstChild; data.doesNotAddBorder = (checkDiv.offsetTop !== 5); data.doesAddBorderForTableAndCells = (td.offsetTop === 5); innerDiv.style.overflow = 'hidden', innerDiv.style.position = 'relative'; data.subtractsBorderForOverflowNotVisible = (checkDiv.offsetTop === -5); var bodyMarginTop = body.style.marginTop; body.style.marginTop = '1px'; data.doesNotIncludeMarginInBodyOffset = (body.offsetTop === 0); body.style.marginTop = bodyMarginTop; body.removeChild(container); }; function jQuery_offset (node) { if (node === node.ownerDocument.body) return jQuery_bodyOffset (node); if (node.getBoundingClientRect) { var box = node.getBoundingClientRect (); var scroll = LAPI.Pos.scrollOffset (); return {x : (box.left + scroll.x), y : (box.top + scroll.y)}; } if (!data) jQuery_init (); var elem = node; var offsetParent = elem.offsetParent; var prevOffsetParent = elem; var doc = elem.ownerDocument; var prevComputedStyle = doc.defaultView.getComputedStyle(elem, null); var computedStyle; var top = elem.offsetTop; var left = elem.offsetLeft; while ( (elem = elem.parentNode) && elem !== doc.body && elem !== doc.documentElement ) { computedStyle = doc.defaultView.getComputedStyle(elem, null); top -= elem.scrollTop, left -= elem.scrollLeft; if ( elem === offsetParent ) { top += elem.offsetTop, left += elem.offsetLeft; if ( data.doesNotAddBorder && !(data.doesAddBorderForTableAndCells && /^t(able|d|h)$/i.test(elem.tagName)) ) { top += parseInt (computedStyle.borderTopWidth, 10) || 0; left += parseInt (computedStyle.borderLeftWidth, 10) || 0; } prevOffsetParent = offsetParent; offsetParent = elem.offsetParent; } if (data.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== 'visible') { top += parseInt (computedStyle.borderTopWidth, 10) || 0; left += parseInt (computedStyle.borderLeftWidth, 10) || 0; } prevComputedStyle = computedStyle; } if (prevComputedStyle.position === 'relative' || prevComputedStyle.position === 'static') { top += doc.body.offsetTop; left += doc.body.offsetLeft; } if (prevComputedStyle.position === 'fixed') { top += Math.max (doc.documentElement.scrollTop, doc.body.scrollTop); left += Math.max (doc.documentElement.scrollLeft, doc.body.scrollLeft); } return {x: left, y: top}; } function jQuery_bodyOffset (body) { if (!data) jQuery_init(); var top = body.offsetTop, left = body.offsetLeft; if (data.doesNotIncludeMarginInBodyOffset) { top += parseInt (LAPI.DOM.currentStyle (body, 'margin-top'), 10) || 0; left += parseInt (LAPI.DOM.currentStyle (body, 'margin-left'), 10) || 0; } return {x: left, y: top}; } return jQuery_offset; })(), isWithin : function (node, x, y) { if (!node || !node.parentNode) return false; var pos = LAPI.Pos.position (node); return (x == null || x > pos.x && x < pos.x + node.offsetWidth) && (y == null || y > pos.y && y < pos.y + node.offsetHeight); }, // Private: // IE has some strange offset... mouse_offset : function () { if (LAPI.Browser.is_ie) { var doc_elem = document.documentElement; if (doc_elem) { if (typeof (doc_elem.getBoundingClientRect) == 'function') { var tmp = doc_elem.getBoundingClientRect (); return {x : tmp.left, y : tmp.top}; } else { return {x : doc_elem.clientLeft, y : doc_elem.clientTop}; } } } return {x: 0, y : 0}; } }; // end LAPI.Pos } // end if (guard) if (typeof (LAPI.Evt) == 'undefined') { LAPI.Evt = { listenTo : function (object, node, evt, f, capture) { var listener = LAPI.Evt.makeListener (object, f); LAPI.Evt.attach (node, evt, listener, capture); }, attach : function (node, evt, f, capture) { if (node.attachEvent) node.attachEvent ('on' + evt, f); else if (node.addEventListener) node.addEventListener (evt, f, capture); else node['on' + evt] = f; }, remove : function (node, evt, f, capture) { if (node.detachEvent) node.detachEvent ('on' + evt, f); else if (node.removeEventListener) node.removeEventListener (evt, f, capture); else node['on' + evt] = null; }, makeListener : function (obj, listener) { // Some hacking around to make sure 'this' is set correctly var object = obj, f = listener; return function (evt) { return f.apply (object, [evt || window.event]); } // Alternative implementation: // var f = listener.bind (obj); // return function (evt) { return f (evt || window.event); }; }, kill : function (evt) { if (typeof (evt.preventDefault) == 'function') { evt.stopPropagation (); evt.preventDefault (); // Don't follow the link } else if (typeof (evt.cancelBubble) != 'undefined') { // IE... evt.cancelBubble = true; } return false; // Don't follow the link (IE) } }; // end LAPI.Evt } // end if (guard) if (typeof (LAPI.Edit) == 'undefined') { LAPI.Edit = function () {this.initialize.apply (this, arguments);}; LAPI.Edit.SAVE = 1; LAPI.Edit.PREVIEW = 2; LAPI.Edit.REVERT = 4; LAPI.Edit.CANCEL = 8; LAPI.Edit.prototype = { initialize : function (initial_text, columns, rows, labels, handlers) { var my_labels = {box : null, preview : null, save : 'Save', cancel : 'Cancel', nullsave : null, revert : null, post: null}; if (labels) my_labels = Object.merge (labels, my_labels); this.labels = my_labels; this.timestamp = (new Date ()).getTime (); this.id = 'simpleedit_' + this.timestamp; this.view = LAPI.make ('div', {id : this.id}, {marginRight: '1em'}); // Somehow, the textbox extends beyond the bounding box of the view. Don't know why, but // adding a small margin fixes the layout more or less. this.form = LAPI.make ( 'form' , { id : this.id + '_form' ,action : "" ,onsubmit: (function () {}) } ); if (my_labels.box) { var label = LAPI.make ('div'); label.appendChild (LAPI.DOM.makeLabel (this.id + '_label', my_labels.box, this.id + '_text')); this.form.appendChild (label); } this.textarea = LAPI.make ( 'textarea' , { id : this.id + '_text' ,cols : columns ,rows : rows ,value: (initial_text ? initial_text.toString () : "") } ); LAPI.Evt.attach (this.textarea, 'keyup', LAPI.Evt.makeListener (this, this.text_changed)); // Catch cut/copy/paste through the context menu. Some browsers support oncut, oncopy, // onpaste events for this, but since that's only IE, FF 3, Safari 3, and Chrome, we // cannot rely on this. Instead, we check again as soon as we leave the textarea. Only // minor catch is that on FF 3, the next focus target is determined before the blur event // fires. Since in practice save will always be enabled, this shouldn't be a problem. LAPI.Evt.attach (this.textarea, 'mouseout', LAPI.Evt.makeListener (this, this.text_changed)); LAPI.Evt.attach (this.textarea, 'blur', LAPI.Evt.makeListener (this, this.text_changed)); this.form.appendChild (this.textarea); this.form.appendChild (LAPI.make ('br')); this.preview_section = LAPI.make ('div', null, {borderBottom: '1px solid #8888aa', display: 'none'}); this.view.insertBefore (this.preview_section, this.view.firstChild); this.save = LAPI.DOM.makeButton (this.id + '_save', my_labels.save, LAPI.Evt.makeListener (this, this.do_save)); this.form.appendChild (this.save); if (my_labels.preview) { this.preview = LAPI.DOM.makeButton (this.id + '_preview', my_labels.preview, LAPI.Evt.makeListener (this, this.do_preview)); this.form.appendChild (this.preview); } this.cancel = LAPI.DOM.makeButton (this.id + '_cancel', my_labels.cancel, LAPI.Evt.makeListener (this, this.do_cancel)); this.form.appendChild (this.cancel); this.view.appendChild (this.form); if (my_labels.post) { this.post_text = LAPI.DOM.setContent (LAPI.make ('div'), my_labels.post); this.view.appendChild (this.post_text); } if (handlers) Object.merge (handlers, this); if (typeof (this.ongettext) != 'function') this.ongettext = function (text) { return text;}; // Default: no modifications this.current_mask = LAPI.Edit.SAVE + LAPI.Edit.PREVIEW + LAPI.Edit.REVERT + LAPI.Edit.CANCEL; if ((!initial_text || initial_text.trim ().length == 0) && this.preview) this.preview.disabled = true; if (my_labels.revert) { this.revert = LAPI.DOM.makeButton (this.id + '_revert', my_labels.revert, LAPI.Evt.makeListener (this, this.do_revert)); this.form.insertBefore (this.revert, this.cancel); } this.original_text = ""; }, getView : function () { return this.view; }, getText : function () { return this.ongettext (this.textarea.value); }, setText : function (text) { this.textarea.value = text; this.original_text = text; this.text_changed (); }, changeText : function (text) { this.textarea.value = text; this.text_changed (); }, hidePreview : function () { this.preview_section.style.display = 'none'; if (this.onpreview) this.onpreview (this); }, showPreview : function () { this.preview_section.style.display = ""; if (this.onpreview) this.onpreview (this); }, setPreview : function (html) { if (html.nodeName) { LAPI.DOM.removeChildren (this.preview_section); this.preview_section.appendChild (html); } else { this.preview_section.innerHTML = html; } }, busy : function (show) { if (show) LAPI.Ajax.injectSpinner (this.cancel, this.id + '_spinner'); else LAPI.Ajax.removeSpinner (this.id + '_spinner'); }, do_save : function (evt) { if (this.onsave) this.onsave (this); return true; }, do_revert : function (evt) { this.changeText (this.original_text); return true; }, do_cancel : function (evt) { if (this.oncancel) this.oncancel (this); return true; }, do_preview : function (evt) { var self = this; this.busy (true); LAPI.Ajax.parseWikitext ( this.getText () , function (text, failureFunc) { self.busy (false); self.setPreview (text); self.showPreview (); } , function (req, json_result) { // Error. TODO: user feedback? self.busy (false); } , true , mw.config.get('wgUserLanguage') || null , mw.config.get('wgPageName') || null ); return true; }, enable : function (bit_set) { var call_text_changed = false; this.current_mask = bit_set; this.save.disabled = ((bit_set & LAPI.Edit.SAVE) == 0); this.cancel.disabled = ((bit_set & LAPI.Edit.CANCEL) == 0); if (this.preview) { if ((bit_set & LAPI.Edit.PREVIEW) == 0) this.preview.disabled = true; else call_text_changed = true; } if (this.revert) { if ((bit_set & LAPI.Edit.REVERT) == 0) this.revert.disabled = true; else call_text_changed = true; } if (call_text_changed) this.text_changed (); }, text_changed : function (evt) { var text = this.textarea.value; text = text.trim (); var length = text.length; if (this.preview && (this.current_mask & LAPI.Edit.PREVIEW) != 0) { // Preview is basically enabled this.preview.disabled = (length <= 0); } if (this.labels.nullsave) { if (length > 0) { this.save.value = this.labels.save; } else { this.save.value = this.labels.nullsave; } } if (this.revert) { this.revert.disabled = (text == this.original_text || this.textarea.value == this.original_text); } return true; } }; // end LAPI.Edit } // end if (guard) // </source> g167kmarps7j49pqzx7dhhwzeeb9f23 269960 269959 2026-09-06T20:32:29Z Ladsgroup 41749 269960 javascript text/javascript // <source lang=javascript"> /* Small JS library containing stuff I use often. Author: [[User:Lupo]], June 2009 License: Quadruple licensed GFDL, GPL, LGPL and Creative Commons Attribution 3.0 (CC-BY-3.0) Choose whichever license of these you like best :-) Includes the following components: - Object enhancements (clone, merge) - String enhancements (trim, ...) - Array enhancements (JS 1.6) - Function enhancements (bind) - LAPI Most basic DOM functions: $ (getElementById), make - LAPI.Ajax Ajax request implementation, tailored for MediaWiki/WMF sites - LAPI.Browser Browser detection (general) - LAPI.DOM DOM helpers, including a cross-browser DOM parser - LAPI.WP MediaWiki/WMF-specific DOM routines - LAPI.Edit Simple editor implementation with save, cancel, preview (for WMF sites) - LAPI.Evt Event handler routines (general) - LAPI.Pos Position calculations (general) */ // Global: importScript (from wiki.js, for MediaWiki:AjaxSubmit.js) // Configuration: set this to the URL of your image server. The value is a string representation // of a regular expression. For instance, for Wikia, use "http://images\\d\\.wikia\\.nocookie\\.net". // Remember to double-escape the backslash. if (typeof (LAPI_file_store) == 'undefined') var LAPI_file_store = "(https?:)?//(?:upload|thumb)\\.wikimedia\\.org/"; // Some basic routines, mainly enhancements of the String, Array, and Function objects. // Some taken from Javascript 1.6, some own. /** Object enhancements ************/ // Note: adding these to the prototype may break other code that assumes that // {} has no properties at all. Object.clone = function (source, includeInherited) { if (!source) return null; var result = {}; for (var key in source) { if (includeInherited || source.hasOwnProperty (key)) result[key] = source[key]; } return result; }; Object.merge = function (from, into, includeInherited) { if (!from) return into; for (var key in from) { if (includeInherited || from.hasOwnProperty (key)) into[key] = from[key]; } return into; }; Object.mergeSome = function (from, into, includeInherited, predicate) { if (!from) return into; if (typeof (predicate) == 'undefined') return Object.merge (from, into, includeInherited); for (var key in from) { if ((includeInherited || from.hasOwnProperty (key)) && predicate (from, into, key)) into[key] = from[key]; } return into; }; Object.mergeSet = function (from, into, includeInherited) { return Object.mergeSome (from, into, includeInherited, function (src, tgt, key) {return src[key] != null;}); } /** String enhancements (Javascript 1.6) ************/ // Removes given characters from both ends of the string. // If no characters are given, defaults to removing whitespace. if (!String.prototype.trim) { String.prototype.trim = function (chars) { if (!chars) return this.replace (/^\s+|\s+$/g, ""); return this.trimRight (chars).trimLeft (chars); }; } // Removes given characters from the beginning of the string. // If no characters are given, defaults to removing whitespace. if (!String.prototype.trimLeft) { String.prototype.trimLeft = function (chars) { if (!chars) return this.replace (/^\s\s*/, ""); return this.replace (new RegExp ('^[' + chars.escapeRE () + ']+'), ""); }; } String.prototype.trimFront = String.prototype.trimLeft; // Synonym // Removes given characters from the end of the string. // If no characters are given, defaults to removing whitespace. if (!String.prototype.trimRight) { String.prototype.trimRight = function (chars) { if (!chars) return this.replace (/\s\s*$/, ""); return this.replace (new RegExp ('[' + chars.escapeRE () + ']+$'), ""); }; } String.prototype.trimEnd = String.prototype.trimRight; // Synonym /** Further String enhancements ************/ // Returns true if the string begins with prefix. String.prototype.startsWith = function (prefix) { return this.indexOf (prefix) == 0; }; // Returns true if the string ends in suffix String.prototype.endsWith = function (suffix) { return this.lastIndexOf (suffix) + suffix.length == this.length; }; // Returns true if the string contains s. String.prototype.contains = function (s) { return this.indexOf (s) >= 0; }; // Replace all occurrences of a string pattern by replacement. String.prototype.replaceAll = function (pattern, replacement) { return this.split (pattern).join (replacement); }; // Escape all backslashes and single or double quotes such that the result can // be used in Javascript inside quotes or double quotes. String.prototype.stringifyJS = function () { return this.replace (/([\\\'\"]|%5C|%27|%22)/g, '\\$1') // ' // Fix syntax coloring .replace (/\n/g, '\\n'); } // Escape all RegExp special characters such that the result can be safely used // in a RegExp as a literal. String.prototype.escapeRE = function () { return this.replace (/([\\{}()|.?*+^$\[\]])/g, "\\$1"); }; String.prototype.escapeXML = function (quot, apos) { var s = this.replace (/&/g, '&amp;') .replace (/\xa0/g, '&nbsp;') .replace (/</g, '&lt;') .replace (/>/g, '&gt;'); if (quot) s = s.replace (/\"/g, '&quot;'); // " // Fix syntax coloring if (apos) s = s.replace (/\'/g, '&apos;'); // ' // Fix syntax coloring return s; }; String.prototype.decodeXML = function () { return this.replace(/&quot;/g, '"') .replace(/&apos;/g, "'") .replace(/&gt;/g, '>') .replace(/&lt;/g, '<') .replace(/&nbsp;/g, '\xa0') .replace(/&amp;/g, '&'); }; String.prototype.capitalizeFirst = function () { return this.substring (0, 1).toUpperCase() + this.substring (1); }; String.prototype.lowercaseFirst = function () { return this.substring (0, 1).toLowerCase() + this.substring (1); }; // This is actually a function on URLs, but since URLs typically are strings in // Javascript, let's include this one here, too. String.prototype.getParamValue = function (param) { var re = new RegExp ('[&?]' + param.escapeRE () + '=([^&#]*)'); var m = re.exec (this); if (m && m.length >= 2) return decodeURIComponent (m[1]); return null; }; String.getParamValue = function (param, url) { if (typeof (url) == 'undefined' || url === null) url = document.location.href; try { return url.getParamValue (param); } catch (e) { return null; } }; /** Function enhancements ************/ if (!Function.prototype.bind) { // Return a function that calls the function with 'this' bound to 'thisObject' Function.prototype.bind = function (thisObject) { var f = this, obj = thisObject, slice = Array.prototype.slice, prefixedArgs = slice.call (arguments, 1); return function () { return f.apply (obj, prefixedArgs.concat (slice.call (arguments))); }; }; } /** Array enhancements (Javascript 1.6) ************/ // Note that contrary to JS 1.6, we treat the thisObject as optional. // Don't add to the prototype, that would break for (var key in array) loops! // Returns a new array containing only those elements for which predicate // is true. if (!Array.filter) { Array.filter = function (target, predicate, thisObject) { if (target === null) return null; if (typeof (target.filter) == 'function') return target.filter (predicate, thisObject); if (typeof (predicate) != 'function') throw new Error ('Array.filter: predicate must be a function'); var l = target.length; var result = []; if (thisObject) predicate = predicate.bind (thisObject); for (var i=0; l && i < l; i++) { if (i in target) { var curr = target[i]; if (predicate (curr, i, target)) result[result.length] = curr; } } return result; }; } Array.select = Array.filter; // Synonym // Calls iterator on all elements of the array if (!Array.forEach) { Array.forEach = function (target, iterator, thisObject) { if (target === null) return; if (typeof (target.forEach) == 'function') { target.forEach (iterator, thisObject); return; } if (typeof (iterator) != 'function') throw new Error ('Array.forEach: iterator must be a function'); var l = target.length; if (thisObject) iterator = iterator.bind (thisObject); for (var i=0; l && i < l; i++) { if (i in target) iterator (target[i], i, target); } }; } // Returns true if predicate is true for every element of the array, false otherwise if (!Array.every) { Array.every = function (target, predicate, thisObject) { if (target === null) return true; if (typeof (target.every) == 'function') return target.every (predicate, thisObject); if (typeof (predicate) != 'function') throw new Error ('Array.every: predicate must be a function'); var l = target.length; if (thisObject) predicate = predicate.bind (thisObject); for (var i=0; l && i < l; i++) { if (i in target && !predicate (target[i], i, target)) return false; } return true; }; } Array.forAll = Array.every; // Synonym // Returns true if predicate is true for at least one element of the array, false otherwise. if (!Array.some) { Array.some = function (target, predicate, thisObject) { if (target === null) return false; if (typeof (target.some) == 'function') return target.some (predicate, thisObject); if (typeof (predicate) != 'function') throw new Error ('Array.some: predicate must be a function'); var l = target.length; if (thisObject) predicate = predicate.bind (thisObject); for (var i=0; l && i < l; i++) { if (i in target && predicate (target[i], i, target)) return true; } return false; }; } Array.exists = Array.some; // Synonym // Returns a new array built by applying mapper to all elements. if (!Array.map) { Array.map = function (target, mapper, thisObject) { if (target === null) return null; if (typeof (target.map) == 'function') return target.map (mapper, thisObject); if (typeof (mapper) != 'function') throw new Error ('Array.map: mapper must be a function'); var l = target.length; var result = []; if (thisObject) mapper = mapper.bind (thisObject); for (var i=0; l && i < l; i++) { if (i in target) result[i] = mapper (target[i], i, target); } return result; }; } if (!Array.indexOf) { Array.indexOf = function (target, elem, from) { if (target === null) return -1; if (typeof (target.indexOf) == 'function') return target.indexOf (elem, from); if (typeof (target.length) == 'undefined') return -1; var l = target.length; if (isNaN (from)) from = 0; else from = from || 0; from = (from < 0) ? Math.ceil (from) : Math.floor (from); if (from < 0) from += l; if (from < 0) from = 0; while (from < l) { if (from in target && target[from] === elem) return from; from += 1; } return -1; }; } if (!Array.lastIndexOf) { Array.lastIndexOf = function (target, elem, from) { if (target === null) return -1; if (typeof (target.lastIndexOf) == 'function') return target.lastIndexOf (elem, from); if (typeof (target.length) == 'undefined') return -1; var l = target.length; if (isNaN (from)) from = l-1; else from = from || (l-1); from = (from < 0) ? Math.ceil (from) : Math.floor (from); if (from < 0) from += l; else if (from >= l) from = l-1; while (from >= 0) { if (from in target && target[from] === elem) return from; from -= 1; } return -1; }; } /** Additional Array enhancements ************/ Array.remove = function (target, elem) { var i = Array.indexOf (target, elem); if (i >= 0) target.splice (i, 1); }; Array.contains = function (target, elem) { return Array.indexOf (target, elem) >= 0; }; Array.flatten = function (target) { var result = []; Array.forEach (target, function (elem) {result = result.concat (elem);}); return result; }; // Calls selector on the array elements until it returns a non-null object // and then returns that object. If selector always returns null, any also // returns null. See also Array.map. Array.any = function (target, selector, thisObject) { if (target === null) return null; if (typeof (selector) != 'function') throw new Error ('Array.any: selector must be a function'); var l = target.length; var result = null; if (thisObject) selector = selector.bind (thisObject); for (var i=0; l && i < l; i++) { if (i in target) { result = selector (target[i], i, target); if (result != null) return result; } } return null; }; // Return a contiguous array of the contents of source, which may be an array or pseudo-array, // basically anything that has a length and can be indexed. (E.g. live HTMLCollections, but also // Strings, or objects, or the arguments "variable". Array.make = function (source) { if (!source || typeof (source.length) == 'undefined') return null; var result = []; var l = source.length; for (var i=0; i < l; i++) { if (i in source) result[result.length] = source[i]; } return result; }; if (typeof (window.LAPI) == 'undefined') { window.LAPI = { Ajax : { getRequest : function () { var request = null; try { request = new XMLHttpRequest(); } catch (anything) { request = null; if (!!window.ActiveXObject) { if (typeof (LAPI.Ajax.getRequest.msXMLHttpID) == 'undefined') { var XHR_ids = [ 'MSXML2.XMLHTTP.6.0', 'MSXML2.XMLHTTP.3.0' , 'MSXML2.XMLHTTP', 'Microsoft.XMLHTTP' ]; for (var i=0; i < XHR_ids.length && !request; i++) { try { request = new ActiveXObject (XHR_ids[i]); if (request) LAPI.Ajax.getRequest.msXMLHttpID = XHR_ids[i]; } catch (ex) { request = null; } } if (!request) LAPI.Ajax.getRequest.msXMLHttpID = null; } else if (LAPI.Ajax.getRequest.msXMLHttpID) { request = new ActiveXObject (LAPI.Ajax.getRequest.msXMLHttpID); } } // end if IE } // end try-catch return request; } }, $ : function (selector, doc, multi) { if (!selector || selector.length == 0) return null; doc = doc || document; if (typeof (selector) == 'string') { if (selector.charAt (0) == '#') selector = selector.substring (1); if (selector.length > 0) return doc.getElementById (selector); return null; } else { if (multi) return Array.map (selector, function (id) {return LAPI.$ (id, doc);}); return Array.any (selector, function (id) {return LAPI.$ (id, doc);}); } }, make : function (tag, attribs, css, doc) { doc = doc || document; if (!tag || tag.length == 0) throw new Error ('No tag for LAPI.make'); var result = doc.createElement (tag); Object.mergeSet (attribs, result); Object.mergeSet (css, result.style); if (/^(form|input|button|select|textarea)$/.test (tag) && result.id && result.id.length > 0 && !result.name ) { result.name = result.id; } return result; }, formatException : function (ex, asDOM) { var name = ex.name || ""; var msg = ex.message || ""; var file = null; var line = null; if (msg && msg.length > 0 && msg.charAt (0) == '#') { // User msg: don't confuse users with error locations. (Note: could also use // custom exception types, but that doesn't work right on IE6.) msg = msg.substring (1); } else { file = ex.fileName || ex.sourceURL || null; // Gecko, Webkit, others line = ex.lineNumber || ex.line || null; // Gecko, Webkit, others } if (name || msg) { if (!asDOM) { return 'Exception ' + name + ': ' + msg + (file ? '\nFile ' + file + (line ? ' (' + line + ')' : "") : "") ; } else { var ex_msg = LAPI.make ('div'); ex_msg.appendChild (document.createTextNode ('Exception ' + name + ': ' + msg)); if (file) { ex_msg.appendChild (LAPI.make ('br')); ex_msg.appendChild (document.createTextNode ('File ' + file + (line ? ' (' + line + ')' : ""))); } return ex_msg; } } else { return null; } } }; } // end if (guard) if (typeof (LAPI.Browser) == 'undefined') { // Yes, usually it's better to test for available features. But sometimes there's no // way around testing for specific browsers (differences in dimensions, layout errors, // etc.) LAPI.Browser = (function (agent) { var result = {}; result.client = agent; var m = agent.match(/applewebkit\/(\d+)/); result.is_webkit = (m != null); result.is_safari = result.is_webkit && !agent.contains ('spoofer'); result.webkit_version = (m ? parseInt (m[1]) : 0); result.is_khtml = navigator.vendor == 'KDE' || (document.childNodes && !document.all && !navigator.taintEnabled && navigator.accentColorName); result.is_gecko = agent.contains ('gecko') && !/khtml|spoofer|netscape\/7\.0/.test (agent); result.is_ff_1 = agent.contains ('firefox/1'); result.is_ff_2 = agent.contains ('firefox/2'); result.is_ff_ge_2 = /firefox\/[2-9]|minefield\/3/.test (agent); result.is_ie = agent.contains ('msie') || !!window.ActiveXObject; result.is_ie_lt_7 = false; if (result.is_ie) { var version = /msie ((\d|\.)+)/.exec (agent); result.is_ie_lt_7 = (version != null && (parseFloat(version[1]) < 7)); } result.is_opera = agent.contains ('opera'); result.is_opera_ge_9 = false; result.is_opera_95 = false; if (result.is_opera) { m = /opera\/((\d|\.)+)/.exec (agent); result.is_opera_95 = m && (parseFloat (m[1]) >= 9.5); result.is_opera_ge_9 = m && (parseFloat (m[1]) >= 9.0); } result.is_mac = agent.contains ('mac'); return result; })(navigator.userAgent.toLowerCase ()); } // end if (guard) if (typeof (LAPI.DOM) == 'undefined') { LAPI.DOM = { // IE6 doesn't have these Node constants in Node, so put them here ELEMENT_NODE : 1, ATTRIBUTE_NODE : 2, TEXT_NODE : 3, CDATA_SECTION_NODE : 4, ENTITY_REFERENCE_NODE : 5, ENTITY_NODE : 6, PROCESSING_INSTRUCTION_NODE : 7, COMMENT_NODE : 8, DOCUMENT_NODE : 9, DOCUMENT_TYPE_NODE : 10, DOCUMENT_FRAGMENT_NODE : 11, NOTATION_NODE : 12, cleanAttributeName : function (attr_name) { if (!LAPI.Browser.is_ie) return attr_name; if (!LAPI.DOM.cleanAttributeName._names) { LAPI.DOM.cleanAttributeName._names = { 'class' : 'className' ,'cellspacing' : 'cellSpacing' ,'cellpadding' : 'cellPadding' ,'colspan' : 'colSpan' ,'maxlength' : 'maxLength' ,'readonly' : 'readOnly' ,'rowspan' : 'rowSpan' ,'tabindex' : 'tabIndex' ,'valign' : 'vAlign' }; } var cleaned = attr_name.toLowerCase (); return LAPI.DOM.cleanAttributeName._names[cleaned] || cleaned; }, importNode : function (into, node, deep) { if (!node) return null; if (into.importNode) return into.importNode (node, deep); if (node.ownerDocument == into) return node.cloneNode (deep); var new_node = null; switch (node.nodeType) { case LAPI.DOM.ELEMENT_NODE : new_node = into.createElement (node.nodeName); Array.forEach ( node.attributes , function (attr) { if (attr && attr.nodeValue && attr.nodeValue.length > 0) new_node.setAttribute (LAPI.DOM.cleanAttributeName (attr.name), attr.nodeValue); } ); new_node.style.cssText = node.style.cssText; if (deep) { Array.forEach ( node.childNodes , function (child) { var copy = LAPI.DOM.importNode (into, child, true); if (copy) new_node.appendChild (copy); } ); } return new_node; case LAPI.DOM.TEXT_NODE : return into.createTextNode (node.nodeValue); case LAPI.DOM.CDATA_SECTION_NODE : return (into.createCDATASection ? into.createCDATASection (node.nodeValue) : into.createTextNode (node.nodeValue) ); case LAPI.DOM.COMMENT_NODE : return into.createComment (node.nodeValue); default : return null; } // end switch }, parse : function (str, content_type) { function getDocument (str, content_type) { if (typeof (DOMParser) != 'undefined') { var parser = new DOMParser (); if (parser && parser.parseFromString) return parser.parseFromString (str, content_type); } // We don't have DOMParser if (LAPI.Browser.is_ie) { var doc = null; // Apparently, these can be installed side-by-side. Try to get the newest one available. // Unfortunately, one finds a variety of version strings on the net. I have no idea which // ones are correct. if (typeof (LAPI.DOM.parse.msDOMDocumentID) == 'undefined') { // If we find a parser, we cache it. If we cannot find one, we also remember that. var parsers = [ 'MSXML6.DOMDocument','MSXML5.DOMDocument','MSXML4.DOMDocument','MSXML3.DOMDocument' ,'MSXML2.DOMDocument.5.0','MSXML2.DOMDocument.4.0','MSXML2.DOMDocument.3.0' ,'MSXML2.DOMDocument','MSXML.DomDocument','Microsoft.XmlDom']; for (var i=0; i < parsers.length && !doc; i++) { try { doc = new ActiveXObject (parsers[i]); if (doc) LAPI.DOM.parse.msDOMDocumentID = parsers[i]; } catch (ex) { doc = null; } } if (!doc) LAPI.DOM.parse.msDOMDocumentID = null; } else if (LAPI.DOM.parse.msDOMDocumentID) { doc = new ActiveXObject (LAPI.DOM.parse.msDOMDocumentID); } if (doc) { doc.async = false; doc.loadXML (str); return doc; } } // Try using a "data" URI (http://www.ietf.org/rfc/rfc2397). Reported to work on // older Safaris. content_type = content_type || 'application/xml'; var req = LAPI.Ajax.getRequest (); if (req) { // Synchronous is OK, since "data" URIs are local req.open ('GET', 'data:' + content_type + ';charset=utf-8,' + encodeURIComponent (str), false); if (req.overrideMimeType) req.overrideMimeType (content_type); req.send (null); return req.responseXML; } return null; } // end getDocument var doc = null; try { doc = getDocument (str, content_type); } catch (ex) { doc = null; } if ( ( (!doc || !doc.documentElement) && ( str.search (/^\s*(<xml[^>]*>\s*)?<!doctype\s+html/i) >= 0 || str.search (/^\s*<html/i) >= 0 ) ) || (doc && ( LAPI.Browser.is_ie && (!doc.documentElement && doc.parseError && doc.parseError.errorCode != 0 && doc.parseError.reason.contains ('Error processing resource') && doc.parseError.reason.contains ('http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd') ) ) ) ) { // Either the text specified an (X)HTML document, but we failed to get a Document, or we // hit the walls of the single-origin policy on IE which tries to get the DTD from the // URI specified... Let's fake a document: doc = LAPI.DOM.fakeHTMLDocument (str); } return doc; }, parseHTML : function (str, sanity_check) { // Always use a faked document; parsing as XML and then treating the result as HTML doesn't work right with HTML5. return LAPI.DOM.fakeHTMLDocument (str); }, fakeHTMLDocument : function (str) { var body_tag = /<body.*?>/.exec (str); if (!body_tag || body_tag.length == 0) return null; body_tag = body_tag.index + body_tag[0].length; // Index after the opening body tag var body_end = str.lastIndexOf ('</body>'); if (body_end < 0) return null; var content = str.substring (body_tag, body_end); // Anything in between content = content.replace(/<script(.|\s)*?\/script>/g, ""); // Sanitize: strip scripts return new LAPI.DOM.DocumentFacade (content); }, isValid : function (doc) { if (!doc) return doc; if (typeof (doc.parseError) != 'undefined') { // IE if (doc.parseError.errorCode != 0) { throw new Error ( 'XML parse error: ' + doc.parseError.reason + ' line ' + doc.parseError.line + ' col ' + doc.parseError.linepos + '\nsrc = ' + doc.parseError.srcText); } } else { // FF... others? var root = doc.documentElement; if (/^parsererror$/i.test (root.tagName)) { throw new Error ('XML parse error: ' + root.getInnerText ()); } } return doc; }, hasClass : function (node, className) { if (!node) return false; return (' ' + node.className + ' ').contains (' ' + className + ' '); }, setContent : function (node, content) { if (content == null) return node; LAPI.DOM.removeChildren (node); if (content.nodeName) { // presumably a DOM tree, like a span or a document fragment node.appendChild (content); } else if (typeof (node.innerHTML) != 'undefined') { node.innerHTML = content.toString (); } else { node.appendChild (document.createTextNode (content.toString ())); } return node; }, makeImage : function (src, width, height, title, doc) { return LAPI.make ( 'img' , {src : src, width: "" + width, height : "" + height, title : title} , doc ); }, makeButton : function (id, text, f, submit, doc) { return LAPI.make ( 'input' , {id : id || "", type: (submit ? 'submit' : 'button'), value: text, onclick: f} , doc ); }, makeLabel : function (id, text, for_elem, doc) { var label = LAPI.make ('label', {id: id || "", htmlFor: for_elem}, null, doc); return LAPI.DOM.setContent (label, text); }, makeLink : function (url, text, tooltip, onclick, doc) { var lk = LAPI.make ('a', {href: url, title: tooltip, onclick: onclick}, null, doc); return LAPI.DOM.setContent (lk, text || url); }, // Unfortunately, extending Node.prototype may not work on some browsers, // most notably (you've guessed it) IE... getInnerText : function (node) { if (node.textContent) return node.textContent; if (node.innerText) return node.innerText; var result = ""; if (node.nodeType == LAPI.DOM.TEXT_NODE) { result = node.nodeValue; } else { Array.forEach (node.childNodes, function (elem) { switch (elem.nodeType) { case LAPI.DOM.ELEMENT_NODE: result += LAPI.DOM.getInnerText (elem); break; case LAPI.DOM.TEXT_NODE: result += elem.nodeValue; break; } } ); } return result; }, removeNode : function (node) { if (node.parentNode) node.parentNode.removeChild (node); return node; }, removeChildren : function (node) { // if (typeof (node.innerHTML) != 'undefined') node.innerHTML = ""; // Not a good idea. On IE this destroys all contained nodes, even if they're still referenced // from JavaScript! Can't have that... while (node.firstChild) node.removeChild (node.firstChild); return node; }, insertNode : function (node, before) { before.parentNode.insertBefore (node, before); return node; }, insertAfter : function (node, after) { var next = after.nextSibling; after.parentNode.insertBefore (node, next); return node; }, replaceNode : function (node, newNode) { node.parentNode.replaceChild (node, newNode); return newNode; }, isParentOf : function (parent, child) { while (child && child != parent && child.parentNode) child = child.parentNode; return child == parent; }, // Property is to be in CSS style, e.g. 'background-color', not in JS style ('backgroundColor')! // Use standard 'cssFloat' for float property. currentStyle : function (element, property) { function normalize (prop) { // Don't use a regexp with a lambda function (available only in JS 1.3)... and I once had a // case where IE6 goofed grossly with a lambda function. Since then I try to avoid those // (though they're neat). if (prop == 'cssFloat') return 'styleFloat'; // We'll try both variants below, standard first... var result = prop.split ('-'); result = Array.map (result, function (s) { if (s) return s.capitalizeFirst (); else return s;}); result = result.join (""); return result.lowercaseFirst (); } if (element.ownerDocument.defaultView && element.ownerDocument.defaultView.getComputedStyle) { // Gecko etc. if (property == 'cssFloat') property = 'float'; return element.ownerDocument.defaultView.getComputedStyle (element, null).getPropertyValue (property); } else { var result; if (element.currentStyle) { // IE, has subtle differences to getComputedStyle result = element.currentStyle[property] || element.currentStyle[normalize (property)]; } else // Not exactly right, but best effort result = element.style[property] || element.style[normalize (property)]; // Convert em etc. to pixels. Kudos to Dean Edwards; see // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 if (!/^\d+(px)?$/i.test (result) && /^\d/.test (result) && element.runtimeStyle) { var style = element.style.left; var runtimeStyle = element.runtimeStyle.left; element.runtimeStyle.left = element.currentStyle.left; element.style.left = result || 0; result = elem.style.pixelLeft + "px"; element.style.left = style; element.runtimeStyle.left = runtimeStyle; } } }, // Load a given image in a given size. Parameters: // title // Full title of the image, including the "File:" namespace // url // If != null, URL of an existing thumb for that image. If width is null, may contain the url // of the full image. // width // If != null, desired width of the image, otherwise load the full image // height // If width != null, height should also be set. // auto_thumbs // True if missing thumbnails are generated automatically. // success // Function to be called once the image is loaded. Takes one parameter: the IMG-tag of // the loaded image // failure // Function to be called if the image cannot be loaded. Takes one parameter: a string // containing an error message. loadImage : function (title, url, width, height, auto_thumbs, success, failure) { if (auto_thumbs && url) { // MediaWiki-style with 404 handler. Set condition to false if your wiki does not have such a // setup. var img_src = null; if (width) { var i = url.lastIndexOf ('/'); if (i >= 0) { img_src = url.substring (0, i) + url.substring (i).replace (/^\/\d+px-/, '/' + width + 'px-'); } } else if (url) { img_src = url; } if (!img_src) { failure ("Cannot load image from url " + url); return; } var img_loader = LAPI.make ( 'img' , {src: img_src} , {position: 'absolute', top: '0px', left: '0px', display: 'none'} ); if (width) img_loader.width = "" + width; if (height) img_loader.height = "" + height; LAPI.Evt.attach (img_loader, 'load', function () {success (img_loader);}); document.body.appendChild (img_loader); // Now the browser goes loading the image } else { // No url to work with. Use parseWikitext to have a thumb generated an to get its URL. LAPI.Ajax.parseWikitext ( '[[' + title + (width ? '|' + width + 'px' : "") + ']]' , function (html, failureFunc) { var dummy = LAPI.make ( 'div' , null , {position: 'absolute', top: '0px', left: '0px', display: 'none'} ); document.body.appendChild (dummy); // Now start loading the image dummy.innerHTML = html; var imgs = dummy.getElementsByTagName ('img'); LAPI.Evt.attach ( imgs[0], 'load' , function () { success (imgs[0]); LAPI.DOM.removeNode (dummy); } ); } , function (request, json_result) { failure ("Image loading failed: " + request.status + ' ' + request.statusText); } , false // Not as preview , null // user language: don't care , null // on page: don't care , 3600 // Cache for an hour ); } } }; // end LAPI.DOM LAPI.DOM.DocumentFacade = function () {this.initialize.apply (this, arguments);}; LAPI.DOM.DocumentFacade.prototype = { initialize : function (text) { // It's not a real document, but it will behave like one for our purposes. this.documentElement = LAPI.make ('div', null, {display: 'none', position: 'absolute'}); this.body = LAPI.make ('div', null, {position: 'relative'}); this.documentElement.appendChild (this.body); document.body.appendChild (this.documentElement); this.body.innerHTML = text; // Find all forms var forms = document.getElementsByTagName ('form'); var self = this; this.forms = Array.select (forms, function (f) {return LAPI.DOM.isParentOf (self.body, f);}); // Konqueror 4.2.3/4.2.4 clears form.elements when the containing div is removed from the // parent document?! if (!LAPI.Browser.is_khtml) { LAPI.DOM.removeNode (this.documentElement); } else { this.dispose = function () {LAPI.DOM.removeNode (this.documentElement);}; // Since we must leave the stuff *in* the original document on Konqueror, we'll also need a // dispose routine... what an ugly hack. } this.allIDs = {}; this.isFake = true; }, createElement : function (tag) { return document.createElement (tag); }, createDocumentFragment : function () { return document.createDocumentFragment (); }, createTextNode : function (text) { return document.createTextNode (text); }, createComment : function (text) { return document.createComment (text); }, createCDATASection : function (text) { return document.createCDATASection (text); }, createAttribute : function (name) { return document.createAttribute (name); }, createEntityReference : function (name) { return document.createEntityReference (name); }, createProcessingInstruction : function (target, data) { return document.createProcessingInstruction (target, data); }, getElementsByTagName : function (tag) { // Grossly inefficient, but deprecated anyway var res = []; function traverse (node, tag) { if (node.nodeName.toLowerCase () == tag) res[res.length] = node; var curr = node.firstChild; while (curr) { traverse (curr, tag); curr = curr.nextSibling; } } traverse (this.body, tag.toLowerCase ()); return res; }, getElementById : function (id) { function traverse (elem, id) { if (elem.id == id) return elem; var res = null; var curr = elem.firstChild; while (curr && !res) { res = traverse (curr, id); curr = curr.nextSibling; } return res; } if (!this.allIDs[id]) this.allIDs[id] = traverse (this.body, id); return this.allIDs[id]; } // ...NS operations omitted }; // end DocumentFacade if (document.importNode) { LAPI.DOM.DocumentFacade.prototype.importNode = function (node, deep) { document.importNode (node, deep); }; } } // end if (guard) if (typeof (LAPI.WP) == 'undefined') { LAPI.WP = { getContentDiv : function (doc) { // Monobook, modern, classic skins return LAPI.$ (['bodyContent', 'mw_contentholder', 'article'], doc); }, fullImageSizeFromPage : function (doc) { // Get the full img size. This is screenscraping :-( but there are times where you don't // want to get this info from the server using an Ajax call. // Note: we get the size from the file history table because the text just below the image // is all scrambled on RTL wikis. For instance, on ar-WP, it is // "‏ (1,806 × 1,341 بكسل، حجم الملف: 996 كيلوبايت، نوع الملف: image/jpeg) and with uselang=en, // it is at ar-WP "‏ (1,806 × 1,341 pixels, file size: 996 KB, MIME type: image/jpeg)" // However, in the file history table, it looks good no matter the language and writing // direction. // Update: this fails on e.g. ar-WP because someone had the great idea to use localized // numerals, but the digit transform table is empty! var result = {width : 0, height : 0}; var file_hist = LAPI.$ ('mw-imagepage-section-filehistory', doc); if (!file_hist) return result; try { var $file_curr = window.jQuery ? $(file_hist).find('td.filehistory-selected') : getElementsByClassName(file_hist, 'td', 'filehistory-selected'); // Did they change the column order here? It once was nextSibling.nextSibling... but somehow // the thumbnails seem to be gone... Right: // http://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/includes/ImagePage.php?r1=52385&r2=53130 file_hist = LAPI.DOM.getInnerText ($file_curr[0].nextSibling); if (!file_hist.contains ('×')) { file_hist = LAPI.DOM.getInnerText ($file_curr[0].nextSibling.nextSibling); if (!file_hist.contains ('×')) file_hist = null; } } catch (ex) { return result; } // Now we have "number×number" followed by something arbitrary if (file_hist) { file_hist = file_hist.split ('×', 2); result.width = parseInt (file_hist.shift ().replace (/[^0-9]/g, ""), 10); // Height is a bit more difficult because e.g. uselang=eo uses a space as the thousands // separator. Hence we have to extract this more carefully file_hist = file_hist.pop(); // Everything after the "×" // Remove any white space embedded between digits file_hist = file_hist.replace (/(\d)\s*(\d)/g, '$1$2'); file_hist = file_hist.split (" ",2).shift ().replace (/[^0-9]/g, ""); result.height = parseInt (file_hist, 10); if (isNaN (result.width) || isNaN (result.height)) result = {width : 0, height : 0}; } return result; }, getPreviewImage : function (title, doc) { var file_div = LAPI.$ ('file', doc); if (!file_div) return null; // Catch page without file... var imgs = file_div.getElementsByTagName ('img'); title = title || mw.config.get('wgTitle'); for (var i = 0; i < imgs.length; i++) { var src = decodeURIComponent (imgs[i].getAttribute ('src', 2)).replace ('%26', '&'); if (src.search (new RegExp ('^' + LAPI_file_store + '.*/' + title.replace (/ /g, '_').escapeRE () + '(/.*)?$')) == 0) return imgs[i]; } return null; }, pageFromLink : function (lk) { if (!lk) return null; var href = lk.getAttribute ('href', 2); if (!href) return null; // This is a bit tricky to get right, because wgScript can be a substring prefix of // wgArticlePath, or vice versa. var script = mw.config.get('wgScript') + '?'; if (href.startsWith (script) || href.startsWith (mw.config.get('wgServer') + script) || mw.config.get('wgServer').startsWith('//') && href.startsWith (document.location.protocol + mw.config.get('wgServer') + script)) { // href="/w/index.php?title=..." return href.getParamValue ('title'); } // Now try wgArticlePath: href="/wiki/..." var prefix = mw.config.get('wgArticlePath').replace ('$1', ""); if (!href.startsWith (prefix)) prefix = mw.config.get('wgServer') + prefix; // Fully expanded URL? if (!href.startsWith (prefix) && prefix.startsWith ('//')) prefix = document.location.protocol + prefix; // Protocol-relative wgServer? if (href.startsWith (prefix)) return decodeURIComponent (href.substring (prefix.length)); // Do we have variants? var variants = mw.config.get('wgVariantArticlePath'); if (variants && variants.length > 0) { var re = new RegExp (variants.escapeRE().replace ('\\$2', "[^\\/]*").replace ('\\$1', "(.*)")); var m = re.exec (href); if (m && m.length > 1) return decodeURIComponent (m[m.length-1]); } // Finally alternative action paths var actions = mw.config.get('wgActionPaths'); if (actions) { for (var i=0; i < actions.length; i++) { var p = actions[i]; if (p && p.length > 0) { p = p.replace('$1', ""); if (!href.startsWith (p)) p = mw.config.get('wgServer') + p; if (!href.startsWith (p) && p.startsWith('//')) p = document.location.protocol + p; if (href.startsWith (p)) return decodeURIComponent (href.substring (p.length)); } } } return null; }, revisionFromHtml : function (htmlOfPage) { var revision_id = null; if (window.mediaWiki) { // MW 1.17+ revision_id = htmlOfPage.match (/(mediaWiki|mw).config.set\(\{.*"wgCurRevisionId"\s*:\s*(\d+),/); if (revision_id) revision_id = parseInt (revision_id[2], 10); } else { // MW < 1.17 revision_id = htmlOfPage.match (/wgCurRevisionId\s*=\s*(\d+)[;,]/); if (revision_id) revision_id = parseInt (revision_id[1], 10); } return revision_id; } }; // end LAPI.WP } // end if (guard) if (typeof (LAPI.Ajax.doAction) == 'undefined') { importScript ('MediaWiki:AjaxSubmit.js'); // Legacy code: ajaxSubmit LAPI.Ajax.getXML = function (request, failureFunc) { var doc = null; if (request.responseXML && request.responseXML.documentElement) { doc = request.responseXML; } else { try { doc = LAPI.DOM.parse (request.responseText, 'text/xml'); } catch (ex) { if (typeof (failureFunc) == 'function') failureFunc (request, ex); doc = null; } } if (doc) { try { doc = LAPI.DOM.isValid (doc); } catch (ex) { if (typeof (failureFunc) == 'function') failureFunc (request, ex); doc = null; } } return doc; }; LAPI.Ajax.getHTML = function (request, failureFunc, sanity_check) { // Konqueror sometimes has severe problems with responseXML. It does set it, but getElementById // may fail to find elements known to exist. var doc = null; // Always use our own parser instead of responseXML; that doesn't work right with HTML5. (It did work with XHTML, though.) // if ( request.responseXML && request.responseXML.documentElement // && request.responseXML.documentElement.tagName == 'HTML' // && (!sanity_check || request.responseXML.getElementById (sanity_check) != null) // ) // { // doc = request.responseXML; // } else { try { doc = LAPI.DOM.parseHTML (request.responseText, sanity_check); if (!doc) throw new Error ('#Could not understand request result'); } catch (ex) { if (typeof (failureFunc) == 'function') failureFunc (request, ex); doc = null; } // } if (doc) { try { doc = LAPI.DOM.isValid (doc); } catch (ex) { if (typeof (failureFunc) == 'function') failureFunc (request, ex); doc = null; } } if (doc === null) return doc; // We've gotten XML. There is a subtle difference between XML and (X)HTML concerning leading newlines in textareas: // XML is required to pass through any whitespace (http://www.w3.org/TR/2004/REC-xml-20040204/#sec-white-space), whereas // HTML may or must not (e.g. http://www.w3.org/TR/html4/appendix/notes.html#h-B.3.1, though it is unclear whether that // really applies to the content of a textarea, but the draft HTML 5 spec explicitly says that the first newline in a // <textarea> is swallowed in HTML: // http://www.whatwg.org/specs/web-apps/current-work/multipage/syntax.html#element-restrictions). // Because of the latter MW1.18+ adds a newline after the <textarea> start tag if the value starts with a newline. That // solves bug 12130 (leading newlines swallowed), but since XML passes us this extra newline, we might end up adding a // leading newline upon each edit. // Let's try to make sure that all textarea's values are as they should be in HTML. // Note: since the above change to always use our own parser, which always returns a faked HTML document, this should be // unnecessary since doc.isFake should always be true. if (typeof (LAPI.Ajax.getHTML.extraNewlineRE) == 'undefined') { // Feature detection. Compare value after parsing with value after .innerHTML. LAPI.Ajax.getHTML.extraNewlineRE = null; // Don't know; hence do nothing try { var testTA = '<textarea id="test">\nTest</textarea>'; var testString = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">\n' + '<html xmlns="http://www.w3.org/1999/xhtml" lang="en" dir="ltr">\n' + '<head><title>Test</title></head><body><form>' + testTA + '</form></body>\n' + '</html>'; var testDoc = LAPI.DOM.parseHTML (testString, 'test'); var testVal = "" + testDoc.getElementById ('test').value; if (testDoc.dispose) testDoc.dispose(); var testDiv = LAPI.make ('div', null, {display: 'none'}); document.body.appendChild (testDiv); testDiv.innerHTML = testTA; if (testDiv.firstChild.value != testVal) { LAPI.Ajax.getHTML.extraNewlineRE = /^\r?\n/; if (testDiv.firstChild.value != testVal.replace(LAPI.Ajax.getHTML.extraNewlineRE, "")) { // Huh? Not the expected difference: go back to "don't know" mode LAPI.Ajax.getHTML.extraNewlineRE = null; } } LAPI.DOM.removeNode (testDiv); } catch (any) { LAPI.Ajax.getHTML.extraNewlineRE = null; } } if (!doc.isFake && LAPI.Ajax.getHTML.extraNewlineRE !== null) { // If have a "fake" doc, then we did parse through .innerHTML anyway. No need to fix anything. // (Hm. Maybe we should just always use a fake doc?) var tas = doc.getElementsByTagName ('textarea'); for (var i = 0, l = tas.length; i < l; i++) { tas[i].value = tas[i].value.replace(LAPI.Ajax.getHTML.extraNewlineRE, ""); } } return doc; }; LAPI.Ajax.get = function (uri, params, success, failure, config) { var original_failure = failure; if (!failure || typeof (failure) != 'function') failure = function () {}; if (!success || typeof (success) != 'function') throw new Error ('No success function supplied for LAPI.Ajax.get ' + uri + ' with arguments ' + params.toString ()); var request = LAPI.Ajax.getRequest (); if (!request) { failure (request); return; } var args = ""; var question_mark = uri.indexOf ('?'); if (question_mark) { args = uri.substring (question_mark + 1); uri = uri.substring (0, question_mark); } if (params != null) { if (typeof (params) == 'string' && params.length > 0) { args += (args.length > 0 ? '&' : "") + ((params.charAt (0) == '&' || params.charAt (0) == '?') ? params.substring (1) : params ); // Must already be encoded! } else { for (var param in params) { args += (args.length > 0 ? '&' : "") + param; if (params[param] != null) args += '=' + encodeURIComponent (params[param]); } } } var method; if (uri.startsWith ('//')) uri = document.location.protocol + uri; // Avoid protocol-relative URIs (IE7 bug) if (uri.length + args.length + 1 < (LAPI.Browser.is_ie ? 2040 : 4080)) { // Both browsers and web servers may have limits on URL length. IE has a limit of 2083 characters // (2048 in the path part), and the WMF servers seem to impose a limit of 4kB. method = 'GET'; uri += '?' + args; args = null; } else { method = 'POST'; // We'll lose caching, but at least we can make the request. } request.open (method, uri, true); request.setRequestHeader ('Pragma', 'cache=yes'); request.setRequestHeader ( 'Cache-Control' , 'no-transform' + (params && params.maxage ? ', max-age=' + params.maxage : "") + (params && params.smaxage ? ', s-maxage=' + params.smaxage : "") ); if (config) { for (var conf in config) { if (conf == 'overrideMimeType') { if (config[conf] && config[conf].length > 0 && request.overrideMimeType) request.overrideMimeType (config[conf]); } else { request.setRequestHeader (conf, config[conf]); } } } if (args) request.setRequestHeader ('Content-Type', 'application/x-www-form-urlencoded'); request.onreadystatechange = function () { if (request.readyState != 4) return; // Wait until the request has completed. try { if (request.status != 200) throw new Error ('#Request to server failed. Status: ' + request.status + ' ' + request.statusText + ' URI: ' + uri); if (!request.responseText) throw new Error ('#Empty response from server for request ' + uri); } catch (ex) { failure (request, ex); return; } success (request, original_failure); }; request.send (args); }; LAPI.Ajax.getPage = function (page, action, params, success, failure) { var uri = mw.config.get('wgServer') + mw.config.get('wgScript') + '?title=' + encodeURIComponent (page) + (action ? '&action=' + action : ""); LAPI.Ajax.get (uri, params, success, failure, {overrideMimeType : 'application/xml'}); }; // modify is supposed to save the changes at the end, e.g. using LAPI.Ajax.submit. // modify is called with three parameters: the document, possibly the form, and the optional // failure function. The failure function is called with the request as the first parameter, // and possibly an exception as the second parameter. LAPI.Ajax.doAction = function (page, action, form, modify, failure) { if (!page || !action || !modify || typeof (modify) != 'function') throw new Error ('Parameter inconsistency in LAPI.Ajax.doAction.'); var original_failure = failure; if (!failure || typeof (failure) != 'function') failure = function () {}; LAPI.Ajax.getPage ( page, action, null // No additional parameters , function (request, failureFunc) { var doc = null; var the_form = null; var revision_id = null; try { // Convert responseText into DOM tree. doc = LAPI.Ajax.getHTML (request, failureFunc, form); if (!doc) return; var err_msg = LAPI.$ ('permissions-errors', doc); if (err_msg) throw new Error ('#' + LAPI.DOM.getInnerText (err_msg)); if (form) { the_form = LAPI.$ (form, doc); if (!the_form) throw new Error ('#Server reply does not contain mandatory form.'); } revision_id = LAPI.WP.revisionFromHtml (request.responseText); } catch (ex) { failureFunc (request, ex); return; } modify (doc, the_form, original_failure, revision_id) } , failure ); }; // end LAPI.Ajax.doAction LAPI.Ajax.submit = function (form, after_submit) { try { ajaxSubmit (form, null, after_submit, true); // Legacy code from MediaWiki:AjaxSubmit } catch (ex) { after_submit (null, ex); } }; // end LAPI.Ajax.submit LAPI.Ajax.editPage = function (page, modify, failure) { LAPI.Ajax.doAction (page, 'edit', 'editform', modify, failure); }; // end LAPI.Ajax.editPage LAPI.Ajax.checkEdit = function (request) { if (!request) return true; // Check for previews (session token lost?) or edit forms (edit conflict). try { var doc = LAPI.Ajax.getHTML (request, function () {throw new Error ('Cannot check HTML');}); if (!doc) return false; return LAPI.$ (['wikiPreview', 'editform'], doc) == null; } catch (anything) { return false; } }; // end LAPI.Ajax.checkEdit LAPI.Ajax.submitEdit = function (form, success, failure) { if (!success || typeof (success) != 'function') success = function () {}; if (!failure || typeof (failure) != 'function') failure = function () {}; LAPI.Ajax.submit ( form , function (request, ex) { if (ex) { failure (request, ex); } else { var successful = false; try { successful = request && request.status == 200 && LAPI.Ajax.checkEdit (request); } catch (some_error) { failure (request, some_error); return; } if (successful) success (request); else failure (request); } } ); }; // end LAPI.Ajax.submitEdit LAPI.Ajax.apiGet = function (action, params, success, failure) { var original_failure = failure; if (!failure || typeof (failure) != 'function') failure = function () {}; if (!success || typeof (success) != 'function') throw new Error ('No success function supplied for LAPI.Ajax.apiGet ' + action + ' with arguments ' + params.toString ()); var is_json = false; if (params != null) { if (typeof (params) == 'string') { if (!/format=[^&]+/.test (params)) params += '&format=json'; is_json = /format=json(&|$)/.test (params); // Exclude jsonfm, which actually serves XHTML } else { if (typeof (params['format']) != 'string' || params.format.length == 0) params.format = 'json'; is_json = params.format == 'json'; } } var uri = mw.config.get('wgServer') + mw.config.get('wgScriptPath') + '/api.php' + (action ? '?action=' + action : ""); LAPI.Ajax.get ( uri, params , function (request, failureFunc) { if (is_json && request.responseText.trimLeft().charAt (0) != '{') { failureFunc (request); } else { success ( request , (is_json ? eval ('(' + request.responseText.trimLeft() + ')') : null) , original_failure ); } } , failure ); }; // end LAPI.Ajax.apiGet LAPI.Ajax.parseWikitext = function (wikitext, success, failure, as_preview, user_language, on_page, cache) { if (!failure || typeof (failure) != 'function') failure = function () {}; if (!success || typeof (success) != 'function') throw new Error ('No success function supplied for parseWikitext'); if (!wikitext && !on_page) throw new Error ('No wikitext or page supplied for parseWikitext'); var params = null; if (!wikitext) { params = {pst: null, page: on_page}; } else { params = { pst : null // Do the pre-save-transform: Pipe magic, tilde expansion, etc. ,text : (as_preview ? '\<div style="border:1px solid red; padding:0.5em;"\>' + '\<div class="previewnote"\>' + '\{\{MediaWiki:Previewnote/' + (user_language || mw.config.get('wgUserLanguage')) +'\}\}' + '\<\/div>\<div\>\n' : "") + wikitext + (as_preview ? '\<\/div\>\<div style="clear:both;"\>\<\/div\>\<\/div\>' : "") ,title: on_page || mw.config.get('wgPageName') || "API" }; } params.prop = 'text'; params.uselang = user_language || mw.config.get('wgUserLanguage'); // see bugzilla 22764 if (cache && /^\d+$/.test(cache=cache.toString())) { params.maxage = cache; params.smaxage = cache; } LAPI.Ajax.apiGet ( 'parse' , params , function (req, json_result, failureFunc) { // Success. if (!json_result || !json_result.parse || !json_result.parse.text) { failureFunc (req, json_result); return; } success (json_result.parse.text['*'], failureFunc); } , failure ); }; // end LAPI.Ajax.parseWikitext // Throbber backward-compatibility LAPI.Ajax.injectSpinner = function (elementBefore, id) {}; // No-op, replaced as appropriate below. LAPI.Ajax.removeSpinner = function (id) {}; // No-op, replaced as appropriate below. if (typeof window.jQuery == 'undefined' || typeof window.mediaWiki == 'undefined' || typeof window.mediaWiki.loader == 'undefined') { // Assume old-stlye if (typeof window.injectSpinner != 'undefined') { LAPI.Ajax.injectSpinner = window.injectSpinner; } if (typeof window.removeSpinner != 'undefined') { LAPI.Ajax.removeSpinner = window.removeSpinner; } } else { window.mediaWiki.loader.using('jquery.spinner', function () { LAPI.Ajax.injectSpinner = function (elementBefore, id) { window.jQuery(elementBefore).injectSpinner(id); } LAPI.Ajax.removeSpinner = function (id) { window.jQuery.removeSpinner(id); } }); } } // end if (guard) if (typeof (LAPI.Pos) == 'undefined') { LAPI.Pos = { // Returns the global coordinates of the mouse pointer within the document. mousePosition : function (evt) { if (!evt || (typeof (evt.pageX) == 'undefined' && typeof (evt.clientX) == 'undefined')) // No way to calculate a mouse pointer position return null; if (typeof (evt.pageX) != 'undefined') return { x : evt.pageX, y : evt.pageY }; var offset = LAPI.Pos.scrollOffset (); var mouse_delta = LAPI.Pos.mouse_offset (); var coor_x = evt.clientX + offset.x - mouse_delta.x; var coor_y = evt.clientY + offset.y - mouse_delta.y; return { x : coor_x, y : coor_y }; }, // Operations on document level: // Returns the scroll offset of the whole document (in other words, the coordinates // of the top left corner of the viewport). scrollOffset : function () { return {x : LAPI.Pos.getScroll ('Left'), y : LAPI.Pos.getScroll ('Top') }; }, getScroll : function (what) { var s = 'scroll' + what; return (document.documentElement ? document.documentElement[s] : 0) || document.body[s] || 0; }, // Returns the size of the viewport (result.x is the width, result.y the height). viewport : function () { return {x : LAPI.Pos.getViewport ('Width'), y : LAPI.Pos.getViewport ('Height') }; }, getViewport : function (what) { if ( LAPI.Browser.is_opera_95 && what == 'Height' || LAPI.Browser.is_safari && !document.evaluate) return window['inner' + what]; var s = 'client' + what; if (LAPI.Browser.is_opera) return document.body[s]; return (document.documentElement ? document.documentElement[s] : 0) || document.body[s] || 0; }, // Operations on DOM nodes position : (function () { // The following is the jQuery.offset implementation. We cannot use jQuery yet in globally // activated scripts (it has strange side effects for Opera 8 users who can't log in anymore, // and it breaks the search box for some users). Note that jQuery does not support Opera 8. // Until the WMF servers serve jQuery by default, this copy from the jQuery 1.3.2 sources is // needed here. If and when we have jQuery available officially, the whole thing here can be // replaced by "var tmp = jQuery (node).offset(); return {x:tmp.left, y:tmp.top};" // Kudos to the jQuery development team. Any errors in this adaptation are my own. (Lupo, // 2009-08-24). var data = null; function jQuery_init () { data = {}; // Capability check from jQuery. var body = document.body; var container = document.createElement('div'); var html = '<div style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;' + 'padding:0;width:1px;height:1px;"><div></div></div><table style="position:absolute;' + 'top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;" ' + 'cellpadding="0" cellspacing="0"><tr><td></td></tr></table>'; var rules = { position: 'absolute', visibility: 'hidden' ,top: 0, left: 0 ,margin: 0, border: 0 ,width: '1px', height: '1px' }; Object.merge (rules, container.style); container.innerHTML = html; body.insertBefore(container, body.firstChild); var innerDiv = container.firstChild; var checkDiv = innerDiv.firstChild; var td = innerDiv.nextSibling.firstChild.firstChild; data.doesNotAddBorder = (checkDiv.offsetTop !== 5); data.doesAddBorderForTableAndCells = (td.offsetTop === 5); innerDiv.style.overflow = 'hidden', innerDiv.style.position = 'relative'; data.subtractsBorderForOverflowNotVisible = (checkDiv.offsetTop === -5); var bodyMarginTop = body.style.marginTop; body.style.marginTop = '1px'; data.doesNotIncludeMarginInBodyOffset = (body.offsetTop === 0); body.style.marginTop = bodyMarginTop; body.removeChild(container); }; function jQuery_offset (node) { if (node === node.ownerDocument.body) return jQuery_bodyOffset (node); if (node.getBoundingClientRect) { var box = node.getBoundingClientRect (); var scroll = LAPI.Pos.scrollOffset (); return {x : (box.left + scroll.x), y : (box.top + scroll.y)}; } if (!data) jQuery_init (); var elem = node; var offsetParent = elem.offsetParent; var prevOffsetParent = elem; var doc = elem.ownerDocument; var prevComputedStyle = doc.defaultView.getComputedStyle(elem, null); var computedStyle; var top = elem.offsetTop; var left = elem.offsetLeft; while ( (elem = elem.parentNode) && elem !== doc.body && elem !== doc.documentElement ) { computedStyle = doc.defaultView.getComputedStyle(elem, null); top -= elem.scrollTop, left -= elem.scrollLeft; if ( elem === offsetParent ) { top += elem.offsetTop, left += elem.offsetLeft; if ( data.doesNotAddBorder && !(data.doesAddBorderForTableAndCells && /^t(able|d|h)$/i.test(elem.tagName)) ) { top += parseInt (computedStyle.borderTopWidth, 10) || 0; left += parseInt (computedStyle.borderLeftWidth, 10) || 0; } prevOffsetParent = offsetParent; offsetParent = elem.offsetParent; } if (data.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== 'visible') { top += parseInt (computedStyle.borderTopWidth, 10) || 0; left += parseInt (computedStyle.borderLeftWidth, 10) || 0; } prevComputedStyle = computedStyle; } if (prevComputedStyle.position === 'relative' || prevComputedStyle.position === 'static') { top += doc.body.offsetTop; left += doc.body.offsetLeft; } if (prevComputedStyle.position === 'fixed') { top += Math.max (doc.documentElement.scrollTop, doc.body.scrollTop); left += Math.max (doc.documentElement.scrollLeft, doc.body.scrollLeft); } return {x: left, y: top}; } function jQuery_bodyOffset (body) { if (!data) jQuery_init(); var top = body.offsetTop, left = body.offsetLeft; if (data.doesNotIncludeMarginInBodyOffset) { top += parseInt (LAPI.DOM.currentStyle (body, 'margin-top'), 10) || 0; left += parseInt (LAPI.DOM.currentStyle (body, 'margin-left'), 10) || 0; } return {x: left, y: top}; } return jQuery_offset; })(), isWithin : function (node, x, y) { if (!node || !node.parentNode) return false; var pos = LAPI.Pos.position (node); return (x == null || x > pos.x && x < pos.x + node.offsetWidth) && (y == null || y > pos.y && y < pos.y + node.offsetHeight); }, // Private: // IE has some strange offset... mouse_offset : function () { if (LAPI.Browser.is_ie) { var doc_elem = document.documentElement; if (doc_elem) { if (typeof (doc_elem.getBoundingClientRect) == 'function') { var tmp = doc_elem.getBoundingClientRect (); return {x : tmp.left, y : tmp.top}; } else { return {x : doc_elem.clientLeft, y : doc_elem.clientTop}; } } } return {x: 0, y : 0}; } }; // end LAPI.Pos } // end if (guard) if (typeof (LAPI.Evt) == 'undefined') { LAPI.Evt = { listenTo : function (object, node, evt, f, capture) { var listener = LAPI.Evt.makeListener (object, f); LAPI.Evt.attach (node, evt, listener, capture); }, attach : function (node, evt, f, capture) { if (node.attachEvent) node.attachEvent ('on' + evt, f); else if (node.addEventListener) node.addEventListener (evt, f, capture); else node['on' + evt] = f; }, remove : function (node, evt, f, capture) { if (node.detachEvent) node.detachEvent ('on' + evt, f); else if (node.removeEventListener) node.removeEventListener (evt, f, capture); else node['on' + evt] = null; }, makeListener : function (obj, listener) { // Some hacking around to make sure 'this' is set correctly var object = obj, f = listener; return function (evt) { return f.apply (object, [evt || window.event]); } // Alternative implementation: // var f = listener.bind (obj); // return function (evt) { return f (evt || window.event); }; }, kill : function (evt) { if (typeof (evt.preventDefault) == 'function') { evt.stopPropagation (); evt.preventDefault (); // Don't follow the link } else if (typeof (evt.cancelBubble) != 'undefined') { // IE... evt.cancelBubble = true; } return false; // Don't follow the link (IE) } }; // end LAPI.Evt } // end if (guard) if (typeof (LAPI.Edit) == 'undefined') { LAPI.Edit = function () {this.initialize.apply (this, arguments);}; LAPI.Edit.SAVE = 1; LAPI.Edit.PREVIEW = 2; LAPI.Edit.REVERT = 4; LAPI.Edit.CANCEL = 8; LAPI.Edit.prototype = { initialize : function (initial_text, columns, rows, labels, handlers) { var my_labels = {box : null, preview : null, save : 'Save', cancel : 'Cancel', nullsave : null, revert : null, post: null}; if (labels) my_labels = Object.merge (labels, my_labels); this.labels = my_labels; this.timestamp = (new Date ()).getTime (); this.id = 'simpleedit_' + this.timestamp; this.view = LAPI.make ('div', {id : this.id}, {marginRight: '1em'}); // Somehow, the textbox extends beyond the bounding box of the view. Don't know why, but // adding a small margin fixes the layout more or less. this.form = LAPI.make ( 'form' , { id : this.id + '_form' ,action : "" ,onsubmit: (function () {}) } ); if (my_labels.box) { var label = LAPI.make ('div'); label.appendChild (LAPI.DOM.makeLabel (this.id + '_label', my_labels.box, this.id + '_text')); this.form.appendChild (label); } this.textarea = LAPI.make ( 'textarea' , { id : this.id + '_text' ,cols : columns ,rows : rows ,value: (initial_text ? initial_text.toString () : "") } ); LAPI.Evt.attach (this.textarea, 'keyup', LAPI.Evt.makeListener (this, this.text_changed)); // Catch cut/copy/paste through the context menu. Some browsers support oncut, oncopy, // onpaste events for this, but since that's only IE, FF 3, Safari 3, and Chrome, we // cannot rely on this. Instead, we check again as soon as we leave the textarea. Only // minor catch is that on FF 3, the next focus target is determined before the blur event // fires. Since in practice save will always be enabled, this shouldn't be a problem. LAPI.Evt.attach (this.textarea, 'mouseout', LAPI.Evt.makeListener (this, this.text_changed)); LAPI.Evt.attach (this.textarea, 'blur', LAPI.Evt.makeListener (this, this.text_changed)); this.form.appendChild (this.textarea); this.form.appendChild (LAPI.make ('br')); this.preview_section = LAPI.make ('div', null, {borderBottom: '1px solid #8888aa', display: 'none'}); this.view.insertBefore (this.preview_section, this.view.firstChild); this.save = LAPI.DOM.makeButton (this.id + '_save', my_labels.save, LAPI.Evt.makeListener (this, this.do_save)); this.form.appendChild (this.save); if (my_labels.preview) { this.preview = LAPI.DOM.makeButton (this.id + '_preview', my_labels.preview, LAPI.Evt.makeListener (this, this.do_preview)); this.form.appendChild (this.preview); } this.cancel = LAPI.DOM.makeButton (this.id + '_cancel', my_labels.cancel, LAPI.Evt.makeListener (this, this.do_cancel)); this.form.appendChild (this.cancel); this.view.appendChild (this.form); if (my_labels.post) { this.post_text = LAPI.DOM.setContent (LAPI.make ('div'), my_labels.post); this.view.appendChild (this.post_text); } if (handlers) Object.merge (handlers, this); if (typeof (this.ongettext) != 'function') this.ongettext = function (text) { return text;}; // Default: no modifications this.current_mask = LAPI.Edit.SAVE + LAPI.Edit.PREVIEW + LAPI.Edit.REVERT + LAPI.Edit.CANCEL; if ((!initial_text || initial_text.trim ().length == 0) && this.preview) this.preview.disabled = true; if (my_labels.revert) { this.revert = LAPI.DOM.makeButton (this.id + '_revert', my_labels.revert, LAPI.Evt.makeListener (this, this.do_revert)); this.form.insertBefore (this.revert, this.cancel); } this.original_text = ""; }, getView : function () { return this.view; }, getText : function () { return this.ongettext (this.textarea.value); }, setText : function (text) { this.textarea.value = text; this.original_text = text; this.text_changed (); }, changeText : function (text) { this.textarea.value = text; this.text_changed (); }, hidePreview : function () { this.preview_section.style.display = 'none'; if (this.onpreview) this.onpreview (this); }, showPreview : function () { this.preview_section.style.display = ""; if (this.onpreview) this.onpreview (this); }, setPreview : function (html) { if (html.nodeName) { LAPI.DOM.removeChildren (this.preview_section); this.preview_section.appendChild (html); } else { this.preview_section.innerHTML = html; } }, busy : function (show) { if (show) LAPI.Ajax.injectSpinner (this.cancel, this.id + '_spinner'); else LAPI.Ajax.removeSpinner (this.id + '_spinner'); }, do_save : function (evt) { if (this.onsave) this.onsave (this); return true; }, do_revert : function (evt) { this.changeText (this.original_text); return true; }, do_cancel : function (evt) { if (this.oncancel) this.oncancel (this); return true; }, do_preview : function (evt) { var self = this; this.busy (true); LAPI.Ajax.parseWikitext ( this.getText () , function (text, failureFunc) { self.busy (false); self.setPreview (text); self.showPreview (); } , function (req, json_result) { // Error. TODO: user feedback? self.busy (false); } , true , mw.config.get('wgUserLanguage') || null , mw.config.get('wgPageName') || null ); return true; }, enable : function (bit_set) { var call_text_changed = false; this.current_mask = bit_set; this.save.disabled = ((bit_set & LAPI.Edit.SAVE) == 0); this.cancel.disabled = ((bit_set & LAPI.Edit.CANCEL) == 0); if (this.preview) { if ((bit_set & LAPI.Edit.PREVIEW) == 0) this.preview.disabled = true; else call_text_changed = true; } if (this.revert) { if ((bit_set & LAPI.Edit.REVERT) == 0) this.revert.disabled = true; else call_text_changed = true; } if (call_text_changed) this.text_changed (); }, text_changed : function (evt) { var text = this.textarea.value; text = text.trim (); var length = text.length; if (this.preview && (this.current_mask & LAPI.Edit.PREVIEW) != 0) { // Preview is basically enabled this.preview.disabled = (length <= 0); } if (this.labels.nullsave) { if (length > 0) { this.save.value = this.labels.save; } else { this.save.value = this.labels.nullsave; } } if (this.revert) { this.revert.disabled = (text == this.original_text || this.textarea.value == this.original_text); } return true; } }; // end LAPI.Edit } // end if (guard) // </source> tau601cd97pdcrd57f1udt409hz3fze Топливная экономичность автомобиля 0 29334 269957 262995 2026-09-06T12:31:24Z Хедин 67628 /* Приёмы экономии топлива зимой */ замена мёртвого url 269957 wikitext text/x-wiki {{Название учебника|Тип=Одностраничный|Категория=Автомобили}} [[Файл:Реоритет авто.JPG|мини|400x400пкс|Можно улучшить топливную экономичность любого автомобиля.<br>Даже такого]] Топливная экономичность автомобиля (км/л) или обратный ей показатель расхода топлива (обычно л/100 км) интересует многих автовладельцев. Какие цифры топливной экономичности должны быть у нашей машины, как и насколько её можно улучшить? В статью включены советы по логистике и экономичному управлению автомобилем, что собственно не является предметом именно экономичности автомобиля, однако их высокая важность (процент экономии) заслуживают рассмотрения здесь, соответственно с целями викиучебника. == Свойства автомобиля, их влияние на расход в различных условиях применения == '''''Масса пустого автомобиля''''' важный показатель будущего расхода. Однажды сэкономив и купив более тяжёлый автомобиль, чем был доступен, мы затем десятки (и даже сотни — при движении в городе) тысяч раз тратим энергию на разгон излишней массы. Нивелировать это можно правильным стилем вождения с минимальным использованием торможения, однако при движении в основном по трассе без частого торможения избыток массы на экономичности сказывается меньше. В горной местности излишний вес прямо ведёт к дополнительному расходу топлива. Коэффициент трения при качении по асфальту около 0,01 — следовательно, каждые 100 кг лишнего веса требуют дополнительной тяги в 10 Н, что эквивалентно расходу 1 МДж/100 км, при обычной эффективности бензинового мотора 300 г/кВт*ч это увеличение расхода на 0,12 л/100 км, это не считая торможений и подъёмов в гору, без учёта потерь в трансмиссии и снижения КПД двигателя при частичных нагрузках. С учётом всего перечисленного — до 0,4 л/100 км. При ожидаемом пробеге до капремонта 200 000 км и цене бензина 50 руб/л каждые лишние 100 кг, следовательно, принесут издержки до 40 000 руб. В дополнению к потерям на трение качения, разгон автомобиля массой 1000 кг до скорости 72 км/ч (20 м/с) требует 0,2 МДж, что эквивалентно 0,024 л топлива без учёта потерь в трансмиссии и при максимальном КПД. Если на пути в 1 км встречается 1 светофор, то разгоны дадут прибавку расхода топлива на 2,4 л/100 км (при максимальном КПД двигателя и нулевых потерях в трансмиссии при разгоне до 72 км/ч). Поскольку каждое торможение с последующим разгоном требует дополнительной энергии, а доля аэродинамического сопротивления ввиду невысокой средней скорости в городе невелика, то именно ''масса автомобиля является определяющим фактором, влияющим на расход топлива при движении в городе''. '''''Лобовое сопротивление''''' вредный показатель, но в некоторых случаях его можно уменьшить. На грузовиках с этой целью устанавливают обтекатели, желательно также устранение выступающих элементов: спойлеров, мухобоек, кенгурятников. Многие гонщики ставят более короткие и жёсткие пружины, чтобы уменьшить лобовое сопротивление, это работает — но машина становится жёстче и может разбить низ об ухабы. Хорошим вариантом для зимы будет закрытие решётки радиатора снаружи, а не внутри, как это обычно делают. Мощность, расходуемая на аэродинамическое сопротивление, растёт с кубом скорости, и следовательно, доля удельного расхода топлива из-за сопротивления воздуха, приходящаяся на километр, растёт пропорционально квадрату скорости. Отсюда важность разумного снижения скорости. При движении со скоростями выше 60-70 км/ч (трасса) аэродинамическое сопротивление превосходит сопротивление качения, и влияние лобового сопротивления (это лобовая площадь, перемноженная на коэффициент аэродинамического качества Cx) быстро возрастает с дальнейшим ростом средней скорости. Тем самым, лобовое сопротивление является решающим фактором расхода топлива на загородной трассе. '''''Установленный двигатель''''', если вы не гонщик, лучше иметь менее мощный. Это может дать экономию топлива порядка 7-10 % (в рамках обычной линейки литража для модели), и кроме того, снизит транспортный налог. Исключением является горная местность: там маломощный двигатель не может дать никакой экономии топлива, но будет отнимать время водителя на подъёмах. То же относится и к службам такси, где использование подразумевает непрерывное маневрирование скоростью. При более расслабленном загородном маршруте либо движении по магистральным дорогам мощный двигатель несколько облегчит обгоны, но будет стабильно увеличивать издержки, и с позиции данной статьи, не нужен, поскольку скорость автомобиля с большинством вариантов двигателя ограничена знаками, а не мощностью. '''''Автоматическая коробка передач''''' даёт удобство и безопасность в населённых пунктах, облегчает управление и в горах, но на горизонтальной трассе количество необходимых переключений скоростей намного уменьшается, и более заметны становятся недостатки АКПП: цена, стоимость обслуживания, и рост расхода топлива. Поэтому при использовании машины в основном за городом, на трассе, нет смысла в покупке машины с автоматической коробкой передач, увеличивающей расход топлива примерно на 10 %. '''''Выбор дизеля или гибрида''''' оправдан с позиции топливной экономичности. Но применение дизеля сомнительно по экономичности на легковых машинах, поскольку значительно дороже обслуживание, и с учётом недешёвой солярки, оно почти съедает всю экономию. Гибриды бывают только легковые, причём и дизельные машины, и гибридные крайне трудно (а то и невозможно) перевести на газ. Так что это дело вкуса. В ближайшие десятилетия ждём распространения электромобилей, но пока нет зарядных станций и льгот на дорожный налог — они редко кому нужны. '''''Свойства автомобиля в целом неизменны с момента его покупки'''''. Мы не можем значительно уменьшить его массу или лобовое сопротивление, но учёт особенностей машины даёт предпосылки экономии. Если мы на стадии выбора машины, вес, объём салона/кузова и тип могут быть ещё выбраны, если же нет, то кроме логистических моментов, мы можем сэкономить на устранении лишнего аэродинамического сопротивления: снять спойлеры, декоративные бамперы и мухобойки (каждый элемент отнимает до 5 % горючего), не возить лишний вес (выгрузить лишний инструмент при недалёких поездках, особенно летом), снять лишние кресла и сиденья, если ими никогда не пользуемся (это правилами не запрещено). Японские машины с целью экономии веса с завода комплектуют колесом-докаткой вместо полноразмерного, которое к тому же занимает меньше места. '''''Важность свойств автомобиля зависит от условий его применения'''.'' Так, при движении с редкими остановками (загородная трасса) нивелируется влияние избытка массы, а при снижении выбранной скорости уменьшается различие между новейшими «обмылками» и «квадратными» моделями. Следовательно, при движении с небольшими скоростями и редкими остановками (к примеру, сельская местность) топливная экономичность старых моделей машин будет не слишком уступать новейшим моделям, в то время как в городе, на автобане, или в горах они покажут неприемлемое ухудшение этих цифр по сравнению с ними. Удешевление топлива снижает денежный ущерб от неоптимальных свойств машины — следовательно, '''''установка газа на авто архаичной конструкции даёт наибольшую по величине денежную экономию'''''. == Логистика == '''''Под логистикой в данном случае понимается разумное использование имеющегося автомобиля''''' с целью снижения расхода топлива путём сокращения маршрута, либо разделения издержек на нескольких пользователей. === Коллективное использование === Ежедневно можно наблюдать движущиеся машины, в которых нет пассажиров. Для России это стало печальной действительностью, но в других странах, с более дорогим топливом и другой культурой людей, принято коллективное использование транспорта. Наиболее яркий пример — Великобритания. Если вы используете машину, чтобы добираться до работы, попробуйте найти других (безлошадных) сотрудников, едущих в том же направлении. Откажитесь от советского мышления, что брать с коллег нельзя — если они экономят деньги, пусть часть этого будет ваша. Помните, такую модель коллективной доставки сотрудников на работу использует даже Microsoft, для чего у них есть специальное внутрифирменное приложение. Английская же модель предусматривает коллективное управление несколькими людьми нескольких машин (разного типа: может понадобиться пикап или автобус), при этом каждый совладелец отмечает свой пробег и соответственно расходу ресурса вносит свою долю на обслуживание и ремонт. Однако, это предполагает более высокий уровень культуры, чем в России. === Оптимизация маршрута === Вы уверены, что едете самым выгодным путём? Километраж по карте не всегда отражает издержки, поскольку работающий на холостом ходу легковой автомобиль расходует примерно 0,5 л/час. Кроме того, вы тратите время. Поэтому следует попробовать альтернативные маршруты, и что самое интересное в данном отношении, другие времена поездок. Если в 8 и 17 часов пик, почему бы вам не обсудить с начальством возможность выполнения заданий со сдвигом от обычного времени работы (например, с 6 до 15)? Если вы живёте далеко, то сэкономите много времени, теряемого раньше в пробках, и бензина. === Полная загрузка === Для легковой машины достигается либо коллективным использованием, либо перевозкой дополнительного груза. Посмотрите объявления, возможно, там есть спрос на регулярные перевозки товара. Например, в расположенный по соседству с вашей работой ларёк хозяин ежедневно завозит хлеб. Когда это происходит? Состыкуйтесь с субарендаторами здания вашей конторы, возможно, у них тоже есть нужда в транспорте, и они вызывают такси? Для грузовых машин — это возможно полное использование грузоподъёмности в тоннах. Если груза мало, и мы не можем возить его реже, то нужно искать попутный груз (гугл в помощь), либо при невозможности этого, подумать о выборе более лёгкого транспорта. == Регулировки и выбор расходных материалов, приёмы экономии топлива зимой == [[Файл:Цены на топливо июль 2022.jpg||мини|400x400пкс|Цены на топливо в России растут как на дрожжах: лето 2022 года]] Регулировками и верным выбором смазок и других расходников можно достичь определённого процента экономии топлива, вне зависимости от других факторов. === Тип шин === При всех рекомендуемых давлениях, радиальные шины имеют меньший коэффициент трения, чем диагональные (последние устанавливают теперь мало) примерно на 40 %. Кроме того, они дольше служат. Большинство изготавливаемых сейчас шин — радиальные. Низкопрофильные шины имеют меньший коэффициент трения, чем высокопрофильные: так, с коэффициентом ширины /60 коэффициент трения на скорости 140 км/ч меньше на 26 %, чем с коэффициентом /80. Однако высокопрофильные шины необходимы, если автомобиль эксплуатируют на разбитых дорогах, с обилием ям и кочек. Различие в коэффициенте трения качения зависит от скорости: низкопрофильные дают меньший выигрыш (10..15 %) на скоростях до 80 км/ч, но с ростом скорости разрыв увеличивается. Смотря по слойности, шины демонстрируют различный расход топлива. Шины с меньшей слойностью (не имеющие в обозначении буквы C = Commercial) более мягкие, и при прочих равных условиях покажут лучшую топливную экономичность. Однако применение на машине шин, не разрешённых изготовителем, может вызвать проблемы с ДПС. Так, родная резина на «Газель» имеет типоразмер 185/75R16C и стоит заметно дороже «некоммерческой» 205/65R16. Некоммерческая имеет почти равную грузоподъёмность, экономит топливо, но по утверждениям некоторых водителей, может идти грыжами при установке вместо коммерческой. === Давление воздуха в колёсах === Стандартное давление в колёсах оптимально при движении с умеренными скоростями (до 100 км/ч). Если перед нами трасса, где мы постоянно будем двигаться со скоростью 100 км/ч и больше, многие руководства рекомендуют увеличить давление в шинах на 10 %. Следует учитывать, что на трение качения колёс автомобиля среднего класса при скорости 100 км/ч, на диагональных шинах, расходуется 4..6 кВт мощности. Это примерно 1/4 всей мощности, затрачиваемой на движение, поскольку на воздушное сопротивление расходуется 17..18 кВт<ref>{{Книга|автор=Мацкерле Ю.|заглавие=Современный экономичный автомобиль|год=1987|место=Москва|издательство=Машиностроение}}</ref>. Соответственно, резина при движении значительно нагревается. Повышение давления в колёсах даёт экономию топлива около 2 %, и может применяться и при меньших скоростях. Однако, на меньших скоростях на неровной дороге машина станет более жёсткой для пассажиров. Увеличение давления благотворно сказывается и на ресурсе колёс, поскольку они меньше деформируются и нагреваются, поэтому этим часто пользуются водители фур (там колёс много, и они дорогие). Однако повышать давление стоит только в исправных, качественных баллонах. Для справки: разрешённое производителем шин давление значительно превосходит рекомендуемое (из соображений комфорта) производителем автомобилей. Для легковых шин это обычно 3..3,5 бар (эти цифры есть на баллоне, на английском). Для любых, а особенно грузовых машин, издержки на колёса снижаются при уменьшении летней дневной скорости движения из-за меньшего нагрева и старения резины. По этой причине, а также для защиты асфальта, в развитых странах летом практикуется ночное движение тяжеловесного транспорта с большим числом колёс. === Развал и схождение === Зачастую новые владельцы пребывают в неведении о таких настройках машины, а между тем, и развал и схождение существенно сказываются на расходе топлива и особенно — износе колёс. Следует проверять развал-схождение после смены деталей передней подвески (рычаги, стойки, пружины, рулевые шарниры, рулевая рейка), либо в случае неравномерного износа передних колёс, либо после ударов подвески, ДТП (обычно, при этом машину «ведёт»), ну и желательно разово сразу после покупки. Нарушенный развал-сход может дать до 5-7 % лишнего расхода топлива, и очень быстрый износ колёс. Явным признаком грубого нарушения развала-схода является визг резины на поворотах, увод («руль тянет») машины в сторону, или ступенчатый износ резины. === Смазочные материалы === Однажды купленное, более густое, чем нужно, масло влечёт за собой дополнительные расходы. Масло следует заливать минимально возможной вязкости (базовое масло для автомобилей Toyota имеет вязкость 0w20), однако для старых машин это может иметь препятствия из-за утечек или повышения шума. При каждом запуске зимой двигатель разово расходует избыточное количество топлива, расходуемого на прогрев масла в картере. Однако в дальнейшем масло подогревается за счёт тепла, отдаваемого в систему охлаждения, и на расход топлива уже почти не влияет. Но более густое масло создаёт проблему зимнему запуску. В агрегатах трансмиссии (коробка, мост) и подвески (ступичные подшипники) неоправданно густая смазка ''непрерывно'' расходует мощность двигателя на трение, вызывающее её разогрев и снижение вязкости, особенно зимой. Однако, можно сразу использовать менее вязкое масло и экономить топливо. === Приёмы экономии топлива зимой === Хотя густое масло в двигателе и привлекает всё внимание владельцев, как препятствующее зимнему пуску, его доля в расходовании топлива невелика. Наибольшие траты в мороз связаны с разогревом из-за трения масла и ''поддержания тёплыми'' массивных агрегатов трансмиссии: коробки передач, раздаточной коробки, мостов, а также ступиц. В этом смысле самыми выгодными зимой являются легковые машины с передним приводом, так как коробка и главная передача объединены вместе и отчасти подогреваются двигателем. Тем не менее, для всех машин в суровых (особенно сибирских) условиях очень выгодно сезонно использовать более жидкое масло или смазку, чем написано в инструкции, которую писали для температур от −20 до +50. Для ступиц можно рекомендовать смесь из 50 % литола и 50 % зимнего моторного масла 0w40 или 0w50, для раздаточных коробок, мостов, и коробок, заправляемых трансмиссионным маслом — смесь синтетического трансмиссионного масла и того же моторного масла в той же пропорции. Никакой опасности от «смешивания присадок» при обычной температуре коробки нет, но для уменьшения возможного износа при снижении вязкости, особенно если мы оставляем жидкое масло на лето, ''стоит добавить присадку для снижения трения'', например, ER. Поскольку масло в агрегатах меняется реже моторного, и объём его мал, затраты на такой «тюнинг» невелики. Синтетические масла тем выгоднее, что их вязкость меньше меняется с температурой — следовательно, в жару они не слишком разжижаются. Следующим важным фактором зимней экономии являются снижение затрат на прогрев. Проблема в том, что после запуска двигатель (особенно бензиновый) из-за богатой смеси расходует значительно больше топлива, чем обычно на холостом ходу, и в то же время много тепла уносится в выпускную систему. Поэтому тепловая эффективность двигателя для прогрева машины автозапуском в несколько раз хуже, чем для пусковых подогревателей («автономок»). Недостатком автономок, в свою очередь, является расходование электроэнергии аккумулятора и довольно высокая стоимость. Самым выгодным с точки зрения стоимости прогрева является электрический, и при наличии розетки, он лучший в любом случае. Однако имеется ещё один редкий вариант: тепловой аккумулятор. Такое устройство на российских машинах анонсировано на «Москвич-Святогор», и несмотря на провал самой модели, продаётся отдельно. Секрет теплового аккумулятора в запасании энергии от системы охлаждения в теплоте плавления нескольких килограммов легкоплавкой смеси. Запаса тепла хватает на разовый подогрев двигателя за несколько минут, запас тепла сохраняется в течение 2-3 суток. Желающие изготовить легкоплавкую смесь самостоятельно могут изучить [https://patents.google.com/patent/RU94031207A/ru соответствующий патент]. Некоторые счастливые обладатели гибридных «Тойот» с успехом используют встроенную батарею гибрида, имеющую напряжение как раз 220 вольт. Обычно она установлена сразу за задним сиденьем. Ёмкость её так велика, что достаточна для однократного разогрева двигателя стандартным подогревателем в 1 кВт. Однако лучше поставить простейший таймер на отключение, сняв таймер часового типа с какой-нибудь печки с грилем (30 минут подогрева достаточно) — во избежание глубокого разряда. Батарея эта во время движения заряжается от двигателя, и служит накопителем при торможении. Можно запитывать от неё чайник, электроплиту, электроинструмент — в общем, любой электроприбор, допускающий работу и от '''постоянного тока'''. На небольшое время — даже при выключенном двигателе. Ёмкость и напряжение в принципе зависит от модели «Приуса», и в гибридных «Лексусах» напряжение достигает 288 вольт, так что будьте бдительны. == Двигатель и трансмиссия == Эффективность этих основных агрегатов любого транспорта сказывается на всей цепочке его использования. Двигатель потребляет топливо, масло, запасные части, и требует расходов на техническое обслуживание. Трансмиссия выводит его на рабочий режим. Подбором подходящего топлива, смазочного масла, систем подогрева, и поддержанием исправности мы можем минимизировать денежные затраты, приходящиеся на километр пути. === Альтернативное топливо === Мы регулярно видим газобаллонные автомобили вокруг. Какие выгоды может дать другое топливо, и как скоро оно окупится? [[Файл:Propane cylinder on GAZ 3302.jpg|мини|300x300пкс|Газобаллонные автомобили мы видим каждый день. Насколько это выгодно? Опасен ли газ?]] К такому вопросу следует подходить очень внимательно. Прежде всего, где эксплуатируется ваш автомобиль, куда вы ездите? Если только по городу, и там есть заправка метаном — это самый дешёвый вариант. Однако баллоны для сжатого метана тяжёлые, и занимают довольно много места. Такой газ чаще всего ставят на грузовики, автобусы — в общем, тяжёлые и большие машины, где эти баллоны не особенно повлияют на грузоподъёмность. Особенностью метана является, что это газобаллонное оборудование (ГБО) можно поставить и на дизельный двигатель, с экономией примерно 60 % топлива (солярка замещается не полностью). Однако ряд изобретателей с успехом переделывал старинные МАЗы и КамАЗы (без наддува) на искровой цикл с установкой зиловского или газоновского трамблёра в развал блока, при этом форсунки заменяли на самодельные свечные колодцы, куда ставили свечу под ключ на 16, причём тяга моторов на газу даже возрастала. Однако такой работе всегда предшествовала [[Капитальный ремонт двигателя|капиталка]], с фрезерованием поршней до степени сжатия 10 (октановое число метана 100 единиц). Следует понимать при расчёте фрезеровки, что для увеличения МАЗовской камеры на 100 кубических сантиметров с головки каждого поршня нужно снять ровно 260 граммов дюраля. Утончение днища поршня можно уменьшить, профрезеровав углубление под камеру в головке. Однако наиболее распространённый и простой вариант, это перевод на пропан-бутановую смесь бензиновых машин. Рассмотрим простой пример перевода на газ обычного УАЗ-Патриота. На 2021 год средняя стоимость установки ГБО на машину со впрыском составляла примерно 40 000 рублей. и с учётом среднего расхода бензина 14,4 л/100 км, а газа 16,6 л/100 км<ref>{{Cite web|url=https://www.zr.ru/content/articles/931095-gaz-40-rublej-za-litr-est/|title=Газ по 40 рублей - есть ли смысл ставить ГБО? Вот полный расчет|website=www.zr.ru|access-date=2021-12-12}}</ref>, для цены бензина 50 руб/л и газа 15 руб/л, будем иметь выгоду на каждом километре 7,2-2,49=4,71 рубля. Следовательно, установка ГБО окупится при данных ценах примерно за 9 000 км. Помните, что установка газа на машины с прямым впрыском (FSI) необычайно дорога (порядка 200 000 р), и если вы планируете ставить газ — не стоит и покупать модель с таким двигателем. За 200 000 км, которые УАЗик пройдёт до переборки при указанных ценах, газовое топливо сэкономит владельцу 4,71 * 200 000 = 942 000 рублей. Расхожие мнения, что газ «угробляет двигатель», «сушит клапана» не были подтверждены на практике<ref name=":0">{{Cite web|url=https://www.zr.ru/content/articles/588208-metanovye-avtomobili-blizhajshee-budushhee/|title=Метановые автомобили - ближайшее будущее?|website=www.zr.ru|access-date=2021-12-16}}</ref><ref>{{Cite web|lang=ru|url=https://zen.yandex.ru/media/autovybor/razobrali-dvigatel-posle-500-tys-km-probega-s-gbo-pokazyvaiu-sostoianie-vnutri-603b8ad549b4e728907afff1|title=Разобрали двигатель после 500 тыс. км пробега с ГБО. Показываю состояние внутри|website=Яндекс Дзен {{!}} Блогерская платформа|access-date=2021-12-16}}</ref><ref name=":1">{{Cite web|url=https://propan-metan.ru/neispravnosti/kak-vliyaet-gaz-na-benzinovyj-dvigatel.html|title=Идет проверка...|website=propan-metan.ru|access-date=2021-12-16}}</ref>. Проводимые МАИ в 1980-х годах тесты показали продление ресурса нормально отрегулированного двигателя, работающего на газу, примерно вдвое. Однако есть три нюанса, более важные для владельцев газобаллонных (чаще карбюраторного типа) машин. Первый, установка слишком раннего зажигания может быть незаметным при работе на газу — однако в это же время из-за повышенного давления (встречная вспышка) поршень испытывает большие нагрузки, перегревается, и может со временем прогореть. На бензине такая работа сопровождается детонационным стуком, и нарушение легко замечается. Второе, при зажатых или уже начавших пропускать (подгорание) клапанах двигатель на газу продолжает работать ровно, что быстро приведёт к неожиданному для владельца сильному прогоранию клапана. И третье: ручная регулировка подачи газа, без использования приборов может привести к работе на обеднённой смеси, в таком случае клапана могут перегреваться со снижением зазоров, и на высоких мощностях — сгорать. Таким образом, владельцу газобаллонной машины следует обращать внимание на работу двигателя на бензине, и при появлении признаков неровной работы, стука — немедленно обращаться в сервис, даже если на газу машина работает ещё ровно. Регулировать винтами подачу газа самостоятельно не следует, за настройкой обращаться в оборудованные газоанализатором мастерские (в противном случае возможно забеднение смеси с последующим перегревом клапанов). Рекомендуется также увеличивать зазоры в клапанах относительно заводских на 0,05 мм<ref name=":1" />. Причиной резко негативного отношения водителей к газобаллонному оборудования во времена СССР называли невозможность воровства топлива<ref name=":1" />. Вопрос безопасности, конечно, очень важен. Прежде всего, машины с установленным ГБО не должны стоять в закрытом помещении (гараж, паркинг). С точки зрения безопасности при автокатастрофе, следует монтировать газовый баллон где-нибудь в середине багажника, тогда он может быть повреждён уже '''после''' того как все в салоне погибнут. Наружная установка, какую можно видеть повсеместно на «Газелях», вынужденное, но не лучшее решение. В остальном современное ГБО настолько надёжнее советского, что куда выше вероятность возгорания машины от бензина, чем от газа. По информации журнала «За рулём» природный газ значительно безопаснее бензина как при попадании в воздух, так и хранении его в баллонах<ref name=":0" />. === Элементы тюнинга двигателя === Разработанный мотор изготовитель добросовестно настраивает на штатное топливо и максимальную мощность. Но в случае старинных машин, рассчитанных на бензин А-76/А-72, а то и А-66 (раритетные машины типа ГАЗ-21, М-401, ГАЗ-51) возникают значительные потери топлива из-за неполного использования октанового числа. Более того, пропан-бутановая смесь имеет более высокое октановое число, составляющее примерно 100 единиц — из чего следует, что при повышении степени сжатия фрезеровкой головок цилиндров мы можем значительно улучшить экономичность мотора. При этом также значительно облегчается тепловой режим клапанов, так как отходящие газы будут холоднее. Однако, тут есть ещё некоторые нюансы, связанные с ростом максимального давления и мощности двигателя, поскольку древние конструкции могут иметь неполноопорный коленвал, разрезные поршни, заливные подшипники. В таком случае разумно одновременно с фрезеровкой головки (что повышает степень сжатия и степень расширения) несколько сменить фазы газораспределения, приведя их в примерное соответствие с циклом Мюллера. Попросту говоря, нужно организовать запаздывание распредвала относительно заводских значений, величина которого составит порядка 15 градусов поворота коленвала. Уменьшение наполняемости пропорционально снижает давление в цилиндрах и максимальную мощность, но даёт ещё большую возможность к увеличению степени сжатия, так как требования к октановому числу ещё больше снижаются. Для машин с OHC, имеющим цепной или ременной привод распредвала, это отчасти происходит автоматически, так как фрезеровка сама по себе приводит с смещению фаз из-за укорочения натянутой ветви цепи/ремня. Для машин же с нижним распредвалом в таком случае окажется подходящим перестановка распредвала на один зуб назад. Соответствующий угол можно пересчитать. Идеальным примером такого тюнинга может служить старинный грузовик ГАЗ-53/ГАЗ-3307, имеющий головки для бензина А-66 и А-72 (степень сжатия 6 и 6,7)соответственно. Простой перевод на газ с расходом 30л/100 км мало удовлетворяет владельцев, которые не догадываются, что его можно уменьшить. Расчётная фрезеровка головок будет идти под степень сжатия 9 (с тем условием, что мы АИ-92 используем лишь для запуска, основное топливо — газ), то есть припуск при фрезеровке составит 6 или 5 мм соответственно. Для некоторого снижения наполнения и следовательно, насосных потерь при движении грузовика мы переставляем распредвал на зуб назад, что составит 12 градусов поворота коленвала. После такого тюнинга расход должен весьма ощутимо сократиться (снижение 15-20 %), главным образом за счёт фрезеровки. При сборке и дальнейшей эксплуатации придётся учесть такие детали: первое, при росте компрессии нам придётся уменьшить зазор в свечах, иначе такой двигатель просто может не завестись. Вместо 0,8..1 мм в свече следует поддерживать зазор не более 0,6 мм. Второе, перед сборкой следует убедиться в отсутствии развальцовки металла головки вокруг силовых шпилек, и в случае необходимости — профрезеровать до ровной поверхности. Характерный для этого мотора, дефект может нарушить прижим головки, так что после выравнивания «пятачков» следует под штатные калёные шайбы установить более основательные, например, от двигателя мод. ЗМЗ-406. Это гарантирует снижение нагрузки на дюраль головки, и следовательно, прижим прокладки со временем не ослабеет. === Тип масла === Как правило, мы выбираем наиболее дешёвое масло, обеспечивающее условия надёжной работы и запуска двигателя зимой. Из этого следует между прочим, что при оборудовании транспорта надёжным пусковым подогревателем исчезает необходимость применения наиболее дорогих полностью синтетических масел, обеспечивающих прокрутку при температуре −35 С и ниже. Однако при любых условиях, из соображений ресурса двигателя, не стоит применять минеральные масла, так как они увеличивают износ и вызывают отложения нагара на поршнях и других деталях двигателя. Кроме того, минеральные масла имеют минимальный (обычно 10000 км) срок замены, в то время как полусинтетические и синтетические можно менять через 20000 и 30000 км соответственно, не забывая про штатную смену фильтра на 10000 км (пробеги указаны для машин типа ВАЗ). Для дизельных моторов, особенно импортных, из-за высокого содержания серы в отечественной солярке, не может идти речь о продлении использования масла сверх нормы 10000, поскольку масло закисляется и быстро изнашивает подшипники. При выборе масел во всяком случае следует опираться на класс качества масла, установленный производителем автомобиля и класс, указанный на банке с маслом, например CD/SG. Применение масла классом ниже недопустимо из-за снижения ресурса, и с другой стороны, применение значительно более высокого не имеет смысла по денежным соображениям. Не следует считать машину живым существом, способным «лакомиться» масляным или топливным деликатесом, и с другой стороны, машина совершенно не способна «терпеть» серьёзные нарушения технического характера, например, нехватку масла в поддоне. === Расход масла === Этот показателей более важен, чем можно подумать, подсчитав на калькуляторе стоимость масла. Как только на современном автомобиле начинает расти расход масла, одновременно ухудшаются другие показатели. Для дизельного двигателя с турбиной это означает чаще всего закоксовывание воздушных каналов после турбины, за чем идёт снижение мощности, чёрный дым и расход топлива; для бензиновых двигателей это неизбежно приближает границу детонации, что вызывает реакцию электронного блока управления, который уменьшает угол опережения зажигания — что опять-таки вызывает расход топлива в том числе и за счёт необходимости движения на более низкой передаче при падении мощности. Повышенный расход масла, попадающего на катализатор, ведёт к ложным показаниям датчика кислорода, нарушает расчёты компьютера и снижает мощность ещё больше. Единственный тип двигателя, малочувствительный к расходу масла — это дизельный двигатель без наддува. В этом случае издержки угара масла в основном идут за счёт его стоимости. Однако, можно заметить, что при росте расхода масла в летнее время возникает также перегрев двигателя — а это чревато более дорогими последствиями. Таким образом, если мы хотим экономичной работы машины, необходимо решать проблему. Самым простейшим (но не гарантированным) является применение присадок типа Ресурс, РиМЕТ и более дорогих импортных, однако в случае изношенных сальников без их замены не обойтись. Экономичному же капитальному ремонту двигателя в этом викиучебнике посвящена [[Капитальный ремонт двигателя|другая статья]]. === Настройки и исправность систем двигателя === Существующие автомобили обычно настроены заводом на компромисс между экономичностью и мощностью, однако в последнее время эти два показателя уступили место экологии. Установленные катализаторы работают в довольно узком диапазоне ''качества смеси'' (это показатель, показывающий соотношение воздуха с горючим), причём выход за эти пределы грозит отказом катализатора, а то и всей системы управления двигателем. Типичным примером такой угрозы являются перебои системы зажигания (пробой проводов, отказ свечи), в результате чего происходит оплавление катализаторов (топливная смесь сгорает в выпускном коллекторе). Поэтому движение с неисправным двигателем крайне нежелательно, и если необходимо, то нужно ехать с небольшой скоростью на аварийке. С точки зрения экономии, вторжение в систему впрыска бесполезно: хотя можно добиться повышения мощности, это будет за счёт расхода. На дизельных машинах такие проблемы меньше. Но, к сожалению, широкое распространение дизельных грузовиков с катализатором ставит те же проблемы: никакой лишней самодеятельности в настройках, движение с минимальной (40-50 км/ч) скоростью при отказах топливной системы (стук форсунки). Основное правило, которое нужно помнить: для достижения экономичности с любым двигателем следует поддерживать чистым воздушный фильтр. Старые карбюраторные машины имеют более широкий диапазон их настройки по топливной экономичности, но и чаще страдают от повышенного расхода. Поскольку они в целом почти вышли из обращения, можно лишь напомнить, что избыточный расход часто лечится в них очистителем карбюратора. Для всех машин с настройкой угла опережения зажигания нужно выставлять оптимальное по приборам, или в отсутствие таковых, по границе детонации. Заметную экономию топлива давали двигатели с вихревым движением обеднённого заряда, с повышенной степенью сжатия (для «Тойоты» это двигатели 4A-E, «Мицубиси» ''Cyclone engine'',…), но к сожалению, такой тип двигателя не отвечает современным требованиям экологии по окислам азота. Однако многочисленные умельцы в России неоднократно тюнинговали свои машины таким же образом. === Присадки, уменьшающие трение === Такие присадки возникли первоначально по военному заказу. Танкистам и авиаторам было нужно обеспечить некоторое время работы двигателя после потери масла из-за боевых повреждений. И такие присадки удалось создать. Неожиданным побочным эффектом их является снижение трения, против применения масла без добавки. Речь идёт о примерно 20..30 % экономии механических потерь двигателя (трение поршней с кольцами и подшипников колен- и распредвала), что с учётом механического КПД двигателя 85..90 % даёт (на полной нагрузке) более скромную экономию 3..4 %. Однако на эту же величину возрастает и максимальная мощность. К тому же, когда двигатель работает на холостом ходу, с механическим КПД равным нулю, экономия от таких присадок возрастает до исходных 20..30 %. Что в зависимости от нагрузки в течение работы, может составлять 8..10 %. Существуют аналогичные присадки, оптимизированные для трансмиссии. Как правило, присадки, снижающие трение в агрегате, увеличивают и его ресурс. Некоторые присадки имеют ограниченные восстановительные свойства. Есть две категории присадок: с продолжающимся после смены масла действием, и добавляемые с каждой доливкой масла. Первые обычно имеют умеренную эффективность, но их применения хватает на 50..100 тысяч километров, вторые эффективнее, но требуют добавки с каждой заменой масла. В любом случае также возрастает ресурс двигателя, обусловленный износом, а в ряде случаев снижается расход масла. На любой СТО с капремонтом вас заверят, что это ложь, но помните: они заинтересованы, чтобы свою проблему вы решили ''именно у них''. === Учёт местных условий === Параметры двигателя производитель рассчитывает, исходя из условий на уровне моря и нормальной температуры. При поездках исключительно в горной местности, где давление меньше, мощность его снижается, но в то же время граница детонации отодвигается ввиду малого давления конца сжатия. Похожее наблюдается на Крайнем Севере, где из-за низкой температуры на впуске температура в конце сжатия также ниже. В обоих случаях есть два варианта снижения издержек: (1) можно использовать бензин с более низким октановым числом, или (2) тюнинговать, повысив степень сжатия, с уменьшением расхода топлива. Во всех случаях при переводе на газ имеется возможность использовать также бензин с более низким октановым числом, если он используется исключительно для запуска и прогрева (обычно так и есть), так как в работающем на малом нажатии на педаль (особенно холодном) двигателе детонация не возникает. Кроме того, бензин с более низким октановым числом легче воспламеняется (улучшение зимнего пуска). При движении в основном по равнинной местности, для машин с достаточно мощным двигателем может оказаться весьма выгодным тюнинг сменой главной передачи. При установке главной передачи, рассчитанной на более лёгкий кузов или более мощный мотор (например, установка главной передачи от «Баргузина» на «Газель»), двигатель при движении на высшей передаче будет совершать меньше оборотов на километр, иначе говоря, будет работать в более выгодном с точки зрения расхода диапазоне оборотов. При движении по ровной трассе экономия может достигать 5-7 %, вне зависимости от остальных изменений. Аналогичный эффект даст смена двигателя с большим литражом и мощностью на меньший, при прежних передаточных числах главной передачи (рассчитанных на мощный мотор). Однако если скорость при движении обычно ограничена мощностью двигателя (подъёмы в горах, постоянные разгоны между светофорами в городе), такой тюнинг не даст экономии. == Экономичное вождение == Стиль вождения является необычайно важным с точки зрения экономии топлива фактором. По результатам в Западной Европе, прошедшие курсы обучения экономичному вождению водители расходовали в городе на 25..30 % топлива меньше. Однако в связи с низкими общей культурой и ценой топлива, таких курсов в России всё ещё нет. === Люк, окна, багажник === В противовес умеренному росту расхода топлива при снижении давления в шинах (+ 0,26 л/100 км при тест-драйве на Форд-Капри 2S), открытые окна показали рост 0,66 л/100 км. Пустой багажник дал рост 0,81 л/100 км, а с четырьмя чемоданами — 2,6 л/100 км. Отсюда невыгодность оставления на крыше неиспользуемого багажника и неэкономичность его вообще. Открытый сдвижной люк на крыше вызвал меньший рост, чем окна: 0,33 л/100 км, что связано с лучшим обтеканием. === Прогрев === Относительно того, выгоднее ли прогревать машину, или трогаться на холодную, большинство водителей имеет разные мнения. Проведённые в Европе тесты показали повышение суммарной экономичности (прогрев+движение) при трогании сразу. Однако есть зимние российские нюансы. # При движении без прогрева хотя бы до 30-40 градусов с большой нагрузкой в мороз возможно забрасывание свечей и остановка двигателя с затруднённым последующим запуском. Это может создать и опасность на дороге. # При этих же условиях (большая нагрузка, то есть высокие передачи) на непрогретых моторах с алюминиевой головкой, а особенно и алюминиевым блоком при недостаточной затяжке болтов можно спровоцировать пробитие газового стыка (прокладка головки). Вероятность этого выше на отечественных машинах, и без того не отличающихся высоким качеством стали, где сырые болты/шпильки головки ежегодно вызывают пробитие прокладки на десятках, если не сотнях тысяч машин. Хорошим решением проблемы для счастливого обладателя ВАЗа или УАЗа будет предупредительная смена низкокачественных оригинальных болтов/шпилек на выточенные из шпилек ЯМЗ, болтов ЗИЛа или другого высококачественного материала, не дожидаясь неизбежного «закипания» мотора. Таким образом, с точки зрения экономии топлива и времени при сохранении ресурса можно рекомендовать зимой минимальный прогрев до 30-40 градусов (стрелка термометра оторвалась от нуля), после чего следует начинать движение на низших передачах (до второй). Через несколько минут, когда двигатель прогреется больше (потепление воздуха из печки), можно разгоняться дальше. Сравнительно медленное движение вначале полезно и с точки зрения постепенного разогрева масла в трансмиссии и амортизаторов подвески: при движении сразу с большими скоростями на морозе возможен отрыв или отказ амортизаторов, преждевременный износ коробки из-за густого масла. В большие морозы бывают случаи даже разрушения корпусов непрогретых мостов, коробок давлением масла. Летом для машин с алюминиевым блоком (BMW, ГАЗ, Suzuki, Москвич) желательно несколько минут после запуска и трогания двигаться с небольшим нажатием на газ, либо прогреть пару минут. Во избежание запотевания стёкол изнутри, и следовательно, скорейшего движения, лучше время предварительного прогрева провести вне салона, на ногах, посвятив его сметанию снега с машины и очистке стёкол. Печку сразу следует включать на полную подачу воздуха, пусть он и холодный — чистота стёкол при этом гарантирует безопасность в движении. === Выбранная скорость движения === Лёгкие машины с хорошей аэродинамикой («обмылки») имеют минимальный расход топлива на скорости 50..60 км/ч на высшей передаче, для машин класса «зубила» это 3,5..4 л/100 км. На скорости 100..110 км/ч расход удваивается. Машины с аэродинамикой похуже («Жигули») имеют минимальный расход на скорости 40..50 км/ч, также на пятой передаче. Почему так никто не ездит? — удивитесь вы, но ответ очевиден — все торопятся. Для грузовиков с высокой парусностью минимальный расход зависит от удельной мощности фуры: более мощный тягач расходует больше топлива на холостом ходу, и потому минимум расхода топлива смещается на более высокие скорости. Для фур этот диапазон скоростей 40..45 км/ч, опять же с условием использования высшей передачи. Почему именно высшая передача? — поскольку при этом двигатель производит меньше оборотов коленвала на километр пути. Все эти выкладки касаются движения по ровной дороге, поскольку очевидно, что в горной местности расход будет определяться в большей степени профилем дороги и тем, насколько нам удастся использовать набранную при подъёме потенциальную энергию для движения накатом. Крутые спуски вынудят тормозить двигателем и/или тормозами, в этом случае никакое снижение скорости не может дать экономии топлива. При регулярных поездках по горам либо крутой холмистой местности (город Севастополь), большую экономию может дать гибридный автомобиль, накапливающий энергию торможения в аккумуляторе. Однако электрические тягачи «Тесла» всё ещё дороги, а гибридных не создано: на Западе предпочитают вместо этого создавать горные дороги, не требующие интенсивного торможения на спуске. Таким образом, оптимумы экономии топлива (~50 км/ч) и экономии времени (как можно быстрее) не совпадают, и мы выбираем промежуточную скорость, учитывающую знаки, нашу утомляемость, состояние подвески и дороги, сезон года и время суток. Однако с точки зрения расхода топлива (и аварийности) лучше всё же придерживаться меньшей скорости. Стоит помнить, что снизив скорость со 100 до 80, мы проедем 400 км на час дольше (и потратим на 5-6 литров бензина меньше) лишь с математической точки зрения, ''не учитывающей движение других машин по трассе''. Фактически, выигрыш времени при росте скорости ещё меньше. Так, в начале 1970-х, в связи с начавшимся топливным кризисом, в Германии был проведён тест-драйв, где два водителя грузовиков с одинаковым грузом и весом получили противоположные инструкции: первому — ехать как можно быстрее, второму — двигаться аккуратно, безопасно, без резких торможений. Трасса составляла примерно 700 км (до Италии). Эксперты считали, что разница во времени прибытия машин будет не менее 2 часов, но оказалось, что составила менее 40 минут. В то же время закреплённые на тягачах датчики зафиксировали десятки экстренных торможений первой машины и очень значительный рост расхода топлива. Отсюда вывод о бесполезности обгонов в условиях плотного потока: средняя скорость возрастает незначительно, но из-за разгонов и торможений возникают опасные ситуации и растёт расход горючего. Выгодно обгонять лишь машины, движущиеся медленнее остальных. === Разгоны и торможения === В ходе разгона энергия топлива переходит в кинетическую энергию автомобиля. Страшного с точки зрения экономии в этом нет, поскольку собственно расход энергии происходит в торможении, а также на сопротивление качению колёс, сопротивление воздуха и подъём. Поэтому оптимально придать машине в ходе разгона не слишком большую скорость, если мы предвидим торможение или остановку. При умело рассчитанном торможении заранее, не дожидаясь утыкания в бампер впереди идущим, можно светофоры проходить без остановки, экономя часть топлива. Следует помнить, что кинетическая энергия пропорциональна квадрату скорости — следовательно, разогнавшись до 40 км/ч вместо 20, мы потратим в 4 раза больше топлива. Поэтому, ''чем короче маршруты между остановками, тем до меньшей скорости стоит разгоняться'', иначе расход энергии на единицу пути сильно возрастает. Интенсивность разгона на более низких передачах выше, однако при этом двигатель совершает больше оборотов коленвала на километр, и следовательно, израсходует больше топлива. Поэтому не следует без надобности энергично разгоняться. Оптимально двигаться на более высоких передачах («внатяг»), и не давить на газ в скором ожидании препятствия (красный свет). Чем меньше мы погасим скорости тормозами, тем больше сэкономим. Сказки, про то, что «двигателю тяжело на маленьких оборотах», восходят к временам «Запорожца» и «Татры», и к современным моторам с водяным охлаждением не относятся никак. === Накат === Был очень популярной темой в советское время. В самом деле, при отсутствии повышающих передач, двигатель машины, движущейся без разгона по ровной дороге всегда будет работать в неэкономичном режиме (насосные потери из-за дросселирования), и в этом случае выгоднее часть пути разгоняться при большом нажатии газа, а затем выключать сцепление и двигаться накатом. В таком случае двигатель совершает меньше оборотов на километр и экономит топливо. Но такой способ утомителен, и более-менее удобен в холмистой местности, где накат получается довольно естественно, а путь выбега «с горки» больше. Современные машины с механической коробкой имеют повышающие передачи, которые как раз позволяют догружать двигатель на прямой и уменьшать долю насосных потерь, автоматическая коробка же при отпускании газа сама переходит в режим наката, рассоединяя двигатель и колёса. Таким образом, наибольший эффект использование наката по-прежнему имеет на старинных машинах советской конструкции, где его можно с успехом применять. === Обгоны === Обгоны неизбежная вещь в условиях обилия тихоходов. Однако многочисленные проверки показали, что попытка двигаться значительно быстрее потока сопряжены не только с опасностью из-за маневрирования, но и повышенным расходом топлива при напряжённом встречном движении. Поэтому, если встречная полоса забита, лучше расслабиться и терпеливо ехать на высшей передаче в потоке. За один дополнительный час к проведённой «смене» за рулём вы можете догнать любого шумахера, какую бы он высокую скорость (кратковременно) не развивал. Кроме безопасности, вы сэкономите деньги на топливе и ресурсе двигателя. == Резюме == С целью достижения экономичности оптимально заранее определить назначение автомобиля и условия его эксплуатации, наличие подходящих заправок, и так далее. Желательна покупка именно такого автомобиля, который при допустимой стоимости отвечал бы требованиям по внутреннему объёму и грузоподъёмности и имел минимально возможные массу и коэффициент лобового сопротивления (важность этих двух показателей зависит от условий применения транспорта). На следующем этапе мы можем максимально технически подготовить купленный или имевшийся транспорт, с целью минимального расхода выбранного топлива (возможен переход на альтернативное) на километр. Затем следуют вопросы эксплуатации. Групповое использование и полная загрузка машины позволяет увеличивать прибыль от эксплуатации на километр, и потому может выглядеть как улучшение экономии. И наконец, умелое вождение машины с сокращением интенсивности и глубины торможений и ограничением максимальной скорости позволит снизить эксплуатационный расход, приблизив к расходу топлива при движении с постоянной скоростью. == См. также == * [[Капитальный ремонт двигателя]] * [[Как открыть автосервис]] == Примечания == {{Примечания}} == Литература == * Хиллиард Д., Спрингер Дж. Топливная экономичность автомобилей с бензиновыми двигателями. — 1988. — Москва: Машиностроение. * Мацкерле Ю. Современный экономичный автомобиль — 1987. — Москва: Машиностроение. * Журнал «За рулём», публикации по экономичности. {{Готовность|100%}} d6omwowwlythm9m8peqe14hrl77xvkf 269958 269957 2026-09-06T12:36:19Z Хедин 67628 /* Приёмы экономии топлива зимой */ вот этот! 269958 wikitext text/x-wiki {{Название учебника|Тип=Одностраничный|Категория=Автомобили}} [[Файл:Реоритет авто.JPG|мини|400x400пкс|Можно улучшить топливную экономичность любого автомобиля.<br>Даже такого]] Топливная экономичность автомобиля (км/л) или обратный ей показатель расхода топлива (обычно л/100 км) интересует многих автовладельцев. Какие цифры топливной экономичности должны быть у нашей машины, как и насколько её можно улучшить? В статью включены советы по логистике и экономичному управлению автомобилем, что собственно не является предметом именно экономичности автомобиля, однако их высокая важность (процент экономии) заслуживают рассмотрения здесь, соответственно с целями викиучебника. == Свойства автомобиля, их влияние на расход в различных условиях применения == '''''Масса пустого автомобиля''''' важный показатель будущего расхода. Однажды сэкономив и купив более тяжёлый автомобиль, чем был доступен, мы затем десятки (и даже сотни — при движении в городе) тысяч раз тратим энергию на разгон излишней массы. Нивелировать это можно правильным стилем вождения с минимальным использованием торможения, однако при движении в основном по трассе без частого торможения избыток массы на экономичности сказывается меньше. В горной местности излишний вес прямо ведёт к дополнительному расходу топлива. Коэффициент трения при качении по асфальту около 0,01 — следовательно, каждые 100 кг лишнего веса требуют дополнительной тяги в 10 Н, что эквивалентно расходу 1 МДж/100 км, при обычной эффективности бензинового мотора 300 г/кВт*ч это увеличение расхода на 0,12 л/100 км, это не считая торможений и подъёмов в гору, без учёта потерь в трансмиссии и снижения КПД двигателя при частичных нагрузках. С учётом всего перечисленного — до 0,4 л/100 км. При ожидаемом пробеге до капремонта 200 000 км и цене бензина 50 руб/л каждые лишние 100 кг, следовательно, принесут издержки до 40 000 руб. В дополнению к потерям на трение качения, разгон автомобиля массой 1000 кг до скорости 72 км/ч (20 м/с) требует 0,2 МДж, что эквивалентно 0,024 л топлива без учёта потерь в трансмиссии и при максимальном КПД. Если на пути в 1 км встречается 1 светофор, то разгоны дадут прибавку расхода топлива на 2,4 л/100 км (при максимальном КПД двигателя и нулевых потерях в трансмиссии при разгоне до 72 км/ч). Поскольку каждое торможение с последующим разгоном требует дополнительной энергии, а доля аэродинамического сопротивления ввиду невысокой средней скорости в городе невелика, то именно ''масса автомобиля является определяющим фактором, влияющим на расход топлива при движении в городе''. '''''Лобовое сопротивление''''' вредный показатель, но в некоторых случаях его можно уменьшить. На грузовиках с этой целью устанавливают обтекатели, желательно также устранение выступающих элементов: спойлеров, мухобоек, кенгурятников. Многие гонщики ставят более короткие и жёсткие пружины, чтобы уменьшить лобовое сопротивление, это работает — но машина становится жёстче и может разбить низ об ухабы. Хорошим вариантом для зимы будет закрытие решётки радиатора снаружи, а не внутри, как это обычно делают. Мощность, расходуемая на аэродинамическое сопротивление, растёт с кубом скорости, и следовательно, доля удельного расхода топлива из-за сопротивления воздуха, приходящаяся на километр, растёт пропорционально квадрату скорости. Отсюда важность разумного снижения скорости. При движении со скоростями выше 60-70 км/ч (трасса) аэродинамическое сопротивление превосходит сопротивление качения, и влияние лобового сопротивления (это лобовая площадь, перемноженная на коэффициент аэродинамического качества Cx) быстро возрастает с дальнейшим ростом средней скорости. Тем самым, лобовое сопротивление является решающим фактором расхода топлива на загородной трассе. '''''Установленный двигатель''''', если вы не гонщик, лучше иметь менее мощный. Это может дать экономию топлива порядка 7-10 % (в рамках обычной линейки литража для модели), и кроме того, снизит транспортный налог. Исключением является горная местность: там маломощный двигатель не может дать никакой экономии топлива, но будет отнимать время водителя на подъёмах. То же относится и к службам такси, где использование подразумевает непрерывное маневрирование скоростью. При более расслабленном загородном маршруте либо движении по магистральным дорогам мощный двигатель несколько облегчит обгоны, но будет стабильно увеличивать издержки, и с позиции данной статьи, не нужен, поскольку скорость автомобиля с большинством вариантов двигателя ограничена знаками, а не мощностью. '''''Автоматическая коробка передач''''' даёт удобство и безопасность в населённых пунктах, облегчает управление и в горах, но на горизонтальной трассе количество необходимых переключений скоростей намного уменьшается, и более заметны становятся недостатки АКПП: цена, стоимость обслуживания, и рост расхода топлива. Поэтому при использовании машины в основном за городом, на трассе, нет смысла в покупке машины с автоматической коробкой передач, увеличивающей расход топлива примерно на 10 %. '''''Выбор дизеля или гибрида''''' оправдан с позиции топливной экономичности. Но применение дизеля сомнительно по экономичности на легковых машинах, поскольку значительно дороже обслуживание, и с учётом недешёвой солярки, оно почти съедает всю экономию. Гибриды бывают только легковые, причём и дизельные машины, и гибридные крайне трудно (а то и невозможно) перевести на газ. Так что это дело вкуса. В ближайшие десятилетия ждём распространения электромобилей, но пока нет зарядных станций и льгот на дорожный налог — они редко кому нужны. '''''Свойства автомобиля в целом неизменны с момента его покупки'''''. Мы не можем значительно уменьшить его массу или лобовое сопротивление, но учёт особенностей машины даёт предпосылки экономии. Если мы на стадии выбора машины, вес, объём салона/кузова и тип могут быть ещё выбраны, если же нет, то кроме логистических моментов, мы можем сэкономить на устранении лишнего аэродинамического сопротивления: снять спойлеры, декоративные бамперы и мухобойки (каждый элемент отнимает до 5 % горючего), не возить лишний вес (выгрузить лишний инструмент при недалёких поездках, особенно летом), снять лишние кресла и сиденья, если ими никогда не пользуемся (это правилами не запрещено). Японские машины с целью экономии веса с завода комплектуют колесом-докаткой вместо полноразмерного, которое к тому же занимает меньше места. '''''Важность свойств автомобиля зависит от условий его применения'''.'' Так, при движении с редкими остановками (загородная трасса) нивелируется влияние избытка массы, а при снижении выбранной скорости уменьшается различие между новейшими «обмылками» и «квадратными» моделями. Следовательно, при движении с небольшими скоростями и редкими остановками (к примеру, сельская местность) топливная экономичность старых моделей машин будет не слишком уступать новейшим моделям, в то время как в городе, на автобане, или в горах они покажут неприемлемое ухудшение этих цифр по сравнению с ними. Удешевление топлива снижает денежный ущерб от неоптимальных свойств машины — следовательно, '''''установка газа на авто архаичной конструкции даёт наибольшую по величине денежную экономию'''''. == Логистика == '''''Под логистикой в данном случае понимается разумное использование имеющегося автомобиля''''' с целью снижения расхода топлива путём сокращения маршрута, либо разделения издержек на нескольких пользователей. === Коллективное использование === Ежедневно можно наблюдать движущиеся машины, в которых нет пассажиров. Для России это стало печальной действительностью, но в других странах, с более дорогим топливом и другой культурой людей, принято коллективное использование транспорта. Наиболее яркий пример — Великобритания. Если вы используете машину, чтобы добираться до работы, попробуйте найти других (безлошадных) сотрудников, едущих в том же направлении. Откажитесь от советского мышления, что брать с коллег нельзя — если они экономят деньги, пусть часть этого будет ваша. Помните, такую модель коллективной доставки сотрудников на работу использует даже Microsoft, для чего у них есть специальное внутрифирменное приложение. Английская же модель предусматривает коллективное управление несколькими людьми нескольких машин (разного типа: может понадобиться пикап или автобус), при этом каждый совладелец отмечает свой пробег и соответственно расходу ресурса вносит свою долю на обслуживание и ремонт. Однако, это предполагает более высокий уровень культуры, чем в России. === Оптимизация маршрута === Вы уверены, что едете самым выгодным путём? Километраж по карте не всегда отражает издержки, поскольку работающий на холостом ходу легковой автомобиль расходует примерно 0,5 л/час. Кроме того, вы тратите время. Поэтому следует попробовать альтернативные маршруты, и что самое интересное в данном отношении, другие времена поездок. Если в 8 и 17 часов пик, почему бы вам не обсудить с начальством возможность выполнения заданий со сдвигом от обычного времени работы (например, с 6 до 15)? Если вы живёте далеко, то сэкономите много времени, теряемого раньше в пробках, и бензина. === Полная загрузка === Для легковой машины достигается либо коллективным использованием, либо перевозкой дополнительного груза. Посмотрите объявления, возможно, там есть спрос на регулярные перевозки товара. Например, в расположенный по соседству с вашей работой ларёк хозяин ежедневно завозит хлеб. Когда это происходит? Состыкуйтесь с субарендаторами здания вашей конторы, возможно, у них тоже есть нужда в транспорте, и они вызывают такси? Для грузовых машин — это возможно полное использование грузоподъёмности в тоннах. Если груза мало, и мы не можем возить его реже, то нужно искать попутный груз (гугл в помощь), либо при невозможности этого, подумать о выборе более лёгкого транспорта. == Регулировки и выбор расходных материалов, приёмы экономии топлива зимой == [[Файл:Цены на топливо июль 2022.jpg||мини|400x400пкс|Цены на топливо в России растут как на дрожжах: лето 2022 года]] Регулировками и верным выбором смазок и других расходников можно достичь определённого процента экономии топлива, вне зависимости от других факторов. === Тип шин === При всех рекомендуемых давлениях, радиальные шины имеют меньший коэффициент трения, чем диагональные (последние устанавливают теперь мало) примерно на 40 %. Кроме того, они дольше служат. Большинство изготавливаемых сейчас шин — радиальные. Низкопрофильные шины имеют меньший коэффициент трения, чем высокопрофильные: так, с коэффициентом ширины /60 коэффициент трения на скорости 140 км/ч меньше на 26 %, чем с коэффициентом /80. Однако высокопрофильные шины необходимы, если автомобиль эксплуатируют на разбитых дорогах, с обилием ям и кочек. Различие в коэффициенте трения качения зависит от скорости: низкопрофильные дают меньший выигрыш (10..15 %) на скоростях до 80 км/ч, но с ростом скорости разрыв увеличивается. Смотря по слойности, шины демонстрируют различный расход топлива. Шины с меньшей слойностью (не имеющие в обозначении буквы C = Commercial) более мягкие, и при прочих равных условиях покажут лучшую топливную экономичность. Однако применение на машине шин, не разрешённых изготовителем, может вызвать проблемы с ДПС. Так, родная резина на «Газель» имеет типоразмер 185/75R16C и стоит заметно дороже «некоммерческой» 205/65R16. Некоммерческая имеет почти равную грузоподъёмность, экономит топливо, но по утверждениям некоторых водителей, может идти грыжами при установке вместо коммерческой. === Давление воздуха в колёсах === Стандартное давление в колёсах оптимально при движении с умеренными скоростями (до 100 км/ч). Если перед нами трасса, где мы постоянно будем двигаться со скоростью 100 км/ч и больше, многие руководства рекомендуют увеличить давление в шинах на 10 %. Следует учитывать, что на трение качения колёс автомобиля среднего класса при скорости 100 км/ч, на диагональных шинах, расходуется 4..6 кВт мощности. Это примерно 1/4 всей мощности, затрачиваемой на движение, поскольку на воздушное сопротивление расходуется 17..18 кВт<ref>{{Книга|автор=Мацкерле Ю.|заглавие=Современный экономичный автомобиль|год=1987|место=Москва|издательство=Машиностроение}}</ref>. Соответственно, резина при движении значительно нагревается. Повышение давления в колёсах даёт экономию топлива около 2 %, и может применяться и при меньших скоростях. Однако, на меньших скоростях на неровной дороге машина станет более жёсткой для пассажиров. Увеличение давления благотворно сказывается и на ресурсе колёс, поскольку они меньше деформируются и нагреваются, поэтому этим часто пользуются водители фур (там колёс много, и они дорогие). Однако повышать давление стоит только в исправных, качественных баллонах. Для справки: разрешённое производителем шин давление значительно превосходит рекомендуемое (из соображений комфорта) производителем автомобилей. Для легковых шин это обычно 3..3,5 бар (эти цифры есть на баллоне, на английском). Для любых, а особенно грузовых машин, издержки на колёса снижаются при уменьшении летней дневной скорости движения из-за меньшего нагрева и старения резины. По этой причине, а также для защиты асфальта, в развитых странах летом практикуется ночное движение тяжеловесного транспорта с большим числом колёс. === Развал и схождение === Зачастую новые владельцы пребывают в неведении о таких настройках машины, а между тем, и развал и схождение существенно сказываются на расходе топлива и особенно — износе колёс. Следует проверять развал-схождение после смены деталей передней подвески (рычаги, стойки, пружины, рулевые шарниры, рулевая рейка), либо в случае неравномерного износа передних колёс, либо после ударов подвески, ДТП (обычно, при этом машину «ведёт»), ну и желательно разово сразу после покупки. Нарушенный развал-сход может дать до 5-7 % лишнего расхода топлива, и очень быстрый износ колёс. Явным признаком грубого нарушения развала-схода является визг резины на поворотах, увод («руль тянет») машины в сторону, или ступенчатый износ резины. === Смазочные материалы === Однажды купленное, более густое, чем нужно, масло влечёт за собой дополнительные расходы. Масло следует заливать минимально возможной вязкости (базовое масло для автомобилей Toyota имеет вязкость 0w20), однако для старых машин это может иметь препятствия из-за утечек или повышения шума. При каждом запуске зимой двигатель разово расходует избыточное количество топлива, расходуемого на прогрев масла в картере. Однако в дальнейшем масло подогревается за счёт тепла, отдаваемого в систему охлаждения, и на расход топлива уже почти не влияет. Но более густое масло создаёт проблему зимнему запуску. В агрегатах трансмиссии (коробка, мост) и подвески (ступичные подшипники) неоправданно густая смазка ''непрерывно'' расходует мощность двигателя на трение, вызывающее её разогрев и снижение вязкости, особенно зимой. Однако, можно сразу использовать менее вязкое масло и экономить топливо. === Приёмы экономии топлива зимой === Хотя густое масло в двигателе и привлекает всё внимание владельцев, как препятствующее зимнему пуску, его доля в расходовании топлива невелика. Наибольшие траты в мороз связаны с разогревом из-за трения масла и ''поддержания тёплыми'' массивных агрегатов трансмиссии: коробки передач, раздаточной коробки, мостов, а также ступиц. В этом смысле самыми выгодными зимой являются легковые машины с передним приводом, так как коробка и главная передача объединены вместе и отчасти подогреваются двигателем. Тем не менее, для всех машин в суровых (особенно сибирских) условиях очень выгодно сезонно использовать более жидкое масло или смазку, чем написано в инструкции, которую писали для температур от −20 до +50. Для ступиц можно рекомендовать смесь из 50 % литола и 50 % зимнего моторного масла 0w40 или 0w50, для раздаточных коробок, мостов, и коробок, заправляемых трансмиссионным маслом — смесь синтетического трансмиссионного масла и того же моторного масла в той же пропорции. Никакой опасности от «смешивания присадок» при обычной температуре коробки нет, но для уменьшения возможного износа при снижении вязкости, особенно если мы оставляем жидкое масло на лето, ''стоит добавить присадку для снижения трения'', например, ER. Поскольку масло в агрегатах меняется реже моторного, и объём его мал, затраты на такой «тюнинг» невелики. Синтетические масла тем выгоднее, что их вязкость меньше меняется с температурой — следовательно, в жару они не слишком разжижаются. Следующим важным фактором зимней экономии являются снижение затрат на прогрев. Проблема в том, что после запуска двигатель (особенно бензиновый) из-за богатой смеси расходует значительно больше топлива, чем обычно на холостом ходу, и в то же время много тепла уносится в выпускную систему. Поэтому тепловая эффективность двигателя для прогрева машины автозапуском в несколько раз хуже, чем для пусковых подогревателей («автономок»). Недостатком автономок, в свою очередь, является расходование электроэнергии аккумулятора и довольно высокая стоимость. Самым выгодным с точки зрения стоимости прогрева является электрический, и при наличии розетки, он лучший в любом случае. Однако имеется ещё один редкий вариант: тепловой аккумулятор. Такое устройство на российских машинах анонсировано на «Москвич-Святогор», и несмотря на провал самой модели, продаётся отдельно. Секрет теплового аккумулятора в запасании энергии от системы охлаждения в теплоте плавления нескольких килограммов легкоплавкой смеси. Запаса тепла хватает на разовый подогрев двигателя за несколько минут, запас тепла сохраняется в течение 2-3 суток. Желающие изготовить легкоплавкую смесь самостоятельно могут изучить [https://patents.google.com/patent/RU2104291C1/ru соответствующий патент]. Некоторые счастливые обладатели гибридных «Тойот» с успехом используют встроенную батарею гибрида, имеющую напряжение как раз 220 вольт. Обычно она установлена сразу за задним сиденьем. Ёмкость её так велика, что достаточна для однократного разогрева двигателя стандартным подогревателем в 1 кВт. Однако лучше поставить простейший таймер на отключение, сняв таймер часового типа с какой-нибудь печки с грилем (30 минут подогрева достаточно) — во избежание глубокого разряда. Батарея эта во время движения заряжается от двигателя, и служит накопителем при торможении. Можно запитывать от неё чайник, электроплиту, электроинструмент — в общем, любой электроприбор, допускающий работу и от '''постоянного тока'''. На небольшое время — даже при выключенном двигателе. Ёмкость и напряжение в принципе зависит от модели «Приуса», и в гибридных «Лексусах» напряжение достигает 288 вольт, так что будьте бдительны. == Двигатель и трансмиссия == Эффективность этих основных агрегатов любого транспорта сказывается на всей цепочке его использования. Двигатель потребляет топливо, масло, запасные части, и требует расходов на техническое обслуживание. Трансмиссия выводит его на рабочий режим. Подбором подходящего топлива, смазочного масла, систем подогрева, и поддержанием исправности мы можем минимизировать денежные затраты, приходящиеся на километр пути. === Альтернативное топливо === Мы регулярно видим газобаллонные автомобили вокруг. Какие выгоды может дать другое топливо, и как скоро оно окупится? [[Файл:Propane cylinder on GAZ 3302.jpg|мини|300x300пкс|Газобаллонные автомобили мы видим каждый день. Насколько это выгодно? Опасен ли газ?]] К такому вопросу следует подходить очень внимательно. Прежде всего, где эксплуатируется ваш автомобиль, куда вы ездите? Если только по городу, и там есть заправка метаном — это самый дешёвый вариант. Однако баллоны для сжатого метана тяжёлые, и занимают довольно много места. Такой газ чаще всего ставят на грузовики, автобусы — в общем, тяжёлые и большие машины, где эти баллоны не особенно повлияют на грузоподъёмность. Особенностью метана является, что это газобаллонное оборудование (ГБО) можно поставить и на дизельный двигатель, с экономией примерно 60 % топлива (солярка замещается не полностью). Однако ряд изобретателей с успехом переделывал старинные МАЗы и КамАЗы (без наддува) на искровой цикл с установкой зиловского или газоновского трамблёра в развал блока, при этом форсунки заменяли на самодельные свечные колодцы, куда ставили свечу под ключ на 16, причём тяга моторов на газу даже возрастала. Однако такой работе всегда предшествовала [[Капитальный ремонт двигателя|капиталка]], с фрезерованием поршней до степени сжатия 10 (октановое число метана 100 единиц). Следует понимать при расчёте фрезеровки, что для увеличения МАЗовской камеры на 100 кубических сантиметров с головки каждого поршня нужно снять ровно 260 граммов дюраля. Утончение днища поршня можно уменьшить, профрезеровав углубление под камеру в головке. Однако наиболее распространённый и простой вариант, это перевод на пропан-бутановую смесь бензиновых машин. Рассмотрим простой пример перевода на газ обычного УАЗ-Патриота. На 2021 год средняя стоимость установки ГБО на машину со впрыском составляла примерно 40 000 рублей. и с учётом среднего расхода бензина 14,4 л/100 км, а газа 16,6 л/100 км<ref>{{Cite web|url=https://www.zr.ru/content/articles/931095-gaz-40-rublej-za-litr-est/|title=Газ по 40 рублей - есть ли смысл ставить ГБО? Вот полный расчет|website=www.zr.ru|access-date=2021-12-12}}</ref>, для цены бензина 50 руб/л и газа 15 руб/л, будем иметь выгоду на каждом километре 7,2-2,49=4,71 рубля. Следовательно, установка ГБО окупится при данных ценах примерно за 9 000 км. Помните, что установка газа на машины с прямым впрыском (FSI) необычайно дорога (порядка 200 000 р), и если вы планируете ставить газ — не стоит и покупать модель с таким двигателем. За 200 000 км, которые УАЗик пройдёт до переборки при указанных ценах, газовое топливо сэкономит владельцу 4,71 * 200 000 = 942 000 рублей. Расхожие мнения, что газ «угробляет двигатель», «сушит клапана» не были подтверждены на практике<ref name=":0">{{Cite web|url=https://www.zr.ru/content/articles/588208-metanovye-avtomobili-blizhajshee-budushhee/|title=Метановые автомобили - ближайшее будущее?|website=www.zr.ru|access-date=2021-12-16}}</ref><ref>{{Cite web|lang=ru|url=https://zen.yandex.ru/media/autovybor/razobrali-dvigatel-posle-500-tys-km-probega-s-gbo-pokazyvaiu-sostoianie-vnutri-603b8ad549b4e728907afff1|title=Разобрали двигатель после 500 тыс. км пробега с ГБО. Показываю состояние внутри|website=Яндекс Дзен {{!}} Блогерская платформа|access-date=2021-12-16}}</ref><ref name=":1">{{Cite web|url=https://propan-metan.ru/neispravnosti/kak-vliyaet-gaz-na-benzinovyj-dvigatel.html|title=Идет проверка...|website=propan-metan.ru|access-date=2021-12-16}}</ref>. Проводимые МАИ в 1980-х годах тесты показали продление ресурса нормально отрегулированного двигателя, работающего на газу, примерно вдвое. Однако есть три нюанса, более важные для владельцев газобаллонных (чаще карбюраторного типа) машин. Первый, установка слишком раннего зажигания может быть незаметным при работе на газу — однако в это же время из-за повышенного давления (встречная вспышка) поршень испытывает большие нагрузки, перегревается, и может со временем прогореть. На бензине такая работа сопровождается детонационным стуком, и нарушение легко замечается. Второе, при зажатых или уже начавших пропускать (подгорание) клапанах двигатель на газу продолжает работать ровно, что быстро приведёт к неожиданному для владельца сильному прогоранию клапана. И третье: ручная регулировка подачи газа, без использования приборов может привести к работе на обеднённой смеси, в таком случае клапана могут перегреваться со снижением зазоров, и на высоких мощностях — сгорать. Таким образом, владельцу газобаллонной машины следует обращать внимание на работу двигателя на бензине, и при появлении признаков неровной работы, стука — немедленно обращаться в сервис, даже если на газу машина работает ещё ровно. Регулировать винтами подачу газа самостоятельно не следует, за настройкой обращаться в оборудованные газоанализатором мастерские (в противном случае возможно забеднение смеси с последующим перегревом клапанов). Рекомендуется также увеличивать зазоры в клапанах относительно заводских на 0,05 мм<ref name=":1" />. Причиной резко негативного отношения водителей к газобаллонному оборудования во времена СССР называли невозможность воровства топлива<ref name=":1" />. Вопрос безопасности, конечно, очень важен. Прежде всего, машины с установленным ГБО не должны стоять в закрытом помещении (гараж, паркинг). С точки зрения безопасности при автокатастрофе, следует монтировать газовый баллон где-нибудь в середине багажника, тогда он может быть повреждён уже '''после''' того как все в салоне погибнут. Наружная установка, какую можно видеть повсеместно на «Газелях», вынужденное, но не лучшее решение. В остальном современное ГБО настолько надёжнее советского, что куда выше вероятность возгорания машины от бензина, чем от газа. По информации журнала «За рулём» природный газ значительно безопаснее бензина как при попадании в воздух, так и хранении его в баллонах<ref name=":0" />. === Элементы тюнинга двигателя === Разработанный мотор изготовитель добросовестно настраивает на штатное топливо и максимальную мощность. Но в случае старинных машин, рассчитанных на бензин А-76/А-72, а то и А-66 (раритетные машины типа ГАЗ-21, М-401, ГАЗ-51) возникают значительные потери топлива из-за неполного использования октанового числа. Более того, пропан-бутановая смесь имеет более высокое октановое число, составляющее примерно 100 единиц — из чего следует, что при повышении степени сжатия фрезеровкой головок цилиндров мы можем значительно улучшить экономичность мотора. При этом также значительно облегчается тепловой режим клапанов, так как отходящие газы будут холоднее. Однако, тут есть ещё некоторые нюансы, связанные с ростом максимального давления и мощности двигателя, поскольку древние конструкции могут иметь неполноопорный коленвал, разрезные поршни, заливные подшипники. В таком случае разумно одновременно с фрезеровкой головки (что повышает степень сжатия и степень расширения) несколько сменить фазы газораспределения, приведя их в примерное соответствие с циклом Мюллера. Попросту говоря, нужно организовать запаздывание распредвала относительно заводских значений, величина которого составит порядка 15 градусов поворота коленвала. Уменьшение наполняемости пропорционально снижает давление в цилиндрах и максимальную мощность, но даёт ещё большую возможность к увеличению степени сжатия, так как требования к октановому числу ещё больше снижаются. Для машин с OHC, имеющим цепной или ременной привод распредвала, это отчасти происходит автоматически, так как фрезеровка сама по себе приводит с смещению фаз из-за укорочения натянутой ветви цепи/ремня. Для машин же с нижним распредвалом в таком случае окажется подходящим перестановка распредвала на один зуб назад. Соответствующий угол можно пересчитать. Идеальным примером такого тюнинга может служить старинный грузовик ГАЗ-53/ГАЗ-3307, имеющий головки для бензина А-66 и А-72 (степень сжатия 6 и 6,7)соответственно. Простой перевод на газ с расходом 30л/100 км мало удовлетворяет владельцев, которые не догадываются, что его можно уменьшить. Расчётная фрезеровка головок будет идти под степень сжатия 9 (с тем условием, что мы АИ-92 используем лишь для запуска, основное топливо — газ), то есть припуск при фрезеровке составит 6 или 5 мм соответственно. Для некоторого снижения наполнения и следовательно, насосных потерь при движении грузовика мы переставляем распредвал на зуб назад, что составит 12 градусов поворота коленвала. После такого тюнинга расход должен весьма ощутимо сократиться (снижение 15-20 %), главным образом за счёт фрезеровки. При сборке и дальнейшей эксплуатации придётся учесть такие детали: первое, при росте компрессии нам придётся уменьшить зазор в свечах, иначе такой двигатель просто может не завестись. Вместо 0,8..1 мм в свече следует поддерживать зазор не более 0,6 мм. Второе, перед сборкой следует убедиться в отсутствии развальцовки металла головки вокруг силовых шпилек, и в случае необходимости — профрезеровать до ровной поверхности. Характерный для этого мотора, дефект может нарушить прижим головки, так что после выравнивания «пятачков» следует под штатные калёные шайбы установить более основательные, например, от двигателя мод. ЗМЗ-406. Это гарантирует снижение нагрузки на дюраль головки, и следовательно, прижим прокладки со временем не ослабеет. === Тип масла === Как правило, мы выбираем наиболее дешёвое масло, обеспечивающее условия надёжной работы и запуска двигателя зимой. Из этого следует между прочим, что при оборудовании транспорта надёжным пусковым подогревателем исчезает необходимость применения наиболее дорогих полностью синтетических масел, обеспечивающих прокрутку при температуре −35 С и ниже. Однако при любых условиях, из соображений ресурса двигателя, не стоит применять минеральные масла, так как они увеличивают износ и вызывают отложения нагара на поршнях и других деталях двигателя. Кроме того, минеральные масла имеют минимальный (обычно 10000 км) срок замены, в то время как полусинтетические и синтетические можно менять через 20000 и 30000 км соответственно, не забывая про штатную смену фильтра на 10000 км (пробеги указаны для машин типа ВАЗ). Для дизельных моторов, особенно импортных, из-за высокого содержания серы в отечественной солярке, не может идти речь о продлении использования масла сверх нормы 10000, поскольку масло закисляется и быстро изнашивает подшипники. При выборе масел во всяком случае следует опираться на класс качества масла, установленный производителем автомобиля и класс, указанный на банке с маслом, например CD/SG. Применение масла классом ниже недопустимо из-за снижения ресурса, и с другой стороны, применение значительно более высокого не имеет смысла по денежным соображениям. Не следует считать машину живым существом, способным «лакомиться» масляным или топливным деликатесом, и с другой стороны, машина совершенно не способна «терпеть» серьёзные нарушения технического характера, например, нехватку масла в поддоне. === Расход масла === Этот показателей более важен, чем можно подумать, подсчитав на калькуляторе стоимость масла. Как только на современном автомобиле начинает расти расход масла, одновременно ухудшаются другие показатели. Для дизельного двигателя с турбиной это означает чаще всего закоксовывание воздушных каналов после турбины, за чем идёт снижение мощности, чёрный дым и расход топлива; для бензиновых двигателей это неизбежно приближает границу детонации, что вызывает реакцию электронного блока управления, который уменьшает угол опережения зажигания — что опять-таки вызывает расход топлива в том числе и за счёт необходимости движения на более низкой передаче при падении мощности. Повышенный расход масла, попадающего на катализатор, ведёт к ложным показаниям датчика кислорода, нарушает расчёты компьютера и снижает мощность ещё больше. Единственный тип двигателя, малочувствительный к расходу масла — это дизельный двигатель без наддува. В этом случае издержки угара масла в основном идут за счёт его стоимости. Однако, можно заметить, что при росте расхода масла в летнее время возникает также перегрев двигателя — а это чревато более дорогими последствиями. Таким образом, если мы хотим экономичной работы машины, необходимо решать проблему. Самым простейшим (но не гарантированным) является применение присадок типа Ресурс, РиМЕТ и более дорогих импортных, однако в случае изношенных сальников без их замены не обойтись. Экономичному же капитальному ремонту двигателя в этом викиучебнике посвящена [[Капитальный ремонт двигателя|другая статья]]. === Настройки и исправность систем двигателя === Существующие автомобили обычно настроены заводом на компромисс между экономичностью и мощностью, однако в последнее время эти два показателя уступили место экологии. Установленные катализаторы работают в довольно узком диапазоне ''качества смеси'' (это показатель, показывающий соотношение воздуха с горючим), причём выход за эти пределы грозит отказом катализатора, а то и всей системы управления двигателем. Типичным примером такой угрозы являются перебои системы зажигания (пробой проводов, отказ свечи), в результате чего происходит оплавление катализаторов (топливная смесь сгорает в выпускном коллекторе). Поэтому движение с неисправным двигателем крайне нежелательно, и если необходимо, то нужно ехать с небольшой скоростью на аварийке. С точки зрения экономии, вторжение в систему впрыска бесполезно: хотя можно добиться повышения мощности, это будет за счёт расхода. На дизельных машинах такие проблемы меньше. Но, к сожалению, широкое распространение дизельных грузовиков с катализатором ставит те же проблемы: никакой лишней самодеятельности в настройках, движение с минимальной (40-50 км/ч) скоростью при отказах топливной системы (стук форсунки). Основное правило, которое нужно помнить: для достижения экономичности с любым двигателем следует поддерживать чистым воздушный фильтр. Старые карбюраторные машины имеют более широкий диапазон их настройки по топливной экономичности, но и чаще страдают от повышенного расхода. Поскольку они в целом почти вышли из обращения, можно лишь напомнить, что избыточный расход часто лечится в них очистителем карбюратора. Для всех машин с настройкой угла опережения зажигания нужно выставлять оптимальное по приборам, или в отсутствие таковых, по границе детонации. Заметную экономию топлива давали двигатели с вихревым движением обеднённого заряда, с повышенной степенью сжатия (для «Тойоты» это двигатели 4A-E, «Мицубиси» ''Cyclone engine'',…), но к сожалению, такой тип двигателя не отвечает современным требованиям экологии по окислам азота. Однако многочисленные умельцы в России неоднократно тюнинговали свои машины таким же образом. === Присадки, уменьшающие трение === Такие присадки возникли первоначально по военному заказу. Танкистам и авиаторам было нужно обеспечить некоторое время работы двигателя после потери масла из-за боевых повреждений. И такие присадки удалось создать. Неожиданным побочным эффектом их является снижение трения, против применения масла без добавки. Речь идёт о примерно 20..30 % экономии механических потерь двигателя (трение поршней с кольцами и подшипников колен- и распредвала), что с учётом механического КПД двигателя 85..90 % даёт (на полной нагрузке) более скромную экономию 3..4 %. Однако на эту же величину возрастает и максимальная мощность. К тому же, когда двигатель работает на холостом ходу, с механическим КПД равным нулю, экономия от таких присадок возрастает до исходных 20..30 %. Что в зависимости от нагрузки в течение работы, может составлять 8..10 %. Существуют аналогичные присадки, оптимизированные для трансмиссии. Как правило, присадки, снижающие трение в агрегате, увеличивают и его ресурс. Некоторые присадки имеют ограниченные восстановительные свойства. Есть две категории присадок: с продолжающимся после смены масла действием, и добавляемые с каждой доливкой масла. Первые обычно имеют умеренную эффективность, но их применения хватает на 50..100 тысяч километров, вторые эффективнее, но требуют добавки с каждой заменой масла. В любом случае также возрастает ресурс двигателя, обусловленный износом, а в ряде случаев снижается расход масла. На любой СТО с капремонтом вас заверят, что это ложь, но помните: они заинтересованы, чтобы свою проблему вы решили ''именно у них''. === Учёт местных условий === Параметры двигателя производитель рассчитывает, исходя из условий на уровне моря и нормальной температуры. При поездках исключительно в горной местности, где давление меньше, мощность его снижается, но в то же время граница детонации отодвигается ввиду малого давления конца сжатия. Похожее наблюдается на Крайнем Севере, где из-за низкой температуры на впуске температура в конце сжатия также ниже. В обоих случаях есть два варианта снижения издержек: (1) можно использовать бензин с более низким октановым числом, или (2) тюнинговать, повысив степень сжатия, с уменьшением расхода топлива. Во всех случаях при переводе на газ имеется возможность использовать также бензин с более низким октановым числом, если он используется исключительно для запуска и прогрева (обычно так и есть), так как в работающем на малом нажатии на педаль (особенно холодном) двигателе детонация не возникает. Кроме того, бензин с более низким октановым числом легче воспламеняется (улучшение зимнего пуска). При движении в основном по равнинной местности, для машин с достаточно мощным двигателем может оказаться весьма выгодным тюнинг сменой главной передачи. При установке главной передачи, рассчитанной на более лёгкий кузов или более мощный мотор (например, установка главной передачи от «Баргузина» на «Газель»), двигатель при движении на высшей передаче будет совершать меньше оборотов на километр, иначе говоря, будет работать в более выгодном с точки зрения расхода диапазоне оборотов. При движении по ровной трассе экономия может достигать 5-7 %, вне зависимости от остальных изменений. Аналогичный эффект даст смена двигателя с большим литражом и мощностью на меньший, при прежних передаточных числах главной передачи (рассчитанных на мощный мотор). Однако если скорость при движении обычно ограничена мощностью двигателя (подъёмы в горах, постоянные разгоны между светофорами в городе), такой тюнинг не даст экономии. == Экономичное вождение == Стиль вождения является необычайно важным с точки зрения экономии топлива фактором. По результатам в Западной Европе, прошедшие курсы обучения экономичному вождению водители расходовали в городе на 25..30 % топлива меньше. Однако в связи с низкими общей культурой и ценой топлива, таких курсов в России всё ещё нет. === Люк, окна, багажник === В противовес умеренному росту расхода топлива при снижении давления в шинах (+ 0,26 л/100 км при тест-драйве на Форд-Капри 2S), открытые окна показали рост 0,66 л/100 км. Пустой багажник дал рост 0,81 л/100 км, а с четырьмя чемоданами — 2,6 л/100 км. Отсюда невыгодность оставления на крыше неиспользуемого багажника и неэкономичность его вообще. Открытый сдвижной люк на крыше вызвал меньший рост, чем окна: 0,33 л/100 км, что связано с лучшим обтеканием. === Прогрев === Относительно того, выгоднее ли прогревать машину, или трогаться на холодную, большинство водителей имеет разные мнения. Проведённые в Европе тесты показали повышение суммарной экономичности (прогрев+движение) при трогании сразу. Однако есть зимние российские нюансы. # При движении без прогрева хотя бы до 30-40 градусов с большой нагрузкой в мороз возможно забрасывание свечей и остановка двигателя с затруднённым последующим запуском. Это может создать и опасность на дороге. # При этих же условиях (большая нагрузка, то есть высокие передачи) на непрогретых моторах с алюминиевой головкой, а особенно и алюминиевым блоком при недостаточной затяжке болтов можно спровоцировать пробитие газового стыка (прокладка головки). Вероятность этого выше на отечественных машинах, и без того не отличающихся высоким качеством стали, где сырые болты/шпильки головки ежегодно вызывают пробитие прокладки на десятках, если не сотнях тысяч машин. Хорошим решением проблемы для счастливого обладателя ВАЗа или УАЗа будет предупредительная смена низкокачественных оригинальных болтов/шпилек на выточенные из шпилек ЯМЗ, болтов ЗИЛа или другого высококачественного материала, не дожидаясь неизбежного «закипания» мотора. Таким образом, с точки зрения экономии топлива и времени при сохранении ресурса можно рекомендовать зимой минимальный прогрев до 30-40 градусов (стрелка термометра оторвалась от нуля), после чего следует начинать движение на низших передачах (до второй). Через несколько минут, когда двигатель прогреется больше (потепление воздуха из печки), можно разгоняться дальше. Сравнительно медленное движение вначале полезно и с точки зрения постепенного разогрева масла в трансмиссии и амортизаторов подвески: при движении сразу с большими скоростями на морозе возможен отрыв или отказ амортизаторов, преждевременный износ коробки из-за густого масла. В большие морозы бывают случаи даже разрушения корпусов непрогретых мостов, коробок давлением масла. Летом для машин с алюминиевым блоком (BMW, ГАЗ, Suzuki, Москвич) желательно несколько минут после запуска и трогания двигаться с небольшим нажатием на газ, либо прогреть пару минут. Во избежание запотевания стёкол изнутри, и следовательно, скорейшего движения, лучше время предварительного прогрева провести вне салона, на ногах, посвятив его сметанию снега с машины и очистке стёкол. Печку сразу следует включать на полную подачу воздуха, пусть он и холодный — чистота стёкол при этом гарантирует безопасность в движении. === Выбранная скорость движения === Лёгкие машины с хорошей аэродинамикой («обмылки») имеют минимальный расход топлива на скорости 50..60 км/ч на высшей передаче, для машин класса «зубила» это 3,5..4 л/100 км. На скорости 100..110 км/ч расход удваивается. Машины с аэродинамикой похуже («Жигули») имеют минимальный расход на скорости 40..50 км/ч, также на пятой передаче. Почему так никто не ездит? — удивитесь вы, но ответ очевиден — все торопятся. Для грузовиков с высокой парусностью минимальный расход зависит от удельной мощности фуры: более мощный тягач расходует больше топлива на холостом ходу, и потому минимум расхода топлива смещается на более высокие скорости. Для фур этот диапазон скоростей 40..45 км/ч, опять же с условием использования высшей передачи. Почему именно высшая передача? — поскольку при этом двигатель производит меньше оборотов коленвала на километр пути. Все эти выкладки касаются движения по ровной дороге, поскольку очевидно, что в горной местности расход будет определяться в большей степени профилем дороги и тем, насколько нам удастся использовать набранную при подъёме потенциальную энергию для движения накатом. Крутые спуски вынудят тормозить двигателем и/или тормозами, в этом случае никакое снижение скорости не может дать экономии топлива. При регулярных поездках по горам либо крутой холмистой местности (город Севастополь), большую экономию может дать гибридный автомобиль, накапливающий энергию торможения в аккумуляторе. Однако электрические тягачи «Тесла» всё ещё дороги, а гибридных не создано: на Западе предпочитают вместо этого создавать горные дороги, не требующие интенсивного торможения на спуске. Таким образом, оптимумы экономии топлива (~50 км/ч) и экономии времени (как можно быстрее) не совпадают, и мы выбираем промежуточную скорость, учитывающую знаки, нашу утомляемость, состояние подвески и дороги, сезон года и время суток. Однако с точки зрения расхода топлива (и аварийности) лучше всё же придерживаться меньшей скорости. Стоит помнить, что снизив скорость со 100 до 80, мы проедем 400 км на час дольше (и потратим на 5-6 литров бензина меньше) лишь с математической точки зрения, ''не учитывающей движение других машин по трассе''. Фактически, выигрыш времени при росте скорости ещё меньше. Так, в начале 1970-х, в связи с начавшимся топливным кризисом, в Германии был проведён тест-драйв, где два водителя грузовиков с одинаковым грузом и весом получили противоположные инструкции: первому — ехать как можно быстрее, второму — двигаться аккуратно, безопасно, без резких торможений. Трасса составляла примерно 700 км (до Италии). Эксперты считали, что разница во времени прибытия машин будет не менее 2 часов, но оказалось, что составила менее 40 минут. В то же время закреплённые на тягачах датчики зафиксировали десятки экстренных торможений первой машины и очень значительный рост расхода топлива. Отсюда вывод о бесполезности обгонов в условиях плотного потока: средняя скорость возрастает незначительно, но из-за разгонов и торможений возникают опасные ситуации и растёт расход горючего. Выгодно обгонять лишь машины, движущиеся медленнее остальных. === Разгоны и торможения === В ходе разгона энергия топлива переходит в кинетическую энергию автомобиля. Страшного с точки зрения экономии в этом нет, поскольку собственно расход энергии происходит в торможении, а также на сопротивление качению колёс, сопротивление воздуха и подъём. Поэтому оптимально придать машине в ходе разгона не слишком большую скорость, если мы предвидим торможение или остановку. При умело рассчитанном торможении заранее, не дожидаясь утыкания в бампер впереди идущим, можно светофоры проходить без остановки, экономя часть топлива. Следует помнить, что кинетическая энергия пропорциональна квадрату скорости — следовательно, разогнавшись до 40 км/ч вместо 20, мы потратим в 4 раза больше топлива. Поэтому, ''чем короче маршруты между остановками, тем до меньшей скорости стоит разгоняться'', иначе расход энергии на единицу пути сильно возрастает. Интенсивность разгона на более низких передачах выше, однако при этом двигатель совершает больше оборотов коленвала на километр, и следовательно, израсходует больше топлива. Поэтому не следует без надобности энергично разгоняться. Оптимально двигаться на более высоких передачах («внатяг»), и не давить на газ в скором ожидании препятствия (красный свет). Чем меньше мы погасим скорости тормозами, тем больше сэкономим. Сказки, про то, что «двигателю тяжело на маленьких оборотах», восходят к временам «Запорожца» и «Татры», и к современным моторам с водяным охлаждением не относятся никак. === Накат === Был очень популярной темой в советское время. В самом деле, при отсутствии повышающих передач, двигатель машины, движущейся без разгона по ровной дороге всегда будет работать в неэкономичном режиме (насосные потери из-за дросселирования), и в этом случае выгоднее часть пути разгоняться при большом нажатии газа, а затем выключать сцепление и двигаться накатом. В таком случае двигатель совершает меньше оборотов на километр и экономит топливо. Но такой способ утомителен, и более-менее удобен в холмистой местности, где накат получается довольно естественно, а путь выбега «с горки» больше. Современные машины с механической коробкой имеют повышающие передачи, которые как раз позволяют догружать двигатель на прямой и уменьшать долю насосных потерь, автоматическая коробка же при отпускании газа сама переходит в режим наката, рассоединяя двигатель и колёса. Таким образом, наибольший эффект использование наката по-прежнему имеет на старинных машинах советской конструкции, где его можно с успехом применять. === Обгоны === Обгоны неизбежная вещь в условиях обилия тихоходов. Однако многочисленные проверки показали, что попытка двигаться значительно быстрее потока сопряжены не только с опасностью из-за маневрирования, но и повышенным расходом топлива при напряжённом встречном движении. Поэтому, если встречная полоса забита, лучше расслабиться и терпеливо ехать на высшей передаче в потоке. За один дополнительный час к проведённой «смене» за рулём вы можете догнать любого шумахера, какую бы он высокую скорость (кратковременно) не развивал. Кроме безопасности, вы сэкономите деньги на топливе и ресурсе двигателя. == Резюме == С целью достижения экономичности оптимально заранее определить назначение автомобиля и условия его эксплуатации, наличие подходящих заправок, и так далее. Желательна покупка именно такого автомобиля, который при допустимой стоимости отвечал бы требованиям по внутреннему объёму и грузоподъёмности и имел минимально возможные массу и коэффициент лобового сопротивления (важность этих двух показателей зависит от условий применения транспорта). На следующем этапе мы можем максимально технически подготовить купленный или имевшийся транспорт, с целью минимального расхода выбранного топлива (возможен переход на альтернативное) на километр. Затем следуют вопросы эксплуатации. Групповое использование и полная загрузка машины позволяет увеличивать прибыль от эксплуатации на километр, и потому может выглядеть как улучшение экономии. И наконец, умелое вождение машины с сокращением интенсивности и глубины торможений и ограничением максимальной скорости позволит снизить эксплуатационный расход, приблизив к расходу топлива при движении с постоянной скоростью. == См. также == * [[Капитальный ремонт двигателя]] * [[Как открыть автосервис]] == Примечания == {{Примечания}} == Литература == * Хиллиард Д., Спрингер Дж. Топливная экономичность автомобилей с бензиновыми двигателями. — 1988. — Москва: Машиностроение. * Мацкерле Ю. Современный экономичный автомобиль — 1987. — Москва: Машиностроение. * Журнал «За рулём», публикации по экономичности. {{Готовность|100%}} pxoqndlo35e8ukx3ozllbzcrsxz5jc1