@mdn/fred 2.5.0 → 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 (112) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/components/a11y-menu/server.js +2 -2
  3. package/components/breadcrumbs/server.js +6 -1
  4. package/components/collection-save-button/element.js +15 -0
  5. package/components/color-theme/element.js +17 -1
  6. package/components/compat-table/element.js +10 -1
  7. package/components/content-section/server.css +5 -0
  8. package/components/curriculum-landing/server.js +4 -1
  9. package/components/dropdown/element.js +12 -1
  10. package/components/featured-articles/server.js +10 -3
  11. package/components/footer/server.js +6 -1
  12. package/components/generic-about/server.js +1 -0
  13. package/components/generic-toc/server.js +6 -1
  14. package/components/language-always-redirect-button/element.js +2 -0
  15. package/components/language-switcher/element.js +5 -0
  16. package/components/latest-news/server.js +10 -3
  17. package/components/login-button/element.js +1 -1
  18. package/components/menu/server.js +3 -1
  19. package/components/observatory-form/element.js +3 -0
  20. package/components/observatory-results/element.js +6 -1
  21. package/components/observatory-results/rating.js +6 -1
  22. package/components/placement-top/element.js +16 -4
  23. package/components/playground/element.js +17 -4
  24. package/components/recent-contributions/server.js +4 -1
  25. package/components/reference-toc/server.js +5 -1
  26. package/components/scrim-inline/element.js +13 -1
  27. package/components/sidebar-filter/element.js +11 -0
  28. package/components/translation-banner/server.js +4 -1
  29. package/components/user-menu/element.js +1 -0
  30. package/components/viewed-controller/viewed-controller.js +15 -59
  31. package/components/viewed-controller/viewed-observer.js +79 -0
  32. package/hooks/glean-init.js +49 -6
  33. package/l10n/locales/zh-CN.ftl +317 -1
  34. package/out/service-worker.js +1 -1
  35. package/out/static/client/{2173.c74593faf7e14cbc.js → 2173.a662dd40657c8426.js} +2 -2
  36. package/out/static/client/{2173.c74593faf7e14cbc.js.map → 2173.a662dd40657c8426.js.map} +1 -1
  37. package/out/static/client/{2868.b09544148fff25a1.js → 2868.03299f12d5927ed2.js} +2 -2
  38. package/out/static/client/{2868.b09544148fff25a1.js.map → 2868.03299f12d5927ed2.js.map} +1 -1
  39. package/out/static/client/{291.91650ffed3a60424.js → 291.c90e43b3c10a9a1f.js} +27 -16
  40. package/out/static/client/291.c90e43b3c10a9a1f.js.map +1 -0
  41. package/out/static/client/{3259.ac8a5539251c647a.js → 3259.c4b809cd884ad515.js} +29 -24
  42. package/out/static/client/3259.c4b809cd884ad515.js.map +1 -0
  43. package/out/static/client/{3775.c64e09b2fc1d9b21.js → 3775.cf93e968f166d25a.js} +2 -1
  44. package/out/static/client/{3775.c64e09b2fc1d9b21.js.map → 3775.cf93e968f166d25a.js.map} +1 -1
  45. package/out/static/client/{4060.9a7bb116e3112c13.js → 4060.a23acd149888555c.js} +2 -2
  46. package/out/static/client/{4060.9a7bb116e3112c13.js.map → 4060.a23acd149888555c.js.map} +1 -1
  47. package/out/static/client/4249.19f86ee6b57a13b0.js +2 -0
  48. package/out/static/client/4249.19f86ee6b57a13b0.js.map +1 -0
  49. package/out/static/client/{4759.8981c79d33088d8f.js → 4759.5852733a45132491.js} +2 -2
  50. package/out/static/client/{4759.8981c79d33088d8f.js.map → 4759.5852733a45132491.js.map} +1 -1
  51. package/out/static/client/{4920.033c028023478df5.js → 4920.929203da4613c89a.js} +3 -2
  52. package/out/static/client/4920.929203da4613c89a.js.map +1 -0
  53. package/out/static/client/{5183.6c532a6e7b0d0bbb.js → 5183.48a6059ffe2931f0.js} +12 -12
  54. package/out/static/client/5183.48a6059ffe2931f0.js.map +1 -0
  55. package/out/static/client/{544.1ed814181b24bcc8.js → 544.2ccdc928c5292631.js} +3 -3
  56. package/out/static/client/544.2ccdc928c5292631.js.map +1 -0
  57. package/out/static/client/{5667.332b2c6c10792ad6.js → 5667.edd77c81ec38a7e2.js} +5 -4
  58. package/out/static/client/5667.edd77c81ec38a7e2.js.map +1 -0
  59. package/out/static/client/5685.d30658d4c274ac13.js +4 -0
  60. package/out/static/client/5685.d30658d4c274ac13.js.map +1 -0
  61. package/out/static/client/{6002.60280d36f6ea71ff.js → 6002.bb83fdc66501b6e4.js} +27 -27
  62. package/out/static/client/6002.bb83fdc66501b6e4.js.map +1 -0
  63. package/out/static/client/{6067.a60fb66b75579c98.js → 6067.169a9ec1f47871ae.js} +3 -3
  64. package/out/static/client/6067.169a9ec1f47871ae.js.map +1 -0
  65. package/out/static/client/{8364.13d1d6770264c970.js → 8364.f7862244a6ff6a1b.js} +3 -3
  66. package/out/static/client/8364.f7862244a6ff6a1b.js.map +1 -0
  67. package/out/static/client/{9204.f8aa5d9f8a79f3d4.js → 9204.f45896951abfbd28.js} +3 -3
  68. package/out/static/client/{9204.f8aa5d9f8a79f3d4.js.map → 9204.f45896951abfbd28.js.map} +1 -1
  69. package/out/static/client/9324.9646c5839948c8ff.js +27 -0
  70. package/out/static/client/{9324.61778c81f15bcebc.js.map → 9324.9646c5839948c8ff.js.map} +1 -1
  71. package/out/static/client/9456.a17794342ff9086a.js +319 -0
  72. package/out/static/client/9456.a17794342ff9086a.js.map +1 -0
  73. package/out/static/client/index.c41c45d10cf2b50b.js +221 -0
  74. package/out/static/client/index.c41c45d10cf2b50b.js.map +1 -0
  75. package/out/static/client/{runtime.5a92d4f860d6d798.js → runtime.796d2e8ab20fb93f.js} +2 -2
  76. package/out/static/client/{runtime.5a92d4f860d6d798.js.map → runtime.796d2e8ab20fb93f.js.map} +1 -1
  77. package/out/static/client/stats.json +134 -134
  78. package/out/static/client/{styles-content-section.049c2352791fac9b.css → styles-content-section.cd5635d274b730e9.css} +2 -2
  79. package/out/static/client/{styles-content-section.049c2352791fac9b.css.map → styles-content-section.cd5635d274b730e9.css.map} +1 -1
  80. package/out/static/client/{styles-global.64aa0c760d2688b4.js → styles-global.2a17ab4cc47bca1f.js} +1 -1
  81. package/out/static/client/styles-global.79b14873ea7ab386.css +2 -0
  82. package/out/static/client/{styles-global.274bf4a7ba3e88ae.css.map → styles-global.79b14873ea7ab386.css.map} +1 -1
  83. package/out/static/ssr/9456.js +317 -1
  84. package/out/static/ssr/9456.js.map +1 -1
  85. package/out/static/ssr/index.js +209 -145
  86. package/out/static/ssr/index.js.map +1 -1
  87. package/out/static/ssr/stats.json +4 -4
  88. package/package.json +6 -6
  89. package/out/static/client/291.91650ffed3a60424.js.map +0 -1
  90. package/out/static/client/3259.ac8a5539251c647a.js.map +0 -1
  91. package/out/static/client/4920.033c028023478df5.js.map +0 -1
  92. package/out/static/client/5183.6c532a6e7b0d0bbb.js.map +0 -1
  93. package/out/static/client/544.1ed814181b24bcc8.js.map +0 -1
  94. package/out/static/client/5649.00f25ce181894101.js +0 -2
  95. package/out/static/client/5649.00f25ce181894101.js.map +0 -1
  96. package/out/static/client/5667.332b2c6c10792ad6.js.map +0 -1
  97. package/out/static/client/5685.46c8d178d072b8ac.js +0 -4
  98. package/out/static/client/5685.46c8d178d072b8ac.js.map +0 -1
  99. package/out/static/client/6002.60280d36f6ea71ff.js.map +0 -1
  100. package/out/static/client/6067.a60fb66b75579c98.js.map +0 -1
  101. package/out/static/client/8364.13d1d6770264c970.js.map +0 -1
  102. package/out/static/client/9324.61778c81f15bcebc.js +0 -26
  103. package/out/static/client/9456.dd8c12ddf52e2a7d.js +0 -3
  104. package/out/static/client/9456.dd8c12ddf52e2a7d.js.map +0 -1
  105. package/out/static/client/index.ae68bec0e2489b62.js +0 -221
  106. package/out/static/client/index.ae68bec0e2489b62.js.map +0 -1
  107. package/out/static/client/styles-global.274bf4a7ba3e88ae.css +0 -2
  108. /package/out/static/client/{291.91650ffed3a60424.js.LICENSE.txt → 291.c90e43b3c10a9a1f.js.LICENSE.txt} +0 -0
  109. /package/out/static/client/{5183.6c532a6e7b0d0bbb.js.LICENSE.txt → 5183.48a6059ffe2931f0.js.LICENSE.txt} +0 -0
  110. /package/out/static/client/{6067.a60fb66b75579c98.js.LICENSE.txt → 6067.169a9ec1f47871ae.js.LICENSE.txt} +0 -0
  111. /package/out/static/client/{8364.13d1d6770264c970.js.LICENSE.txt → 8364.f7862244a6ff6a1b.js.LICENSE.txt} +0 -0
  112. /package/out/static/client/{index.ae68bec0e2489b62.js.LICENSE.txt → index.c41c45d10cf2b50b.js.LICENSE.txt} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
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
+
3
24
  ## [2.5.0](https://github.com/mdn/fred/compare/v2.4.3...v2.5.0) (2026-04-23)
4
25
 
5
26
 
@@ -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>
@@ -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"
@@ -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
@@ -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;
@@ -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() {
@@ -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>
@@ -186,6 +186,7 @@ export class Footer extends ServerComponent {
186
186
  rel="noopener"
187
187
  aria-label=${item.ariaLabel}
188
188
  data-icon=${item.icon}
189
+ data-glean-id=${`footer: social -> ${item.icon}`}
189
190
  ></a>
190
191
  </li>
191
192
  `,
@@ -206,6 +207,7 @@ export class Footer extends ServerComponent {
206
207
  class=${link.external ? "external" : ""}
207
208
  target=${link.external ? "_blank" : undefined}
208
209
  rel=${link.external ? "noopener" : undefined}
210
+ data-glean-id=${`footer: link -> ${link.href}`}
209
211
  >
210
212
  ${link.text}
211
213
  </a>
@@ -230,7 +232,10 @@ export class Footer extends ServerComponent {
230
232
  ${mozillaLinks(context).map(
231
233
  (item) => html`
232
234
  <li>
233
- <a href=${item.href} class=${item.external ? "external" : ""}
235
+ <a
236
+ href=${item.href}
237
+ class=${item.external ? "external" : ""}
238
+ data-glean-id=${`footer: mozilla -> ${item.href}`}
234
239
  >${item.text}</a
235
240
  >
236
241
  </li>
@@ -80,6 +80,7 @@ export class GenericAbout extends ServerComponent {
80
80
  slot="tab"
81
81
  href="#${h3Value.id}"
82
82
  data-panel-id=${h3Value.id}
83
+ data-glean-id=${`about: tab -> ${h3Value.id}`}
83
84
  >
84
85
  ${h3Value.title}
85
86
  </a>
@@ -25,7 +25,12 @@ export class GenericToc extends ServerComponent {
25
25
  ${context.hyData.toc.map(
26
26
  ({ id, text }) =>
27
27
  html`<li class="generic-toc__item">
28
- <a class="generic-toc__link" href="#${id}">${text}</a>
28
+ <a
29
+ class="generic-toc__link"
30
+ href="#${id}"
31
+ data-glean-id="toc_click: #${id}"
32
+ >${text}</a
33
+ >
29
34
  </li>`,
30
35
  )}
31
36
  </ul>
@@ -1,5 +1,6 @@
1
1
  import { LitElement, html } from "lit";
2
2
 
3
+ import { gleanClick } from "../../utils/glean.js";
3
4
  import { setPreferredLocale } from "../preferred-locale/utils.js";
4
5
 
5
6
  export class MDNLanguageAlwaysRedirectButton extends LitElement {
@@ -15,6 +16,7 @@ export class MDNLanguageAlwaysRedirectButton extends LitElement {
15
16
  }
16
17
 
17
18
  _handleClick() {
19
+ gleanClick(`language: ${this.locale} -> ${this.to} (always)`);
18
20
  setPreferredLocale(this.to);
19
21
  const url = document.location.pathname.replace(
20
22
  `/${this.locale}/`,
@@ -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
 
6
7
  import infoIcon from "../icon/info.svg?lit";
7
8
  import { getEnglishDoc } from "../not-found/utils.js";
@@ -63,12 +64,15 @@ export class MDNLanguageSwitcher extends L10nMixin(LitElement) {
63
64
 
64
65
  _togglePreferredLocale() {
65
66
  if (this.notFound) return;
67
+ const oldValue = this._preferredLocale ?? "0";
66
68
  if (this._isLocalePreferred) {
67
69
  resetPreferredLocale();
68
70
  this._preferredLocale = undefined;
71
+ gleanClick(`language_remember: ${oldValue} -> 0`);
69
72
  } else {
70
73
  setPreferredLocale(this.locale);
71
74
  this._preferredLocale = this.locale;
75
+ gleanClick(`language_remember: ${oldValue} -> ${this.locale}`);
72
76
  }
73
77
  }
74
78
 
@@ -160,6 +164,7 @@ export class MDNLanguageSwitcher extends L10nMixin(LitElement) {
160
164
  `/${notFound ? "en-US" : locale}/`,
161
165
  `/${translation.locale}/`,
162
166
  )}
167
+ data-glean-id=${`language: ${locale} -> ${translation.locale}`}
163
168
  >${translation.native}</a
164
169
  >
165
170
  </li>
@@ -14,16 +14,23 @@ export class LatestNews extends ServerComponent {
14
14
 
15
15
  return html`<ul class="latest-news">
16
16
  ${items.map(
17
- (item) =>
17
+ (item, index) =>
18
18
  html`<li>
19
19
  <article class="latest-news__item">
20
20
  ${item.source
21
- ? html`<a class="latest-news__source" href=${item.source.url}
21
+ ? html`<a
22
+ class="latest-news__source"
23
+ href=${item.source.url}
24
+ data-glean-id=${`homepage: news_source ${index + 1} -> ${item.source.url}`}
22
25
  >${item.source.name}</a
23
26
  >`
24
27
  : nothing}
25
28
  <h3 class="latest-news__title">
26
- <a href=${item.url}>${item.title}</a>
29
+ <a
30
+ href=${item.url}
31
+ data-glean-id=${`homepage: news ${index + 1} -> ${item.url}`}
32
+ >${item.title}</a
33
+ >
27
34
  </h3>
28
35
  <time class="latest-news__date" datetime=${item.published_at}>
29
36
  ${rtf.format(
@@ -16,7 +16,7 @@ export class MDNLoginButton extends L10nMixin(LitElement) {
16
16
  }
17
17
 
18
18
  render() {
19
- return html`<mdn-button href=${this._loginUrl}
19
+ return html`<mdn-button href=${this._loginUrl} data-glean-id="login_button"
20
20
  >${this.l10n("login-button-login")`Login`}</mdn-button
21
21
  >`;
22
22
  }
@@ -71,7 +71,9 @@ export class Menu extends ServerComponent {
71
71
  data-glean-id=${`menu_click_link: top-level -> ${tab.href}`}
72
72
  >${tab.buttonText}</a
73
73
  >`
74
- : html`<mdn-dropdown>
74
+ : html`<mdn-dropdown
75
+ data-glean-toggle-open=${`menu_toggle: ${tab.id}`}
76
+ >
75
77
  <button class="menu__tab-button" type="button" slot="button">
76
78
  ${typeof tab.buttonText === "string"
77
79
  ? html`<span class="menu__tab-label"
@@ -1,6 +1,7 @@
1
1
  import { LitElement, html, nothing } from "lit";
2
2
  import { createRef, ref } from "lit/directives/ref.js";
3
3
 
4
+ import { gleanClick } from "../../utils/glean.js";
4
5
  import { OBSERVATORY_API_URL } from "../env/index.js";
5
6
  import "../progress-bar/element.js";
6
7
  import "../button/element.js";
@@ -61,6 +62,7 @@ export class MDNObservatoryForm extends LitElement {
61
62
  this._hostname = host;
62
63
  }
63
64
  this._queryRunning = true;
65
+ gleanClick("observatory: scan");
64
66
  try {
65
67
  const apiUrl = new URL(
66
68
  OBSERVATORY_API_URL +
@@ -77,6 +79,7 @@ export class MDNObservatoryForm extends LitElement {
77
79
  } catch (error) {
78
80
  // @ts-expect-error
79
81
  this._errorMessage = `${ERROR_MAP[error.name] || "message" in error ? error["message"] : error}`;
82
+ gleanClick(`observatory: error -> ${this._errorMessage}`);
80
83
  } finally {
81
84
  this._queryRunning = false;
82
85
  }
@@ -2,6 +2,7 @@ import { Task } from "@lit/task";
2
2
  import { LitElement, html } from "lit";
3
3
  import { nothing } from "lit";
4
4
 
5
+ import { gleanClick } from "../../utils/glean.js";
5
6
  import { OBSERVATORY_API_URL } from "../env/index.js";
6
7
 
7
8
  import styles from "./element.css?lit";
@@ -62,7 +63,9 @@ export class MDNObservatoryResults extends LitElement {
62
63
  }
63
64
  return await res.json();
64
65
  } catch (error) {
65
- throw new Error("Observatory API request for scan data failed", {
66
+ const message = "Observatory API request for scan data failed";
67
+ gleanClick(`observatory: error -> ${message}`);
68
+ throw new Error(message, {
66
69
  cause: error,
67
70
  });
68
71
  }
@@ -116,6 +119,7 @@ export class MDNObservatoryResults extends LitElement {
116
119
  if (!this.host) {
117
120
  return;
118
121
  }
122
+ gleanClick("observatory: rescan");
119
123
  this._usePostInApi = true;
120
124
  this._apiTask.run();
121
125
  }
@@ -127,6 +131,7 @@ export class MDNObservatoryResults extends LitElement {
127
131
  */
128
132
  _handleTabSelect(index, key) {
129
133
  this.selectedTab = index;
134
+ gleanClick(`observatory: tab -> ${key}`);
130
135
  globalThis.history.replaceState(
131
136
  "",
132
137
  "",
@@ -65,7 +65,12 @@ export function Rating({ result, host, rescan }) {
65
65
  @click=${rescan}
66
66
  ></mdn-observatory-rescan-button>
67
67
  <div class="scan-another">
68
- <a href="/en-US/observatory"> Scan another website </a>
68
+ <a
69
+ href="/en-US/observatory"
70
+ data-glean-id="observatory: scan-another"
71
+ >
72
+ Scan another website
73
+ </a>
69
74
  </div>
70
75
  </section>
71
76
  </section>
@@ -1,12 +1,14 @@
1
1
  import { LitElement, html, nothing } from "lit";
2
- import { ref } from "lit/directives/ref.js";
2
+ import { createRef, ref } from "lit/directives/ref.js";
3
3
  import { styleMap } from "lit/directives/style-map.js";
4
4
 
5
+ import { gleanClick } from "../../utils/glean.js";
6
+ import { PlacementMixin } from "../placement/mixin.js";
7
+ import { ViewedController } from "../viewed-controller/viewed-controller.js";
8
+
5
9
  import "../placement-note/element.js";
6
10
  import "../placement-no/element.js";
7
11
 
8
- import { PlacementMixin } from "../placement/mixin.js";
9
-
10
12
  import styles from "./element.css?lit";
11
13
 
12
14
  /**
@@ -17,6 +19,16 @@ import styles from "./element.css?lit";
17
19
  export class MDNPlacementTop extends PlacementMixin(LitElement) {
18
20
  static styles = styles;
19
21
 
22
+ /** @type {import("lit/directives/ref.js").Ref<HTMLElement>} */
23
+ _fallbackRef = createRef();
24
+
25
+ constructor() {
26
+ super();
27
+ new ViewedController(this, this._fallbackRef, () => {
28
+ gleanClick("banner_scrimba_view");
29
+ });
30
+ }
31
+
20
32
  /**
21
33
  *
22
34
  * @returns {TemplateResult | symbol}
@@ -27,7 +39,7 @@ export class MDNPlacementTop extends PlacementMixin(LitElement) {
27
39
 
28
40
  renderFallback() {
29
41
  return html`
30
- <div class="fallback">
42
+ <div ${ref(this._fallbackRef)} class="fallback">
31
43
  <p class="fallback__copy">
32
44
  Learn frontend, backend, and AI from our course partner
33
45
  <a
@@ -342,6 +342,7 @@ ${"```"}`,
342
342
  @click=${this._share}
343
343
  ?disabled=${!hasCode}
344
344
  data-id="share"
345
+ data-glean-id="playground: share-click"
345
346
  >${this.l10n("playground-share")`Share`}</mdn-button
346
347
  >
347
348
  <mdn-button
@@ -356,6 +357,7 @@ ${"```"}`,
356
357
  variant="secondary"
357
358
  @click=${this._reset}
358
359
  ?disabled=${!isResettable}
360
+ data-glean-id="playground: reset-click"
359
361
  >${this.l10n("playground-reset")`Reset`}</mdn-button
360
362
  >`
361
363
  : nothing}
@@ -391,6 +393,7 @@ ${"```"}`,
391
393
  @click=${this._reportOpen}
392
394
  variant="secondary"
393
395
  .icon=${warningIcon}
396
+ data-glean-id="playground: flag-click"
394
397
  >
395
398
  ${this.l10n(
396
399
  "playground-seeing-something-inappropriate",
@@ -437,8 +440,14 @@ ${"```"}`,
437
440
  )`Share your code via Permalink`}
438
441
  </h2>
439
442
  ${this._user.render({
440
- initial: () => html`<mdn-login-button></mdn-login-button>`,
441
- pending: () => html`<mdn-login-button></mdn-login-button>`,
443
+ initial: () =>
444
+ html`<mdn-login-button
445
+ data-glean-id="playground: banner-login"
446
+ ></mdn-login-button>`,
447
+ pending: () =>
448
+ html`<mdn-login-button
449
+ data-glean-id="playground: banner-login"
450
+ ></mdn-login-button>`,
442
451
  complete: (user) =>
443
452
  user.isAuthenticated
444
453
  ? this._permalink && !isResettable
@@ -453,12 +462,16 @@ ${"```"}`,
453
462
  )`Copy to clipboard`}</mdn-button
454
463
  >
455
464
  `
456
- : html`<mdn-button @click=${this._createPermalink}
465
+ : html`<mdn-button
466
+ @click=${this._createPermalink}
467
+ data-glean-id="playground: share-permalink"
457
468
  >${this.l10n(
458
469
  "playground-create-link",
459
470
  )`Create link`}</mdn-button
460
471
  >`
461
- : html`<mdn-login-button></mdn-login-button>`,
472
+ : html`<mdn-login-button
473
+ data-glean-id="playground: banner-login"
474
+ ></mdn-login-button>`,
462
475
  })}
463
476
  </section>
464
477
  </mdn-modal>
@@ -33,7 +33,10 @@ export class RecentContributions extends ServerComponent {
33
33
  )}
34
34
  </time>
35
35
  <div class="recent-contributions__title">
36
- <a class="external" href=${contribution.url}
36
+ <a
37
+ class="external"
38
+ href=${contribution.url}
39
+ data-glean-id=${`homepage: contribution ${index + 1} -> ${contribution.url}`}
37
40
  >${contribution.title}</a
38
41
  >
39
42
  </div>
@@ -21,7 +21,11 @@ export class ReferenceToc extends ServerComponent {
21
21
  <ul>
22
22
  ${toc?.map(
23
23
  ({ id, text }) =>
24
- html`<li><a href="#${id}">${unsafeHTML(text)}</a></li>`,
24
+ html`<li>
25
+ <a href="#${id}" data-glean-id="toc_click: #${id}"
26
+ >${unsafeHTML(text)}</a
27
+ >
28
+ </li>`,
25
29
  )}
26
30
  </ul>
27
31
  </nav>`;