@khanacademy/perseus 12.4.0 → 13.1.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.
@@ -16,7 +16,9 @@ KeypadProps & {
16
16
  item: PerseusItem | any;
17
17
  onShowCalculator?: () => unknown;
18
18
  onShowChi2Table?: () => unknown;
19
- onShowPeriodicTable?: () => unknown;
19
+ onShowPeriodicTable?: (options?: {
20
+ showKey: boolean;
21
+ }) => unknown;
20
22
  onShowTTable?: () => unknown;
21
23
  onShowZTable?: () => unknown;
22
24
  problemNum: number;
@@ -413,17 +413,19 @@ export type PerseusPassageRefWidgetOptions = {
413
413
  referenceNumber: number;
414
414
  summaryText: string;
415
415
  };
416
+ export declare const plotterPlotTypes: readonly ["bar", "line", "pic", "histogram", "dotplot"];
417
+ export type PlotType = typeof plotterPlotTypes[number];
416
418
  export type PerseusPlotterWidgetOptions = {
417
419
  labels: ReadonlyArray<string>;
418
420
  categories: ReadonlyArray<string>;
419
- type: string;
421
+ type: PlotType;
420
422
  maxY: number;
421
423
  scaleY: number;
422
424
  labelInterval: number | null | undefined;
423
425
  snapsPerLine: number;
424
426
  starting: ReadonlyArray<number>;
425
427
  correct: ReadonlyArray<number>;
426
- picUrl: number | null | undefined;
428
+ picUrl: string | null | undefined;
427
429
  picSize: number | null | undefined;
428
430
  picBoxHeight: number | null | undefined;
429
431
  plotDimensions: ReadonlyArray<number>;
@@ -0,0 +1,3 @@
1
+ export declare const strings: {
2
+ hideAnswersToggleLabel: string;
3
+ };
@@ -1,25 +1,11 @@
1
1
  import * as React from "react";
2
- import type { APIOptions } from "../types";
3
- declare const plotTypes: string[];
4
- type PlotType = typeof plotTypes[number];
5
- type Props = {
6
- type: PlotType;
7
- labels: ReadonlyArray<string>;
8
- categories: ReadonlyArray<string | number>;
9
- scaleY: number;
10
- maxY: number;
11
- snapsPerLine: number;
12
- picSize: number;
13
- picBoxHeight: number;
14
- picUrl: string;
15
- plotDimensions: ReadonlyArray<number>;
16
- labelInterval: number;
17
- starting: Array<number>;
18
- static: boolean;
19
- markings: any;
20
- onChange: any;
21
- trackInteraction: () => void;
22
- apiOptions: APIOptions;
2
+ import type { PerseusPlotterWidgetOptions } from "../perseus-types";
3
+ import type { WidgetProps } from "../types";
4
+ type Rubric = PerseusPlotterWidgetOptions;
5
+ type RenderProps = PerseusPlotterWidgetOptions;
6
+ type Props = WidgetProps<RenderProps, Rubric> & {
7
+ labelInterval: NonNullable<PerseusPlotterWidgetOptions["labelInterval"]>;
8
+ picSize: NonNullable<PerseusPlotterWidgetOptions["picSize"]>;
23
9
  };
24
10
  type DefaultProps = {
25
11
  type: Props["type"];
@@ -35,7 +21,7 @@ type DefaultProps = {
35
21
  labelInterval: Props["labelInterval"];
36
22
  };
37
23
  type State = {
38
- values: Array<number>;
24
+ values: ReadonlyArray<number>;
39
25
  categoryHeights: Record<string, number>;
40
26
  };
41
27
  export declare class Plotter extends React.Component<Props, State> {
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Core Perseus API (includes renderers and widgets)",
4
4
  "author": "Khan Academy",
5
5
  "license": "MIT",
6
- "version": "12.4.0",
6
+ "version": "13.1.0",
7
7
  "publishConfig": {
8
8
  "access": "public"
9
9
  },
@@ -24,7 +24,7 @@
24
24
  "dependencies": {
25
25
  "@khanacademy/kas": "^0.3.5",
26
26
  "@khanacademy/kmath": "^0.1.6",
27
- "@khanacademy/math-input": "^14.2.2",
27
+ "@khanacademy/math-input": "^15.0.1",
28
28
  "@khanacademy/perseus-core": "1.3.0",
29
29
  "@khanacademy/perseus-linter": "^0.3.8",
30
30
  "@khanacademy/pure-markdown": "^0.2.10",
@@ -48,6 +48,7 @@
48
48
  "@khanacademy/wonder-blocks-tooltip": "^2.1.2",
49
49
  "@khanacademy/wonder-blocks-typography": "^2.1.3",
50
50
  "@khanacademy/wonder-stuff-core": "^1.5.1",
51
+ "@phosphor-icons/core": "^2.0.2",
51
52
  "aphrodite": "^1.2.5",
52
53
  "classnames": "^1.1.4",
53
54
  "create-react-class": "^15.6.3",
@@ -82,6 +83,7 @@
82
83
  "@khanacademy/wonder-blocks-tooltip": "^2.1.2",
83
84
  "@khanacademy/wonder-blocks-typography": "^2.1.3",
84
85
  "@khanacademy/wonder-stuff-core": "^1.5.1",
86
+ "@phosphor-icons/core": "^2.0.2",
85
87
  "aphrodite": "^1.2.5",
86
88
  "classnames": "^1.1.4",
87
89
  "create-react-class": "^15.6.3",
@@ -805,6 +805,47 @@ describe("item renderer", () => {
805
805
  expect(onShowTTable).toHaveBeenCalled();
806
806
  expect(onShowChi2Table).toHaveBeenCalled();
807
807
  });
808
+
809
+ it("should call onShowPeriodicTable with showKey option if selected", () => {
810
+ // Arrange
811
+ const item = {
812
+ ...itemWithInput,
813
+ answerArea: {
814
+ zTable: false,
815
+ chi2Table: false,
816
+ tTable: false,
817
+ calculator: false,
818
+ periodicTable: true,
819
+ periodicTableWithKey: true,
820
+ },
821
+ } as const;
822
+
823
+ const onShowCalculator = jest.fn();
824
+ const onShowPeriodicTable = jest.fn();
825
+ const onShowZTable = jest.fn();
826
+ const onShowTTable = jest.fn();
827
+ const onShowChi2Table = jest.fn();
828
+ const localExtraProps = {
829
+ ...extraProps,
830
+ onShowCalculator,
831
+ onShowPeriodicTable,
832
+ onShowZTable,
833
+ onShowTTable,
834
+ onShowChi2Table,
835
+ } as const;
836
+
837
+ // Act
838
+ renderQuestion(item, undefined, localExtraProps);
839
+
840
+ // Assert
841
+ expect(onShowCalculator).not.toHaveBeenCalled();
842
+ expect(onShowZTable).not.toHaveBeenCalled();
843
+ expect(onShowTTable).not.toHaveBeenCalled();
844
+ expect(onShowChi2Table).not.toHaveBeenCalled();
845
+ expect(onShowPeriodicTable).toHaveBeenCalledWith(
846
+ expect.objectContaining({showKey: true}),
847
+ );
848
+ });
808
849
  });
809
850
 
810
851
  describe("controlPeripherals is enabled", () => {
package/src/index.ts CHANGED
@@ -62,7 +62,10 @@ export {default as KhanMath} from "./util/math";
62
62
  export {default as LoadingContext} from "./loading-context";
63
63
  export {default as mediaQueries} from "./styles/media-queries";
64
64
  export {default as PerseusMarkdown} from "./perseus-markdown";
65
- export {PerseusExpressionAnswerFormConsidered} from "./perseus-types";
65
+ export {
66
+ PerseusExpressionAnswerFormConsidered,
67
+ plotterPlotTypes,
68
+ } from "./perseus-types";
66
69
  export {traverse} from "./traversal";
67
70
  export {isItemRenderableByVersion} from "./renderability";
68
71
  export {violatingWidgets} from "./a11y";
@@ -123,6 +126,7 @@ export type {
123
126
  PerseusInteractiveGraphWidgetOptions,
124
127
  PerseusRadioWidgetOptions,
125
128
  PerseusExpressionWidgetOptions,
129
+ PerseusPlotterWidgetOptions,
126
130
  PerseusRenderer,
127
131
  } from "./perseus-types";
128
132
  export type {Coord} from "./interactive2/types";
@@ -39,7 +39,7 @@ type Props = // These props are used by the ProvideKeypad mixin.
39
39
  item: PerseusItem | any /* any for _multi items */;
40
40
  onShowCalculator?: () => unknown;
41
41
  onShowChi2Table?: () => unknown;
42
- onShowPeriodicTable?: () => unknown;
42
+ onShowPeriodicTable?: (options?: {showKey: boolean}) => unknown;
43
43
  onShowTTable?: () => unknown;
44
44
  onShowZTable?: () => unknown;
45
45
  problemNum: number;
@@ -254,7 +254,9 @@ class ItemRenderer extends React.Component<Props, State> {
254
254
  }
255
255
  if (answerArea.periodicTable) {
256
256
  this.props.onShowPeriodicTable &&
257
- this.props.onShowPeriodicTable();
257
+ this.props.onShowPeriodicTable({
258
+ showKey: answerArea.periodicTableWithKey,
259
+ });
258
260
  }
259
261
  if (answerArea.zTable) {
260
262
  this.props.onShowZTable && this.props.onShowZTable();
@@ -848,13 +848,22 @@ export type PerseusPassageRefWidgetOptions = {
848
848
  summaryText: string;
849
849
  };
850
850
 
851
+ export const plotterPlotTypes = [
852
+ "bar",
853
+ "line",
854
+ "pic",
855
+ "histogram",
856
+ "dotplot",
857
+ ] as const;
858
+ export type PlotType = typeof plotterPlotTypes[number];
859
+
851
860
  export type PerseusPlotterWidgetOptions = {
852
861
  // Translatable Text; The Axis labels. e.g. ["X Label", "Y Label"]
853
862
  labels: ReadonlyArray<string>;
854
863
  // Translatable Text; Categories to display along the X access. e.g. [">0", ">6", ">12", ">18"]
855
864
  categories: ReadonlyArray<string>;
856
865
  // The type of the graph. options "bar", "line", "pic", "histogram", "dotplot"
857
- type: string;
866
+ type: PlotType;
858
867
  // The maximimum Y tick to display in the graph
859
868
  maxY: number;
860
869
  // The scale of the Y Axis
@@ -868,7 +877,7 @@ export type PerseusPlotterWidgetOptions = {
868
877
  // The Y values that represent the correct answer expected
869
878
  correct: ReadonlyArray<number>;
870
879
  // A picture to represent items in a graph.
871
- picUrl: number | null | undefined;
880
+ picUrl: string | null | undefined;
872
881
  // deprecated
873
882
  picSize: number | null | undefined;
874
883
  // deprecated
@@ -1,7 +1,7 @@
1
1
  import * as React from "react";
2
2
 
3
3
  import {RendererWithDebugUI} from "../../../../../testing/renderer-with-debug-ui";
4
- import {question1} from "../__testdata__/video.testdata";
4
+ import {question1, question2, question3} from "../__testdata__/video.testdata";
5
5
 
6
6
  export default {
7
7
  title: "Perseus/Widgets/Video",
@@ -12,3 +12,11 @@ type StoryArgs = Record<any, any>;
12
12
  export const Question1 = (args: StoryArgs): React.ReactElement => {
13
13
  return <RendererWithDebugUI question={question1} />;
14
14
  };
15
+
16
+ export const Question2 = (args: StoryArgs): React.ReactElement => {
17
+ return <RendererWithDebugUI question={question2} />;
18
+ };
19
+
20
+ export const Question3 = (args: StoryArgs): React.ReactElement => {
21
+ return <RendererWithDebugUI question={question3} />;
22
+ };
@@ -19,3 +19,43 @@ export const question1: PerseusRenderer = {
19
19
  },
20
20
  replace: false,
21
21
  };
22
+
23
+ export const question2: PerseusRenderer = {
24
+ content:
25
+ "Watch the WMF at Angkor Wat video to find the answer.\n\n[[\u2603 video 1]]\n\n",
26
+ images: {},
27
+ widgets: {
28
+ "video 1": {
29
+ graded: true,
30
+ version: {major: 0, minor: 0},
31
+ static: false,
32
+ type: "video",
33
+ options: {
34
+ static: false,
35
+ location: "https://player.vimeo.com/video/4754041",
36
+ },
37
+ alignment: "block",
38
+ },
39
+ },
40
+ replace: false,
41
+ };
42
+
43
+ export const question3: PerseusRenderer = {
44
+ content:
45
+ "Watch the WMF at Angkor Wat video to find the answer.\n\n[[\u2603 video 1]]\n\n",
46
+ images: {},
47
+ widgets: {
48
+ "video 1": {
49
+ graded: true,
50
+ version: {major: 0, minor: 0},
51
+ static: false,
52
+ type: "video",
53
+ options: {
54
+ static: false,
55
+ location: "https://player.vimeo.com/video/4754041?h=64fbc32a6e",
56
+ },
57
+ alignment: "block",
58
+ },
59
+ },
60
+ replace: false,
61
+ };
@@ -4,13 +4,12 @@ import React from "react";
4
4
 
5
5
  import {testDependencies} from "../../../../../testing/test-dependencies";
6
6
  import {setDependencies} from "../../dependencies";
7
+ import {ApiOptions} from "../../perseus-api";
7
8
  import {question1} from "../__testdata__/plotter.testdata";
8
9
  import {Plotter} from "../plotter";
9
10
 
10
11
  import {renderQuestion} from "./renderQuestion";
11
12
 
12
- import type {APIOptions} from "../../types";
13
-
14
13
  describe("plotter widget", () => {
15
14
  beforeEach(() => {
16
15
  setDependencies(testDependencies);
@@ -26,15 +25,17 @@ describe("plotter widget", () => {
26
25
 
27
26
  it("should show drag text when not static", () => {
28
27
  // Arrange and Act
29
- const apiOptions: APIOptions = {isMobile: true};
30
28
  render(
29
+ // @ts-expect-error - TS2769 - test works, but I can't figure out how to make the types happy
31
30
  <Plotter
32
31
  static={false}
33
32
  trackInteraction={() => {}}
34
33
  onChange={() => {}}
35
- apiOptions={apiOptions}
34
+ apiOptions={{
35
+ ...ApiOptions.defaults,
36
+ isMobile: true,
37
+ }}
36
38
  starting={[0]}
37
- markings={""}
38
39
  />,
39
40
  );
40
41
 
@@ -46,15 +47,17 @@ describe("plotter widget", () => {
46
47
 
47
48
  it("should not show drag text when static", () => {
48
49
  // Arrange and Act
49
- const apiOptions: APIOptions = {isMobile: true};
50
50
  render(
51
+ // @ts-expect-error - TS2769 - test works, but I can't figure out how to make the types happy
51
52
  <Plotter
52
53
  static={true}
53
54
  trackInteraction={() => {}}
54
55
  onChange={() => {}}
55
- apiOptions={apiOptions}
56
+ apiOptions={{
57
+ ...ApiOptions.defaults,
58
+ isMobile: true,
59
+ }}
56
60
  starting={[0]}
57
- markings={""}
58
61
  />,
59
62
  );
60
63
 
@@ -2,7 +2,7 @@ import "@testing-library/jest-dom";
2
2
 
3
3
  import {testDependencies} from "../../../../../testing/test-dependencies";
4
4
  import * as Dependencies from "../../dependencies";
5
- import {question1} from "../__testdata__/video.testdata";
5
+ import {question1, question2} from "../__testdata__/video.testdata";
6
6
 
7
7
  import {renderQuestion} from "./renderQuestion";
8
8
 
@@ -50,4 +50,20 @@ describe("video widget", () => {
50
50
  // Assert
51
51
  expect(container).toMatchSnapshot("first mobile render");
52
52
  });
53
+
54
+ it("vimeo widget should contain dnt param", () => {
55
+ // Arrange
56
+ const apiOptions: APIOptions = {
57
+ isMobile: false,
58
+ };
59
+
60
+ // Act
61
+ renderQuestion(question2, apiOptions);
62
+
63
+ // Assert
64
+ // eslint-disable-next-line testing-library/no-node-access
65
+ expect(document.getElementsByTagName("iframe")[0].src).toContain(
66
+ "dnt=1",
67
+ );
68
+ });
53
69
  });
@@ -0,0 +1,3 @@
1
+ export const strings = {
2
+ hideAnswersToggleLabel: "Hide answer choices",
3
+ };
@@ -13,38 +13,17 @@ import KhanColors from "../util/colors";
13
13
  import GraphUtils from "../util/graph-utils";
14
14
  import KhanMath from "../util/math";
15
15
 
16
- import type {APIOptions, WidgetExports} from "../types";
16
+ import type {PerseusPlotterWidgetOptions} from "../perseus-types";
17
+ import type {WidgetExports, WidgetProps} from "../types";
17
18
 
18
19
  const {deepEq} = Util;
19
20
 
20
- const BAR = "bar";
21
- const LINE = "line";
22
- const PIC = "pic";
23
- const HISTOGRAM = "histogram";
24
- const DOTPLOT = "dotplot";
25
-
26
- const plotTypes = [BAR, LINE, PIC, HISTOGRAM, DOTPLOT];
27
- type PlotType = typeof plotTypes[number];
28
-
29
- type Props = {
30
- type: PlotType;
31
- labels: ReadonlyArray<string>;
32
- categories: ReadonlyArray<string | number>;
33
- scaleY: number;
34
- maxY: number;
35
- snapsPerLine: number;
36
- picSize: number;
37
- picBoxHeight: number;
38
- picUrl: string;
39
- plotDimensions: ReadonlyArray<number>;
40
- labelInterval: number;
41
-
42
- starting: Array<number>;
43
- static: boolean;
44
- markings: any;
45
- onChange: any;
46
- trackInteraction: () => void;
47
- apiOptions: APIOptions;
21
+ type Rubric = PerseusPlotterWidgetOptions;
22
+ type RenderProps = PerseusPlotterWidgetOptions;
23
+
24
+ type Props = WidgetProps<RenderProps, Rubric> & {
25
+ labelInterval: NonNullable<PerseusPlotterWidgetOptions["labelInterval"]>;
26
+ picSize: NonNullable<PerseusPlotterWidgetOptions["picSize"]>;
48
27
  };
49
28
 
50
29
  type DefaultProps = {
@@ -62,7 +41,7 @@ type DefaultProps = {
62
41
  };
63
42
 
64
43
  type State = {
65
- values: Array<number>;
44
+ values: ReadonlyArray<number>;
66
45
  categoryHeights: Record<string, number>;
67
46
  };
68
47
 
@@ -75,7 +54,7 @@ export class Plotter extends React.Component<Props, State> {
75
54
  graphie: any;
76
55
 
77
56
  static defaultProps: DefaultProps = {
78
- type: BAR,
57
+ type: "bar",
79
58
  labels: ["", ""],
80
59
  categories: [""],
81
60
 
@@ -175,11 +154,11 @@ export class Plotter extends React.Component<Props, State> {
175
154
  self.graphie.pics = [];
176
155
  self.graphie.dotTicks = [];
177
156
 
178
- const isBar = self.props.type === BAR;
179
- const isLine = self.props.type === LINE;
180
- const isPic = self.props.type === PIC;
181
- const isHistogram = self.props.type === HISTOGRAM;
182
- const isDotplot = self.props.type === DOTPLOT;
157
+ const isBar = self.props.type === "bar";
158
+ const isLine = self.props.type === "line";
159
+ const isPic = self.props.type === "pic";
160
+ const isHistogram = self.props.type === "histogram";
161
+ const isDotplot = self.props.type === "dotplot";
183
162
 
184
163
  const isTiledPlot = isPic || isDotplot;
185
164
 
@@ -462,7 +441,7 @@ export class Plotter extends React.Component<Props, State> {
462
441
  };
463
442
 
464
443
  showHairlines: (arg1: any) => void = (point) => {
465
- if (this.props.apiOptions.isMobile && this.props.markings !== "none") {
444
+ if (this.props.apiOptions.isMobile) {
466
445
  // Hairlines are already initialized when the graph is loaded, so
467
446
  // here we just move them to the updated location and make them
468
447
  // visible.
@@ -546,7 +525,7 @@ export class Plotter extends React.Component<Props, State> {
546
525
  // The deferred measurements returned from `labelCategory`.
547
526
  const categoryHeightPromises = [];
548
527
 
549
- if (self.props.type === HISTOGRAM) {
528
+ if (self.props.type === "histogram") {
550
529
  // Histograms with n labels/categories have n - 1 buckets
551
530
  _.times(self.props.categories.length - 1, function (i) {
552
531
  self.setupBar({
@@ -580,29 +559,29 @@ export class Plotter extends React.Component<Props, State> {
580
559
  const startHeight = self.state.values[i];
581
560
  let x;
582
561
 
583
- if (self.props.type === BAR) {
562
+ if (self.props.type === "bar") {
584
563
  x = self.setupBar({
585
564
  index: i,
586
565
  startHeight: startHeight,
587
566
  config: config,
588
567
  isHistogram: false,
589
568
  });
590
- } else if (self.props.type === LINE) {
569
+ } else if (self.props.type === "line") {
591
570
  x = self.setupLine(i, startHeight, config);
592
- } else if (self.props.type === PIC) {
571
+ } else if (self.props.type === "pic") {
593
572
  x = self.setupPic(i, config);
594
- } else if (self.props.type === DOTPLOT) {
573
+ } else if (self.props.type === "dotplot") {
595
574
  x = self.setupDotplot(i, config);
596
575
  }
597
576
 
598
577
  let tickStart = 0;
599
578
  let tickEnd = -6 / c.scale[1];
600
579
 
601
- if (self.props.type === DOTPLOT && !isMobile) {
580
+ if (self.props.type === "dotplot" && !isMobile) {
602
581
  tickStart = -tickEnd;
603
582
  }
604
583
 
605
- if (self.props.type === DOTPLOT) {
584
+ if (self.props.type === "dotplot") {
606
585
  // Dotplot lets you specify to only show labels every 'n'
607
586
  // ticks. It also looks nicer if it makes the labelled
608
587
  // ticks a bit bigger.
@@ -804,7 +783,7 @@ export class Plotter extends React.Component<Props, State> {
804
783
  onMove: function () {
805
784
  const y = config.graph.lines[i].coord()[1];
806
785
 
807
- const values = _.clone(self.state.values);
786
+ const values = [...self.state.values];
808
787
  values[i] = y;
809
788
  self.setState({values: values});
810
789
  self.changeAndTrack({values: values});
@@ -851,7 +830,7 @@ export class Plotter extends React.Component<Props, State> {
851
830
  this.transform();
852
831
  }
853
832
 
854
- const values = _.clone(self.state.values);
833
+ const values = [...self.state.values];
855
834
  values[i] = y;
856
835
  self.setState({values: values});
857
836
  self.changeAndTrack({values: values});
@@ -901,7 +880,7 @@ export class Plotter extends React.Component<Props, State> {
901
880
  onMove: function () {
902
881
  const y = c.graph.points[i].coord()[1];
903
882
 
904
- const values = _.clone(self.state.values);
883
+ const values = [...self.state.values];
905
884
  values[i] = y;
906
885
  self.setState({values: values});
907
886
  self.changeAndTrack({values: values});
@@ -941,7 +920,7 @@ export class Plotter extends React.Component<Props, State> {
941
920
  });
942
921
  c.graph.points[i].onMove = function (x, y) {
943
922
  y = Math.min(Math.max(y, 0), c.dimY);
944
- const values = _.clone(self.state.values);
923
+ const values = [...self.state.values];
945
924
  values[i] = y;
946
925
  self.setState({values: values});
947
926
  self.changeAndTrack({values: values});
@@ -1095,7 +1074,7 @@ export class Plotter extends React.Component<Props, State> {
1095
1074
  };
1096
1075
 
1097
1076
  setPicHeight: (arg1: number, arg2: number) => void = (i, y) => {
1098
- const values = _.clone(this.state.values);
1077
+ const values = [...this.state.values];
1099
1078
  values[i] = y;
1100
1079
  this.drawPicHeights(values, this.state.values);
1101
1080
  this.setState({values: values});
@@ -1126,7 +1105,7 @@ export class Plotter extends React.Component<Props, State> {
1126
1105
  const y = (j + 1) * self.props.scaleY;
1127
1106
  const show = y <= values[i];
1128
1107
 
1129
- if (self.props.type === DOTPLOT) {
1108
+ if (self.props.type === "dotplot") {
1130
1109
  const wasShown = y <= prevValues[i];
1131
1110
  const wasJustShown = show && !wasShown;
1132
1111
  if (wasJustShown) {
@@ -24,6 +24,7 @@ const DEFAULT_HEIGHT = 720;
24
24
  const KA_EMBED = "{host}/embed_video?slug={slug}" + "&internal_video_only=1";
25
25
  const IS_URL = /^https?:\/\//;
26
26
  const IS_KA_SITE = /(khanacademy\.org|localhost)/;
27
+ const IS_VIMEO = /(vimeo\.com)/;
27
28
 
28
29
  type UserInput = null;
29
30
  type Rubric = PerseusVideoWidgetOptions;
@@ -75,6 +76,16 @@ class Video extends React.Component<Props> {
75
76
 
76
77
  if (IS_URL.test(location)) {
77
78
  url = location;
79
+ if (IS_VIMEO.test(url)) {
80
+ // If this is a vimeo video, we need to add the query string
81
+ // parameter "dnt" so that analytics/tracking cookies aren't set.
82
+ // https://help.vimeo.com/hc/en-us/articles/12426260232977-Player-parameters-overview
83
+ if (url.indexOf("?") === -1) {
84
+ url += "?dnt=1";
85
+ } else {
86
+ url += "&dnt=1";
87
+ }
88
+ }
78
89
  } else {
79
90
  url = KA_EMBED.replace("{slug}", location);
80
91
  let embedHostname = "https://www.khanacademy.org";