placementt-core 1.400.1151 → 1.400.1153

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/lib/letters.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Descendant } from "slate";
2
- import type { ProductTag } from "./typeDefinitions";
2
+ import type { CohortData, InstituteData, ProductTag } from "./typeDefinitions";
3
3
  export type WexLetterKey = "employer" | "parent" | "student";
4
4
  export type WexLetterOverride = {
5
5
  body: Descendant[];
@@ -20,3 +20,31 @@ export declare const wexLetters: {
20
20
  export declare function fillLetterParams(text: string, values: {
21
21
  [key: string]: string | undefined;
22
22
  }): string;
23
+ export declare const CAREERTHREAD_LETTER_ADDRESS: string[];
24
+ export declare const CAREERTHREAD_LETTER_CONTACT: string[];
25
+ export declare const CAREERTHREAD_LETTER_FOOTER = "CareerThread is the trading name of Placementt Ltd, a company registered in England and Wales with company number 14481303.";
26
+ export declare function wexLetterValues({ institute, cohort, staff }: {
27
+ institute?: Partial<InstituteData>;
28
+ cohort?: Partial<CohortData>;
29
+ staff?: {
30
+ forename?: string;
31
+ surname?: string;
32
+ email?: string;
33
+ };
34
+ }): {
35
+ [key: string]: string | undefined;
36
+ };
37
+ export type LetterRun = {
38
+ text: string;
39
+ bold?: boolean;
40
+ italic?: boolean;
41
+ underline?: boolean;
42
+ href?: string;
43
+ };
44
+ export type LetterBlock = {
45
+ type: "heading-one" | "heading-two" | "paragraph";
46
+ runs: LetterRun[];
47
+ };
48
+ export declare function wexLetterBlocks(body: Descendant[], values: {
49
+ [key: string]: string | undefined;
50
+ }): LetterBlock[];
package/lib/letters.js CHANGED
@@ -1,7 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.wexLetters = exports.wexLetterParams = void 0;
3
+ exports.CAREERTHREAD_LETTER_FOOTER = exports.CAREERTHREAD_LETTER_CONTACT = exports.CAREERTHREAD_LETTER_ADDRESS = exports.wexLetters = exports.wexLetterParams = void 0;
4
4
  exports.fillLetterParams = fillLetterParams;
5
+ exports.wexLetterValues = wexLetterValues;
6
+ exports.wexLetterBlocks = wexLetterBlocks;
7
+ const benchmarkEvidenceRules_1 = require("./benchmarkEvidenceRules");
5
8
  // Params available in every letter. Filled in when the letter is previewed or downloaded.
6
9
  exports.wexLetterParams = ["instituteName", "academicYear", "cohortName", "placementStart", "placementEnd", "staffName", "staffEmail"];
7
10
  // Builds Slate content from lines. "# " and "## " start headings, **text** is bold, {{param}} becomes a param node.
@@ -19,7 +22,7 @@ function toLetterBody(lines) {
19
22
  return { type, children: children.length ? children : [{ text: "" }] };
20
23
  });
21
24
  }
22
- const signOff = ["Kind regards,", "", "Thomas Barton", "Founder, Director", "tom@careerthread.co.uk", "CareerThread"];
25
+ const signOff = ["Kind regards,", "", "", "**Thomas Barton**", "Founder, Director", "tom@careerthread.co.uk"];
23
26
  exports.wexLetters = {
24
27
  employer: {
25
28
  name: "Employer letter",
@@ -72,4 +75,34 @@ exports.wexLetters = {
72
75
  function fillLetterParams(text, values) {
73
76
  return text.replace(/\{\{(\w+)\}\}/g, (match, key) => values[key] ?? match);
74
77
  }
78
+ // Printed top right of every letter, under the CareerThread name.
79
+ // TODO(Tom): fill in the postal address.
80
+ exports.CAREERTHREAD_LETTER_ADDRESS = [];
81
+ exports.CAREERTHREAD_LETTER_CONTACT = ["tom@careerthread.co.uk", "careerthread.co.uk"];
82
+ exports.CAREERTHREAD_LETTER_FOOTER = "CareerThread is the trading name of Placementt Ltd, a company registered in England and Wales with company number 14481303.";
83
+ // Values for the letter params. Shared by the web preview and the backend PDF, so both say the same thing.
84
+ function wexLetterValues({ institute, cohort, staff }) {
85
+ const formatDate = (d) => d ? new Date(d).toLocaleDateString("en-GB", { day: "numeric", month: "long", year: "numeric" }) : undefined;
86
+ return {
87
+ instituteName: institute?.name,
88
+ academicYear: (0, benchmarkEvidenceRules_1.academicYearForDate)(cohort?.startPlacements || new Date()).replace("-", "/"),
89
+ cohortName: cohort?.name,
90
+ placementStart: formatDate(cohort?.startPlacements),
91
+ placementEnd: formatDate(cohort?.endPlacements),
92
+ staffName: [staff?.forename, staff?.surname].filter(Boolean).join(" ") || undefined,
93
+ staffEmail: staff?.email,
94
+ };
95
+ }
96
+ // Flattens Slate content into blocks of styled text runs with params filled in. The web preview and the PDF both draw from this.
97
+ function wexLetterBlocks(body, values) {
98
+ const runsOf = (node, href) => {
99
+ if (typeof node.text === "string")
100
+ return [{ text: fillLetterParams(node.text, values), bold: node.bold, italic: node.italic, underline: node.underline, href }];
101
+ return (node.children || []).flatMap((child) => runsOf(child, node.type === "link" ? node.href : href));
102
+ };
103
+ return body.map((node) => ({
104
+ type: node.type === "heading-one" || node.type === "heading-two" ? node.type : "paragraph",
105
+ runs: runsOf(node).filter((r) => r.text),
106
+ }));
107
+ }
75
108
  //# sourceMappingURL=letters.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"letters.js","sourceRoot":"","sources":["../src/letters.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"letters.js","sourceRoot":"","sources":["../src/letters.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "private": false,
3
3
  "name": "placementt-core",
4
4
  "author": "Placementt",
5
- "version": "1.400.1151",
5
+ "version": "1.400.1153",
6
6
  "main": "lib/index.js",
7
7
  "types": "lib/index.d.ts",
8
8
  "scripts": {
package/src/letters.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import {Descendant} from "slate";
2
- import type {ProductTag} from "./typeDefinitions";
2
+ import type {CohortData, InstituteData, ProductTag} from "./typeDefinitions";
3
+ import {academicYearForDate} from "./benchmarkEvidenceRules";
3
4
 
4
5
  // Work experience letters schools hand to employers, parents and students. We provide the defaults below.
5
6
  // A school can edit a letter once, saved to InstituteData.wexLetters[key], and that version is used for all their cohorts.
@@ -34,7 +35,7 @@ function toLetterBody(lines: string[]): Descendant[] {
34
35
  });
35
36
  }
36
37
 
37
- const signOff = ["Kind regards,", "", "Thomas Barton", "Founder, Director", "tom@careerthread.co.uk", "CareerThread"];
38
+ const signOff = ["Kind regards,", "", "", "**Thomas Barton**", "Founder, Director", "tom@careerthread.co.uk"];
38
39
 
39
40
  export const wexLetters: {[key in WexLetterKey]: WexLetterConfig} = {
40
41
  employer: {
@@ -89,3 +90,39 @@ export const wexLetters: {[key in WexLetterKey]: WexLetterConfig} = {
89
90
  export function fillLetterParams(text: string, values: {[key: string]: string|undefined}) {
90
91
  return text.replace(/\{\{(\w+)\}\}/g, (match, key) => values[key] ?? match);
91
92
  }
93
+
94
+ // Printed top right of every letter, under the CareerThread name.
95
+ // TODO(Tom): fill in the postal address.
96
+ export const CAREERTHREAD_LETTER_ADDRESS: string[] = [];
97
+ export const CAREERTHREAD_LETTER_CONTACT = ["tom@careerthread.co.uk", "careerthread.co.uk"];
98
+ export const CAREERTHREAD_LETTER_FOOTER = "CareerThread is the trading name of Placementt Ltd, a company registered in England and Wales with company number 14481303.";
99
+
100
+ // Values for the letter params. Shared by the web preview and the backend PDF, so both say the same thing.
101
+ export function wexLetterValues({institute, cohort, staff}:{institute?: Partial<InstituteData>, cohort?: Partial<CohortData>, staff?: {forename?: string, surname?: string, email?: string}}): {[key: string]: string|undefined} {
102
+ const formatDate = (d?: string) => d ? new Date(d).toLocaleDateString("en-GB", {day: "numeric", month: "long", year: "numeric"}) : undefined;
103
+ return {
104
+ instituteName: institute?.name,
105
+ academicYear: academicYearForDate(cohort?.startPlacements || new Date()).replace("-", "/"),
106
+ cohortName: cohort?.name,
107
+ placementStart: formatDate(cohort?.startPlacements),
108
+ placementEnd: formatDate(cohort?.endPlacements),
109
+ staffName: [staff?.forename, staff?.surname].filter(Boolean).join(" ") || undefined,
110
+ staffEmail: staff?.email,
111
+ };
112
+ }
113
+
114
+ export type LetterRun = {text: string, bold?: boolean, italic?: boolean, underline?: boolean, href?: string};
115
+ export type LetterBlock = {type: "heading-one"|"heading-two"|"paragraph", runs: LetterRun[]};
116
+
117
+ // Flattens Slate content into blocks of styled text runs with params filled in. The web preview and the PDF both draw from this.
118
+ export function wexLetterBlocks(body: Descendant[], values: {[key: string]: string|undefined}): LetterBlock[] {
119
+ type Node = {type?: string, href?: string, text?: string, bold?: boolean, italic?: boolean, underline?: boolean, children?: Node[]};
120
+ const runsOf = (node: Node, href?: string): LetterRun[] => {
121
+ if (typeof node.text === "string") return [{text: fillLetterParams(node.text, values), bold: node.bold, italic: node.italic, underline: node.underline, href}];
122
+ return (node.children || []).flatMap((child) => runsOf(child, node.type === "link" ? node.href : href));
123
+ };
124
+ return (body as unknown as Node[]).map((node) => ({
125
+ type: node.type === "heading-one" || node.type === "heading-two" ? node.type : "paragraph",
126
+ runs: runsOf(node).filter((r) => r.text),
127
+ }));
128
+ }