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