@reuters-graphics/graphics-components 3.0.14 → 3.0.16

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.
@@ -0,0 +1,23 @@
1
+ import { Meta } from '@storybook/blocks';
2
+
3
+ import * as UtilFunctionStories from './Utils.stories.svelte';
4
+
5
+ <Meta of={UtilFunctionStories} />
6
+
7
+ # Util functions
8
+
9
+ This library provides utility functions that can be used across various components and applications.
10
+
11
+ ## Prettify date in the Reuters format
12
+
13
+ The function `prettifyDate` formats the input string, which is expected to be in English, to format the month and time designator (AM/PM) according to the Reuters style guide. The function is case agnostic and will format both full month names and their 3-letter abbreviations (i.e. `Mar` or `Jun`) correctly.
14
+
15
+ ```javascript
16
+ import { prettifyDate } from '@reuters-graphics/graphics-components';
17
+
18
+ // Example usage
19
+ prettifyDate('January 1, 2023, 10:00 AM'); // returns 'Jan. 1, 2023, 10:00 a.m.'
20
+ prettifyDate('Jan 1, 2023, 10:00 PM'); // returns 'Jan. 1, 2023, 10:00 p.m.'
21
+ prettifyDate('MAR. 2025'); // returns 'March 2025'
22
+ prettifyDate('sep. 1, 2023, 10:00PM'); // returns 'Sept. 1, 2023, 10:00 p.m.'
23
+ ```
@@ -0,0 +1,9 @@
1
+ <script module lang="ts">
2
+ import { defineMeta } from '@storybook/addon-svelte-csf';
3
+
4
+ const { Story } = defineMeta({
5
+ title: 'Components/Utilities/Functions',
6
+ });
7
+ </script>
8
+
9
+ <Story name="Demo" tags={['!autodocs', '!dev']} />
@@ -0,0 +1,18 @@
1
+ interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
2
+ new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
3
+ $$bindings?: Bindings;
4
+ } & Exports;
5
+ (internal: unknown, props: {
6
+ $$events?: Events;
7
+ $$slots?: Slots;
8
+ }): Exports & {
9
+ $set?: any;
10
+ $on?: any;
11
+ };
12
+ z_$$bindings?: Bindings;
13
+ }
14
+ declare const Utils: $$__sveltets_2_IsomorphicComponent<Record<string, never>, {
15
+ [evt: string]: CustomEvent<any>;
16
+ }, {}, {}, string>;
17
+ type Utils = InstanceType<typeof Utils>;
18
+ export default Utils;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export { default as cssVariables } from './actions/cssVariables/index';
2
2
  export { default as resizeObserver } from './actions/resizeObserver/index';
3
+ export { prettifyDate } from './utils/index';
3
4
  export { default as Analytics, registerPageview, } from './components/Analytics/Analytics.svelte';
4
5
  export { default as Article } from './components/Article/Article.svelte';
5
6
  export { default as AdScripts } from './components/AdSlot/AdScripts.svelte';
package/dist/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  // Actions
2
2
  export { default as cssVariables } from './actions/cssVariables/index';
3
3
  export { default as resizeObserver } from './actions/resizeObserver/index';
4
+ // Utils
5
+ export { prettifyDate } from './utils/index';
4
6
  // Components
5
7
  export { default as Analytics, registerPageview, } from './components/Analytics/Analytics.svelte';
6
8
  export { default as Article } from './components/Article/Article.svelte';
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,141 @@
1
+ import { prettifyDate } from '../utils/index';
2
+ import { describe, it, expect } from 'vitest';
3
+ process.env.TESTING = 'true';
4
+ describe('Utils tests', () => {
5
+ it('should format full month correctly', () => {
6
+ const unformatted = 'January 1, 2023, 10:00 AM';
7
+ const formatted = 'Jan. 1, 2023, 10:00 a.m.';
8
+ expect(prettifyDate(unformatted)).toBe(formatted);
9
+ });
10
+ it('should format 3-letter abbreviated month correctly', () => {
11
+ const unformatted = 'Jan 1, 2023, 10:00 AM';
12
+ const formatted = 'Jan. 1, 2023, 10:00 a.m.';
13
+ expect(prettifyDate(unformatted)).toBe(formatted);
14
+ });
15
+ it('should format March, April, June, July correctly', () => {
16
+ const unformattedMarch = 'Mar 1, 2023, 10:00 AM';
17
+ const formattedMarch = 'March 1, 2023, 10:00 a.m.';
18
+ expect(prettifyDate(unformattedMarch)).toBe(formattedMarch);
19
+ const unformattedApril = 'Apr 1, 2023, 10:00 AM';
20
+ const formattedApril = 'April 1, 2023, 10:00 a.m.';
21
+ expect(prettifyDate(unformattedApril)).toBe(formattedApril);
22
+ const unformattedJune = 'Jun 1, 2023, 10:00 AM';
23
+ const formattedJune = 'June 1, 2023, 10:00 a.m.';
24
+ expect(prettifyDate(unformattedJune)).toBe(formattedJune);
25
+ const unformattedJuly = 'Jul 1, 2023, 10:00 am';
26
+ const formattedJuly = 'July 1, 2023, 10:00 a.m.';
27
+ expect(prettifyDate(unformattedJuly)).toBe(formattedJuly);
28
+ const unformattedSept = 'Sep 1, 2023, 10:00 AM';
29
+ const formattedSept = 'Sept. 1, 2023, 10:00 a.m.';
30
+ expect(prettifyDate(unformattedSept)).toBe(formattedSept);
31
+ });
32
+ it('should format months with periods correctly', () => {
33
+ const unformattedMarch = 'Mar. 1, 2023, 10:00 pm';
34
+ const formattedMarch = 'March 1, 2023, 10:00 p.m.';
35
+ expect(prettifyDate(unformattedMarch)).toBe(formattedMarch);
36
+ const unformattedApril = 'Apr. 1, 2023, 10:00 AM';
37
+ const formattedApril = 'April 1, 2023, 10:00 a.m.';
38
+ expect(prettifyDate(unformattedApril)).toBe(formattedApril);
39
+ const unformattedMay = 'May. 1, 2023, 10:00 am';
40
+ const formattedMay = 'May 1, 2023, 10:00 a.m.';
41
+ expect(prettifyDate(unformattedMay)).toBe(formattedMay);
42
+ const unformattedJune = 'Jun. 1, 2023, 10:00 PM';
43
+ const formattedJune = 'June 1, 2023, 10:00 p.m.';
44
+ expect(prettifyDate(unformattedJune)).toBe(formattedJune);
45
+ const unformattedJuly = 'Jul. 1, 2023, 10:00 PM';
46
+ const formattedJuly = 'July 1, 2023, 10:00 p.m.';
47
+ expect(prettifyDate(unformattedJuly)).toBe(formattedJuly);
48
+ const unformattedSept = 'Sep. 1, 2023, 10:00 PM';
49
+ const formattedSept = 'Sept. 1, 2023, 10:00 p.m.';
50
+ expect(prettifyDate(unformattedSept)).toBe(formattedSept);
51
+ });
52
+ it('should format months on their own properly', () => {
53
+ const unformattedMarch = 'Mar.';
54
+ const formattedMarch = 'March';
55
+ expect(prettifyDate(unformattedMarch)).toBe(formattedMarch);
56
+ const unformattedApril = 'Apr.';
57
+ const formattedApril = 'April';
58
+ expect(prettifyDate(unformattedApril)).toBe(formattedApril);
59
+ const unformattedMay = 'May.';
60
+ const formattedMay = 'May';
61
+ expect(prettifyDate(unformattedMay)).toBe(formattedMay);
62
+ const unformattedJune = 'JUN.';
63
+ const formattedJune = 'June';
64
+ expect(prettifyDate(unformattedJune)).toBe(formattedJune);
65
+ const unformattedJuly = 'Jul.';
66
+ const formattedJuly = 'July';
67
+ expect(prettifyDate(unformattedJuly)).toBe(formattedJuly);
68
+ const unformattedSept = 'sep.';
69
+ const formattedSept = 'Sept.';
70
+ expect(prettifyDate(unformattedSept)).toBe(formattedSept);
71
+ });
72
+ it('should format months with year properly', () => {
73
+ const unformattedMarch = 'MAR. 2025';
74
+ const formattedMarch = 'March 2025';
75
+ expect(prettifyDate(unformattedMarch)).toBe(formattedMarch);
76
+ const unformattedApril = 'apr. 2025';
77
+ const formattedApril = 'April 2025';
78
+ expect(prettifyDate(unformattedApril)).toBe(formattedApril);
79
+ const unformattedMay = 'May. 2025';
80
+ const formattedMay = 'May 2025';
81
+ expect(prettifyDate(unformattedMay)).toBe(formattedMay);
82
+ const unformattedJune = 'Jun. 2025';
83
+ const formattedJune = 'June 2025';
84
+ expect(prettifyDate(unformattedJune)).toBe(formattedJune);
85
+ const unformattedJuly = 'Jul. 2025';
86
+ const formattedJuly = 'July 2025';
87
+ expect(prettifyDate(unformattedJuly)).toBe(formattedJuly);
88
+ const unformattedSept = 'Sep. 2025';
89
+ const formattedSept = 'Sept. 2025';
90
+ expect(prettifyDate(unformattedSept)).toBe(formattedSept);
91
+ const unformattedOct = 'Oct. 2025';
92
+ const formattedOct = 'Oct. 2025';
93
+ expect(prettifyDate(unformattedOct)).toBe(formattedOct);
94
+ const unformattedNov = 'Nov 2025';
95
+ const formattedNov = 'Nov. 2025';
96
+ expect(prettifyDate(unformattedNov)).toBe(formattedNov);
97
+ const unformattedDec = 'DEC 2025';
98
+ const formattedDec = 'Dec. 2025';
99
+ expect(prettifyDate(unformattedDec)).toBe(formattedDec);
100
+ });
101
+ it('should fix spacing between time and am/pm', () => {
102
+ const unformattedMarch = 'Mar. 1, 2023, 10:00pm';
103
+ const formattedMarch = 'March 1, 2023, 10:00 p.m.';
104
+ expect(prettifyDate(unformattedMarch)).toBe(formattedMarch);
105
+ const unformattedApril = 'Apr. 1, 2023, 10:00AM';
106
+ const formattedApril = 'April 1, 2023, 10:00 a.m.';
107
+ expect(prettifyDate(unformattedApril)).toBe(formattedApril);
108
+ const unformattedMay = 'May. 1, 2023, 10:00am';
109
+ const formattedMay = 'May 1, 2023, 10:00 a.m.';
110
+ expect(prettifyDate(unformattedMay)).toBe(formattedMay);
111
+ const unformattedJune = 'Jun. 1, 2023, 10:00AM';
112
+ const formattedJune = 'June 1, 2023, 10:00 a.m.';
113
+ expect(prettifyDate(unformattedJune)).toBe(formattedJune);
114
+ const unformattedAug = 'aug 1, 2023, 10:00PM';
115
+ const formattedAug = 'Aug. 1, 2023, 10:00 p.m.';
116
+ expect(prettifyDate(unformattedAug)).toBe(formattedAug);
117
+ const unformattedSept = 'sep. 1, 2023, 10:00PM';
118
+ const formattedSept = 'Sept. 1, 2023, 10:00 p.m.';
119
+ expect(prettifyDate(unformattedSept)).toBe(formattedSept);
120
+ const unformattedOct = 'oct 1, 2023, 10:00PM';
121
+ const formattedOct = 'Oct. 1, 2023, 10:00 p.m.';
122
+ expect(prettifyDate(unformattedOct)).toBe(formattedOct);
123
+ });
124
+ it('should work with lower or upper case', () => {
125
+ const unformattedMarch = 'MAR. 1, 2023, 10:00pm';
126
+ const formattedMarch = 'March 1, 2023, 10:00 p.m.';
127
+ expect(prettifyDate(unformattedMarch)).toBe(formattedMarch);
128
+ const unformattedApril = 'APR. 1, 2023, 10:00AM';
129
+ const formattedApril = 'April 1, 2023, 10:00 a.m.';
130
+ expect(prettifyDate(unformattedApril)).toBe(formattedApril);
131
+ const unformattedMay = 'may. 1, 2023, 10:00am';
132
+ const formattedMay = 'May 1, 2023, 10:00 a.m.';
133
+ expect(prettifyDate(unformattedMay)).toBe(formattedMay);
134
+ const unformattedJune = 'JUN. 1, 2023, 10:00AM';
135
+ const formattedJune = 'June 1, 2023, 10:00 a.m.';
136
+ expect(prettifyDate(unformattedJune)).toBe(formattedJune);
137
+ const unformattedAug = 'AUG. 1, 2023, 10:00PM';
138
+ const formattedAug = 'Aug. 1, 2023, 10:00 p.m.';
139
+ expect(prettifyDate(unformattedAug)).toBe(formattedAug);
140
+ });
141
+ });
@@ -4,3 +4,23 @@ export declare const random4: () => string;
4
4
  * Custom function that returns an author page URL.
5
5
  */
6
6
  export declare const getAuthorPageUrl: (author: string) => string;
7
+ /** Formats a string containing a full or 3-letter abbreviated month, AM/PM, and am/pm to match the Reuters style.
8
+ *
9
+ * All months, full or abbreviated to 3 letters, are formatted to:
10
+ * - Jan.
11
+ * - Feb.
12
+ * - March
13
+ * - April]
14
+ * - May
15
+ * - June
16
+ * - July
17
+ * - Aug.
18
+ * - Sept.
19
+ * - Oct.
20
+ * - Nov.
21
+ * - Dec.
22
+ *
23
+ * AM and PM are formatted as lowercase.
24
+ *
25
+ */
26
+ export declare const prettifyDate: (input: string) => string;
@@ -10,3 +10,72 @@ export const getAuthorPageUrl = (author) => {
10
10
  const authorSlug = slugify(author.trim(), { lower: true });
11
11
  return `https://www.reuters.com/authors/${authorSlug}/`;
12
12
  };
13
+ /** Formats a string containing a full or 3-letter abbreviated month, AM/PM, and am/pm to match the Reuters style.
14
+ *
15
+ * All months, full or abbreviated to 3 letters, are formatted to:
16
+ * - Jan.
17
+ * - Feb.
18
+ * - March
19
+ * - April]
20
+ * - May
21
+ * - June
22
+ * - July
23
+ * - Aug.
24
+ * - Sept.
25
+ * - Oct.
26
+ * - Nov.
27
+ * - Dec.
28
+ *
29
+ * AM and PM are formatted as lowercase.
30
+ *
31
+ */
32
+ export const prettifyDate = (input) => {
33
+ // Define an object to map full month names to their Reuters style equivalents
34
+ const conversions = {
35
+ // full months
36
+ january: 'Jan.',
37
+ february: 'Feb.',
38
+ august: 'Aug.',
39
+ september: 'Sept.',
40
+ october: 'Oct.',
41
+ november: 'Nov.',
42
+ december: 'Dec.',
43
+ // 3-letter abbreviations that need fixing
44
+ jan: 'Jan.',
45
+ feb: 'Feb.',
46
+ mar: 'March',
47
+ apr: 'April',
48
+ jun: 'June',
49
+ jul: 'July',
50
+ aug: 'Aug.',
51
+ sep: 'Sept.',
52
+ oct: 'Oct.',
53
+ nov: 'Nov.',
54
+ dec: 'Dec.',
55
+ };
56
+ // If the key in conversions is found in the input (case insensitive), replace it with the corresponding value
57
+ const formatted = Object.keys(conversions).reduce((acc, key) => {
58
+ const regex = new RegExp(`\\b${key}\\b`, 'gi'); // Added 'i' flag for case insensitive
59
+ return acc.replace(regex, conversions[key]);
60
+ }, input);
61
+ // Fix rogue periods in abbreviations (case insensitive)
62
+ const fixedAbbr = formatted
63
+ .replace(/\bmar\./gi, 'March')
64
+ .replace(/\bmarch\./gi, 'March')
65
+ .replace(/\bapr\./gi, 'April')
66
+ .replace(/\bapril\./gi, 'April')
67
+ .replace(/\bmay\./gi, 'May')
68
+ .replace(/\bjune\./gi, 'June')
69
+ .replace(/\bjuly\./gi, 'July')
70
+ .replace(/\bsep\./gi, 'Sept.');
71
+ // Replace double periods with a single period
72
+ const fixedPeriods = fixedAbbr.replace(/\.{2,}/g, '.');
73
+ // Fix am/pm formatting
74
+ return prettifyAmPm(fixedPeriods);
75
+ };
76
+ const prettifyAmPm = (text) => {
77
+ return text.replace(/(\d)\s*(am|AM|pm|PM)\b/g, (_match, digit, timeDesignator) => {
78
+ const formattedDesignator = timeDesignator.toLowerCase() === 'am' ? 'a.m.' : 'p.m.';
79
+ return `${digit} ${formattedDesignator}`;
80
+ });
81
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reuters-graphics/graphics-components",
3
- "version": "3.0.14",
3
+ "version": "3.0.16",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "homepage": "https://reuters-graphics.github.io/graphics-components",
@@ -89,14 +89,15 @@
89
89
  "dayjs": "^1.11.13",
90
90
  "es-toolkit": "^1.35.0",
91
91
  "journalize": "^2.6.0",
92
+ "mp4box": "^0.5.4",
92
93
  "proper-url-join": "^2.1.2",
93
94
  "pym.js": "^1.3.2",
94
95
  "slugify": "^1.6.6",
95
96
  "storybook-addon-rtl": "^1.1.0",
96
97
  "svelte-fa": "^4.0.3",
97
98
  "svelte-intersection-observer": "^1.0.0",
98
- "mp4box": "^0.5.4",
99
- "ua-parser-js": "^2.0.3"
99
+ "ua-parser-js": "^2.0.3",
100
+ "vitest": "^3.2.4"
100
101
  },
101
102
  "exports": {
102
103
  ".": {
@@ -121,6 +122,7 @@
121
122
  "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
122
123
  "changeset:version": "changeset version",
123
124
  "changeset:publish": "git add --all && changeset publish",
124
- "knip": "knip"
125
+ "knip": "knip",
126
+ "test": "vitest"
125
127
  }
126
128
  }