diff --git a/app/assets/javascripts/discourse/app/lib/tracked-media-query.js b/app/assets/javascripts/discourse/app/lib/tracked-media-query.js new file mode 100644 index 00000000000..76143440069 --- /dev/null +++ b/app/assets/javascripts/discourse/app/lib/tracked-media-query.js @@ -0,0 +1,20 @@ +import { tracked } from "@glimmer/tracking"; + +export default class TrackedMediaQuery { + @tracked matches; + #matcher; + + #handleChange = () => { + this.matches = this.#matcher.matches; + }; + + constructor(query) { + this.#matcher = window.matchMedia(query); + this.#matcher.addEventListener("change", this.#handleChange); + this.matches = this.#matcher.matches; + } + + teardown() { + this.#matcher.removeEventListener("change", this.#handleChange); + } +} diff --git a/app/assets/javascripts/discourse/app/services/capabilities.js b/app/assets/javascripts/discourse/app/services/capabilities.js index 109c2001e57..1fc6b4fdcb9 100644 --- a/app/assets/javascripts/discourse/app/services/capabilities.js +++ b/app/assets/javascripts/discourse/app/services/capabilities.js @@ -1,8 +1,19 @@ +import TrackedMediaQuery from "discourse/lib/tracked-media-query"; + const APPLE_NAVIGATOR_PLATFORMS = /iPhone|iPod|iPad|Macintosh|MacIntel/; const APPLE_USER_AGENT_DATA_PLATFORM = /macOS/; const ua = navigator.userAgent; +// Values match those in viewport.scss +const breakpointQueries = { + sm: new TrackedMediaQuery("(min-width: 40rem)"), + md: new TrackedMediaQuery("(min-width: 48rem)"), + lg: new TrackedMediaQuery("(min-width: 64rem)"), + xl: new TrackedMediaQuery("(min-width: 80rem)"), + "2xl": new TrackedMediaQuery("(min-width: 96rem)"), +}; + class Capabilities { touch = navigator.maxTouchPoints > 1 || "ontouchstart" in window; @@ -37,6 +48,24 @@ class Capabilities { window.location.search.includes("discourse_app=1"); isAppWebview = window.ReactNativeWebView !== undefined; + viewport = { + get sm() { + return breakpointQueries.sm.matches; + }, + get md() { + return breakpointQueries.md.matches; + }, + get lg() { + return breakpointQueries.lg.matches; + }, + get xl() { + return breakpointQueries.xl.matches; + }, + get "2xl"() { + return breakpointQueries["2xl"].matches; + }, + }; + get userHasBeenActive() { return ( !("userActivation" in navigator) || navigator.userActivation.hasBeenActive diff --git a/app/assets/javascripts/discourse/tests/setup-tests.js b/app/assets/javascripts/discourse/tests/setup-tests.js index 60cdc9cfdc5..c791ea28ad0 100644 --- a/app/assets/javascripts/discourse/tests/setup-tests.js +++ b/app/assets/javascripts/discourse/tests/setup-tests.js @@ -1,6 +1,7 @@ /* eslint-disable simple-import-sort/imports */ import Application from "../app"; import "./loader-shims"; +import "discourse/static/markdown-it"; /* eslint-enable simple-import-sort/imports */ import { getOwner } from "@ember/owner"; diff --git a/app/assets/javascripts/discourse/tests/test-helper.js b/app/assets/javascripts/discourse/tests/test-helper.js index d24afddcf77..6f3b3b60a7a 100644 --- a/app/assets/javascripts/discourse/tests/test-helper.js +++ b/app/assets/javascripts/discourse/tests/test-helper.js @@ -1 +1 @@ -import "discourse/static/markdown-it"; +// We don't currently use this file, but it is require'd by ember-cli's test bundle diff --git a/spec/system/capabilities_service_spec.rb b/spec/system/capabilities_service_spec.rb new file mode 100644 index 00000000000..3f6c5544cc6 --- /dev/null +++ b/spec/system/capabilities_service_spec.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +describe "capabilities service", type: :system do + describe "viewport helpers" do + it "works" do + def matches(name) + page.evaluate_script("Discourse.lookup('service:capabilities').viewport[#{name.to_json}]") + end + + visit "/" + expect(page).to have_css("#site-logo") + + expect(matches("sm")).to eq(true) + expect(matches("lg")).to eq(true) + + resize_window(width: 700) do + try_until_success do + expect(matches("sm")).to eq(true) + expect(matches("lg")).to eq(false) + end + end + end + end +end