diff --git a/README.md b/README.md index 0cbfcd4f974..2b914eab914 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,7 @@ Discourse supports the **latest, stable releases** of all major browsers and pla | Microsoft Edge | | | | Mozilla Firefox | | | -Additionally, we aim to support Safari on iOS 12.5+ until January 2023 (Discourse 3.0). +Additionally, we aim to support Safari on iOS 15.7+. ## Built With diff --git a/app/assets/javascripts/discourse/app/initializers/safari-13-deprecation.js b/app/assets/javascripts/discourse/app/initializers/safari-13-deprecation.js deleted file mode 100644 index 521cd6c0a38..00000000000 --- a/app/assets/javascripts/discourse/app/initializers/safari-13-deprecation.js +++ /dev/null @@ -1,30 +0,0 @@ -import I18n from "I18n"; -import { withPluginApi } from "discourse/lib/plugin-api"; - -function setupMessage(api) { - const isSafari = navigator.vendor === "Apple Computer, Inc."; - if (!isSafari) { - return; - } - - let safariMajorVersion = navigator.userAgent.match(/Version\/(\d+)\./)?.[1]; - safariMajorVersion = safariMajorVersion - ? parseInt(safariMajorVersion, 10) - : null; - - if (safariMajorVersion && safariMajorVersion <= 13) { - api.addGlobalNotice( - I18n.t("safari_13_warning"), - "browser-deprecation-warning", - { dismissable: true, dismissDuration: moment.duration(1, "week") } - ); - } -} - -export default { - name: "safari-13-deprecation", - - initialize() { - withPluginApi("0.8.37", setupMessage); - }, -}; diff --git a/app/assets/javascripts/discourse/config/targets.js b/app/assets/javascripts/discourse/config/targets.js index 13213df3789..09210acbf6b 100644 --- a/app/assets/javascripts/discourse/config/targets.js +++ b/app/assets/javascripts/discourse/config/targets.js @@ -10,8 +10,7 @@ const browsers = [ ]; if (isCI || isProduction) { - // https://meta.discourse.org/t/224747 - browsers.push("Safari 12"); + browsers.push("Safari 15"); } module.exports = { diff --git a/app/assets/javascripts/discourse/scripts/browser-detect.js b/app/assets/javascripts/discourse/scripts/browser-detect.js index 86cce506a88..b205e684ebb 100644 --- a/app/assets/javascripts/discourse/scripts/browser-detect.js +++ b/app/assets/javascripts/discourse/scripts/browser-detect.js @@ -1,7 +1,14 @@ /* eslint-disable no-var */ // `let` is not supported in very old browsers (function () { - if (!window.WeakMap || !window.Promise || typeof globalThis === "undefined") { + if ( + !window.WeakMap || + !window.Promise || + typeof globalThis === "undefined" || + !String.prototype.replaceAll || + !CSS.supports || + !CSS.supports("aspect-ratio: 1") + ) { window.unsupportedBrowser = true; } else { // Some implementations of `WeakMap.prototype.has` do not accept false diff --git a/app/assets/javascripts/polyfills.js b/app/assets/javascripts/polyfills.js index 381b769e394..c5c0caaa899 100644 --- a/app/assets/javascripts/polyfills.js +++ b/app/assets/javascripts/polyfills.js @@ -1,325 +1,5 @@ /* eslint-disable */ -// Needed for iOS <= 13.3 -if (!String.prototype.replaceAll) { - String.prototype.replaceAll = function ( - pattern, - replacementStringOrFunction - ) { - let regex; - - if ( - Object.prototype.toString.call(pattern).toLowerCase() === - "[object regexp]" - ) { - regex = pattern; - } else { - const escapedStr = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - regex = new RegExp(escapedStr, "g"); - } - - return this.replace(regex, replacementStringOrFunction); - }; -} - -// Needed for Safari 15.2 and below -// from: https://github.com/iamdustan/smoothscroll -(function () { - "use strict"; - - function e() { - var e = window; - var t = document; - if ( - "scrollBehavior" in t.documentElement.style && - e.__forceSmoothScrollPolyfill__ !== true - ) { - return; - } - var o = e.HTMLElement || e.Element; - var r = 1.8; - var l = { - scroll: e.scroll || e.scrollTo, - scrollBy: e.scrollBy, - elementScroll: o.prototype.scroll || s, - scrollIntoView: o.prototype.scrollIntoView, - }; - var n = - e.performance && e.performance.now - ? e.performance.now.bind(e.performance) - : Date.now; - - function i(e) { - var t = ["MSIE ", "Trident/", "Edge/"]; - return new RegExp(t.join("|")).test(e); - } - var f = i(e.navigator.userAgent) ? 1 : 0; - - function s(e, t) { - this.scrollLeft = e; - this.scrollTop = t; - } - - function c(e) { - return 0.5 * (1 - Math.cos(Math.PI * e)); - } - - function a(e) { - if ( - e === null || - typeof e !== "object" || - e.behavior === undefined || - e.behavior === "auto" || - e.behavior === "instant" - ) { - return true; - } - if (typeof e === "object" && e.behavior === "smooth") { - return false; - } - throw new TypeError( - "behavior member of ScrollOptions " + - e.behavior + - " is not a valid value for enumeration ScrollBehavior." - ); - } - - function u(e, t) { - if (t === "Y") { - return e.clientHeight + f < e.scrollHeight; - } - if (t === "X") { - return e.clientWidth + f < e.scrollWidth; - } - } - - function d(t, o) { - var r = e.getComputedStyle(t, null)["overflow" + o]; - return r === "auto" || r === "scroll"; - } - - function p(e) { - var t = u(e, "Y") && d(e, "Y"); - var o = u(e, "X") && d(e, "X"); - return t || o; - } - - function h(e) { - while (e !== t.body && p(e) === false) { - e = e.parentNode || e.host; - } - return e; - } - - function v(e, t) { - var o = r / t; - var l = Math.pow(1.16, Math.max(e / 80, 1)); - return o * e * (1 / l); - } - - function y(t) { - var o = n(); - var r = e.devicePixelRatio; - var l; - var i; - var f; - var s; - var a = v(Math.abs(t.x - t.startX), r); - var u = v(Math.abs(t.y - t.startY), r); - var d = (o - t.startTime) / a; - var p = (o - t.startTime) / u; - d = d > 1 ? 1 : d; - p = p > 1 ? 1 : p; - l = c(d); - i = c(p); - f = t.startX + (t.x - t.startX) * l; - s = t.startY + (t.y - t.startY) * i; - t.method.call(t.scrollable, f, s); - if (f !== t.x || s !== t.y) { - e.requestAnimationFrame(y.bind(e, t)); - } - } - - function m(o, r, i) { - var f; - var c; - var a; - var u; - var d = n(); - if (o === t.body) { - f = e; - c = e.scrollX || e.pageXOffset; - a = e.scrollY || e.pageYOffset; - u = l.scroll; - } else { - f = o; - c = o.scrollLeft; - a = o.scrollTop; - u = s; - } - y({ - scrollable: f, - method: u, - startTime: d, - startX: c, - startY: a, - x: r, - y: i, - }); - } - e.scroll = e.scrollTo = function () { - if (arguments[0] === undefined) { - return; - } - if (a(arguments[0]) === true) { - l.scroll.call( - e, - arguments[0].left !== undefined - ? arguments[0].left - : typeof arguments[0] !== "object" - ? arguments[0] - : e.scrollX || e.pageXOffset, - arguments[0].top !== undefined - ? arguments[0].top - : arguments[1] !== undefined - ? arguments[1] - : e.scrollY || e.pageYOffset - ); - return; - } - m.call( - e, - t.body, - arguments[0].left !== undefined - ? ~~arguments[0].left - : e.scrollX || e.pageXOffset, - arguments[0].top !== undefined - ? ~~arguments[0].top - : e.scrollY || e.pageYOffset - ); - }; - e.scrollBy = function () { - if (arguments[0] === undefined) { - return; - } - if (a(arguments[0])) { - l.scrollBy.call( - e, - arguments[0].left !== undefined - ? arguments[0].left - : typeof arguments[0] !== "object" - ? arguments[0] - : 0, - arguments[0].top !== undefined - ? arguments[0].top - : arguments[1] !== undefined - ? arguments[1] - : 0 - ); - return; - } - m.call( - e, - t.body, - ~~arguments[0].left + (e.scrollX || e.pageXOffset), - ~~arguments[0].top + (e.scrollY || e.pageYOffset) - ); - }; - o.prototype.scroll = o.prototype.scrollTo = function () { - if (arguments[0] === undefined) { - return; - } - if (a(arguments[0]) === true) { - if (typeof arguments[0] === "number" && arguments[1] === undefined) { - throw new SyntaxError("Value could not be converted"); - } - l.elementScroll.call( - this, - arguments[0].left !== undefined - ? ~~arguments[0].left - : typeof arguments[0] !== "object" - ? ~~arguments[0] - : this.scrollLeft, - arguments[0].top !== undefined - ? ~~arguments[0].top - : arguments[1] !== undefined - ? ~~arguments[1] - : this.scrollTop - ); - return; - } - var e = arguments[0].left; - var t = arguments[0].top; - m.call( - this, - this, - typeof e === "undefined" ? this.scrollLeft : ~~e, - typeof t === "undefined" ? this.scrollTop : ~~t - ); - }; - o.prototype.scrollBy = function () { - if (arguments[0] === undefined) { - return; - } - if (a(arguments[0]) === true) { - l.elementScroll.call( - this, - arguments[0].left !== undefined - ? ~~arguments[0].left + this.scrollLeft - : ~~arguments[0] + this.scrollLeft, - arguments[0].top !== undefined - ? ~~arguments[0].top + this.scrollTop - : ~~arguments[1] + this.scrollTop - ); - return; - } - this.scroll({ - left: ~~arguments[0].left + this.scrollLeft, - top: ~~arguments[0].top + this.scrollTop, - behavior: arguments[0].behavior, - }); - }; - o.prototype.scrollIntoView = function () { - if (a(arguments[0]) === true) { - l.scrollIntoView.call( - this, - arguments[0] === undefined ? true : arguments[0] - ); - return; - } - var o = h(this); - var r = o.getBoundingClientRect(); - var n = this.getBoundingClientRect(); - if (o !== t.body) { - m.call( - this, - o, - o.scrollLeft + n.left - r.left, - o.scrollTop + n.top - r.top - ); - if (e.getComputedStyle(o).position !== "fixed") { - e.scrollBy({ - left: r.left, - top: r.top, - behavior: "smooth", - }); - } - } else { - e.scrollBy({ - left: n.left, - top: n.top, - behavior: "smooth", - }); - } - }; - } - if (typeof exports === "object" && typeof module !== "undefined") { - module.exports = { - polyfill: e, - }; - } else { - e(); - } -})(); +// Polyfills for old browsers can be added here /* eslint-enable */ diff --git a/app/models/theme.rb b/app/models/theme.rb index 23701642e55..6b3b2ee7508 100644 --- a/app/models/theme.rb +++ b/app/models/theme.rb @@ -6,7 +6,7 @@ require "json_schemer" class Theme < ActiveRecord::Base include GlobalPath - BASE_COMPILER_VERSION = 69 + BASE_COMPILER_VERSION = 70 attr_accessor :child_components diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 0bd807d42d7..308b01f608f 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -3987,8 +3987,6 @@ en: browser_update: 'Unfortunately, your browser is unsupported. Please switch to a supported browser to view rich content, log in and reply.' - safari_13_warning: This site will soon remove support for iOS and Safari versions 13 and below. A simplified read-only version will remain available. (more information) - permission_types: full: "Create / Reply / See" create_post: "Reply / See" diff --git a/lib/discourse_js_processor.rb b/lib/discourse_js_processor.rb index ae167ffe57f..74167a7ec4b 100644 --- a/lib/discourse_js_processor.rb +++ b/lib/discourse_js_processor.rb @@ -6,22 +6,12 @@ class DiscourseJsProcessor class TranspileError < StandardError end + # To generate a list of babel plugins used by ember-cli, set + # babel: { debug: true } in ember-cli-build.js, then run `yarn ember build -prod` DISCOURSE_COMMON_BABEL_PLUGINS = [ - "proposal-optional-chaining", ["proposal-decorators", { legacy: true }], - "transform-template-literals", - "proposal-class-properties", "proposal-class-static-block", - "proposal-private-property-in-object", - "proposal-private-methods", - "proposal-numeric-separator", - "proposal-logical-assignment-operators", - "proposal-nullish-coalescing-operator", - "proposal-json-strings", - "proposal-optional-catch-binding", "transform-parameters", - "proposal-async-generator-functions", - "proposal-object-rest-spread", "proposal-export-namespace-from", ] diff --git a/lib/mobile_detection.rb b/lib/mobile_detection.rb index 1ff3361a15d..b92523059e1 100644 --- a/lib/mobile_detection.rb +++ b/lib/mobile_detection.rb @@ -26,8 +26,8 @@ module MobileDetection MODERN_MOBILE_REGEX = %r{ - \(.*iPhone\ OS\ 1[3-9].*\)| - \(.*iPad.*OS\ 1[3-9].*\)| + \(.*iPhone\ OS\ 1[5-9].*\)| + \(.*iPad.*OS\ 1[5-9].*\)| Chrome\/8[89]| Chrome\/9[0-9]| Chrome\/1[0-9][0-9]| diff --git a/spec/lib/discourse_js_processor_spec.rb b/spec/lib/discourse_js_processor_spec.rb index 3c2eeac8033..b23dc86536c 100644 --- a/spec/lib/discourse_js_processor_spec.rb +++ b/spec/lib/discourse_js_processor_spec.rb @@ -37,6 +37,45 @@ RSpec.describe DiscourseJsProcessor do end end + it "passes through modern JS syntaxes which are supported in our target browsers" do + script = <<~JS.chomp + optional?.chaining; + const template = func`test`; + class MyClass { + classProperty = 1; + #privateProperty = 1; + #privateMethod() { + console.log("hello world"); + } + } + let numericSeparator = 100_000_000; + logicalAssignment ||= 2; + nullishCoalescing ?? 'works'; + try { + "optional catch binding"; + } catch { + "works"; + } + async function* asyncGeneratorFunction() { + yield await Promise.resolve('a'); + } + let a = { + x, + y, + ...spreadRest + }; + JS + + result = DiscourseJsProcessor.transpile(script, "blah", "blah/mymodule") + expect(result).to eq <<~JS.strip + define("blah/mymodule", [], function () { + "use strict"; + + #{script.indent(2)} + }); + JS + end + it "correctly transpiles widget hbs" do result = DiscourseJsProcessor.transpile(<<~JS, "blah", "blah/mymodule") import hbs from "discourse/widgets/hbs-compiler"; diff --git a/spec/lib/mobile_detection_spec.rb b/spec/lib/mobile_detection_spec.rb index 853fe24b1be..584e20ebda6 100644 --- a/spec/lib/mobile_detection_spec.rb +++ b/spec/lib/mobile_detection_spec.rb @@ -10,6 +10,8 @@ RSpec.describe MobileDetection do Mozilla/5.0 (iPhone; CPU iPhone OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5376e Safari/8536.25 Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Mobile Safari/537.36 (comp Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.3626.121 Mobile Safari/537.36 (comp + Mozilla/5.0 (iPhone; CPU iPhone OS 14_8_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.2 Mobile/15E148 Safari/604.1 + Mozilla/5.0 (iPhone; CPU iPhone OS 14_8 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/99.0.4844.59 Mobile/15E148 Safari/604.1 STR end @@ -17,7 +19,6 @@ RSpec.describe MobileDetection do (<<~STR).split("\n") Mozilla/5.0 (iPhone; CPU iPhone OS 15_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.3 Mobile/15E148 Safari/604.1 Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.84 Mobile Safari/537.36 (compatible; - Mozilla/5.0 (iPhone; CPU iPhone OS 14_8_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.2 Mobile/15E148 Safari/604.1 Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.60 Mobile Safari/537.36 (compatible Mozilla/5.0 (Android 12; Mobile; rv:98.0) Gecko/98.0 Firefox/98.0 Mozilla/5.0 (iPad; CPU OS 15_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/99.0.4844.59 Mobile/15E148 Safari/604.1 @@ -26,7 +27,6 @@ RSpec.describe MobileDetection do Mozilla/5.0 (iPhone; CPU iPhone OS 15_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 DiscourseHub Mozilla/5.0 (Linux; Android 10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.88 Mobile Safari/537.36 Mozilla/5.0 (Android 12; Mobile; rv:99.0) Gecko/99.0 Firefox/99.0 -Mozilla/5.0 (iPhone; CPU iPhone OS 14_8 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/99.0.4844.59 Mobile/15E148 Safari/604.1 Mozilla/5.0 (Linux; Android 12; Pixel 6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.88 Mobile Safari/537.36 Mozilla/5.0 (Linux; Android 12; Pixel 4a) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.88 Mobile Safari/537.36 Mozilla/5.0 (Linux; Android 10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.60 Mobile Safari/537.36 @@ -36,11 +36,17 @@ Mozilla/5.0 (iPhone; CPU iPhone OS 14_8 like Mac OS X) AppleWebKit/605.1.15 (KHT it "detects modern browsers correctly" do modern_user_agents.each do |agent| - expect(MobileDetection.modern_mobile_device?(agent)).to eq(true) + expect(MobileDetection.modern_mobile_device?(agent)).to( + eq(true), + "Failed User Agent: '#{agent}'", + ) end old_user_agents.each do |agent| - expect(MobileDetection.modern_mobile_device?(agent)).to eq(false) + expect(MobileDetection.modern_mobile_device?(agent)).to( + eq(false), + "Failed User Agent: '#{agent}'", + ) end end end