education-playlists 0.1.36 → 0.1.37
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.
- package/README.md +3 -0
- package/dist/components/EducationPlaylists.d.ts +9 -2
- package/dist/components/PlaylistView.d.ts +4 -2
- package/dist/components/styles.d.ts +1 -0
- package/dist/index.cjs +28 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +28 -15
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,6 +22,9 @@ the answers into searches and the searches into a playlist.
|
|
|
22
22
|
community list or AI search, and whether the URL has been verified.
|
|
23
23
|
- **Quiz hook.** Pass a `quizGenerator` (for example NotebookLM over the item's
|
|
24
24
|
material) and each item gets a "Quiz me" button.
|
|
25
|
+
- **Open items in the host.** Pass `onOpenItem` and clicking an item calls it
|
|
26
|
+
instead of opening the page; a small ↗ beside the title still opens the page
|
|
27
|
+
in a new tab. qwksearch uses this to read the page and ask about it.
|
|
25
28
|
|
|
26
29
|
The bundled catalog is a hand-curated seed of 28 MIT OpenCourseWare courses in
|
|
27
30
|
11 playlists. Course pages link to their canonical `ocw.mit.edu/courses/…`
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* `compact` it is the full-page view.
|
|
9
9
|
*/
|
|
10
10
|
import React from 'react';
|
|
11
|
-
import type { EducationCatalog } from '../types';
|
|
11
|
+
import type { EducationCatalog, PlaylistItem } from '../types';
|
|
12
12
|
import type { QuizGenerator } from '../quiz';
|
|
13
13
|
export interface EducationPlaylistsProps {
|
|
14
14
|
className?: string;
|
|
@@ -40,5 +40,12 @@ export interface EducationPlaylistsProps {
|
|
|
40
40
|
playlistsEndpoint?: string;
|
|
41
41
|
/** Turns on "Quiz me" for each item (NotebookLM or another quiz source). */
|
|
42
42
|
quizGenerator?: QuizGenerator;
|
|
43
|
+
/**
|
|
44
|
+
* Called when the learner clicks an item, instead of opening its page. The
|
|
45
|
+
* host can read the page and start a conversation about it (qwksearch opens
|
|
46
|
+
* its article reader, which extracts the page's text and asks about it).
|
|
47
|
+
* Without it, items are plain links that open in a new tab.
|
|
48
|
+
*/
|
|
49
|
+
onOpenItem?: (item: PlaylistItem) => void;
|
|
43
50
|
}
|
|
44
|
-
export declare function EducationPlaylists({ className, style, compact, catalog: catalogProp, planEndpoint, openHref, shareBaseHref, importFromHash, userId, playlistsEndpoint, quizGenerator, }: EducationPlaylistsProps): React.JSX.Element;
|
|
51
|
+
export declare function EducationPlaylists({ className, style, compact, catalog: catalogProp, planEndpoint, openHref, shareBaseHref, importFromHash, userId, playlistsEndpoint, quizGenerator, onOpenItem, }: EducationPlaylistsProps): React.JSX.Element;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* sharing controls.
|
|
5
5
|
*/
|
|
6
6
|
import React from 'react';
|
|
7
|
-
import type { Playlist } from '../types';
|
|
7
|
+
import type { Playlist, PlaylistItem } from '../types';
|
|
8
8
|
import type { QuizGenerator } from '../quiz';
|
|
9
9
|
export interface PlaylistViewProps {
|
|
10
10
|
playlist: Playlist;
|
|
@@ -23,5 +23,7 @@ export interface PlaylistViewProps {
|
|
|
23
23
|
/** Saved to a server-side account, so invites reach the people invited. */
|
|
24
24
|
inAccount?: boolean;
|
|
25
25
|
quizGenerator?: QuizGenerator;
|
|
26
|
+
/** Called instead of following an item's link; see `EducationPlaylistsProps.onOpenItem`. */
|
|
27
|
+
onOpenItem?: (item: PlaylistItem) => void;
|
|
26
28
|
}
|
|
27
|
-
export declare function PlaylistView({ playlist, done, onToggle, onChange, onSaveCopy, minutesPerWeek, shareBaseHref, canShare, inAccount, quizGenerator }: PlaylistViewProps): React.JSX.Element;
|
|
29
|
+
export declare function PlaylistView({ playlist, done, onToggle, onChange, onSaveCopy, minutesPerWeek, shareBaseHref, canShare, inAccount, quizGenerator, onOpenItem }: PlaylistViewProps): React.JSX.Element;
|
package/dist/index.cjs
CHANGED
|
@@ -944,6 +944,7 @@ var s = {
|
|
|
944
944
|
list: { listStyle: "none", margin: 0, padding: 0, display: "flex", flexDirection: "column", gap: 4 },
|
|
945
945
|
item: { display: "flex", alignItems: "flex-start", gap: 8, padding: "6px 4px", borderBottom: "1px solid rgba(127,127,127,0.15)" },
|
|
946
946
|
link: { color: "inherit", textDecoration: "none", fontWeight: 500 },
|
|
947
|
+
textButton: { border: "none", background: "transparent", padding: 0, font: "inherit", textAlign: "left", cursor: "pointer", minWidth: 0 },
|
|
947
948
|
badge: { fontSize: 10, border: line, borderRadius: 4, padding: "0 5px", opacity: 0.8, whiteSpace: "nowrap" },
|
|
948
949
|
section: { display: "flex", flexDirection: "column", gap: 6 }
|
|
949
950
|
};
|
|
@@ -981,7 +982,8 @@ function ItemRow({
|
|
|
981
982
|
checked,
|
|
982
983
|
onToggle,
|
|
983
984
|
onRemove,
|
|
984
|
-
quizGenerator
|
|
985
|
+
quizGenerator,
|
|
986
|
+
onOpenItem
|
|
985
987
|
}) {
|
|
986
988
|
const [quiz, setQuiz] = (0, import_react.useState)(null);
|
|
987
989
|
const [quizState, setQuizState] = (0, import_react.useState)("idle");
|
|
@@ -995,6 +997,7 @@ function ItemRow({
|
|
|
995
997
|
setQuizState("error");
|
|
996
998
|
}
|
|
997
999
|
};
|
|
1000
|
+
const titleStyle = { ...s.link, textDecoration: checked ? "line-through" : "none", opacity: checked ? 0.6 : 1 };
|
|
998
1001
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { style: s.item, children: [
|
|
999
1002
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1000
1003
|
"input",
|
|
@@ -1007,16 +1010,23 @@ function ItemRow({
|
|
|
1007
1010
|
}
|
|
1008
1011
|
),
|
|
1009
1012
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { flex: 1, minWidth: 0, display: "flex", flexDirection: "column", gap: 2 }, children: [
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1013
|
+
onOpenItem ? (
|
|
1014
|
+
// The host reads the page and asks about it; the page itself is
|
|
1015
|
+
// still one click away on the arrow beside the title.
|
|
1016
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { display: "flex", alignItems: "baseline", gap: 6, minWidth: 0 }, children: [
|
|
1017
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1018
|
+
"button",
|
|
1019
|
+
{
|
|
1020
|
+
type: "button",
|
|
1021
|
+
onClick: () => onOpenItem(item),
|
|
1022
|
+
title: "Read this page and ask questions about it",
|
|
1023
|
+
style: { ...titleStyle, ...s.textButton },
|
|
1024
|
+
children: item.title
|
|
1025
|
+
}
|
|
1026
|
+
),
|
|
1027
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { href: item.url, target: "_blank", rel: "noopener noreferrer", style: { ...s.link, ...s.muted }, "aria-label": `Open "${item.title}" in a new tab`, title: "Open the page", children: "\u2197" })
|
|
1028
|
+
] })
|
|
1029
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { href: item.url, target: "_blank", rel: "noopener noreferrer", style: titleStyle, children: item.title }),
|
|
1020
1030
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { ...s.row, ...s.muted }, children: [
|
|
1021
1031
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: s.badge, children: KIND_LABEL[item.kind] }),
|
|
1022
1032
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { title: item.estimateBasis, children: [
|
|
@@ -1106,7 +1116,7 @@ function SharingControls({ playlist, onChange, shareBaseHref, inAccount }) {
|
|
|
1106
1116
|
message && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { ...s.muted, wordBreak: "break-all" }, children: message })
|
|
1107
1117
|
] });
|
|
1108
1118
|
}
|
|
1109
|
-
function PlaylistView({ playlist, done, onToggle, onChange, onSaveCopy, minutesPerWeek, shareBaseHref, canShare = true, inAccount = false, quizGenerator }) {
|
|
1119
|
+
function PlaylistView({ playlist, done, onToggle, onChange, onSaveCopy, minutesPerWeek, shareBaseHref, canShare = true, inAccount = false, quizGenerator, onOpenItem }) {
|
|
1110
1120
|
const time = playlistTime(playlist, done);
|
|
1111
1121
|
const weeks = weeksToFinish(time.remainingMinutes, minutesPerWeek);
|
|
1112
1122
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: s.section, children: [
|
|
@@ -1143,7 +1153,8 @@ function PlaylistView({ playlist, done, onToggle, onChange, onSaveCopy, minutesP
|
|
|
1143
1153
|
checked: done.has(item.id),
|
|
1144
1154
|
onToggle: () => onToggle(item.id),
|
|
1145
1155
|
onRemove: onChange ? () => onChange(removeItem(playlist, item.id)) : void 0,
|
|
1146
|
-
quizGenerator
|
|
1156
|
+
quizGenerator,
|
|
1157
|
+
onOpenItem
|
|
1147
1158
|
},
|
|
1148
1159
|
item.id
|
|
1149
1160
|
)) }),
|
|
@@ -1282,7 +1293,8 @@ function EducationPlaylists({
|
|
|
1282
1293
|
importFromHash = false,
|
|
1283
1294
|
userId,
|
|
1284
1295
|
playlistsEndpoint,
|
|
1285
|
-
quizGenerator
|
|
1296
|
+
quizGenerator,
|
|
1297
|
+
onOpenItem
|
|
1286
1298
|
}) {
|
|
1287
1299
|
const catalog = (0, import_react3.useMemo)(() => catalogProp ?? getDefaultCatalog(), [catalogProp]);
|
|
1288
1300
|
const progressStore = (0, import_react3.useMemo)(() => createProgressStore(), []);
|
|
@@ -1466,7 +1478,8 @@ function EducationPlaylists({
|
|
|
1466
1478
|
inAccount: Boolean(remote),
|
|
1467
1479
|
minutesPerWeek: open.plan?.minutesPerWeek,
|
|
1468
1480
|
shareBaseHref: shareBase,
|
|
1469
|
-
quizGenerator
|
|
1481
|
+
quizGenerator,
|
|
1482
|
+
onOpenItem
|
|
1470
1483
|
}
|
|
1471
1484
|
)
|
|
1472
1485
|
] });
|