@prezly/theme-kit-nextjs 1.0.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.
Files changed (164) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +28 -0
  3. package/build/adapter-nextjs/getBasePageProps.d.ts +9 -0
  4. package/build/adapter-nextjs/getBasePageProps.js +11 -0
  5. package/build/adapter-nextjs/getBasePageProps.js.map +1 -0
  6. package/build/adapter-nextjs/index.d.ts +2 -0
  7. package/build/adapter-nextjs/index.js +3 -0
  8. package/build/adapter-nextjs/index.js.map +1 -0
  9. package/build/adapter-nextjs/processRequest.d.ts +3 -0
  10. package/build/adapter-nextjs/processRequest.js +20 -0
  11. package/build/adapter-nextjs/processRequest.js.map +1 -0
  12. package/build/data-fetching/api/PrezlyApi.d.ts +43 -0
  13. package/build/data-fetching/api/PrezlyApi.js +139 -0
  14. package/build/data-fetching/api/PrezlyApi.js.map +1 -0
  15. package/build/data-fetching/api/getPrezlyApi.d.ts +4 -0
  16. package/build/data-fetching/api/getPrezlyApi.js +17 -0
  17. package/build/data-fetching/api/getPrezlyApi.js.map +1 -0
  18. package/build/data-fetching/api/index.d.ts +2 -0
  19. package/build/data-fetching/api/index.js +3 -0
  20. package/build/data-fetching/api/index.js.map +1 -0
  21. package/build/data-fetching/api/languages.d.ts +18 -0
  22. package/build/data-fetching/api/languages.js +117 -0
  23. package/build/data-fetching/api/languages.js.map +1 -0
  24. package/build/data-fetching/api/queries.d.ts +45 -0
  25. package/build/data-fetching/api/queries.js +35 -0
  26. package/build/data-fetching/api/queries.js.map +1 -0
  27. package/build/data-fetching/constants.d.ts +2 -0
  28. package/build/data-fetching/constants.js +3 -0
  29. package/build/data-fetching/constants.js.map +1 -0
  30. package/build/data-fetching/getAssetsUrl.d.ts +1 -0
  31. package/build/data-fetching/getAssetsUrl.js +5 -0
  32. package/build/data-fetching/getAssetsUrl.js.map +1 -0
  33. package/build/data-fetching/getLocalizedCategoryData.d.ts +10 -0
  34. package/build/data-fetching/getLocalizedCategoryData.js +31 -0
  35. package/build/data-fetching/getLocalizedCategoryData.js.map +1 -0
  36. package/build/data-fetching/getNewsroomLogoUrl.d.ts +3 -0
  37. package/build/data-fetching/getNewsroomLogoUrl.js +14 -0
  38. package/build/data-fetching/getNewsroomLogoUrl.js.map +1 -0
  39. package/build/data-fetching/getPrivacyPortalUrl.d.ts +8 -0
  40. package/build/data-fetching/getPrivacyPortalUrl.js +9 -0
  41. package/build/data-fetching/getPrivacyPortalUrl.js.map +1 -0
  42. package/build/data-fetching/getStoryPublicationDate.d.ts +3 -0
  43. package/build/data-fetching/getStoryPublicationDate.jsx +10 -0
  44. package/build/data-fetching/getStoryPublicationDate.jsx.map +1 -0
  45. package/build/data-fetching/getUploadcareGroupUrl.d.ts +1 -0
  46. package/build/data-fetching/getUploadcareGroupUrl.js +5 -0
  47. package/build/data-fetching/getUploadcareGroupUrl.js.map +1 -0
  48. package/build/data-fetching/index.d.ts +9 -0
  49. package/build/data-fetching/index.js +10 -0
  50. package/build/data-fetching/index.js.map +1 -0
  51. package/build/data-fetching/lib/getAlgoliaSettings.d.ts +4 -0
  52. package/build/data-fetching/lib/getAlgoliaSettings.js +18 -0
  53. package/build/data-fetching/lib/getAlgoliaSettings.js.map +1 -0
  54. package/build/data-fetching/lib/getEnvVariables.d.ts +4 -0
  55. package/build/data-fetching/lib/getEnvVariables.js +37 -0
  56. package/build/data-fetching/lib/getEnvVariables.js.map +1 -0
  57. package/build/data-fetching/types.d.ts +11 -0
  58. package/build/data-fetching/types.js +2 -0
  59. package/build/data-fetching/types.js.map +1 -0
  60. package/build/index.d.ts +5 -0
  61. package/build/index.js +6 -0
  62. package/build/index.js.map +1 -0
  63. package/build/intl/index.d.ts +4 -0
  64. package/build/intl/index.js +5 -0
  65. package/build/intl/index.js.map +1 -0
  66. package/build/intl/lang.d.ts +12 -0
  67. package/build/intl/lang.js +25 -0
  68. package/build/intl/lang.js.map +1 -0
  69. package/build/intl/locale.d.ts +10 -0
  70. package/build/intl/locale.js +93 -0
  71. package/build/intl/locale.js.map +1 -0
  72. package/build/intl/localeConfig.d.ts +2 -0
  73. package/build/intl/localeConfig.js +113 -0
  74. package/build/intl/localeConfig.js.map +1 -0
  75. package/build/intl/localeObject.d.ts +22 -0
  76. package/build/intl/localeObject.js +59 -0
  77. package/build/intl/localeObject.js.map +1 -0
  78. package/build/newsroom-context/context.d.ts +26 -0
  79. package/build/newsroom-context/context.jsx +22 -0
  80. package/build/newsroom-context/context.jsx.map +1 -0
  81. package/build/newsroom-context/hooks/index.d.ts +11 -0
  82. package/build/newsroom-context/hooks/index.js +12 -0
  83. package/build/newsroom-context/hooks/index.js.map +1 -0
  84. package/build/newsroom-context/hooks/useAlgoliaSettings.d.ts +1 -0
  85. package/build/newsroom-context/hooks/useAlgoliaSettings.js +6 -0
  86. package/build/newsroom-context/hooks/useAlgoliaSettings.js.map +1 -0
  87. package/build/newsroom-context/hooks/useCategories.d.ts +1 -0
  88. package/build/newsroom-context/hooks/useCategories.js +6 -0
  89. package/build/newsroom-context/hooks/useCategories.js.map +1 -0
  90. package/build/newsroom-context/hooks/useCompanyInformation.d.ts +1 -0
  91. package/build/newsroom-context/hooks/useCompanyInformation.js +6 -0
  92. package/build/newsroom-context/hooks/useCompanyInformation.js.map +1 -0
  93. package/build/newsroom-context/hooks/useCurrentLocale.d.ts +1 -0
  94. package/build/newsroom-context/hooks/useCurrentLocale.js +6 -0
  95. package/build/newsroom-context/hooks/useCurrentLocale.js.map +1 -0
  96. package/build/newsroom-context/hooks/useGetLinkLocaleSlug.d.ts +2 -0
  97. package/build/newsroom-context/hooks/useGetLinkLocaleSlug.js +21 -0
  98. package/build/newsroom-context/hooks/useGetLinkLocaleSlug.js.map +1 -0
  99. package/build/newsroom-context/hooks/useGetTranslationUrl.d.ts +2 -0
  100. package/build/newsroom-context/hooks/useGetTranslationUrl.js +36 -0
  101. package/build/newsroom-context/hooks/useGetTranslationUrl.js.map +1 -0
  102. package/build/newsroom-context/hooks/useLanguages.d.ts +1 -0
  103. package/build/newsroom-context/hooks/useLanguages.js +6 -0
  104. package/build/newsroom-context/hooks/useLanguages.js.map +1 -0
  105. package/build/newsroom-context/hooks/useNewsroom.d.ts +1 -0
  106. package/build/newsroom-context/hooks/useNewsroom.js +6 -0
  107. package/build/newsroom-context/hooks/useNewsroom.js.map +1 -0
  108. package/build/newsroom-context/hooks/useNewsroomContext.d.ts +2 -0
  109. package/build/newsroom-context/hooks/useNewsroomContext.js +12 -0
  110. package/build/newsroom-context/hooks/useNewsroomContext.js.map +1 -0
  111. package/build/newsroom-context/hooks/useSelectedCategory.d.ts +1 -0
  112. package/build/newsroom-context/hooks/useSelectedCategory.js +6 -0
  113. package/build/newsroom-context/hooks/useSelectedCategory.js.map +1 -0
  114. package/build/newsroom-context/hooks/useSelectedStory.d.ts +1 -0
  115. package/build/newsroom-context/hooks/useSelectedStory.js +6 -0
  116. package/build/newsroom-context/hooks/useSelectedStory.js.map +1 -0
  117. package/build/newsroom-context/index.d.ts +2 -0
  118. package/build/newsroom-context/index.js +3 -0
  119. package/build/newsroom-context/index.js.map +1 -0
  120. package/build/types.d.ts +38 -0
  121. package/build/types.js +2 -0
  122. package/build/types.js.map +1 -0
  123. package/package.json +60 -0
  124. package/src/@types/global.d.ts +7 -0
  125. package/src/adapter-nextjs/getBasePageProps.ts +23 -0
  126. package/src/adapter-nextjs/index.ts +2 -0
  127. package/src/adapter-nextjs/processRequest.ts +31 -0
  128. package/src/data-fetching/api/PrezlyApi.ts +240 -0
  129. package/src/data-fetching/api/getPrezlyApi.ts +24 -0
  130. package/src/data-fetching/api/index.ts +2 -0
  131. package/src/data-fetching/api/languages.ts +172 -0
  132. package/src/data-fetching/api/queries.ts +44 -0
  133. package/src/data-fetching/constants.ts +2 -0
  134. package/src/data-fetching/getAssetsUrl.ts +5 -0
  135. package/src/data-fetching/getLocalizedCategoryData.ts +47 -0
  136. package/src/data-fetching/getNewsroomLogoUrl.ts +21 -0
  137. package/src/data-fetching/getPrivacyPortalUrl.ts +22 -0
  138. package/src/data-fetching/getStoryPublicationDate.tsx +15 -0
  139. package/src/data-fetching/getUploadcareGroupUrl.ts +5 -0
  140. package/src/data-fetching/index.ts +9 -0
  141. package/src/data-fetching/lib/getAlgoliaSettings.ts +29 -0
  142. package/src/data-fetching/lib/getEnvVariables.ts +44 -0
  143. package/src/data-fetching/types.ts +12 -0
  144. package/src/index.ts +5 -0
  145. package/src/intl/index.ts +4 -0
  146. package/src/intl/lang.ts +36 -0
  147. package/src/intl/locale.ts +118 -0
  148. package/src/intl/localeConfig.js +116 -0
  149. package/src/intl/localeObject.ts +73 -0
  150. package/src/newsroom-context/context.tsx +73 -0
  151. package/src/newsroom-context/hooks/index.ts +11 -0
  152. package/src/newsroom-context/hooks/useAlgoliaSettings.ts +7 -0
  153. package/src/newsroom-context/hooks/useCategories.ts +7 -0
  154. package/src/newsroom-context/hooks/useCompanyInformation.ts +7 -0
  155. package/src/newsroom-context/hooks/useCurrentLocale.ts +7 -0
  156. package/src/newsroom-context/hooks/useGetLinkLocaleSlug.ts +33 -0
  157. package/src/newsroom-context/hooks/useGetTranslationUrl.ts +61 -0
  158. package/src/newsroom-context/hooks/useLanguages.ts +7 -0
  159. package/src/newsroom-context/hooks/useNewsroom.ts +7 -0
  160. package/src/newsroom-context/hooks/useNewsroomContext.ts +14 -0
  161. package/src/newsroom-context/hooks/useSelectedCategory.ts +7 -0
  162. package/src/newsroom-context/hooks/useSelectedStory.ts +7 -0
  163. package/src/newsroom-context/index.ts +2 -0
  164. package/src/types.ts +56 -0
@@ -0,0 +1,240 @@
1
+ import PrezlySDK, {
2
+ Category,
3
+ ExtraStoryFields,
4
+ Newsroom,
5
+ NewsroomLanguageSettings,
6
+ Story,
7
+ } from '@prezly/sdk';
8
+ import type { IncomingMessage } from 'http';
9
+
10
+ import { LocaleObject } from '../../intl';
11
+ import { BasePageProps } from '../../types';
12
+ import { DEFAULT_PAGE_SIZE } from '../constants';
13
+ import { getAlgoliaSettings } from '../lib/getAlgoliaSettings';
14
+
15
+ import {
16
+ getCompanyInformation,
17
+ getDefaultLanguage,
18
+ getLanguageFromNextLocaleIsoCode,
19
+ getLanguageFromStory,
20
+ getShortestLocaleCode,
21
+ } from './languages';
22
+ import {
23
+ getContactsQuery,
24
+ getGalleriesQuery,
25
+ getSlugQuery,
26
+ getSortByPublishedDate,
27
+ getStoriesQuery,
28
+ } from './queries';
29
+
30
+ const DEFAULT_SORT_ORDER: SortOrder = 'desc';
31
+
32
+ type SortOrder = 'desc' | 'asc';
33
+
34
+ interface GetStoriesOptions {
35
+ page?: number;
36
+ pageSize?: number;
37
+ order?: SortOrder;
38
+ include?: (keyof ExtraStoryFields)[];
39
+ localeCode?: string;
40
+ }
41
+
42
+ interface GetGalleriesOptions {
43
+ page?: number;
44
+ pageSize: number;
45
+ }
46
+
47
+ export class PrezlyApi {
48
+ private readonly sdk: PrezlySDK;
49
+
50
+ private readonly newsroomUuid: Newsroom['uuid'];
51
+
52
+ constructor(accessToken: string, newsroomUuid: Newsroom['uuid']) {
53
+ const baseUrl = process.env.API_BASE_URL_OVERRIDE ?? undefined;
54
+ this.sdk = new PrezlySDK({
55
+ accessToken,
56
+ baseUrl,
57
+ headers: { 'X-Convert-v1-To-v3': 'true' },
58
+ });
59
+ this.newsroomUuid = newsroomUuid;
60
+ }
61
+
62
+ getStory(uuid: string) {
63
+ return this.sdk.stories.get(uuid);
64
+ }
65
+
66
+ async getNewsroom() {
67
+ return this.sdk.newsrooms.get(this.newsroomUuid);
68
+ }
69
+
70
+ async getNewsroomContacts() {
71
+ return this.sdk.newsroomContacts.search(this.newsroomUuid, {
72
+ query: JSON.stringify(getContactsQuery()),
73
+ });
74
+ }
75
+
76
+ async getNewsroomLanguages(): Promise<NewsroomLanguageSettings[]> {
77
+ return (await this.sdk.newsroomLanguages.list(this.newsroomUuid)).languages;
78
+ }
79
+
80
+ async getAllStories(order: SortOrder = DEFAULT_SORT_ORDER) {
81
+ const sortOrder = getSortByPublishedDate(order);
82
+ const newsroom = await this.getNewsroom();
83
+ const jsonQuery = JSON.stringify(getStoriesQuery(newsroom.uuid));
84
+ const maxStories = newsroom.stories_number;
85
+ const chunkSize = 200;
86
+
87
+ const pages = Math.ceil(maxStories / chunkSize);
88
+ const storiesPromises = Array.from({ length: pages }, (_, pageIndex) =>
89
+ this.searchStories({
90
+ limit: chunkSize,
91
+ sortOrder,
92
+ jsonQuery,
93
+ offset: pageIndex * chunkSize,
94
+ }),
95
+ );
96
+
97
+ const stories = (await Promise.all(storiesPromises)).flatMap(
98
+ (response) => response.stories,
99
+ );
100
+
101
+ return stories;
102
+ }
103
+
104
+ async getStories({
105
+ page = undefined,
106
+ pageSize = DEFAULT_PAGE_SIZE,
107
+ order = DEFAULT_SORT_ORDER,
108
+ include,
109
+ localeCode,
110
+ }: GetStoriesOptions = {}) {
111
+ const sortOrder = getSortByPublishedDate(order);
112
+ const jsonQuery = JSON.stringify(getStoriesQuery(this.newsroomUuid, undefined, localeCode));
113
+
114
+ const { stories, pagination } = await this.searchStories({
115
+ limit: pageSize,
116
+ offset: typeof page === 'undefined' ? undefined : (page - 1) * pageSize,
117
+ sortOrder,
118
+ jsonQuery,
119
+ include,
120
+ });
121
+
122
+ const storiesTotal = pagination.matched_records_number;
123
+
124
+ return { stories, storiesTotal };
125
+ }
126
+
127
+ async getStoriesFromCategory(
128
+ category: Category,
129
+ {
130
+ page = undefined,
131
+ pageSize = DEFAULT_PAGE_SIZE,
132
+ order = DEFAULT_SORT_ORDER,
133
+ include,
134
+ localeCode,
135
+ }: GetStoriesOptions = {},
136
+ ) {
137
+ const sortOrder = getSortByPublishedDate(order);
138
+ const jsonQuery = JSON.stringify(
139
+ getStoriesQuery(this.newsroomUuid, category.id, localeCode),
140
+ );
141
+
142
+ const { stories, pagination } = await this.searchStories({
143
+ limit: pageSize,
144
+ offset: typeof page === 'undefined' ? undefined : (page - 1) * pageSize,
145
+ sortOrder,
146
+ jsonQuery,
147
+ include,
148
+ });
149
+
150
+ const storiesTotal = pagination.matched_records_number;
151
+
152
+ return { stories, storiesTotal };
153
+ }
154
+
155
+ async getStoryBySlug(slug: string) {
156
+ const jsonQuery = JSON.stringify(getSlugQuery(this.newsroomUuid, slug));
157
+ const { stories } = await this.searchStories({
158
+ limit: 1,
159
+ jsonQuery,
160
+ });
161
+
162
+ if (stories[0]) {
163
+ return this.getStory(stories[0].uuid);
164
+ }
165
+
166
+ return null;
167
+ }
168
+
169
+ async getCategories(): Promise<Category[]> {
170
+ const categories = await this.sdk.newsroomCategories.list(this.newsroomUuid);
171
+
172
+ return Array.isArray(categories) ? categories : Object.values(categories);
173
+ }
174
+
175
+ async getCategoryBySlug(slug: string) {
176
+ const categories = await this.getCategories();
177
+
178
+ return categories.find((category) =>
179
+ Object.values(category.i18n).some((t) => t.slug === slug),
180
+ );
181
+ }
182
+
183
+ searchStories: typeof PrezlySDK.prototype.stories.search = (options) =>
184
+ this.sdk.stories.search(options);
185
+
186
+ async getGalleries({ page = undefined, pageSize }: GetGalleriesOptions) {
187
+ return this.sdk.newsroomGalleries.list(this.newsroomUuid, {
188
+ limit: pageSize,
189
+ offset: typeof page === 'undefined' ? undefined : (page - 1) * pageSize,
190
+ scope: getGalleriesQuery(),
191
+ });
192
+ }
193
+
194
+ async getGallery(uuid: string) {
195
+ return this.sdk.newsroomGalleries.get(this.newsroomUuid, uuid);
196
+ }
197
+
198
+ async getThemePreset() {
199
+ return this.sdk.newsroomThemes.getActive(this.newsroomUuid);
200
+ }
201
+
202
+ async getBasePageProps(
203
+ request: IncomingMessage | undefined,
204
+ nextLocaleIsoCode?: string,
205
+ story?: Story,
206
+ ): Promise<BasePageProps> {
207
+ const [newsroom, languages, categories, themePreset] = await Promise.all([
208
+ this.getNewsroom(),
209
+ this.getNewsroomLanguages(),
210
+ this.getCategories(),
211
+ this.getThemePreset(),
212
+ ]);
213
+
214
+ const currentLanguage = story
215
+ ? getLanguageFromStory(languages, story)
216
+ : getLanguageFromNextLocaleIsoCode(languages, nextLocaleIsoCode);
217
+ const defaultLanguage = getDefaultLanguage(languages);
218
+
219
+ const { code: localeCode } = currentLanguage || defaultLanguage;
220
+ const locale = LocaleObject.fromAnyCode(localeCode);
221
+ const shortestLocaleCode = getShortestLocaleCode(languages, locale);
222
+
223
+ // TODO: if no information given for current language, show boilerplate from default language
224
+ const companyInformation = getCompanyInformation(languages, locale);
225
+
226
+ const algoliaSettings = getAlgoliaSettings(request);
227
+
228
+ return {
229
+ newsroom,
230
+ companyInformation,
231
+ categories,
232
+ languages,
233
+ localeCode,
234
+ shortestLocaleCode,
235
+ localeResolved: Boolean(currentLanguage),
236
+ themePreset,
237
+ algoliaSettings,
238
+ };
239
+ }
240
+ }
@@ -0,0 +1,24 @@
1
+ import type { IncomingMessage } from 'http';
2
+
3
+ import { getEnvVariables } from '../lib/getEnvVariables';
4
+
5
+ import { PrezlyApi } from './PrezlyApi';
6
+
7
+ export const getPrezlyApi = (req?: IncomingMessage): PrezlyApi => {
8
+ if (process.browser) {
9
+ throw new Error('"getPrezlyApi" should only be used on back-end side.');
10
+ }
11
+
12
+ // `getEnvVariables` handles both cases for envs parsing - .env and request headers
13
+ const { PREZLY_ACCESS_TOKEN, PREZLY_NEWSROOM_UUID } = getEnvVariables(req);
14
+
15
+ if (!PREZLY_ACCESS_TOKEN) {
16
+ throw new Error('"PREZLY_ACCESS_TOKEN" is not set in env variables.');
17
+ }
18
+
19
+ if (!PREZLY_NEWSROOM_UUID) {
20
+ throw new Error('"PREZLY_NEWSROOM_UUID" is not set in env variables.');
21
+ }
22
+
23
+ return new PrezlyApi(PREZLY_ACCESS_TOKEN, PREZLY_NEWSROOM_UUID);
24
+ };
@@ -0,0 +1,2 @@
1
+ export * from './getPrezlyApi';
2
+ export * from './languages';
@@ -0,0 +1,172 @@
1
+ import type { NewsroomLanguageSettings, Story } from '@prezly/sdk';
2
+
3
+ import { DUMMY_DEFAULT_LOCALE, LocaleObject } from '../../intl';
4
+
5
+ export function getDefaultLanguage(languages: NewsroomLanguageSettings[]) {
6
+ return languages.find(({ is_default }) => is_default) || languages[0];
7
+ }
8
+
9
+ export function getUsedLanguages(languages: NewsroomLanguageSettings[]) {
10
+ return languages.filter((language) => language.public_stories_count > 0);
11
+ }
12
+
13
+ function getLanguageByExactLocaleCode(languages: NewsroomLanguageSettings[], locale: LocaleObject) {
14
+ const localeCode = locale.toUnderscoreCode();
15
+ return languages.find(({ code }) => code === localeCode);
16
+ }
17
+
18
+ // See https://github.com/prezly/prezly/blob/master/lib/model/CulturePeer.php#L123
19
+ function getLanguageByNeutralLocaleCode(
20
+ languages: NewsroomLanguageSettings[],
21
+ locale: LocaleObject,
22
+ ) {
23
+ const neutralLanguageCode = locale.toNeutralLanguageCode();
24
+
25
+ // Prefer default language
26
+ const defaultLanguage = getDefaultLanguage(languages);
27
+ if (defaultLanguage.code === locale.toUnderscoreCode()) {
28
+ return defaultLanguage;
29
+ }
30
+
31
+ // Try to look in used cultures first (giving priority to used ones)
32
+ const usedLanguages = getUsedLanguages(languages);
33
+ const usedLanguage = usedLanguages.find(
34
+ ({ code }) =>
35
+ LocaleObject.fromAnyCode(code).toNeutralLanguageCode() === neutralLanguageCode ||
36
+ code === neutralLanguageCode,
37
+ );
38
+ if (usedLanguage) {
39
+ return usedLanguage;
40
+ }
41
+
42
+ // Search in all languages
43
+ return languages.find(
44
+ ({ code }) =>
45
+ LocaleObject.fromAnyCode(code).toNeutralLanguageCode() === neutralLanguageCode ||
46
+ code === neutralLanguageCode,
47
+ );
48
+ }
49
+
50
+ // See https://github.com/prezly/prezly/blob/master/lib/model/CulturePeer.php#L159
51
+ function getLanguageByShortRegionCode(languages: NewsroomLanguageSettings[], locale: LocaleObject) {
52
+ const shortRegionCode = locale.toRegionCode();
53
+
54
+ // Prefer default language
55
+ const defaultLanguage = getDefaultLanguage(languages);
56
+ if (LocaleObject.fromAnyCode(defaultLanguage.code).toRegionCode() === shortRegionCode) {
57
+ return defaultLanguage;
58
+ }
59
+
60
+ // Try to look in used cultures first (giving priority to used ones)
61
+ const usedLanguages = getUsedLanguages(languages);
62
+ const usedLanguage = usedLanguages.find(
63
+ ({ code }) => LocaleObject.fromAnyCode(code).toRegionCode() === shortRegionCode,
64
+ );
65
+ if (usedLanguage) {
66
+ return usedLanguage;
67
+ }
68
+
69
+ // Search in all languages
70
+ return languages.find(
71
+ ({ code }) => LocaleObject.fromAnyCode(code).toRegionCode() === shortRegionCode,
72
+ );
73
+ }
74
+
75
+ /**
76
+ * Retrieve a culture with fallback to legacy codes support.
77
+ * Pulled from https://github.com/prezly/prezly/blob/de9900d9890a33502780494aa3fb85c9a732b3c3/lib/model/CulturePeer.php#L91-L114
78
+ */
79
+ export function getLanguageFromNextLocaleIsoCode(
80
+ languages: NewsroomLanguageSettings[],
81
+ nextLocaleIsoCode?: string,
82
+ ): NewsroomLanguageSettings | undefined {
83
+ const defaultLanguage = getDefaultLanguage(languages);
84
+
85
+ if (!nextLocaleIsoCode || nextLocaleIsoCode === DUMMY_DEFAULT_LOCALE) {
86
+ return defaultLanguage;
87
+ }
88
+
89
+ const locale = LocaleObject.fromAnyCode(nextLocaleIsoCode);
90
+ let targetLanguage: NewsroomLanguageSettings | undefined;
91
+
92
+ if (nextLocaleIsoCode.length === 2) {
93
+ targetLanguage =
94
+ getLanguageByExactLocaleCode(languages, locale) ||
95
+ getLanguageByNeutralLocaleCode(languages, locale) ||
96
+ getLanguageByShortRegionCode(languages, locale);
97
+ if (targetLanguage) {
98
+ return targetLanguage;
99
+ }
100
+ } else {
101
+ targetLanguage = getLanguageByExactLocaleCode(languages, locale);
102
+ }
103
+
104
+ return targetLanguage;
105
+ }
106
+
107
+ export function getLanguageFromStory(languages: NewsroomLanguageSettings[], story: Story) {
108
+ const { code: storyLocaleCode } = story.culture;
109
+
110
+ return languages.find(({ code }) => code === storyLocaleCode);
111
+ }
112
+
113
+ export function getCompanyInformation(languages: NewsroomLanguageSettings[], locale: LocaleObject) {
114
+ const currentLanguage =
115
+ getLanguageByExactLocaleCode(languages, locale) || getDefaultLanguage(languages);
116
+
117
+ return currentLanguage.company_information;
118
+ }
119
+
120
+ /**
121
+ * Get shortest locale code possible from full locale code
122
+ * First: try shorting to neutral language code (there should be no locales with the same language code)
123
+ * Then: try shorting to region code (there should be no locales with the same region code)
124
+ * Finally: return the original locale code (shorting is not possible)
125
+ */
126
+ export function getShortestLocaleCode(
127
+ languages: NewsroomLanguageSettings[],
128
+ locale: LocaleObject,
129
+ ): string | false {
130
+ const localeCode = locale.toUnderscoreCode();
131
+ const defaultLanguage = getDefaultLanguage(languages);
132
+ // If it's a default locale, return false (no locale needed in URL)
133
+ if (localeCode === defaultLanguage.code) {
134
+ return false;
135
+ }
136
+
137
+ // Try shorting to neutral language code
138
+ const neutralLanguageCode = locale.toNeutralLanguageCode();
139
+ // The code is already as short as possible
140
+ if (neutralLanguageCode === localeCode) {
141
+ return localeCode;
142
+ }
143
+
144
+ const matchingLanguagesByNeutralCode = languages.filter(
145
+ ({ code }) =>
146
+ LocaleObject.fromAnyCode(code).toNeutralLanguageCode() === neutralLanguageCode ||
147
+ code === neutralLanguageCode,
148
+ );
149
+ if (matchingLanguagesByNeutralCode.length === 1) {
150
+ return neutralLanguageCode;
151
+ }
152
+
153
+ // Try shorting to region code
154
+ const shortRegionCode = locale.toRegionCode();
155
+ const matchingLanguagesByRegionCode = languages.filter(
156
+ ({ code }) => LocaleObject.fromAnyCode(code).toRegionCode() === shortRegionCode,
157
+ );
158
+ // Prevent collision with neutral language codes
159
+ const mathchingNeutralLanguagesByRegionCode = languages.filter(
160
+ ({ code }) =>
161
+ LocaleObject.fromAnyCode(code).toNeutralLanguageCode() === shortRegionCode ||
162
+ code === shortRegionCode,
163
+ );
164
+ if (
165
+ matchingLanguagesByRegionCode.length === 1 &&
166
+ !mathchingNeutralLanguagesByRegionCode.length
167
+ ) {
168
+ return shortRegionCode;
169
+ }
170
+
171
+ return localeCode;
172
+ }
@@ -0,0 +1,44 @@
1
+ const publishedAndAccessible = [
2
+ { lifecycle_status: { $in: ['published'] } },
3
+ { visibility: { $in: ['public', 'private'] } },
4
+ ];
5
+
6
+ const publishedAndPublic = [
7
+ { lifecycle_status: { $in: ['published'] } },
8
+ { visibility: { $in: ['public'] } },
9
+ ];
10
+
11
+ export const getSlugQuery = (newsroomUuid: string, slug: string) => ({
12
+ $and: [
13
+ { 'newsroom.uuid': { $in: [newsroomUuid] } },
14
+ { slug: { $eq: slug } },
15
+ ...publishedAndAccessible,
16
+ ],
17
+ });
18
+
19
+ export const getStoriesQuery = (newsroomUuid: string, categoryId?: number, localeCode?: string) => {
20
+ const query: any = {
21
+ $and: [...publishedAndPublic, { 'newsroom.uuid': { $in: [newsroomUuid] } }],
22
+ };
23
+
24
+ if (categoryId) {
25
+ query.$and.push({ 'category.id': { $any: [categoryId] } });
26
+ }
27
+
28
+ if (localeCode) {
29
+ query.$and.push({ locale: { $in: [localeCode] } });
30
+ }
31
+
32
+ return query;
33
+ };
34
+
35
+ export const getSortByPublishedDate = (order: 'asc' | 'desc') =>
36
+ order === 'desc' ? '-published_at' : 'published_at';
37
+
38
+ export const getGalleriesQuery = () => ({
39
+ $and: [{ status: { $eq: 'public' } }, { images_number: { $gt: 0 } }],
40
+ });
41
+
42
+ export const getContactsQuery = () => ({
43
+ $and: [{ is_featured: { $eq: true } }],
44
+ });
@@ -0,0 +1,2 @@
1
+ export const ASSETS_URL = 'https://cdn.uc.assets.prezly.com';
2
+ export const DEFAULT_PAGE_SIZE = 12;
@@ -0,0 +1,5 @@
1
+ import { ASSETS_URL } from './constants';
2
+
3
+ export function getAssetsUrl(uuid: string) {
4
+ return `${ASSETS_URL}/${uuid}/`;
5
+ }
@@ -0,0 +1,47 @@
1
+ import type { Category } from '@prezly/sdk';
2
+
3
+ import { LocaleObject } from '../intl';
4
+ import { AlgoliaCategoryRef } from '../types';
5
+
6
+ function isAlgoliaCategory(
7
+ category: Category | AlgoliaCategoryRef,
8
+ ): category is AlgoliaCategoryRef {
9
+ return 'name' in category && 'slug' in category;
10
+ }
11
+
12
+ export function getLocalizedCategoryData(
13
+ category: Category | AlgoliaCategoryRef,
14
+ locale: LocaleObject,
15
+ ) {
16
+ if (isAlgoliaCategory(category)) {
17
+ return {
18
+ description: null,
19
+ name: category.name,
20
+ slug: category.slug,
21
+ };
22
+ }
23
+
24
+ const targetLocaleCode = locale.toUnderscoreCode();
25
+ const { i18n } = category;
26
+ const populatedLocales = Object.keys(i18n).filter((localeCode) => i18n[localeCode].name);
27
+ const targetLocale =
28
+ populatedLocales.find((localeCode) => localeCode === targetLocaleCode) ||
29
+ populatedLocales.find((localeCode) => i18n[localeCode].name === category.display_name) ||
30
+ populatedLocales[0];
31
+
32
+ const { locale: _, ...localizedData } = i18n[targetLocale];
33
+
34
+ return localizedData;
35
+ }
36
+
37
+ export function getCategoryUrl(category: Category | AlgoliaCategoryRef, locale: LocaleObject) {
38
+ const { slug } = getLocalizedCategoryData(category, locale);
39
+ return `/category/${slug}`;
40
+ }
41
+
42
+ export function getCategoryHasTranslation(category: Category, locale: LocaleObject) {
43
+ const targetLocaleCode = locale.toUnderscoreCode();
44
+ const { i18n } = category;
45
+ const populatedLocales = Object.keys(i18n).filter((localeCode) => i18n[localeCode].name);
46
+ return Boolean(populatedLocales.find((localeCode) => localeCode === targetLocaleCode));
47
+ }
@@ -0,0 +1,21 @@
1
+ import type { Newsroom } from '@prezly/sdk';
2
+
3
+ import { getAssetsUrl } from './getAssetsUrl';
4
+
5
+ export function getNewsroomLogoUrl(newsroom: Newsroom): string {
6
+ if (newsroom.newsroom_logo) {
7
+ getAssetsUrl(newsroom.newsroom_logo.uuid);
8
+ }
9
+
10
+ return '';
11
+ }
12
+
13
+ export function getNewsroomFaviconUrl(newsroom: Newsroom, previewSize = 400): string {
14
+ if (newsroom.icon) {
15
+ return `${getAssetsUrl(
16
+ newsroom.icon.uuid,
17
+ )}-/preview/${previewSize}x${previewSize}/-/quality/best/`;
18
+ }
19
+
20
+ return '';
21
+ }
@@ -0,0 +1,22 @@
1
+ import type { Newsroom } from '@prezly/sdk';
2
+
3
+ import { LocaleObject } from '../intl';
4
+
5
+ type PrivacyPortalUrlOptions = { email?: string; action?: 'subscribe' | 'unsubscribe' };
6
+
7
+ export function getPrivacyPortalUrl(
8
+ newsroom: Newsroom,
9
+ locale: LocaleObject,
10
+ options?: PrivacyPortalUrlOptions,
11
+ ) {
12
+ const { email, action = 'subscribe' } = options || {};
13
+
14
+ const url = new URL(
15
+ `/${locale.toUrlSlug()}/newsroom/${newsroom.uuid}/${action}`,
16
+ 'https://privacy.prezly.com',
17
+ );
18
+ if (email) {
19
+ url.searchParams.append('email', email);
20
+ }
21
+ return url.toString();
22
+ }
@@ -0,0 +1,15 @@
1
+ import type { Story } from '@prezly/sdk';
2
+
3
+ import { AlgoliaStory } from '../types';
4
+
5
+ export function getStoryPublicationDate(story: Story | AlgoliaStory): Date | null {
6
+ const { published_at } = story;
7
+
8
+ if (!published_at) {
9
+ return null;
10
+ }
11
+
12
+ return typeof published_at === 'string'
13
+ ? new Date(published_at)
14
+ : new Date(published_at * 1000);
15
+ }
@@ -0,0 +1,5 @@
1
+ import { ASSETS_URL } from './constants';
2
+
3
+ export function getUploadcareGroupUrl(uuid: string, title: string) {
4
+ return `${ASSETS_URL}/${uuid}/archive/zip/${encodeURI(title)}.zip`;
5
+ }
@@ -0,0 +1,9 @@
1
+ export * from './api';
2
+ export * from './constants';
3
+ export * from './getAssetsUrl';
4
+ export * from './getLocalizedCategoryData';
5
+ export * from './getNewsroomLogoUrl';
6
+ export * from './getPrivacyPortalUrl';
7
+ export * from './getStoryPublicationDate';
8
+ export * from './getUploadcareGroupUrl';
9
+ export * from './types';
@@ -0,0 +1,29 @@
1
+ import type { IncomingMessage } from 'http';
2
+
3
+ import { AlgoliaSettings } from '../types';
4
+
5
+ import { getEnvVariables } from './getEnvVariables';
6
+
7
+ export const getAlgoliaSettings = (req?: IncomingMessage): AlgoliaSettings => {
8
+ if (process.browser) {
9
+ throw new Error('"getAlgoliaSettings" should only be used on back-end side.');
10
+ }
11
+
12
+ // `getEnvVariables` handles both cases for envs parsing - .env and request headers
13
+ const {
14
+ ALGOLIA_API_KEY = '',
15
+ ALGOLIA_APP_ID = 'UI4CNRAHQB',
16
+ ALGOLIA_INDEX = 'public_stories_prod',
17
+ } = getEnvVariables(req);
18
+
19
+ if (!ALGOLIA_API_KEY) {
20
+ // eslint-disable-next-line no-console
21
+ console.error('"ALGOLIA_API_KEY" is not set in env variables. Search will not be enabled');
22
+ }
23
+
24
+ return {
25
+ ALGOLIA_API_KEY,
26
+ ALGOLIA_APP_ID,
27
+ ALGOLIA_INDEX,
28
+ };
29
+ };