@examplary/ui 1.84.0 → 1.85.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.
- package/dist/components/question-types/fetch-component.d.ts +5 -1
- package/dist/components/question-types/fetch-component.js +13 -4
- package/dist/components/question-types/format-question-type.d.ts +4 -1
- package/dist/components/ui/command-palette.js +5 -2
- package/dist/types/frontend-components.d.ts +40 -1
- package/package.json +2 -2
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { ComponentType } from "react";
|
|
2
2
|
export declare const loadComponentStyles: (url: string) => void;
|
|
3
3
|
export declare const evalComponent: (js: string) => Promise<ComponentType>;
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Loads and evaluates the component at `url`. With a `sha256`, a component
|
|
6
|
+
* that doesn't match it (e.g. replaced after a review) is refused.
|
|
7
|
+
*/
|
|
8
|
+
export declare const fetchComponent: (url: string, sha256?: string) => Promise<ComponentType>;
|
|
@@ -78,9 +78,15 @@ export const evalComponent = async (js) => {
|
|
|
78
78
|
})();
|
|
79
79
|
return _componentResult;
|
|
80
80
|
};
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
81
|
+
const sha256Of = async (text) => Array.from(new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text))), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
82
|
+
/**
|
|
83
|
+
* Loads and evaluates the component at `url`. With a `sha256`, a component
|
|
84
|
+
* that doesn't match it (e.g. replaced after a review) is refused.
|
|
85
|
+
*/
|
|
86
|
+
export const fetchComponent = async (url, sha256) => {
|
|
87
|
+
const cacheKey = sha256 ? `${sha256}:${url}` : url;
|
|
88
|
+
if (componentCache.has(cacheKey)) {
|
|
89
|
+
return componentCache.get(cacheKey);
|
|
84
90
|
}
|
|
85
91
|
const js = await fetch(url).then((res) => {
|
|
86
92
|
if (!res.ok) {
|
|
@@ -88,7 +94,10 @@ export const fetchComponent = async (url) => {
|
|
|
88
94
|
}
|
|
89
95
|
return res.text();
|
|
90
96
|
});
|
|
97
|
+
if (sha256 && (await sha256Of(js)) !== sha256) {
|
|
98
|
+
throw new Error(`The component at ${url} doesn't match its digest`);
|
|
99
|
+
}
|
|
91
100
|
const res = await evalComponent(js);
|
|
92
|
-
componentCache.set(
|
|
101
|
+
componentCache.set(cacheKey, res);
|
|
93
102
|
return res;
|
|
94
103
|
};
|
|
@@ -84,6 +84,7 @@ export declare const formatQuestionType: (type: QuestionType, i18n: i18n) => {
|
|
|
84
84
|
"settings-area"?: string | undefined;
|
|
85
85
|
results?: string | undefined;
|
|
86
86
|
} | undefined;
|
|
87
|
+
hooks?: Record<string, string> | undefined;
|
|
87
88
|
public?: boolean | undefined;
|
|
88
89
|
deprecated?: boolean | undefined;
|
|
89
90
|
translations?: Record<string, string | Record<string, string>> | undefined;
|
|
@@ -117,9 +118,11 @@ export declare const formatQuestionType: (type: QuestionType, i18n: i18n) => {
|
|
|
117
118
|
settings?: Record<string, string | number | boolean | any[] | Record<string, any>> | undefined;
|
|
118
119
|
} | undefined;
|
|
119
120
|
} | undefined;
|
|
120
|
-
metadata?: Record<string,
|
|
121
|
+
metadata?: Record<string, import("zod").JSONType> | undefined;
|
|
121
122
|
readme?: string | undefined;
|
|
123
|
+
readmeUrls?: Record<string, string> | undefined;
|
|
122
124
|
previewImage?: string | undefined;
|
|
125
|
+
previewImageUrls?: Record<string, string> | undefined;
|
|
123
126
|
previewData?: string | undefined;
|
|
124
127
|
stylesheet?: string | undefined;
|
|
125
128
|
index?: number | undefined;
|
|
@@ -75,9 +75,12 @@ export const CommandPalette = ({ placeholderText = "Type a command or search..."
|
|
|
75
75
|
}, [commands, additionalCommands]);
|
|
76
76
|
// additionalCommands are already filtered by the caller, so bypass cmdk's filter
|
|
77
77
|
const unfilteredGroups = new Set(additionalCommands?.map((c) => c.group));
|
|
78
|
-
return (_jsxs(CommandDialog, { open: open, onOpenChange: setOpen, children: [_jsx(CommandInput, { value: value, onValueChange: onValueChange, placeholder: placeholderText }), _jsxs(CommandList, { children: [!additionalCommands?.length && (_jsx(CommandEmpty, { children: noResultsText })), Object.entries(groupedCommands).map(([group, commands]) => (_jsx(CommandGroup
|
|
78
|
+
return (_jsxs(CommandDialog, { open: open, onOpenChange: setOpen, children: [_jsx(CommandInput, { value: value, onValueChange: onValueChange, placeholder: placeholderText }), _jsxs(CommandList, { children: [!additionalCommands?.length && (_jsx(CommandEmpty, { children: noResultsText })), Object.entries(groupedCommands).map(([group, commands], index) => (_jsx(CommandGroup
|
|
79
|
+
// Headings are often translated; keying by them remounts the
|
|
80
|
+
// group on a language switch, which crashes cmdk mid-search
|
|
81
|
+
, { heading: group, forceMount: unfilteredGroups.has(group), children: commands.length > 0 &&
|
|
79
82
|
commands.map(({ icon: Icon, ...command }) => (_jsxs(CommandItem, { value: command.id, className: command.className, keywords: [command.label], onSelect: () => {
|
|
80
83
|
command.action();
|
|
81
84
|
setOpen(false);
|
|
82
|
-
}, children: [Icon && _jsx(Icon, {}), _jsx("span", { children: command.label }), command.shortcut && (_jsx(CommandShortcut, { children: command.shortcut }))] }, command.id))) },
|
|
85
|
+
}, children: [Icon && _jsx(Icon, {}), _jsx("span", { children: command.label }), command.shortcut && (_jsx(CommandShortcut, { children: command.shortcut }))] }, command.id))) }, index)))] })] }));
|
|
83
86
|
};
|
|
@@ -30,7 +30,6 @@ export type FrontendBaseComponentProps = {
|
|
|
30
30
|
t: (key: string | string[], options?: any) => string;
|
|
31
31
|
api: PublicApiInstance;
|
|
32
32
|
i18n: any;
|
|
33
|
-
environment: FrontendComponentEnvironment;
|
|
34
33
|
};
|
|
35
34
|
export type FrontendAssessmentComponent = ComponentType<FrontendBaseComponentProps & {
|
|
36
35
|
question: any;
|
|
@@ -39,16 +38,19 @@ export type FrontendAssessmentComponent = ComponentType<FrontendBaseComponentPro
|
|
|
39
38
|
reviewMode?: boolean;
|
|
40
39
|
answer?: Answer;
|
|
41
40
|
saveAnswer: (answer: Answer) => Promise<void>;
|
|
41
|
+
environment: FrontendComponentEnvironment;
|
|
42
42
|
}>;
|
|
43
43
|
export type FrontendPrintComponent = ComponentType<FrontendBaseComponentProps & {
|
|
44
44
|
question: any;
|
|
45
45
|
answerBoxes: boolean;
|
|
46
46
|
api: OrgApiInstance;
|
|
47
|
+
environment: FrontendComponentEnvironment;
|
|
47
48
|
}>;
|
|
48
49
|
export type FrontendResultsComponent = ComponentType<FrontendBaseComponentProps & {
|
|
49
50
|
question: any;
|
|
50
51
|
answer: Answer;
|
|
51
52
|
api: OrgApiInstance;
|
|
53
|
+
environment: FrontendComponentEnvironment;
|
|
52
54
|
}>;
|
|
53
55
|
export type FrontendQuestionSettingsAreaComponent = ComponentType<FrontendBaseComponentProps & {
|
|
54
56
|
question: any;
|
|
@@ -56,5 +58,42 @@ export type FrontendQuestionSettingsAreaComponent = ComponentType<FrontendBaseCo
|
|
|
56
58
|
api: OrgApiInstance;
|
|
57
59
|
setSetting: (settingId: string, value: any) => void;
|
|
58
60
|
setMultipleSettings: (newSettings: Record<string, any>) => void;
|
|
61
|
+
environment: FrontendComponentEnvironment;
|
|
62
|
+
}>;
|
|
63
|
+
export type FrontendSettingsFieldComponent = ComponentType<FrontendBaseComponentProps & {
|
|
64
|
+
setValue: (field: string, value: any) => void;
|
|
65
|
+
submit: () => Promise<any>;
|
|
66
|
+
}>;
|
|
67
|
+
type PrerequisiteComponentProps = FrontendBaseComponentProps & {
|
|
68
|
+
/** The prerequisite's settings for this exam */
|
|
69
|
+
settings: Record<string, any>;
|
|
70
|
+
/** The app's icon */
|
|
71
|
+
icon?: string;
|
|
72
|
+
/** Runs one of the prerequisite's functions on the server, returning its result */
|
|
73
|
+
callFunction: (name: string, input?: unknown) => Promise<any>;
|
|
74
|
+
};
|
|
75
|
+
/** Shown in the exam settings once an app's prerequisite is turned on. */
|
|
76
|
+
export type FrontendPrerequisiteSettingsComponent = ComponentType<PrerequisiteComponentProps & {
|
|
77
|
+
exam: {
|
|
78
|
+
id: string;
|
|
79
|
+
published: boolean;
|
|
80
|
+
startDateTime?: string;
|
|
81
|
+
endDateTime?: string;
|
|
82
|
+
};
|
|
83
|
+
}>;
|
|
84
|
+
/** Shown to students while an app's prerequisite isn't met. */
|
|
85
|
+
export type FrontendPrerequisitePlayerComponent = ComponentType<PrerequisiteComponentProps & {
|
|
86
|
+
exam: {
|
|
87
|
+
id: string;
|
|
88
|
+
privateUrl: string;
|
|
89
|
+
};
|
|
90
|
+
user: {
|
|
91
|
+
isAuthenticated: boolean;
|
|
92
|
+
role?: string;
|
|
93
|
+
};
|
|
94
|
+
/** Passes a value to the prerequisite's `isMet` and lets the student start */
|
|
95
|
+
setValue: (value: string) => void;
|
|
96
|
+
/** The public URL of one of the prerequisite's files */
|
|
97
|
+
fileUrl: (name: string) => string;
|
|
59
98
|
}>;
|
|
60
99
|
export {};
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@examplary/ui",
|
|
3
3
|
"description": "UI components for the Examplary testing platform.",
|
|
4
4
|
"packageManager": "yarn@4.5.3",
|
|
5
|
-
"version": "1.
|
|
5
|
+
"version": "1.85.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "dist/index.js",
|
|
8
8
|
"files": [
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"@tiptap/core": "^3.17.1",
|
|
45
45
|
"@tiptap/extension-bold": "^3.11.1",
|
|
46
46
|
"@tiptap/extension-collaboration": "^3.22.2",
|
|
47
|
-
"@tiptap/extension-document": "^3.
|
|
47
|
+
"@tiptap/extension-document": "^3.32.0",
|
|
48
48
|
"@tiptap/extension-file-handler": "^3.11.1",
|
|
49
49
|
"@tiptap/extension-highlight": "^3.19.0",
|
|
50
50
|
"@tiptap/extension-image": "^3.22.5",
|