@mdn/fred 2.4.3 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/.nvmrc +1 -1
  2. package/CHANGELOG.md +48 -0
  3. package/README.md +2 -0
  4. package/components/a11y-menu/server.js +2 -2
  5. package/components/baseline-indicator/server.js +86 -32
  6. package/components/breadcrumbs/server.js +6 -1
  7. package/components/collection-save-button/element.js +15 -0
  8. package/components/color-theme/element.js +17 -1
  9. package/components/compat-section/server.js +45 -0
  10. package/components/compat-table/element.js +10 -1
  11. package/components/compat-table/utils.js +10 -0
  12. package/components/compat-table-lazy/element.js +2 -5
  13. package/components/content-section/server.css +5 -0
  14. package/components/content-section/server.js +4 -22
  15. package/components/curriculum/sidebar.css +1 -1
  16. package/components/curriculum-landing/server.js +4 -1
  17. package/components/dropdown/element.js +12 -1
  18. package/components/env/index.js +1 -1
  19. package/components/featured-articles/server.js +10 -3
  20. package/components/footer/server.js +6 -1
  21. package/components/generic-about/server.js +1 -0
  22. package/components/generic-toc/server.js +6 -1
  23. package/components/language-always-redirect-button/element.js +2 -0
  24. package/components/language-switcher/element.js +5 -0
  25. package/components/latest-news/server.js +10 -3
  26. package/components/left-sidebar/server.css +1 -1
  27. package/components/login-button/element.js +1 -1
  28. package/components/menu/server.js +3 -1
  29. package/components/observatory-form/element.js +3 -0
  30. package/components/observatory-results/element.js +6 -1
  31. package/components/observatory-results/rating.js +6 -1
  32. package/components/outer-layout/server.js +3 -3
  33. package/components/placement-top/element.js +16 -4
  34. package/components/playground/element.js +17 -4
  35. package/components/recent-contributions/server.js +4 -1
  36. package/components/reference-toc/server.js +5 -1
  37. package/components/scrim-inline/element.js +13 -1
  38. package/components/server/index.js +4 -0
  39. package/components/sidebar-filter/element.js +11 -0
  40. package/components/sidebar-filter/sidebar-filterer.js +4 -2
  41. package/components/specifications-list/server.js +72 -0
  42. package/components/translation-banner/server.js +4 -1
  43. package/components/user-menu/element.js +1 -0
  44. package/components/viewed-controller/viewed-controller.js +15 -59
  45. package/components/viewed-controller/viewed-observer.js +79 -0
  46. package/hooks/glean-init.js +49 -6
  47. package/l10n/locales/zh-CN.ftl +317 -1
  48. package/out/service-worker.js +1 -1
  49. package/out/static/client/{2173.c74593faf7e14cbc.js → 2173.a662dd40657c8426.js} +2 -2
  50. package/out/static/client/{2173.c74593faf7e14cbc.js.map → 2173.a662dd40657c8426.js.map} +1 -1
  51. package/out/static/client/{251.db6a4b10ef58ad3a.js → 251.f498960ce719d884.js} +6 -6
  52. package/out/static/client/251.f498960ce719d884.js.map +1 -0
  53. package/out/static/client/{2868.b09544148fff25a1.js → 2868.03299f12d5927ed2.js} +2 -2
  54. package/out/static/client/{2868.b09544148fff25a1.js.map → 2868.03299f12d5927ed2.js.map} +1 -1
  55. package/out/static/client/{291.91650ffed3a60424.js → 291.c90e43b3c10a9a1f.js} +27 -16
  56. package/out/static/client/291.c90e43b3c10a9a1f.js.map +1 -0
  57. package/out/static/client/{3259.ac8a5539251c647a.js → 3259.c4b809cd884ad515.js} +29 -24
  58. package/out/static/client/3259.c4b809cd884ad515.js.map +1 -0
  59. package/out/static/client/{3775.c64e09b2fc1d9b21.js → 3775.cf93e968f166d25a.js} +2 -1
  60. package/out/static/client/{3775.c64e09b2fc1d9b21.js.map → 3775.cf93e968f166d25a.js.map} +1 -1
  61. package/out/static/client/{4060.9a7bb116e3112c13.js → 4060.a23acd149888555c.js} +2 -2
  62. package/out/static/client/{4060.9a7bb116e3112c13.js.map → 4060.a23acd149888555c.js.map} +1 -1
  63. package/out/static/client/4249.19f86ee6b57a13b0.js +2 -0
  64. package/out/static/client/4249.19f86ee6b57a13b0.js.map +1 -0
  65. package/out/static/client/{4759.8981c79d33088d8f.js → 4759.5852733a45132491.js} +2 -2
  66. package/out/static/client/{4759.8981c79d33088d8f.js.map → 4759.5852733a45132491.js.map} +1 -1
  67. package/out/static/client/{4920.033c028023478df5.js → 4920.929203da4613c89a.js} +3 -2
  68. package/out/static/client/4920.929203da4613c89a.js.map +1 -0
  69. package/out/static/client/{5183.6c532a6e7b0d0bbb.js → 5183.48a6059ffe2931f0.js} +12 -12
  70. package/out/static/client/5183.48a6059ffe2931f0.js.map +1 -0
  71. package/out/static/client/{5408.a0622390fa1726dc.css → 5408.77c9b71d6a5df982.css} +2 -2
  72. package/out/static/client/{5408.a0622390fa1726dc.css.map → 5408.77c9b71d6a5df982.css.map} +1 -1
  73. package/out/static/client/{544.1ed814181b24bcc8.js → 544.2ccdc928c5292631.js} +3 -3
  74. package/out/static/client/544.2ccdc928c5292631.js.map +1 -0
  75. package/out/static/client/{5667.332b2c6c10792ad6.js → 5667.edd77c81ec38a7e2.js} +5 -4
  76. package/out/static/client/5667.edd77c81ec38a7e2.js.map +1 -0
  77. package/out/static/client/5685.d30658d4c274ac13.js +4 -0
  78. package/out/static/client/5685.d30658d4c274ac13.js.map +1 -0
  79. package/out/static/client/6002.bb83fdc66501b6e4.js +167 -0
  80. package/out/static/client/6002.bb83fdc66501b6e4.js.map +1 -0
  81. package/out/static/client/{6067.a60fb66b75579c98.js → 6067.169a9ec1f47871ae.js} +3 -3
  82. package/out/static/client/6067.169a9ec1f47871ae.js.map +1 -0
  83. package/out/static/client/6216.2a29b5ce2a1bf6f6.js +2 -0
  84. package/out/static/client/6216.2a29b5ce2a1bf6f6.js.map +1 -0
  85. package/out/static/client/{8364.13d1d6770264c970.js → 8364.f7862244a6ff6a1b.js} +3 -3
  86. package/out/static/client/8364.f7862244a6ff6a1b.js.map +1 -0
  87. package/out/static/client/{9204.f8aa5d9f8a79f3d4.js → 9204.f45896951abfbd28.js} +3 -3
  88. package/out/static/client/{9204.f8aa5d9f8a79f3d4.js.map → 9204.f45896951abfbd28.js.map} +1 -1
  89. package/out/static/client/9324.9646c5839948c8ff.js +27 -0
  90. package/out/static/client/9324.9646c5839948c8ff.js.map +1 -0
  91. package/out/static/client/9456.a17794342ff9086a.js +319 -0
  92. package/out/static/client/9456.a17794342ff9086a.js.map +1 -0
  93. package/out/static/client/index.c41c45d10cf2b50b.js +221 -0
  94. package/out/static/client/index.c41c45d10cf2b50b.js.map +1 -0
  95. package/out/static/client/{runtime.8ebca7827bf4c35c.js → runtime.796d2e8ab20fb93f.js} +2 -2
  96. package/out/static/client/runtime.796d2e8ab20fb93f.js.map +1 -0
  97. package/out/static/client/stats.json +142 -142
  98. package/out/static/client/{styles-content-section.049c2352791fac9b.css → styles-content-section.cd5635d274b730e9.css} +2 -2
  99. package/out/static/client/{styles-content-section.049c2352791fac9b.css.map → styles-content-section.cd5635d274b730e9.css.map} +1 -1
  100. package/out/static/client/{styles-global.445b59e1ff23f88c.js → styles-global.2a17ab4cc47bca1f.js} +1 -1
  101. package/out/static/client/{styles-global.3da376739551015c.css → styles-global.79b14873ea7ab386.css} +2 -2
  102. package/out/static/client/{styles-global.3da376739551015c.css.map → styles-global.79b14873ea7ab386.css.map} +1 -1
  103. package/out/static/client/{styles-left-sidebar.6f954e8939555f77.css → styles-left-sidebar.517b9efefbdeed1d.css} +2 -2
  104. package/out/static/client/{styles-left-sidebar.6f954e8939555f77.css.map → styles-left-sidebar.517b9efefbdeed1d.css.map} +1 -1
  105. package/out/static/legacy/6753.072394f4f09652a0.js +27 -0
  106. package/out/static/legacy/6753.072394f4f09652a0.js.map +1 -0
  107. package/out/static/legacy/asset-manifest.json +5 -5
  108. package/out/static/legacy/{index.85161ba7fe682ee8.html → index.c05c933009dda5b3.html} +1 -1
  109. package/out/static/legacy/stats.json +9 -9
  110. package/out/static/legacy/{yari.01d97d56e30935ec.js → yari.e457418f21984506.js} +3 -3
  111. package/out/static/legacy/{yari.01d97d56e30935ec.js.map → yari.e457418f21984506.js.map} +1 -1
  112. package/out/static/ssr/9456.js +317 -1
  113. package/out/static/ssr/9456.js.map +1 -1
  114. package/out/static/ssr/index.js +315 -235
  115. package/out/static/ssr/index.js.map +1 -1
  116. package/out/static/ssr/stats.json +4 -4
  117. package/package.json +14 -8
  118. package/components/specifications-list/index.js +0 -60
  119. package/out/static/client/251.db6a4b10ef58ad3a.js.map +0 -1
  120. package/out/static/client/291.91650ffed3a60424.js.map +0 -1
  121. package/out/static/client/3259.ac8a5539251c647a.js.map +0 -1
  122. package/out/static/client/4920.033c028023478df5.js.map +0 -1
  123. package/out/static/client/5183.6c532a6e7b0d0bbb.js.map +0 -1
  124. package/out/static/client/544.1ed814181b24bcc8.js.map +0 -1
  125. package/out/static/client/5649.00f25ce181894101.js +0 -2
  126. package/out/static/client/5649.00f25ce181894101.js.map +0 -1
  127. package/out/static/client/5667.332b2c6c10792ad6.js.map +0 -1
  128. package/out/static/client/5685.46c8d178d072b8ac.js +0 -4
  129. package/out/static/client/5685.46c8d178d072b8ac.js.map +0 -1
  130. package/out/static/client/6067.a60fb66b75579c98.js.map +0 -1
  131. package/out/static/client/6216.f526aaf8b3dedd72.js +0 -2
  132. package/out/static/client/6216.f526aaf8b3dedd72.js.map +0 -1
  133. package/out/static/client/718.3a790c4c45336906.js +0 -167
  134. package/out/static/client/718.3a790c4c45336906.js.map +0 -1
  135. package/out/static/client/8364.13d1d6770264c970.js.map +0 -1
  136. package/out/static/client/9324.9bb064ea7377127e.js +0 -26
  137. package/out/static/client/9324.9bb064ea7377127e.js.map +0 -1
  138. package/out/static/client/9456.dd8c12ddf52e2a7d.js +0 -3
  139. package/out/static/client/9456.dd8c12ddf52e2a7d.js.map +0 -1
  140. package/out/static/client/index.b2ccf995e945e9dc.js +0 -221
  141. package/out/static/client/index.b2ccf995e945e9dc.js.map +0 -1
  142. package/out/static/client/runtime.8ebca7827bf4c35c.js.map +0 -1
  143. package/out/static/legacy/6753.811fcf40c897d487.js +0 -27
  144. package/out/static/legacy/6753.811fcf40c897d487.js.map +0 -1
  145. /package/out/static/client/{291.91650ffed3a60424.js.LICENSE.txt → 291.c90e43b3c10a9a1f.js.LICENSE.txt} +0 -0
  146. /package/out/static/client/{5183.6c532a6e7b0d0bbb.js.LICENSE.txt → 5183.48a6059ffe2931f0.js.LICENSE.txt} +0 -0
  147. /package/out/static/client/{6067.a60fb66b75579c98.js.LICENSE.txt → 6067.169a9ec1f47871ae.js.LICENSE.txt} +0 -0
  148. /package/out/static/client/{8364.13d1d6770264c970.js.LICENSE.txt → 8364.f7862244a6ff6a1b.js.LICENSE.txt} +0 -0
  149. /package/out/static/client/{index.b2ccf995e945e9dc.js.LICENSE.txt → index.c41c45d10cf2b50b.js.LICENSE.txt} +0 -0
  150. /package/out/static/legacy/{6753.811fcf40c897d487.js.LICENSE.txt → 6753.072394f4f09652a0.js.LICENSE.txt} +0 -0
  151. /package/out/static/legacy/{yari.01d97d56e30935ec.js.LICENSE.txt → yari.e457418f21984506.js.LICENSE.txt} +0 -0
package/.nvmrc CHANGED
@@ -1 +1 @@
1
- v24
1
+ v24.15.0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,53 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.6.0](https://github.com/mdn/fred/compare/v2.5.0...v2.6.0) (2026-04-28)
4
+
5
+
6
+ ### Features
7
+
8
+ * **glean:** port missing measurements ([#1279](https://github.com/mdn/fred/issues/1279)) ([bdbc1a1](https://github.com/mdn/fred/commit/bdbc1a166cbd4fd2dd01ce898b0f5ff7837599d9))
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **content-section:** add iframe styling ([#1414](https://github.com/mdn/fred/issues/1414)) ([2696dbc](https://github.com/mdn/fred/commit/2696dbc9ad769855ef9c3356a54ef06ebdcd132c))
14
+
15
+
16
+ ### Miscellaneous
17
+
18
+ * **deps-dev:** bump @mdn/browser-compat-data from 7.3.12 to 7.3.13 ([#1517](https://github.com/mdn/fred/issues/1517)) ([6dbfba4](https://github.com/mdn/fred/commit/6dbfba4c396c0490f753c7a0cbc8e29f267adacb))
19
+ * **deps-dev:** bump postcss from 8.5.10 to 8.5.12 in the npm-dev group ([#1516](https://github.com/mdn/fred/issues/1516)) ([b605a17](https://github.com/mdn/fred/commit/b605a170bb1c003c728ecee9695197add9e07957))
20
+ * **deps-dev:** bump the npm-dev group with 2 updates ([#1519](https://github.com/mdn/fred/issues/1519)) ([e1f4d25](https://github.com/mdn/fred/commit/e1f4d258df237b192d391e337b72e5026ed022d0))
21
+ * **deps:** bump @mdn/rari from 0.2.20 to 0.2.21 ([#1525](https://github.com/mdn/fred/issues/1525)) ([63a0803](https://github.com/mdn/fred/commit/63a08039823627b6816469f7798ed54941b33a30))
22
+ * **I10n:** add Simplified Chinese translation ([#1510](https://github.com/mdn/fred/issues/1510)) ([ae1bf08](https://github.com/mdn/fred/commit/ae1bf083d88e3579d99e3587b687245ce26a8107))
23
+
24
+ ## [2.5.0](https://github.com/mdn/fred/compare/v2.4.3...v2.5.0) (2026-04-23)
25
+
26
+
27
+ ### Features
28
+
29
+ * **baseline-indicator:** add renderSimplified method ([#1500](https://github.com/mdn/fred/issues/1500)) ([556412d](https://github.com/mdn/fred/commit/556412d6ac6b5398e773eedd89fa4ae710ad6f43))
30
+ * **bcd:** render links to JSON in simplified HTML ([#1502](https://github.com/mdn/fred/issues/1502)) ([d82fd33](https://github.com/mdn/fred/commit/d82fd33921bea39e23c2b1aae3123848ddae0b5c))
31
+ * **specifications:** render a list in simplified mode ([#1504](https://github.com/mdn/fred/issues/1504)) ([abf1784](https://github.com/mdn/fred/commit/abf17843f6dc0f6a18f6bff6f523255ed74e37b2))
32
+
33
+
34
+ ### Bug Fixes
35
+
36
+ * **sidebar-filter:** remove only highlight spans on reset ([#1496](https://github.com/mdn/fred/issues/1496)) ([c647e79](https://github.com/mdn/fred/commit/c647e794552c08f1574545378ba5986699633e7f))
37
+
38
+
39
+ ### Miscellaneous
40
+
41
+ * **deps-dev:** bump @codemirror/view from 6.41.0 to 6.41.1 in the npm-dev group ([#1494](https://github.com/mdn/fred/issues/1494)) ([06be168](https://github.com/mdn/fred/commit/06be168411bf3efe7a38bc6a291e9a91b1b6c309))
42
+ * **deps-dev:** bump @codemirror/view in the npm-dev group ([06be168](https://github.com/mdn/fred/commit/06be168411bf3efe7a38bc6a291e9a91b1b6c309))
43
+ * **deps-dev:** bump npm from 11.6.2 to 11.12.1 ([#1490](https://github.com/mdn/fred/issues/1490)) ([fbc5e9a](https://github.com/mdn/fred/commit/fbc5e9a78d5332ade4f99d931011fc681c653f77))
44
+ * **deps-dev:** bump stylelint from 17.8.0 to 17.9.0 in the npm-dev group ([#1514](https://github.com/mdn/fred/issues/1514)) ([87617be](https://github.com/mdn/fred/commit/87617be8e0f3c712385ccce424edf07756b9bf8c))
45
+ * **deps-dev:** bump stylelint in the npm-dev group ([87617be](https://github.com/mdn/fred/commit/87617be8e0f3c712385ccce424edf07756b9bf8c))
46
+ * **deps-dev:** bump typescript-eslint from 8.58.2 to 8.59.0 in the npm-dev group ([#1499](https://github.com/mdn/fred/issues/1499)) ([b25912c](https://github.com/mdn/fred/commit/b25912ceb942de26ac5df8153317b8de172a4e2c))
47
+ * **deps-dev:** bump typescript-eslint in the npm-dev group ([b25912c](https://github.com/mdn/fred/commit/b25912ceb942de26ac5df8153317b8de172a4e2c))
48
+ * **deps:** bump @mdn/rari from 0.2.19 to 0.2.20 ([#1515](https://github.com/mdn/fred/issues/1515)) ([d292b8a](https://github.com/mdn/fred/commit/d292b8aedf464a17ab7f5a3224fbeaef330ade2e))
49
+ * **transcend:** use TRANSCEND_AIRGAP_URL ([#1497](https://github.com/mdn/fred/issues/1497)) ([5e8a62a](https://github.com/mdn/fred/commit/5e8a62a1437718631ca1ee010e53cda377126a1b))
50
+
3
51
  ## [2.4.3](https://github.com/mdn/fred/compare/v2.4.2...v2.4.3) (2026-04-17)
4
52
 
5
53
 
package/README.md CHANGED
@@ -172,6 +172,8 @@ This issue is tracked upstream: https://github.com/lit/lit/issues/1434
172
172
 
173
173
  Any server component can define a `renderSimplified` method to define the simplified form of that component. When in the top-level `renderSimplified` context, any calls of `ServerComponent.render()` will automatically call the `renderSimplified` method of that component, falling back to the `render` method. This is so we can nest components with a `renderSimplified` method ("simplified components") within ones without.
174
174
 
175
+ Server components also have a `simplifiedMode` property set, which is `true` when rendered from the top-level `renderSimplified` function: this allows a small tweak within a `render` method without having to totally re-implement logic in the `renderSimplified` method.
176
+
175
177
  There shouldn't be standalone simplified components: the nesting of components should be defined by the requirements of the `render` method. `renderSimplified` should only be added to a component which already exists with a `render` method to give a simplified view of it. This is especially important as, in the future, we may need to add options of what is/isn't rendered within `renderSimplified` for use in different contexts (one context may require a sidebar, another may not, for instance).
176
178
 
177
179
  You can preview the rendering locally by setting `FRED_SIMPLE_HTML`:
@@ -9,14 +9,14 @@ export class A11yMenu extends ServerComponent {
9
9
  render(context) {
10
10
  return html`<ul class="a11y-menu">
11
11
  <li>
12
- <a href="#content"
12
+ <a href="#content" data-glean-id="a11y_menu: click #content"
13
13
  >${context.l10n(
14
14
  "a11y-menu-skip-to-main-content",
15
15
  )`Skip to main content`}</a
16
16
  >
17
17
  </li>
18
18
  <li>
19
- <a href="#search"
19
+ <a href="#search" data-glean-id="a11y_menu: click #search"
20
20
  >${context.l10n("a11y-menu-skip-to-search")`Skip to search`}</a
21
21
  >
22
22
  </li>
@@ -51,7 +51,43 @@ export class BaselineIndicator extends ServerComponent {
51
51
  static inlineScript = inlineScript;
52
52
 
53
53
  /**
54
- *
54
+ * @param {string | null | undefined} date
55
+ */
56
+ parseDate(date) {
57
+ const lowDateRange = date?.match(/^([^0-9])/)?.[0];
58
+ return date ? new Date(date.slice(lowDateRange ? 1 : 0)) : undefined;
59
+ }
60
+
61
+ /**
62
+ * @param {import("@fred").Context<import("@rari").DocPage>} context
63
+ * @param {string} level
64
+ * @param {Date} [lowDate]
65
+ */
66
+ getExtraText(context, level, lowDate) {
67
+ return level === "high" && lowDate
68
+ ? context.l10n.raw({
69
+ id: "baseline-high-extra",
70
+ args: {
71
+ date: lowDate.toLocaleDateString(context.locale, {
72
+ year: "numeric",
73
+ month: "long",
74
+ }),
75
+ },
76
+ })
77
+ : level === "low" && lowDate
78
+ ? context.l10n.raw({
79
+ id: "baseline-low-extra",
80
+ args: {
81
+ date: lowDate.toLocaleDateString(DEFAULT_LOCALE, {
82
+ year: "numeric",
83
+ month: "long",
84
+ }),
85
+ },
86
+ })
87
+ : context.l10n("baseline-not-extra");
88
+ }
89
+
90
+ /**
55
91
  * @param {import("@fred").Context<import("@rari").DocPage>} context
56
92
  */
57
93
  render(context) {
@@ -71,11 +107,7 @@ export class BaselineIndicator extends ServerComponent {
71
107
  LOCALIZED_BCD_IDS[context.locale] || LOCALIZED_BCD_IDS[DEFAULT_LOCALE]
72
108
  }`;
73
109
 
74
- const low_date_range = status.baseline_low_date?.match(/^([^0-9])/)?.[0];
75
- const low_date = status.baseline_low_date
76
- ? new Date(status.baseline_low_date.slice(low_date_range ? 1 : 0))
77
- : undefined;
78
-
110
+ const lowDate = this.parseDate(status.baseline_low_date);
79
111
  const level = status.baseline || "not";
80
112
 
81
113
  const feedbackLink = `${SURVEY_URL}?page=${encodeURIComponent(context.url)}&level=${level}`;
@@ -154,7 +186,7 @@ export class BaselineIndicator extends ServerComponent {
154
186
  ? context.l10n(
155
187
  "baseline-indicator-widely-available",
156
188
  )`Widely available`
157
- : low_date?.getFullYear()}
189
+ : lowDate?.getFullYear()}
158
190
  </span>
159
191
  ${status.asterisk && " *"}
160
192
  `}
@@ -191,31 +223,7 @@ export class BaselineIndicator extends ServerComponent {
191
223
  <span class="icon icon-chevron"></span>
192
224
  </summary>
193
225
  <div class="extra">
194
- ${level === "high" && low_date
195
- ? html`<p>
196
- ${context.l10n.raw({
197
- id: "baseline-high-extra",
198
- args: {
199
- date: low_date.toLocaleDateString(context.locale, {
200
- year: "numeric",
201
- month: "long",
202
- }),
203
- },
204
- })}
205
- </p>`
206
- : level === "low" && low_date
207
- ? html`<p>
208
- ${context.l10n.raw({
209
- id: "baseline-low-extra",
210
- args: {
211
- date: low_date.toLocaleDateString(DEFAULT_LOCALE, {
212
- year: "numeric",
213
- month: "long",
214
- }),
215
- },
216
- })}
217
- </p>`
218
- : html`<p>${context.l10n("baseline-not-extra")}</p>`}
226
+ <p>${this.getExtraText(context, level, lowDate)}</p>
219
227
  ${status.asterisk
220
228
  ? html`<p>* ${context.l10n("baseline-asterisk")}</p>`
221
229
  : nothing}
@@ -254,4 +262,50 @@ export class BaselineIndicator extends ServerComponent {
254
262
  </div>
255
263
  </details>`;
256
264
  }
265
+
266
+ /**
267
+ * @param {import("@fred").Context<import("@rari").DocPage>} context
268
+ */
269
+ renderSimplified(context) {
270
+ const { doc } = context;
271
+
272
+ if (!doc) {
273
+ return nothing;
274
+ }
275
+
276
+ const status = doc.baseline;
277
+
278
+ if (!status) {
279
+ return nothing;
280
+ }
281
+
282
+ const lowDate = this.parseDate(status.baseline_low_date);
283
+ const level = status.baseline || "not";
284
+
285
+ return html`<p>
286
+ <strong>
287
+ ${level === "not"
288
+ ? context.l10n(
289
+ "baseline-indicator-limited-availability",
290
+ )`Limited availability`
291
+ : context.l10n("baseline-indicator-baseline")`Baseline`}
292
+ ${level === "high"
293
+ ? context.l10n(
294
+ "baseline-indicator-widely-available",
295
+ )`Widely available`
296
+ : level === "low"
297
+ ? html`${lowDate?.getFullYear()}
298
+ ${context.l10n(
299
+ "baseline-indicator-newly-available",
300
+ )`Newly available`}`
301
+ : nothing}
302
+ ${status.asterisk ? " *" : nothing}
303
+ </strong>
304
+ <br />
305
+ ${this.getExtraText(context, level, lowDate)}
306
+ ${status.asterisk
307
+ ? html`<br />* ${context.l10n("baseline-asterisk")}`
308
+ : nothing}
309
+ </p>`;
310
+ }
257
311
  }
@@ -18,6 +18,7 @@ export class Breadcrumbs extends ServerComponent {
18
18
  return nothing;
19
19
  }
20
20
 
21
+ const total = parents.length;
21
22
  return html`
22
23
  <ol
23
24
  class="breadcrumbs"
@@ -27,7 +28,11 @@ export class Breadcrumbs extends ServerComponent {
27
28
  ${parents.map(
28
29
  ({ uri, title }, index) => html`
29
30
  <li property="itemListElement" typeof="ListItem">
30
- <a href=${uri} property="item" typeof="WebPage"
31
+ <a
32
+ href=${uri}
33
+ property="item"
34
+ typeof="WebPage"
35
+ data-glean-id="breadcrumb_click: ${total - index}/${total}"
31
36
  ><span property="name">${title}</span></a
32
37
  >
33
38
  <meta property="position" content=${index + 1} />
@@ -2,6 +2,7 @@ import { Task } from "@lit/task";
2
2
  import { LitElement, html, nothing } from "lit";
3
3
 
4
4
  import { L10nMixin } from "../../l10n/mixin.js";
5
+ import { gleanClick } from "../../utils/glean.js";
5
6
  import { globalUser } from "../user/context.js";
6
7
 
7
8
  import styles from "./element.css?lit";
@@ -89,16 +90,26 @@ export class MDNCollectionSaveButton extends L10nMixin(LitElement) {
89
90
  });
90
91
 
91
92
  _open() {
93
+ this._selectFocusEventTriggered = false;
94
+ gleanClick("article_actions_collections_opened");
92
95
  this._bookmarks.run();
93
96
  this._collections.run();
94
97
  this.shadowRoot?.querySelector("mdn-modal")?.showModal();
95
98
  }
96
99
 
100
+ _selectOpen() {
101
+ if (!this._selectFocusEventTriggered) {
102
+ gleanClick("article_actions_collection_select_opened");
103
+ this._selectFocusEventTriggered = true;
104
+ }
105
+ }
106
+
97
107
  /** @param {Event} event */
98
108
  _selectChange({ target }) {
99
109
  if (target instanceof HTMLSelectElement) {
100
110
  const { value } = target;
101
111
  if (value === ADD_VALUE) {
112
+ gleanClick("article_actions_new_collection");
102
113
  this.shadowRoot?.querySelector("mdn-modal")?.close();
103
114
  open("/en-US/plus/collections");
104
115
  } else {
@@ -149,6 +160,9 @@ export class MDNCollectionSaveButton extends L10nMixin(LitElement) {
149
160
  this._lastAction = "save";
150
161
 
151
162
  const url = `/api/v2/collections/${collectionId}/items/${item ? `${item.id}/` : ""}`;
163
+
164
+ gleanClick("new_collection_modal_submit_article_actions");
165
+
152
166
  const res = await fetch(url, {
153
167
  body: JSON.stringify({
154
168
  url: this.docUrl,
@@ -229,6 +243,7 @@ export class MDNCollectionSaveButton extends L10nMixin(LitElement) {
229
243
  )`Collection:`}
230
244
  <select
231
245
  .value=${this._item?.collection_id}
246
+ @focus=${this._selectOpen}
232
247
  @change=${this._selectChange}
233
248
  >
234
249
  ${collections.map(
@@ -1,6 +1,7 @@
1
1
  import { LitElement, html } from "lit";
2
2
 
3
3
  import { L10nMixin } from "../../l10n/mixin.js";
4
+ import { gleanClick } from "../../utils/glean.js";
4
5
 
5
6
  import styles from "./element.css?lit";
6
7
 
@@ -17,6 +18,7 @@ export class MDNColorTheme extends L10nMixin(LitElement) {
17
18
  super();
18
19
  /** @type {import("./types.js").ColorScheme} */
19
20
  this._mode = "light dark";
21
+ this._closingFromSelect = false;
20
22
  this._options = Object.entries({
21
23
  "light dark": this.l10n("theme-default")`OS default`,
22
24
  light: this.l10n("color-theme-light")`Light`,
@@ -30,6 +32,8 @@ export class MDNColorTheme extends L10nMixin(LitElement) {
30
32
  const mode = target.dataset.mode;
31
33
  if (mode === "light dark" || mode === "light" || mode === "dark") {
32
34
  this._mode = mode;
35
+ const gleanMode = mode === "light dark" ? "os-default" : mode;
36
+ gleanClick(`theme_switcher: switch -> ${gleanMode}`);
33
37
  try {
34
38
  localStorage.setItem("theme", mode);
35
39
  } catch (error) {
@@ -37,12 +41,24 @@ export class MDNColorTheme extends L10nMixin(LitElement) {
37
41
  }
38
42
  const dropdown = this.shadowRoot?.querySelector("mdn-dropdown");
39
43
  if (dropdown) {
44
+ this._closingFromSelect = true;
40
45
  dropdown.open = false;
41
46
  }
42
47
  }
43
48
  }
44
49
  }
45
50
 
51
+ /** @param {Event} event */
52
+ _onDropdownToggle({ target }) {
53
+ if (target instanceof HTMLElement && "open" in target) {
54
+ if (!target.open && this._closingFromSelect) {
55
+ this._closingFromSelect = false;
56
+ return;
57
+ }
58
+ gleanClick(`theme_switcher: ${target.open ? "open" : "close"}`);
59
+ }
60
+ }
61
+
46
62
  /**
47
63
  * @param {import("lit").PropertyValues<this>} changedProperties
48
64
  */
@@ -61,7 +77,7 @@ export class MDNColorTheme extends L10nMixin(LitElement) {
61
77
 
62
78
  render() {
63
79
  return html`<div class="color-theme">
64
- <mdn-dropdown>
80
+ <mdn-dropdown @toggle=${this._onDropdownToggle}>
65
81
  <button
66
82
  part="button"
67
83
  slot="button"
@@ -0,0 +1,45 @@
1
+ import { html } from "@lit-labs/ssr";
2
+ import { nothing } from "lit";
3
+ import { ifDefined } from "lit/directives/if-defined.js";
4
+
5
+ import { queryToUrl } from "../compat-table/utils.js";
6
+ import { HeadingAnchor } from "../heading-anchor/server.js";
7
+ import { ServerComponent } from "../server/index.js";
8
+
9
+ export class CompatSection extends ServerComponent {
10
+ /**
11
+ * @param {import("@fred").Context} _context
12
+ * @param {import("@rari").Compat} section
13
+ */
14
+ render(_context, { id, title, query, isH3 }) {
15
+ const level = isH3 ? 3 : 2;
16
+ return html`<section
17
+ class="content-section"
18
+ aria-labelledby=${ifDefined(id ?? undefined)}
19
+ >
20
+ ${HeadingAnchor.render(level, id ? String(id) : null, String(title))}
21
+ <mdn-compat-table-lazy
22
+ locale="en-US"
23
+ query=${query}
24
+ ></mdn-compat-table-lazy>
25
+ </section>`;
26
+ }
27
+
28
+ /**
29
+ * @param {import("@fred").Context} _context
30
+ * @param {import("@rari").Compat} section
31
+ */
32
+ renderSimplified(_context, { id, title, query, isH3 }) {
33
+ return html`<section
34
+ class="content-section"
35
+ aria-labelledby=${ifDefined(id ?? undefined)}
36
+ >
37
+ ${isH3
38
+ ? nothing
39
+ : HeadingAnchor.render(2, id ? String(id) : null, String(title))}
40
+ <a href=${queryToUrl(query)} target="_blank" rel="noopener"
41
+ ><code>${query}</code></a
42
+ >
43
+ </section>`;
44
+ }
45
+ }
@@ -1,8 +1,11 @@
1
1
  import { LitElement, html, nothing } from "lit";
2
2
  import { ifDefined } from "lit/directives/if-defined.js";
3
+ import { createRef, ref } from "lit/directives/ref.js";
3
4
  import { unsafeHTML } from "lit/directives/unsafe-html.js";
4
5
 
5
6
  import { L10nMixin } from "../../l10n/mixin.js";
7
+ import { gleanClick } from "../../utils/glean.js";
8
+ import { ViewedController } from "../viewed-controller/viewed-controller.js";
6
9
 
7
10
  import { DEFAULT_LOCALE, ISSUE_METADATA_TEMPLATE } from "./constants.js";
8
11
  import styles from "./element.css?lit";
@@ -73,6 +76,9 @@ export class MDNCompatTable extends L10nMixin(LitElement) {
73
76
 
74
77
  static styles = styles;
75
78
 
79
+ /** @type {import("lit/directives/ref.js").Ref<HTMLElement>} */
80
+ _ref = createRef();
81
+
76
82
  constructor() {
77
83
  super();
78
84
  this.query = "";
@@ -88,6 +94,9 @@ export class MDNCompatTable extends L10nMixin(LitElement) {
88
94
  this._browsers = [];
89
95
  /** @type {string|undefined} */
90
96
  this._showTimelineId = undefined;
97
+ new ViewedController(this, this._ref, () => {
98
+ gleanClick(`bcd: view -> ${this.query}`);
99
+ });
91
100
  }
92
101
 
93
102
  get _breadcrumbs() {
@@ -255,7 +264,7 @@ export class MDNCompatTable extends L10nMixin(LitElement) {
255
264
  }
256
265
 
257
266
  _renderTable() {
258
- return html`<figure class="table-container">
267
+ return html`<figure ${ref(this._ref)} class="table-container">
259
268
  <figure class="table-container-inner">
260
269
  ${this._renderIssueLink()}
261
270
  <table
@@ -1,3 +1,5 @@
1
+ import { BCD_BASE_URL } from "../env/index.js";
2
+
1
3
  /**
2
4
  * A list of browsers to be hidden.
3
5
  * @constant {string[]}
@@ -293,3 +295,11 @@ export function getCurrentSupport(support) {
293
295
  // Default (likely never reached).
294
296
  return getFirst(support);
295
297
  }
298
+
299
+ /**
300
+ * Get the URL to the JSON file for a given BCD key
301
+ * @param {string} query
302
+ */
303
+ export function queryToUrl(query) {
304
+ return `${BCD_BASE_URL}/bcd/api/v0/current/${query}.json`;
305
+ }
@@ -8,7 +8,7 @@ import {
8
8
  DEFAULT_LOCALE,
9
9
  ISSUE_METADATA_TEMPLATE,
10
10
  } from "../compat-table/constants.js";
11
- import { BCD_BASE_URL } from "../env/index.js";
11
+ import { queryToUrl } from "../compat-table/utils.js";
12
12
 
13
13
  import styles from "./element.css?lit";
14
14
 
@@ -76,10 +76,7 @@ export class MDNCompatTableLazy extends L10nMixin(LitElement) {
76
76
  _dataTask = new Task(this, {
77
77
  args: () => [this.query],
78
78
  task: async ([query], { signal }) => {
79
- const response = await fetch(
80
- `${BCD_BASE_URL}/bcd/api/v0/current/${query}.json`,
81
- { signal },
82
- );
79
+ const response = await fetch(queryToUrl(query), { signal });
83
80
  if (!response.ok) {
84
81
  console.error("Failed to fetch BCD data:", response);
85
82
 
@@ -138,6 +138,11 @@
138
138
  border-radius: 0.25rem;
139
139
  }
140
140
 
141
+ iframe {
142
+ border: 1px solid var(--color-border-primary);
143
+ border-radius: 0.25rem;
144
+ }
145
+
141
146
  table {
142
147
  width: 100%;
143
148
  border-collapse: collapse;
@@ -2,20 +2,20 @@ import { html } from "@lit-labs/ssr";
2
2
  import { ifDefined } from "lit/directives/if-defined.js";
3
3
  import { unsafeHTML } from "lit/directives/unsafe-html.js";
4
4
 
5
+ import { CompatSection } from "../compat-section/server.js";
5
6
  import { HeadingAnchor } from "../heading-anchor/server.js";
6
7
  import { ServerComponent } from "../server/index.js";
7
- import { SpecificationsList } from "../specifications-list/index.js";
8
+ import { SpecificationsList } from "../specifications-list/server.js";
8
9
 
9
10
  export class ContentSection extends ServerComponent {
10
11
  /**
11
12
  * @param {import("@fred").Context} context
12
13
  * @param {import("@rari").Section} section
13
- * @returns {import("@lit").TemplateResult}
14
14
  */
15
15
  render(context, { type, value }) {
16
16
  switch (type) {
17
17
  case "browser_compatibility": {
18
- return BCD(value);
18
+ return CompatSection.render(context, value);
19
19
  }
20
20
  case "specifications": {
21
21
  return SpecificationsSection(context, value);
@@ -43,24 +43,6 @@ function Prose({ id, title, content, isH3 }) {
43
43
  </section>`;
44
44
  }
45
45
 
46
- /**
47
- * @param {import("@rari").Compat} section
48
- * @returns {import("@lit").TemplateResult}
49
- */
50
- function BCD({ id, title, query, isH3 }) {
51
- const level = isH3 ? 3 : 2;
52
- return html`<section
53
- class="content-section"
54
- aria-labelledby=${ifDefined(id ?? undefined)}
55
- >
56
- ${HeadingAnchor.render(level, id ? String(id) : null, String(title))}
57
- <mdn-compat-table-lazy
58
- locale="en-US"
59
- query=${query}
60
- ></mdn-compat-table-lazy>
61
- </section>`;
62
- }
63
-
64
46
  /**
65
47
  * @param {import("@fred").Context} context
66
48
  * @param {import("@rari").SpecificationSection} section
@@ -72,6 +54,6 @@ function SpecificationsSection(context, { id, title, specifications, isH3 }) {
72
54
  aria-labelledby=${ifDefined(id ?? undefined)}
73
55
  >
74
56
  ${HeadingAnchor.render(level, id ? String(id) : null, String(title))}
75
- ${SpecificationsList(context, specifications)}
57
+ ${SpecificationsList.render(context, specifications)}
76
58
  </section>`;
77
59
  }
@@ -78,7 +78,7 @@
78
78
  }
79
79
 
80
80
  a,
81
- span:not(.highlight-container) {
81
+ span:not(.sidebar-filter-mark-container) {
82
82
  display: inline-flex;
83
83
  padding-block: 0.25rem;
84
84
  }
@@ -257,7 +257,10 @@ export class CurriculumLanding extends ServerComponent {
257
257
  const bannerLight = partnerBannerLight;
258
258
 
259
259
  return html`
260
- <section class="curriculum-partner-banner-container">
260
+ <section
261
+ class="curriculum-partner-banner-container"
262
+ data-glean-view="curriculum: partner banner view"
263
+ >
261
264
  <div class="partner-banner">
262
265
  <section>
263
266
  <h2>Learn the curriculum with Scrimba and become job ready</h2>
@@ -96,8 +96,19 @@ export class MDNDropdown extends LitElement {
96
96
  `;
97
97
  }
98
98
 
99
- updated() {
99
+ /**
100
+ * @param {import("lit").PropertyValues<this>} changedProperties
101
+ */
102
+ updated(changedProperties) {
100
103
  this._setAriaAttributes();
104
+ if (
105
+ changedProperties.has("open") &&
106
+ changedProperties.get("open") !== undefined
107
+ ) {
108
+ this.dispatchEvent(
109
+ new Event("toggle", { bubbles: true, composed: true }),
110
+ );
111
+ }
101
112
  }
102
113
 
103
114
  firstUpdated() {
@@ -50,4 +50,4 @@ export const OBSERVATORY_API_URL = parseString(
50
50
  );
51
51
 
52
52
  // Transcend Consent Management - https://docs.transcend.io/docs/consent
53
- export const TRANSCEND_BUNDLE_ID = parseString("TRANSCEND_BUNDLE_ID", "");
53
+ export const TRANSCEND_AIRGAP_URL = parseString("TRANSCEND_AIRGAP_URL", "");
@@ -10,16 +10,23 @@ export class FeaturedArticles extends ServerComponent {
10
10
  render(articles) {
11
11
  return html`<ul class="featured-articles">
12
12
  ${articles.map(
13
- (article) =>
13
+ (article, index) =>
14
14
  html`<li>
15
15
  <article class="featured-articles__article">
16
16
  ${article.tag
17
- ? html`<a class="featured-articles__tag" href=${article.tag.uri}
17
+ ? html`<a
18
+ class="featured-articles__tag"
19
+ href=${article.tag.uri}
20
+ data-glean-id=${`homepage: article_tag ${index + 1} -> ${article.tag.uri}`}
18
21
  >${article.tag.title}</a
19
22
  >`
20
23
  : nothing}
21
24
  <h3 class="featured-articles__title">
22
- <a href=${article.mdn_url}>${article.title}</a>
25
+ <a
26
+ href=${article.mdn_url}
27
+ data-glean-id=${`homepage: article ${index + 1} -> ${article.mdn_url}`}
28
+ >${article.title}</a
29
+ >
23
30
  </h3>
24
31
  <p class="featured-articles__summary">${article.summary}</p>
25
32
  </article>