From 4abe4454dda59f29f621d5cee20a8057d2001ab8 Mon Sep 17 00:00:00 2001 From: Robin Ward Date: Wed, 11 Dec 2019 09:07:22 -0500 Subject: [PATCH] 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 --- .gitignore | 1 + app/assets/javascripts/polyfills.js | 357 +----------------- .../stylesheets/common/admin/staff_logs.scss | 40 -- lib/plugin/instance.rb | 2 +- lib/plugin/metadata.rb | 3 +- .../assets/stylesheets/ie.scss | 38 ++ .../config/locales/server.en.yml | 3 + .../config/settings.yml | 3 + plugins/discourse-internet-explorer/plugin.rb | 57 +++ .../public/js/ie.js | 347 +++++++++++++++++ .../spec/middleware/anonymous_cache_spec.rb | 26 ++ .../spec/requests/bootstrap_request_spec.rb | 37 ++ 12 files changed, 517 insertions(+), 397 deletions(-) create mode 100644 plugins/discourse-internet-explorer/assets/stylesheets/ie.scss create mode 100644 plugins/discourse-internet-explorer/config/locales/server.en.yml create mode 100644 plugins/discourse-internet-explorer/config/settings.yml create mode 100644 plugins/discourse-internet-explorer/plugin.rb create mode 100644 plugins/discourse-internet-explorer/public/js/ie.js create mode 100644 plugins/discourse-internet-explorer/spec/middleware/anonymous_cache_spec.rb create mode 100644 plugins/discourse-internet-explorer/spec/requests/bootstrap_request_spec.rb diff --git a/.gitignore b/.gitignore index 328093d7a09..5bc058976e7 100644 --- a/.gitignore +++ b/.gitignore @@ -53,6 +53,7 @@ bootsnap-compile-cache/ !/plugins/discourse-narrative-bot !/plugins/discourse-presence !/plugins/discourse-local-dates +!/plugins/discourse-internet-explorer /plugins/*/auto_generated/ /spec/fixtures/plugins/my_plugin/auto_generated diff --git a/app/assets/javascripts/polyfills.js b/app/assets/javascripts/polyfills.js index 9d2b88d162d..ae960cdea53 100644 --- a/app/assets/javascripts/polyfills.js +++ b/app/assets/javascripts/polyfills.js @@ -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 */ -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://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 - }); -} +// Any IE only polyfill should be moved in discourse-internet-explorer plugin // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/flags#Polyfill +// IE and EDGE if (RegExp.prototype.flags === undefined) { Object.defineProperty(RegExp.prototype, "flags", { 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 */ diff --git a/app/assets/stylesheets/common/admin/staff_logs.scss b/app/assets/stylesheets/common/admin/staff_logs.scss index 058fcb4543d..c6839df88d1 100644 --- a/app/assets/stylesheets/common/admin/staff_logs.scss +++ b/app/assets/stylesheets/common/admin/staff_logs.scss @@ -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) { table.staff-logs tr { grid-template-columns: 1fr 1fr 0.5fr; diff --git a/lib/plugin/instance.rb b/lib/plugin/instance.rb index 92db214d798..8723cf2d9f1 100644 --- a/lib/plugin/instance.rb +++ b/lib/plugin/instance.rb @@ -84,7 +84,7 @@ class Plugin::Instance def register_anonymous_cache_key(key, &block) 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.compile_key_builder end diff --git a/lib/plugin/metadata.rb b/lib/plugin/metadata.rb index da74e43a11c..c626d711ca2 100644 --- a/lib/plugin/metadata.rb +++ b/lib/plugin/metadata.rb @@ -72,7 +72,8 @@ class Plugin::Metadata "discourse-rss-polling", "docker_manager", "lazy-yt", - "poll" + "poll", + "discourse-internet-explorer" ]) FIELDS ||= [:name, :about, :version, :authors, :url, :required_version] diff --git a/plugins/discourse-internet-explorer/assets/stylesheets/ie.scss b/plugins/discourse-internet-explorer/assets/stylesheets/ie.scss new file mode 100644 index 00000000000..23b125e2191 --- /dev/null +++ b/plugins/discourse-internet-explorer/assets/stylesheets/ie.scss @@ -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; + } + } + } +} diff --git a/plugins/discourse-internet-explorer/config/locales/server.en.yml b/plugins/discourse-internet-explorer/config/locales/server.en.yml new file mode 100644 index 00000000000..935cbc4b981 --- /dev/null +++ b/plugins/discourse-internet-explorer/config/locales/server.en.yml @@ -0,0 +1,3 @@ +en: + site_settings: + discourse_internet_explorer_enabled: "Internet Explorer support" diff --git a/plugins/discourse-internet-explorer/config/settings.yml b/plugins/discourse-internet-explorer/config/settings.yml new file mode 100644 index 00000000000..23f2be3a202 --- /dev/null +++ b/plugins/discourse-internet-explorer/config/settings.yml @@ -0,0 +1,3 @@ +plugins: + discourse_internet_explorer_enabled: + default: true diff --git a/plugins/discourse-internet-explorer/plugin.rb b/plugins/discourse-internet-explorer/plugin.rb new file mode 100644 index 00000000000..5f80a79ad3b --- /dev/null +++ b/plugins/discourse-internet-explorer/plugin.rb @@ -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 + + JAVASCRIPT + end + end +end diff --git a/plugins/discourse-internet-explorer/public/js/ie.js b/plugins/discourse-internet-explorer/public/js/ie.js new file mode 100644 index 00000000000..5d252b97064 --- /dev/null +++ b/plugins/discourse-internet-explorer/public/js/ie.js @@ -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 */ diff --git a/plugins/discourse-internet-explorer/spec/middleware/anonymous_cache_spec.rb b/plugins/discourse-internet-explorer/spec/middleware/anonymous_cache_spec.rb new file mode 100644 index 00000000000..ccd725e9dac --- /dev/null +++ b/plugins/discourse-internet-explorer/spec/middleware/anonymous_cache_spec.rb @@ -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 diff --git a/plugins/discourse-internet-explorer/spec/requests/bootstrap_request_spec.rb b/plugins/discourse-internet-explorer/spec/requests/bootstrap_request_spec.rb new file mode 100644 index 00000000000..bf31a251c2b --- /dev/null +++ b/plugins/discourse-internet-explorer/spec/requests/bootstrap_request_spec.rb @@ -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