@lookit/templates 0.0.1
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 +6 -0
- package/dist/consentVideoTemplate.d.ts +8 -0
- package/dist/errors.d.ts +18 -0
- package/dist/index.browser.js +24907 -0
- package/dist/index.browser.js.map +1 -0
- package/dist/index.browser.min.js +2 -0
- package/dist/index.browser.min.js.map +1 -0
- package/dist/index.cjs +24904 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +24902 -0
- package/dist/index.js.map +1 -0
- package/dist/utils.d.ts +14 -0
- package/package.json +37 -0
- package/src/consentVideoTemplate.ts +57 -0
- package/src/environment.d.ts +9 -0
- package/src/errors.ts +22 -0
- package/src/index.spec.ts +74 -0
- package/src/index.ts +3 -0
- package/src/string-import.d.ts +14 -0
- package/src/utils.spec.ts +27 -0
- package/src/utils.ts +94 -0
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { PluginInfo, TrialType } from "jspsych";
|
|
2
|
+
/**
|
|
3
|
+
* Pulled from EFP. Function to convert researcher's text to HTML.
|
|
4
|
+
*
|
|
5
|
+
* @param text - Text
|
|
6
|
+
* @returns Formatted string
|
|
7
|
+
*/
|
|
8
|
+
export declare const expFormat: (text?: string | string[]) => string;
|
|
9
|
+
/**
|
|
10
|
+
* Initialize i18next with parameters from trial.
|
|
11
|
+
*
|
|
12
|
+
* @param trial - Trial data including user supplied parameters.
|
|
13
|
+
*/
|
|
14
|
+
export declare const setLocale: (trial: TrialType<PluginInfo>) => void;
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lookit/templates",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "CHS jsPsych trial templates and their translations",
|
|
5
|
+
"homepage": "https://github.com/lookit/lookit-jspsych#readme",
|
|
6
|
+
"bugs": {
|
|
7
|
+
"url": "https://github.com/lookit/lookit-jspsych/issues"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/lookit/lookit-jspsych.git"
|
|
12
|
+
},
|
|
13
|
+
"license": "ISC",
|
|
14
|
+
"author": "Christopher J Green <okaycj@mit.edu> (https://github.com/okaycj)",
|
|
15
|
+
"main": "dist/index.js",
|
|
16
|
+
"unpkg": "dist/index.browser.min.js",
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"files": [
|
|
19
|
+
"src",
|
|
20
|
+
"dist"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "rollup --config",
|
|
24
|
+
"dev": "rollup --config rollup.config.dev.mjs --watch",
|
|
25
|
+
"test": "jest --coverage"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@jspsych/config": "^2.0.0",
|
|
29
|
+
"handlebars": "^4.7.8",
|
|
30
|
+
"rollup-plugin-dotenv": "^0.5.1",
|
|
31
|
+
"rollup-plugin-polyfill-node": "^0.13.0"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"@lookit/data": "^0.0.4",
|
|
35
|
+
"jspsych": "^8.0.2"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { LookitWindow } from "@lookit/data/dist/types";
|
|
2
|
+
import Handlebars from "handlebars";
|
|
3
|
+
import { PluginInfo, TrialType } from "jspsych";
|
|
4
|
+
import consent_garden from "../hbs/consent-garden.hbs";
|
|
5
|
+
import consent_template_5 from "../hbs/consent-template-5.hbs";
|
|
6
|
+
import consentVideoTrialTemplate from "../hbs/consent-video-trial.hbs";
|
|
7
|
+
import { ConsentTemplateNotFound } from "./errors";
|
|
8
|
+
import { setLocale } from "./utils";
|
|
9
|
+
|
|
10
|
+
declare const window: LookitWindow;
|
|
11
|
+
|
|
12
|
+
const video_container_id = "lookit-jspsych-video-container";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Translate, render, and get consent document HTML.
|
|
16
|
+
*
|
|
17
|
+
* @param trial - JsPsych trial object containing trial params
|
|
18
|
+
* @returns Consent document HTML
|
|
19
|
+
*/
|
|
20
|
+
export const consentVideo = (trial: TrialType<PluginInfo>) => {
|
|
21
|
+
const experiment = window.chs.study.attributes;
|
|
22
|
+
const { PIName, PIContact } = trial;
|
|
23
|
+
|
|
24
|
+
setLocale(trial);
|
|
25
|
+
|
|
26
|
+
const consentDocumentTemplate = consentDocument(trial);
|
|
27
|
+
|
|
28
|
+
const consent = Handlebars.compile(consentDocumentTemplate)({
|
|
29
|
+
...trial,
|
|
30
|
+
name: PIName,
|
|
31
|
+
contact: PIContact,
|
|
32
|
+
experiment,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
return Handlebars.compile(consentVideoTrialTemplate)({
|
|
36
|
+
...trial,
|
|
37
|
+
consent,
|
|
38
|
+
video_container_id,
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Get consent template by name.
|
|
44
|
+
*
|
|
45
|
+
* @param trial - Trial data including user supplied parameters.
|
|
46
|
+
* @returns Consent template
|
|
47
|
+
*/
|
|
48
|
+
const consentDocument = (trial: TrialType<PluginInfo>) => {
|
|
49
|
+
switch (trial.template) {
|
|
50
|
+
case "consent-template-5":
|
|
51
|
+
return consent_template_5;
|
|
52
|
+
case "consent-garden":
|
|
53
|
+
return consent_garden;
|
|
54
|
+
default:
|
|
55
|
+
throw new ConsentTemplateNotFound(trial.template);
|
|
56
|
+
}
|
|
57
|
+
};
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Error thrown when specified language isn't found */
|
|
2
|
+
export class LocaleNotFoundError extends Error {
|
|
3
|
+
/**
|
|
4
|
+
* This will be thrown when attempting to init i18n
|
|
5
|
+
*
|
|
6
|
+
* @param baseName - Language a2code with region
|
|
7
|
+
*/
|
|
8
|
+
public constructor(baseName: string) {
|
|
9
|
+
super(`"${baseName}" locale not found.`);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/** Error thrown when researcher selects template that isn't available. */
|
|
13
|
+
export class ConsentTemplateNotFound extends Error {
|
|
14
|
+
/**
|
|
15
|
+
* This will let the researcher know that their template isn't found.
|
|
16
|
+
*
|
|
17
|
+
* @param template - Supplied name of consent template.
|
|
18
|
+
*/
|
|
19
|
+
public constructor(template: string) {
|
|
20
|
+
super(`Consent template "${template}" not found.`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { LookitWindow } from "@lookit/data/dist/types";
|
|
2
|
+
import { PluginInfo, TrialType } from "jspsych";
|
|
3
|
+
import { ConsentTemplateNotFound } from "./errors";
|
|
4
|
+
import chsTemplate from "./index";
|
|
5
|
+
|
|
6
|
+
declare const window: LookitWindow;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Test helper function to create trial object.
|
|
10
|
+
*
|
|
11
|
+
* @param values - Object to replace default trial values
|
|
12
|
+
* @returns Trial object
|
|
13
|
+
*/
|
|
14
|
+
const getTrial = (values: Record<string, string> = {}) => {
|
|
15
|
+
return {
|
|
16
|
+
locale: "en-us",
|
|
17
|
+
template: "consent-template-5",
|
|
18
|
+
...values,
|
|
19
|
+
} as unknown as TrialType<PluginInfo>;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
test("consent video", () => {
|
|
23
|
+
const trial = getTrial();
|
|
24
|
+
const name = "some name";
|
|
25
|
+
window.chs = {
|
|
26
|
+
study: {
|
|
27
|
+
attributes: {
|
|
28
|
+
name,
|
|
29
|
+
duration: "duration",
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
} as typeof window.chs;
|
|
33
|
+
|
|
34
|
+
expect(chsTemplate.consentVideo(trial)).toContain(
|
|
35
|
+
'<div id="consent-video-trial">',
|
|
36
|
+
);
|
|
37
|
+
expect(chsTemplate.consentVideo(trial)).toContain(
|
|
38
|
+
`Consent to participate in research:\n ${name}`,
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("consent video in French", () => {
|
|
43
|
+
const trial = getTrial({ locale: "fr" });
|
|
44
|
+
const name = "some name";
|
|
45
|
+
window.chs = {
|
|
46
|
+
study: {
|
|
47
|
+
attributes: {
|
|
48
|
+
name,
|
|
49
|
+
duration: "duration",
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
} as typeof window.chs;
|
|
53
|
+
|
|
54
|
+
expect(chsTemplate.consentVideo(trial)).toContain(
|
|
55
|
+
'<div id="consent-video-trial">',
|
|
56
|
+
);
|
|
57
|
+
expect(chsTemplate.consentVideo(trial)).toContain(
|
|
58
|
+
`Consentement à participer à la recherche:\n ${name}`,
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
test("consent video with unknown template", () => {
|
|
63
|
+
const trial = getTrial({
|
|
64
|
+
template: "not a real template name",
|
|
65
|
+
});
|
|
66
|
+
expect(() => chsTemplate.consentVideo(trial)).toThrow(
|
|
67
|
+
ConsentTemplateNotFound,
|
|
68
|
+
);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("consent garden template", () => {
|
|
72
|
+
const trial = getTrial({ template: "consent-garden" });
|
|
73
|
+
expect(chsTemplate.consentVideo(trial)).toContain("Project GARDEN");
|
|
74
|
+
});
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { PluginInfo, TrialType } from "jspsych";
|
|
2
|
+
import { LocaleNotFoundError } from "./errors";
|
|
3
|
+
import { expFormat, setLocale } from "./utils";
|
|
4
|
+
|
|
5
|
+
test("expFormat convert written text to format well in HTML", () => {
|
|
6
|
+
expect(expFormat("abcdefg")).toStrictEqual("abcdefg");
|
|
7
|
+
expect(expFormat("AAABBBCCC")).toStrictEqual("AAABBBCCC");
|
|
8
|
+
expect(expFormat("A normal sentence with multiple words.")).toStrictEqual(
|
|
9
|
+
"A normal sentence with multiple words.",
|
|
10
|
+
);
|
|
11
|
+
expect(expFormat(["Array", "of", "strings"])).toStrictEqual(
|
|
12
|
+
"Array<br><br>of<br><br>strings",
|
|
13
|
+
);
|
|
14
|
+
expect(expFormat("carriage return an newline\r\n")).toStrictEqual(
|
|
15
|
+
"carriage return an newline<br>",
|
|
16
|
+
);
|
|
17
|
+
expect(expFormat("new line\n")).toStrictEqual("new line<br>");
|
|
18
|
+
expect(expFormat("carriage return\r")).toStrictEqual("carriage return<br>");
|
|
19
|
+
expect(expFormat("\tTabbed text")).toStrictEqual(
|
|
20
|
+
" Tabbed text",
|
|
21
|
+
);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("setLocale throw error with non-existing locale", () => {
|
|
25
|
+
const trial = { locale: "non-existing" } as unknown as TrialType<PluginInfo>;
|
|
26
|
+
expect(() => setLocale(trial)).toThrow(LocaleNotFoundError);
|
|
27
|
+
});
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import Handlebars from "handlebars";
|
|
2
|
+
import i18next from "i18next";
|
|
3
|
+
import ICU from "i18next-icu";
|
|
4
|
+
import Yaml from "js-yaml";
|
|
5
|
+
import { PluginInfo, TrialType } from "jspsych";
|
|
6
|
+
import en_us from "../i18n/en-us.yaml";
|
|
7
|
+
import eu from "../i18n/eu.yaml";
|
|
8
|
+
import fr from "../i18n/fr.yaml";
|
|
9
|
+
import hu from "../i18n/hu.yaml";
|
|
10
|
+
import it from "../i18n/it.yaml";
|
|
11
|
+
import ja from "../i18n/ja.yaml";
|
|
12
|
+
import nl from "../i18n/nl.yaml";
|
|
13
|
+
import pt_br from "../i18n/pt-br.yaml";
|
|
14
|
+
import pt from "../i18n/pt.yaml";
|
|
15
|
+
import { LocaleNotFoundError } from "./errors";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Pulled from EFP. Function to convert researcher's text to HTML.
|
|
19
|
+
*
|
|
20
|
+
* @param text - Text
|
|
21
|
+
* @returns Formatted string
|
|
22
|
+
*/
|
|
23
|
+
export const expFormat = (text?: string | string[]) => {
|
|
24
|
+
if (!text) {
|
|
25
|
+
return "";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (Array.isArray(text)) {
|
|
29
|
+
text = text.join("\n\n");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return text
|
|
33
|
+
.replace(/(\r\n|\n|\r)/gm, "<br>")
|
|
34
|
+
.replace(/\t/gm, " ");
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Get a translation resources from yaml files.
|
|
39
|
+
*
|
|
40
|
+
* @returns Resources for i18next
|
|
41
|
+
*/
|
|
42
|
+
const resources = () => {
|
|
43
|
+
const translations = {
|
|
44
|
+
"en-us": en_us,
|
|
45
|
+
eu,
|
|
46
|
+
fr,
|
|
47
|
+
hu,
|
|
48
|
+
it,
|
|
49
|
+
ja,
|
|
50
|
+
nl,
|
|
51
|
+
"pt-br": pt_br,
|
|
52
|
+
pt,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
return Object.entries(translations).reduce((prev, [locale, translation]) => {
|
|
56
|
+
const lcl = new Intl.Locale(locale);
|
|
57
|
+
return {
|
|
58
|
+
...prev,
|
|
59
|
+
[lcl.baseName]: {
|
|
60
|
+
translation: Yaml.load(translation) as Record<string, string>,
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}, {});
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Initialize i18next with parameters from trial.
|
|
68
|
+
*
|
|
69
|
+
* @param trial - Trial data including user supplied parameters.
|
|
70
|
+
*/
|
|
71
|
+
export const setLocale = (trial: TrialType<PluginInfo>) => {
|
|
72
|
+
const lcl = new Intl.Locale(trial.locale);
|
|
73
|
+
|
|
74
|
+
if (!i18next.hasResourceBundle(lcl.baseName, "translation")) {
|
|
75
|
+
throw new LocaleNotFoundError(trial.locale);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (i18next.language !== lcl.baseName) {
|
|
79
|
+
i18next.changeLanguage(lcl.baseName);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// Initialize translations
|
|
84
|
+
i18next.use(ICU).init({
|
|
85
|
+
debug: process.env.DEBUG === "true",
|
|
86
|
+
resources: resources(),
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Setup Handlebars' helpers
|
|
90
|
+
Handlebars.registerHelper("exp-format", (context) => expFormat(context));
|
|
91
|
+
Handlebars.registerHelper("t", (context, { hash }) => {
|
|
92
|
+
const txt = String(i18next.t(context, hash));
|
|
93
|
+
return hash.htmlSafe ? new Handlebars.SafeString(txt) : txt;
|
|
94
|
+
});
|