FEATURE: Create IE Support Plugin (#8520)
This core plugin, which could be split off in the future, allows us to load IE specific code on demand. Co-authored-by: jjaffeux <j.jaffeux@gmail.com>
This commit is contained in:
parent
5431ae0a17
commit
4abe4454dd
|
@ -53,6 +53,7 @@ bootsnap-compile-cache/
|
||||||
!/plugins/discourse-narrative-bot
|
!/plugins/discourse-narrative-bot
|
||||||
!/plugins/discourse-presence
|
!/plugins/discourse-presence
|
||||||
!/plugins/discourse-local-dates
|
!/plugins/discourse-local-dates
|
||||||
|
!/plugins/discourse-internet-explorer
|
||||||
/plugins/*/auto_generated/
|
/plugins/*/auto_generated/
|
||||||
|
|
||||||
/spec/fixtures/plugins/my_plugin/auto_generated
|
/spec/fixtures/plugins/my_plugin/auto_generated
|
||||||
|
|
|
@ -1,185 +1,9 @@
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries
|
|
||||||
if (!Object.entries) {
|
|
||||||
Object.entries = function(obj) {
|
|
||||||
var ownProps = Object.keys(obj),
|
|
||||||
i = ownProps.length,
|
|
||||||
resArray = new Array(i); // preallocate the Array
|
|
||||||
while (i--) resArray[i] = [ownProps[i], obj[ownProps[i]]];
|
|
||||||
|
|
||||||
return resArray;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// adapted from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries
|
|
||||||
// missing in ie only
|
|
||||||
if (!Object.values) {
|
|
||||||
Object.values = function(obj) {
|
|
||||||
var ownProps = Object.keys(obj),
|
|
||||||
i = ownProps.length,
|
|
||||||
resArray = new Array(i); // preallocate the Array
|
|
||||||
while (i--) resArray[i] = obj[ownProps[i]];
|
|
||||||
|
|
||||||
return resArray;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
|
|
||||||
if (typeof Object.assign !== "function") {
|
|
||||||
// Must be writable: true, enumerable: false, configurable: true
|
|
||||||
Object.defineProperty(Object, "assign", {
|
|
||||||
value: function assign(target) {
|
|
||||||
// .length of function is 2
|
|
||||||
"use strict";
|
|
||||||
if (target == null) {
|
|
||||||
// TypeError if undefined or null
|
|
||||||
throw new TypeError("Cannot convert undefined or null to object");
|
|
||||||
}
|
|
||||||
|
|
||||||
var to = Object(target);
|
|
||||||
|
|
||||||
for (var index = 1; index < arguments.length; index++) {
|
|
||||||
var nextSource = arguments[index];
|
|
||||||
|
|
||||||
if (nextSource != null) {
|
|
||||||
// Skip over if undefined or null
|
|
||||||
for (var nextKey in nextSource) {
|
|
||||||
// Avoid bugs when hasOwnProperty is shadowed
|
|
||||||
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
|
|
||||||
to[nextKey] = nextSource[nextKey];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return to;
|
|
||||||
},
|
|
||||||
writable: true,
|
|
||||||
configurable: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes#Polyfill
|
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
if (!Array.prototype.includes) {
|
|
||||||
Object.defineProperty(Array.prototype, "includes", {
|
|
||||||
value: function(searchElement, fromIndex) {
|
|
||||||
if (this == null) {
|
|
||||||
throw new TypeError('"this" is null or not defined');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 1. Let O be ? ToObject(this value).
|
// Any IE only polyfill should be moved in discourse-internet-explorer plugin
|
||||||
var o = Object(this);
|
|
||||||
|
|
||||||
// 2. Let len be ? ToLength(? Get(O, "length")).
|
|
||||||
var len = o.length >>> 0;
|
|
||||||
|
|
||||||
// 3. If len is 0, return false.
|
|
||||||
if (len === 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. Let n be ? ToInteger(fromIndex).
|
|
||||||
// (If fromIndex is undefined, this step produces the value 0.)
|
|
||||||
var n = fromIndex | 0;
|
|
||||||
|
|
||||||
// 5. If n ≥ 0, then
|
|
||||||
// a. Let k be n.
|
|
||||||
// 6. Else n < 0,
|
|
||||||
// a. Let k be len + n.
|
|
||||||
// b. If k < 0, let k be 0.
|
|
||||||
var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
|
|
||||||
|
|
||||||
function sameValueZero(x, y) {
|
|
||||||
return (
|
|
||||||
x === y ||
|
|
||||||
(typeof x === "number" &&
|
|
||||||
typeof y === "number" &&
|
|
||||||
isNaN(x) &&
|
|
||||||
isNaN(y))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 7. Repeat, while k < len
|
|
||||||
while (k < len) {
|
|
||||||
// a. Let elementK be the result of ? Get(O, ! ToString(k)).
|
|
||||||
// b. If SameValueZero(searchElement, elementK) is true, return true.
|
|
||||||
if (sameValueZero(o[k], searchElement)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// c. Increase k by 1.
|
|
||||||
k++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 8. Return false
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes#Polyfill
|
|
||||||
if (!String.prototype.includes) {
|
|
||||||
Object.defineProperty(String.prototype, "includes", {
|
|
||||||
value: function(search, start) {
|
|
||||||
if (typeof start !== "number") {
|
|
||||||
start = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (start + search.length > this.length) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return this.indexOf(search, start) !== -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://tc39.github.io/ecma262/#sec-array.prototype.find
|
|
||||||
if (!Array.prototype.find) {
|
|
||||||
Object.defineProperty(Array.prototype, "find", {
|
|
||||||
value: function(predicate) {
|
|
||||||
// 1. Let O be ? ToObject(this value).
|
|
||||||
if (this == null) {
|
|
||||||
throw new TypeError('"this" is null or not defined');
|
|
||||||
}
|
|
||||||
|
|
||||||
var o = Object(this);
|
|
||||||
|
|
||||||
// 2. Let len be ? ToLength(? Get(O, "length")).
|
|
||||||
var len = o.length >>> 0;
|
|
||||||
|
|
||||||
// 3. If IsCallable(predicate) is false, throw a TypeError exception.
|
|
||||||
if (typeof predicate !== "function") {
|
|
||||||
throw new TypeError("predicate must be a function");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. If thisArg was supplied, let T be thisArg; else let T be undefined.
|
|
||||||
var thisArg = arguments[1];
|
|
||||||
|
|
||||||
// 5. Let k be 0.
|
|
||||||
var k = 0;
|
|
||||||
|
|
||||||
// 6. Repeat, while k < len
|
|
||||||
while (k < len) {
|
|
||||||
// a. Let Pk be ! ToString(k).
|
|
||||||
// b. Let kValue be ? Get(O, Pk).
|
|
||||||
// c. Let testResult be ToBoolean(? Call(predicate, T, « kValue, k, O »)).
|
|
||||||
// d. If testResult is true, return kValue.
|
|
||||||
var kValue = o[k];
|
|
||||||
if (predicate.call(thisArg, kValue, k, o)) {
|
|
||||||
return kValue;
|
|
||||||
}
|
|
||||||
// e. Increase k by 1.
|
|
||||||
k++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 7. Return undefined.
|
|
||||||
return undefined;
|
|
||||||
},
|
|
||||||
configurable: true,
|
|
||||||
writable: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/flags#Polyfill
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/flags#Polyfill
|
||||||
|
// IE and EDGE
|
||||||
if (RegExp.prototype.flags === undefined) {
|
if (RegExp.prototype.flags === undefined) {
|
||||||
Object.defineProperty(RegExp.prototype, "flags", {
|
Object.defineProperty(RegExp.prototype, "flags", {
|
||||||
configurable: true,
|
configurable: true,
|
||||||
|
@ -189,181 +13,4 @@ if (RegExp.prototype.flags === undefined) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/uxitten/polyfill/blob/master/string.polyfill.js
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart
|
|
||||||
if (!String.prototype.padStart) {
|
|
||||||
String.prototype.padStart = function padStart(targetLength, padString) {
|
|
||||||
targetLength = targetLength >> 0; //truncate if number, or convert non-number to 0;
|
|
||||||
padString = String(typeof padString !== "undefined" ? padString : " ");
|
|
||||||
if (this.length >= targetLength) {
|
|
||||||
return String(this);
|
|
||||||
} else {
|
|
||||||
targetLength = targetLength - this.length;
|
|
||||||
if (targetLength > padString.length) {
|
|
||||||
padString += padString.repeat(targetLength / padString.length); //append to original to ensure we are longer than needed
|
|
||||||
}
|
|
||||||
return padString.slice(0, targetLength) + String(this);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://github.com/uxitten/polyfill/blob/master/string.polyfill.js
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padEnd
|
|
||||||
if (!String.prototype.padEnd) {
|
|
||||||
String.prototype.padEnd = function padEnd(targetLength, padString) {
|
|
||||||
targetLength = targetLength >> 0; //floor if number or convert non-number to 0;
|
|
||||||
padString = String(typeof padString !== "undefined" ? padString : " ");
|
|
||||||
if (this.length > targetLength) {
|
|
||||||
return String(this);
|
|
||||||
} else {
|
|
||||||
targetLength = targetLength - this.length;
|
|
||||||
if (targetLength > padString.length) {
|
|
||||||
padString += padString.repeat(targetLength / padString.length); //append to original to ensure we are longer than needed
|
|
||||||
}
|
|
||||||
return String(this) + padString.slice(0, targetLength);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from
|
|
||||||
// Production steps of ECMA-262, Edition 6, 22.1.2.1
|
|
||||||
if (!Array.from) {
|
|
||||||
Array.from = (function() {
|
|
||||||
var toStr = Object.prototype.toString;
|
|
||||||
var isCallable = function(fn) {
|
|
||||||
return typeof fn === "function" || toStr.call(fn) === "[object Function]";
|
|
||||||
};
|
|
||||||
var toInteger = function(value) {
|
|
||||||
var number = Number(value);
|
|
||||||
if (isNaN(number)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if (number === 0 || !isFinite(number)) {
|
|
||||||
return number;
|
|
||||||
}
|
|
||||||
return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number));
|
|
||||||
};
|
|
||||||
var maxSafeInteger = Math.pow(2, 53) - 1;
|
|
||||||
var toLength = function(value) {
|
|
||||||
var len = toInteger(value);
|
|
||||||
return Math.min(Math.max(len, 0), maxSafeInteger);
|
|
||||||
};
|
|
||||||
|
|
||||||
// The length property of the from method is 1.
|
|
||||||
return function from(arrayLike /*, mapFn, thisArg */) {
|
|
||||||
// 1. Let C be the this value.
|
|
||||||
var C = this;
|
|
||||||
|
|
||||||
// 2. Let items be ToObject(arrayLike).
|
|
||||||
var items = Object(arrayLike);
|
|
||||||
|
|
||||||
// 3. ReturnIfAbrupt(items).
|
|
||||||
if (arrayLike == null) {
|
|
||||||
throw new TypeError(
|
|
||||||
"Array.from requires an array-like object - not null or undefined"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. If mapfn is undefined, then let mapping be false.
|
|
||||||
var mapFn = arguments.length > 1 ? arguments[1] : void undefined;
|
|
||||||
var T;
|
|
||||||
if (typeof mapFn !== "undefined") {
|
|
||||||
// 5. else
|
|
||||||
// 5. a If IsCallable(mapfn) is false, throw a TypeError exception.
|
|
||||||
if (!isCallable(mapFn)) {
|
|
||||||
throw new TypeError(
|
|
||||||
"Array.from: when provided, the second argument must be a function"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. b. If thisArg was supplied, let T be thisArg; else let T be undefined.
|
|
||||||
if (arguments.length > 2) {
|
|
||||||
T = arguments[2];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 10. Let lenValue be Get(items, "length").
|
|
||||||
// 11. Let len be ToLength(lenValue).
|
|
||||||
var len = toLength(items.length);
|
|
||||||
|
|
||||||
// 13. If IsConstructor(C) is true, then
|
|
||||||
// 13. a. Let A be the result of calling the [[Construct]] internal method
|
|
||||||
// of C with an argument list containing the single item len.
|
|
||||||
// 14. a. Else, Let A be ArrayCreate(len).
|
|
||||||
var A = isCallable(C) ? Object(new C(len)) : new Array(len);
|
|
||||||
|
|
||||||
// 16. Let k be 0.
|
|
||||||
var k = 0;
|
|
||||||
// 17. Repeat, while k < len… (also steps a - h)
|
|
||||||
var kValue;
|
|
||||||
while (k < len) {
|
|
||||||
kValue = items[k];
|
|
||||||
if (mapFn) {
|
|
||||||
A[k] =
|
|
||||||
typeof T === "undefined"
|
|
||||||
? mapFn(kValue, k)
|
|
||||||
: mapFn.call(T, kValue, k);
|
|
||||||
} else {
|
|
||||||
A[k] = kValue;
|
|
||||||
}
|
|
||||||
k += 1;
|
|
||||||
}
|
|
||||||
// 18. Let putStatus be Put(A, "length", len, true).
|
|
||||||
A.length = len;
|
|
||||||
// 20. Return A.
|
|
||||||
return A;
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat#Polyfill
|
|
||||||
if (!String.prototype.repeat) {
|
|
||||||
String.prototype.repeat = function(count) {
|
|
||||||
"use strict";
|
|
||||||
if (this == null)
|
|
||||||
throw new TypeError("can't convert " + this + " to object");
|
|
||||||
|
|
||||||
var str = "" + this;
|
|
||||||
// To convert string to integer.
|
|
||||||
count = +count;
|
|
||||||
// Check NaN
|
|
||||||
if (count != count) count = 0;
|
|
||||||
|
|
||||||
if (count < 0) throw new RangeError("repeat count must be non-negative");
|
|
||||||
|
|
||||||
if (count == Infinity)
|
|
||||||
throw new RangeError("repeat count must be less than infinity");
|
|
||||||
|
|
||||||
count = Math.floor(count);
|
|
||||||
if (str.length == 0 || count == 0) return "";
|
|
||||||
|
|
||||||
// Ensuring count is a 31-bit integer allows us to heavily optimize the
|
|
||||||
// main part. But anyway, most current (August 2014) browsers can't handle
|
|
||||||
// strings 1 << 28 chars or longer, so:
|
|
||||||
if (str.length * count >= 1 << 28)
|
|
||||||
throw new RangeError(
|
|
||||||
"repeat count must not overflow maximum string size"
|
|
||||||
);
|
|
||||||
|
|
||||||
var maxCount = str.length * count;
|
|
||||||
count = Math.floor(Math.log(count) / Math.log(2));
|
|
||||||
while (count) {
|
|
||||||
str += str;
|
|
||||||
count--;
|
|
||||||
}
|
|
||||||
str += str.substring(0, maxCount - str.length);
|
|
||||||
return str;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://developer.mozilla.org/fr/docs/Web/API/NodeList/forEach
|
|
||||||
if (window.NodeList && !NodeList.prototype.forEach) {
|
|
||||||
NodeList.prototype.forEach = function(callback, thisArg) {
|
|
||||||
thisArg = thisArg || window;
|
|
||||||
for (var i = 0; i < this.length; i++) {
|
|
||||||
callback.call(thisArg, this[i], i, this);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/* eslint-enable */
|
/* eslint-enable */
|
||||||
|
|
|
@ -76,46 +76,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//IE11 Support
|
|
||||||
@media screen and (max-width: 767px) {
|
|
||||||
table.staff-logs tr {
|
|
||||||
display: -ms-grid;
|
|
||||||
-ms-grid-columns: 1fr 1fr 1fr 0.5fr;
|
|
||||||
-ms-grid-rows: auto auto;
|
|
||||||
td {
|
|
||||||
display: -ms-grid;
|
|
||||||
&.staff-users {
|
|
||||||
-ms-grid-row: 1;
|
|
||||||
-ms-grid-column: 1;
|
|
||||||
-ms-grid-column-span: 2;
|
|
||||||
}
|
|
||||||
&.created-at {
|
|
||||||
-ms-grid-row: 1;
|
|
||||||
-ms-grid-column: 4;
|
|
||||||
}
|
|
||||||
&.action {
|
|
||||||
-ms-grid-row: 2;
|
|
||||||
-ms-grid-column: 1;
|
|
||||||
}
|
|
||||||
&.subject {
|
|
||||||
-ms-grid-row: 2;
|
|
||||||
-ms-grid-column: 2;
|
|
||||||
-ms-grid-column-span: 3;
|
|
||||||
}
|
|
||||||
&.details {
|
|
||||||
-ms-grid-row: 3;
|
|
||||||
-ms-grid-column: 1;
|
|
||||||
-ms-grid-column-span: 3;
|
|
||||||
}
|
|
||||||
&.context {
|
|
||||||
-ms-grid-row: 4;
|
|
||||||
-ms-grid-column: 1;
|
|
||||||
-ms-grid-column-span: 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include breakpoint(mobile-extra-large) {
|
@include breakpoint(mobile-extra-large) {
|
||||||
table.staff-logs tr {
|
table.staff-logs tr {
|
||||||
grid-template-columns: 1fr 1fr 0.5fr;
|
grid-template-columns: 1fr 1fr 0.5fr;
|
||||||
|
|
|
@ -84,7 +84,7 @@ class Plugin::Instance
|
||||||
|
|
||||||
def register_anonymous_cache_key(key, &block)
|
def register_anonymous_cache_key(key, &block)
|
||||||
key_method = "key_#{key}"
|
key_method = "key_#{key}"
|
||||||
add_to_class(Middleware::AnonymousCache, key_method, &block)
|
add_to_class(Middleware::AnonymousCache::Helper, key_method, &block)
|
||||||
Middleware::AnonymousCache.cache_key_segments[key] = key_method
|
Middleware::AnonymousCache.cache_key_segments[key] = key_method
|
||||||
Middleware::AnonymousCache.compile_key_builder
|
Middleware::AnonymousCache.compile_key_builder
|
||||||
end
|
end
|
||||||
|
|
|
@ -72,7 +72,8 @@ class Plugin::Metadata
|
||||||
"discourse-rss-polling",
|
"discourse-rss-polling",
|
||||||
"docker_manager",
|
"docker_manager",
|
||||||
"lazy-yt",
|
"lazy-yt",
|
||||||
"poll"
|
"poll",
|
||||||
|
"discourse-internet-explorer"
|
||||||
])
|
])
|
||||||
|
|
||||||
FIELDS ||= [:name, :about, :version, :authors, :url, :required_version]
|
FIELDS ||= [:name, :about, :version, :authors, :url, :required_version]
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
@media screen and (max-width: 767px) {
|
||||||
|
table.staff-logs tr {
|
||||||
|
display: -ms-grid;
|
||||||
|
-ms-grid-columns: 1fr 1fr 1fr 0.5fr;
|
||||||
|
-ms-grid-rows: auto auto;
|
||||||
|
td {
|
||||||
|
display: -ms-grid;
|
||||||
|
&.staff-users {
|
||||||
|
-ms-grid-row: 1;
|
||||||
|
-ms-grid-column: 1;
|
||||||
|
-ms-grid-column-span: 2;
|
||||||
|
}
|
||||||
|
&.created-at {
|
||||||
|
-ms-grid-row: 1;
|
||||||
|
-ms-grid-column: 4;
|
||||||
|
}
|
||||||
|
&.action {
|
||||||
|
-ms-grid-row: 2;
|
||||||
|
-ms-grid-column: 1;
|
||||||
|
}
|
||||||
|
&.subject {
|
||||||
|
-ms-grid-row: 2;
|
||||||
|
-ms-grid-column: 2;
|
||||||
|
-ms-grid-column-span: 3;
|
||||||
|
}
|
||||||
|
&.details {
|
||||||
|
-ms-grid-row: 3;
|
||||||
|
-ms-grid-column: 1;
|
||||||
|
-ms-grid-column-span: 3;
|
||||||
|
}
|
||||||
|
&.context {
|
||||||
|
-ms-grid-row: 4;
|
||||||
|
-ms-grid-column: 1;
|
||||||
|
-ms-grid-column-span: 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
en:
|
||||||
|
site_settings:
|
||||||
|
discourse_internet_explorer_enabled: "Internet Explorer support"
|
|
@ -0,0 +1,3 @@
|
||||||
|
plugins:
|
||||||
|
discourse_internet_explorer_enabled:
|
||||||
|
default: true
|
|
@ -0,0 +1,57 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# name: discourse-internet-explorer
|
||||||
|
# about: Attempts to provide backward support for internt explorer
|
||||||
|
# version: 1.0
|
||||||
|
# authors: Joffrey Jaffeux, David Taylor, Daniel Waterworth, Robin Ward
|
||||||
|
# url: https://github.com/discourse/discourse/tree/master/plugins/discourse-internet-explorer
|
||||||
|
|
||||||
|
enabled_site_setting :discourse_internet_explorer_enabled
|
||||||
|
hide_plugin if self.respond_to?(:hide_plugin)
|
||||||
|
|
||||||
|
register_asset 'stylesheets/ie.scss'
|
||||||
|
|
||||||
|
after_initialize do
|
||||||
|
|
||||||
|
# Conditionally load the stylesheet. There is unfortunately no easy way to do this via
|
||||||
|
# Plugin API.
|
||||||
|
reloadable_patch do |plugin|
|
||||||
|
ApplicationHelper.module_eval do
|
||||||
|
alias_method :previous_discourse_stylesheet_link_tag, :discourse_stylesheet_link_tag
|
||||||
|
def discourse_stylesheet_link_tag(name, opts = {})
|
||||||
|
|
||||||
|
if name == 'discourse-internet-explorer'
|
||||||
|
return unless SiteSetting.discourse_internet_explorer_enabled?
|
||||||
|
return unless request.env['HTTP_USER_AGENT'] =~ /MSIE|Trident/
|
||||||
|
end
|
||||||
|
|
||||||
|
previous_discourse_stylesheet_link_tag(name, opts)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
register_anonymous_cache_key(:ie) do
|
||||||
|
unless defined?(@is_ie)
|
||||||
|
session = @env[self.class::RACK_SESSION]
|
||||||
|
# don't initialize params until later
|
||||||
|
# otherwise you get a broken params on the request
|
||||||
|
params = {}
|
||||||
|
|
||||||
|
@is_ie = BrowserDetection.browser(@env[self.class::USER_AGENT]) == :ie
|
||||||
|
end
|
||||||
|
|
||||||
|
@is_ie
|
||||||
|
end
|
||||||
|
|
||||||
|
# not using patch on preload_script as js is fine and we need this file
|
||||||
|
# to be loaded before other files
|
||||||
|
register_html_builder('server:before-script-load') do |controller|
|
||||||
|
if BrowserDetection.browser(controller.request.env['HTTP_USER_AGENT']) == :ie
|
||||||
|
path = controller.helpers.script_asset_path('/plugins/discourse-internet-explorer/js/ie')
|
||||||
|
|
||||||
|
<<~JAVASCRIPT
|
||||||
|
<script src="#{path}"></script>
|
||||||
|
JAVASCRIPT
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,347 @@
|
||||||
|
/* eslint-disable */
|
||||||
|
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries
|
||||||
|
if (!Object.values) {
|
||||||
|
Object.values = function(obj) {
|
||||||
|
var ownProps = Object.keys(obj),
|
||||||
|
i = ownProps.length,
|
||||||
|
resArray = new Array(i); // preallocate the Array
|
||||||
|
while (i--) resArray[i] = obj[ownProps[i]];
|
||||||
|
|
||||||
|
return resArray;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://developer.mozilla.org/fr/docs/Web/API/NodeList/forEach
|
||||||
|
if (window.NodeList && !NodeList.prototype.forEach) {
|
||||||
|
NodeList.prototype.forEach = function(callback, thisArg) {
|
||||||
|
thisArg = thisArg || window;
|
||||||
|
for (var i = 0; i < this.length; i++) {
|
||||||
|
callback.call(thisArg, this[i], i, this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes#Polyfill
|
||||||
|
if (!Array.prototype.includes) {
|
||||||
|
Object.defineProperty(Array.prototype, "includes", {
|
||||||
|
value: function(searchElement, fromIndex) {
|
||||||
|
if (this == null) {
|
||||||
|
throw new TypeError('"this" is null or not defined');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Let O be ? ToObject(this value).
|
||||||
|
var o = Object(this);
|
||||||
|
|
||||||
|
// 2. Let len be ? ToLength(? Get(O, "length")).
|
||||||
|
var len = o.length >>> 0;
|
||||||
|
|
||||||
|
// 3. If len is 0, return false.
|
||||||
|
if (len === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Let n be ? ToInteger(fromIndex).
|
||||||
|
// (If fromIndex is undefined, this step produces the value 0.)
|
||||||
|
var n = fromIndex | 0;
|
||||||
|
|
||||||
|
// 5. If n ≥ 0, then
|
||||||
|
// a. Let k be n.
|
||||||
|
// 6. Else n < 0,
|
||||||
|
// a. Let k be len + n.
|
||||||
|
// b. If k < 0, let k be 0.
|
||||||
|
var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
|
||||||
|
|
||||||
|
function sameValueZero(x, y) {
|
||||||
|
return (
|
||||||
|
x === y ||
|
||||||
|
(typeof x === "number" &&
|
||||||
|
typeof y === "number" &&
|
||||||
|
isNaN(x) &&
|
||||||
|
isNaN(y))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. Repeat, while k < len
|
||||||
|
while (k < len) {
|
||||||
|
// a. Let elementK be the result of ? Get(O, ! ToString(k)).
|
||||||
|
// b. If SameValueZero(searchElement, elementK) is true, return true.
|
||||||
|
if (sameValueZero(o[k], searchElement)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// c. Increase k by 1.
|
||||||
|
k++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8. Return false
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes#Polyfill
|
||||||
|
if (!String.prototype.includes) {
|
||||||
|
Object.defineProperty(String.prototype, "includes", {
|
||||||
|
value: function(search, start) {
|
||||||
|
if (typeof start !== "number") {
|
||||||
|
start = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start + search.length > this.length) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return this.indexOf(search, start) !== -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
|
||||||
|
if (!Array.prototype.find) {
|
||||||
|
Object.defineProperty(Array.prototype, "find", {
|
||||||
|
value: function(predicate) {
|
||||||
|
// 1. Let O be ? ToObject(this value).
|
||||||
|
if (this == null) {
|
||||||
|
throw new TypeError('"this" is null or not defined');
|
||||||
|
}
|
||||||
|
|
||||||
|
var o = Object(this);
|
||||||
|
|
||||||
|
// 2. Let len be ? ToLength(? Get(O, "length")).
|
||||||
|
var len = o.length >>> 0;
|
||||||
|
|
||||||
|
// 3. If IsCallable(predicate) is false, throw a TypeError exception.
|
||||||
|
if (typeof predicate !== "function") {
|
||||||
|
throw new TypeError("predicate must be a function");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. If thisArg was supplied, let T be thisArg; else let T be undefined.
|
||||||
|
var thisArg = arguments[1];
|
||||||
|
|
||||||
|
// 5. Let k be 0.
|
||||||
|
var k = 0;
|
||||||
|
|
||||||
|
// 6. Repeat, while k < len
|
||||||
|
while (k < len) {
|
||||||
|
// a. Let Pk be ! ToString(k).
|
||||||
|
// b. Let kValue be ? Get(O, Pk).
|
||||||
|
// c. Let testResult be ToBoolean(? Call(predicate, T, « kValue, k, O »)).
|
||||||
|
// d. If testResult is true, return kValue.
|
||||||
|
var kValue = o[k];
|
||||||
|
if (predicate.call(thisArg, kValue, k, o)) {
|
||||||
|
return kValue;
|
||||||
|
}
|
||||||
|
// e. Increase k by 1.
|
||||||
|
k++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7. Return undefined.
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
configurable: true,
|
||||||
|
writable: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/uxitten/polyfill/blob/master/string.polyfill.js
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart
|
||||||
|
if (!String.prototype.padStart) {
|
||||||
|
String.prototype.padStart = function padStart(targetLength, padString) {
|
||||||
|
targetLength = targetLength >> 0; //truncate if number, or convert non-number to 0;
|
||||||
|
padString = String(typeof padString !== "undefined" ? padString : " ");
|
||||||
|
if (this.length >= targetLength) {
|
||||||
|
return String(this);
|
||||||
|
} else {
|
||||||
|
targetLength = targetLength - this.length;
|
||||||
|
if (targetLength > padString.length) {
|
||||||
|
padString += padString.repeat(targetLength / padString.length); //append to original to ensure we are longer than needed
|
||||||
|
}
|
||||||
|
return padString.slice(0, targetLength) + String(this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://github.com/uxitten/polyfill/blob/master/string.polyfill.js
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padEnd
|
||||||
|
if (!String.prototype.padEnd) {
|
||||||
|
String.prototype.padEnd = function padEnd(targetLength, padString) {
|
||||||
|
targetLength = targetLength >> 0; //floor if number or convert non-number to 0;
|
||||||
|
padString = String(typeof padString !== "undefined" ? padString : " ");
|
||||||
|
if (this.length > targetLength) {
|
||||||
|
return String(this);
|
||||||
|
} else {
|
||||||
|
targetLength = targetLength - this.length;
|
||||||
|
if (targetLength > padString.length) {
|
||||||
|
padString += padString.repeat(targetLength / padString.length); //append to original to ensure we are longer than needed
|
||||||
|
}
|
||||||
|
return String(this) + padString.slice(0, targetLength);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from
|
||||||
|
// Production steps of ECMA-262, Edition 6, 22.1.2.1
|
||||||
|
if (!Array.from) {
|
||||||
|
Array.from = (function() {
|
||||||
|
var toStr = Object.prototype.toString;
|
||||||
|
var isCallable = function(fn) {
|
||||||
|
return typeof fn === "function" || toStr.call(fn) === "[object Function]";
|
||||||
|
};
|
||||||
|
var toInteger = function(value) {
|
||||||
|
var number = Number(value);
|
||||||
|
if (isNaN(number)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (number === 0 || !isFinite(number)) {
|
||||||
|
return number;
|
||||||
|
}
|
||||||
|
return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number));
|
||||||
|
};
|
||||||
|
var maxSafeInteger = Math.pow(2, 53) - 1;
|
||||||
|
var toLength = function(value) {
|
||||||
|
var len = toInteger(value);
|
||||||
|
return Math.min(Math.max(len, 0), maxSafeInteger);
|
||||||
|
};
|
||||||
|
|
||||||
|
// The length property of the from method is 1.
|
||||||
|
return function from(arrayLike /*, mapFn, thisArg */) {
|
||||||
|
// 1. Let C be the this value.
|
||||||
|
var C = this;
|
||||||
|
|
||||||
|
// 2. Let items be ToObject(arrayLike).
|
||||||
|
var items = Object(arrayLike);
|
||||||
|
|
||||||
|
// 3. ReturnIfAbrupt(items).
|
||||||
|
if (arrayLike == null) {
|
||||||
|
throw new TypeError(
|
||||||
|
"Array.from requires an array-like object - not null or undefined"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. If mapfn is undefined, then let mapping be false.
|
||||||
|
var mapFn = arguments.length > 1 ? arguments[1] : void undefined;
|
||||||
|
var T;
|
||||||
|
if (typeof mapFn !== "undefined") {
|
||||||
|
// 5. else
|
||||||
|
// 5. a If IsCallable(mapfn) is false, throw a TypeError exception.
|
||||||
|
if (!isCallable(mapFn)) {
|
||||||
|
throw new TypeError(
|
||||||
|
"Array.from: when provided, the second argument must be a function"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. b. If thisArg was supplied, let T be thisArg; else let T be undefined.
|
||||||
|
if (arguments.length > 2) {
|
||||||
|
T = arguments[2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 10. Let lenValue be Get(items, "length").
|
||||||
|
// 11. Let len be ToLength(lenValue).
|
||||||
|
var len = toLength(items.length);
|
||||||
|
|
||||||
|
// 13. If IsConstructor(C) is true, then
|
||||||
|
// 13. a. Let A be the result of calling the [[Construct]] internal method
|
||||||
|
// of C with an argument list containing the single item len.
|
||||||
|
// 14. a. Else, Let A be ArrayCreate(len).
|
||||||
|
var A = isCallable(C) ? Object(new C(len)) : new Array(len);
|
||||||
|
|
||||||
|
// 16. Let k be 0.
|
||||||
|
var k = 0;
|
||||||
|
// 17. Repeat, while k < len… (also steps a - h)
|
||||||
|
var kValue;
|
||||||
|
while (k < len) {
|
||||||
|
kValue = items[k];
|
||||||
|
if (mapFn) {
|
||||||
|
A[k] =
|
||||||
|
typeof T === "undefined"
|
||||||
|
? mapFn(kValue, k)
|
||||||
|
: mapFn.call(T, kValue, k);
|
||||||
|
} else {
|
||||||
|
A[k] = kValue;
|
||||||
|
}
|
||||||
|
k += 1;
|
||||||
|
}
|
||||||
|
// 18. Let putStatus be Put(A, "length", len, true).
|
||||||
|
A.length = len;
|
||||||
|
// 20. Return A.
|
||||||
|
return A;
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
|
||||||
|
if (typeof Object.assign !== "function") {
|
||||||
|
// Must be writable: true, enumerable: false, configurable: true
|
||||||
|
Object.defineProperty(Object, "assign", {
|
||||||
|
value: function assign(target) {
|
||||||
|
// .length of function is 2
|
||||||
|
"use strict";
|
||||||
|
if (target == null) {
|
||||||
|
// TypeError if undefined or null
|
||||||
|
throw new TypeError("Cannot convert undefined or null to object");
|
||||||
|
}
|
||||||
|
|
||||||
|
var to = Object(target);
|
||||||
|
|
||||||
|
for (var index = 1; index < arguments.length; index++) {
|
||||||
|
var nextSource = arguments[index];
|
||||||
|
|
||||||
|
if (nextSource != null) {
|
||||||
|
// Skip over if undefined or null
|
||||||
|
for (var nextKey in nextSource) {
|
||||||
|
// Avoid bugs when hasOwnProperty is shadowed
|
||||||
|
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
|
||||||
|
to[nextKey] = nextSource[nextKey];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return to;
|
||||||
|
},
|
||||||
|
writable: true,
|
||||||
|
configurable: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat#Polyfill
|
||||||
|
if (!String.prototype.repeat) {
|
||||||
|
String.prototype.repeat = function(count) {
|
||||||
|
"use strict";
|
||||||
|
if (this == null)
|
||||||
|
throw new TypeError("can't convert " + this + " to object");
|
||||||
|
|
||||||
|
var str = "" + this;
|
||||||
|
// To convert string to integer.
|
||||||
|
count = +count;
|
||||||
|
// Check NaN
|
||||||
|
if (count != count) count = 0;
|
||||||
|
|
||||||
|
if (count < 0) throw new RangeError("repeat count must be non-negative");
|
||||||
|
|
||||||
|
if (count == Infinity)
|
||||||
|
throw new RangeError("repeat count must be less than infinity");
|
||||||
|
|
||||||
|
count = Math.floor(count);
|
||||||
|
if (str.length == 0 || count == 0) return "";
|
||||||
|
|
||||||
|
// Ensuring count is a 31-bit integer allows us to heavily optimize the
|
||||||
|
// main part. But anyway, most current (August 2014) browsers can't handle
|
||||||
|
// strings 1 << 28 chars or longer, so:
|
||||||
|
if (str.length * count >= 1 << 28)
|
||||||
|
throw new RangeError(
|
||||||
|
"repeat count must not overflow maximum string size"
|
||||||
|
);
|
||||||
|
|
||||||
|
var maxCount = str.length * count;
|
||||||
|
count = Math.floor(Math.log(count) / Math.log(2));
|
||||||
|
while (count) {
|
||||||
|
str += str;
|
||||||
|
count--;
|
||||||
|
}
|
||||||
|
str += str.substring(0, maxCount - str.length);
|
||||||
|
return str;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* eslint-enable */
|
|
@ -0,0 +1,26 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require "rails_helper"
|
||||||
|
|
||||||
|
describe Middleware::AnonymousCache::Helper do
|
||||||
|
def env(opts = {})
|
||||||
|
{
|
||||||
|
"HTTP_HOST" => "http://test.com",
|
||||||
|
"REQUEST_URI" => "/path?bla=1",
|
||||||
|
"REQUEST_METHOD" => "GET",
|
||||||
|
"rack.input" => ""
|
||||||
|
}.merge(opts)
|
||||||
|
end
|
||||||
|
|
||||||
|
def new_helper(opts = {})
|
||||||
|
Middleware::AnonymousCache::Helper.new(env(opts))
|
||||||
|
end
|
||||||
|
|
||||||
|
it "includes ie in cache key" do
|
||||||
|
helper = new_helper
|
||||||
|
expect(helper.cache_key).to include("ie=false")
|
||||||
|
|
||||||
|
helper = new_helper("HTTP_USER_AGENT" => "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko")
|
||||||
|
expect(helper.cache_key).to include("ie=true")
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,37 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
describe 'Bootstrapping the Discourse App' do
|
||||||
|
let(:ie_agent) { "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko" }
|
||||||
|
|
||||||
|
context "when disabled" do
|
||||||
|
before do
|
||||||
|
SiteSetting.discourse_internet_explorer_enabled = false
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not include the IE stylesheet or Javascript" do
|
||||||
|
get "/categories", headers: { "HTTP_USER_AGENT" => ie_agent }
|
||||||
|
expect(response.body).not_to match(/discourse-internet-explorer\/js\/ie.js/)
|
||||||
|
expect(response.body).not_to match(/stylesheets\/discourse-internet-explorer/)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when enabled" do
|
||||||
|
before do
|
||||||
|
SiteSetting.discourse_internet_explorer_enabled = true
|
||||||
|
end
|
||||||
|
|
||||||
|
it "includes the IE js and css" do
|
||||||
|
get "/categories", headers: { "HTTP_USER_AGENT" => ie_agent }
|
||||||
|
expect(response.body).to match(/discourse-internet-explorer\/js\/ie.js/)
|
||||||
|
expect(response.body).to match(/stylesheets\/discourse-internet-explorer/)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't include IE stuff for non-IE browsers" do
|
||||||
|
get "/categories", headers: { "HTTP_USER_AGENT" => "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36" }
|
||||||
|
expect(response.body).not_to match(/discourse-internet-explorer\/js\/ie.js/)
|
||||||
|
expect(response.body).not_to match(/stylesheets\/discourse-internet-explorer/)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue