@deepsel/cms-utils 1.1.3 → 1.1.6

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 (51) hide show
  1. package/dist/blog/fetchBlogList.d.ts +15 -0
  2. package/dist/blog/fetchBlogList.js +51 -0
  3. package/dist/blog/fetchBlogPost.d.ts +14 -0
  4. package/dist/blog/fetchBlogPost.js +61 -0
  5. package/dist/blog/index.d.ts +3 -0
  6. package/dist/blog/index.js +3 -0
  7. package/dist/blog/types.d.ts +45 -0
  8. package/dist/blog/types.js +1 -0
  9. package/dist/common/utils/cookieUtils.d.ts +12 -0
  10. package/dist/common/utils/cookieUtils.js +39 -0
  11. package/dist/common/utils/index.d.ts +48 -0
  12. package/dist/common/utils/index.js +95 -0
  13. package/dist/common/utils/isObjectOrArray.d.ts +4 -0
  14. package/dist/common/utils/isObjectOrArray.js +7 -0
  15. package/dist/constants/index.d.ts +2 -0
  16. package/dist/constants/index.js +2 -0
  17. package/dist/constants/pagingTableParams.d.ts +8 -0
  18. package/dist/constants/pagingTableParams.js +8 -0
  19. package/dist/constants/websiteDataTypes.d.ts +7 -0
  20. package/dist/constants/websiteDataTypes.js +6 -0
  21. package/dist/index.d.ts +6 -4
  22. package/dist/index.js +6 -4
  23. package/dist/menus/index.d.ts +2 -2
  24. package/dist/menus/index.js +2 -2
  25. package/dist/menus/isActiveMenu.d.ts +3 -3
  26. package/dist/menus/isActiveMenu.js +2 -2
  27. package/dist/page/fetchPageData.d.ts +10 -2
  28. package/dist/page/fetchPageData.js +23 -23
  29. package/dist/page/fetchPublicSettings.d.ts +1 -1
  30. package/dist/page/fetchSearchResults.d.ts +31 -0
  31. package/dist/page/fetchSearchResults.js +73 -0
  32. package/dist/page/getPathType.d.ts +9 -0
  33. package/dist/page/getPathType.js +46 -0
  34. package/dist/page/index.d.ts +7 -6
  35. package/dist/page/index.js +7 -6
  36. package/dist/page/isCrossingTemplateBoundary.d.ts +1 -0
  37. package/dist/page/isCrossingTemplateBoundary.js +6 -0
  38. package/dist/page/parseSlug.d.ts +12 -0
  39. package/dist/page/{parseSlugForLangAndPath.js → parseSlug.js} +8 -6
  40. package/dist/page/types.d.ts +4 -32
  41. package/dist/types.d.ts +8 -0
  42. package/package.json +9 -64
  43. package/dist/language/index.d.ts +0 -1
  44. package/dist/language/index.js +0 -1
  45. package/dist/page/constants.d.ts +0 -0
  46. package/dist/page/constants.js +0 -1
  47. package/dist/page/fetchFormData.d.ts +0 -4
  48. package/dist/page/fetchFormData.js +0 -38
  49. package/dist/page/parseSlugForLangAndPath.d.ts +0 -5
  50. /package/dist/{language → page}/isValidLanguageCode.d.ts +0 -0
  51. /package/dist/{language → page}/isValidLanguageCode.js +0 -0
@@ -0,0 +1,15 @@
1
+ import type { BlogListData } from './types.js';
2
+ import type { Pagination } from '../page/getPathType.js';
3
+ interface FetchBlogListProps {
4
+ lang?: string;
5
+ pagination?: Pagination;
6
+ astroRequest?: Request;
7
+ authToken?: string;
8
+ backendHost?: string;
9
+ }
10
+ /**
11
+ * Fetches blog list from the backend by language
12
+ * Corresponds to GET /blog_post/website/{lang}
13
+ */
14
+ export declare function fetchBlogList({ astroRequest, pagination, authToken, lang, backendHost, }: FetchBlogListProps): Promise<BlogListData>;
15
+ export {};
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Fetches blog list from the backend by language
3
+ * Corresponds to GET /blog_post/website/{lang}
4
+ */
5
+ export async function fetchBlogList({ astroRequest, pagination, authToken, lang = 'default', backendHost = 'http://localhost:8000/api/v1', }) {
6
+ try {
7
+ let url = `${backendHost}/blog_post/list/${lang}`;
8
+ if (pagination) {
9
+ const searchParams = new URLSearchParams();
10
+ if (pagination.page)
11
+ searchParams.append('page', pagination.page.toString());
12
+ if (pagination.pageSize)
13
+ searchParams.append('page_size', pagination.pageSize.toString());
14
+ url += `?${searchParams.toString()}`;
15
+ }
16
+ const fetchOptions = {
17
+ method: 'GET',
18
+ headers: {
19
+ 'Content-Type': 'application/json',
20
+ },
21
+ };
22
+ let hostname = null;
23
+ if (astroRequest) {
24
+ const url = new URL(astroRequest.url);
25
+ hostname = url.hostname;
26
+ }
27
+ else if (typeof window !== 'undefined') {
28
+ hostname = window.location.hostname;
29
+ }
30
+ if (hostname) {
31
+ fetchOptions.headers['X-Original-Host'] = hostname;
32
+ fetchOptions.headers['X-Frontend-Host'] = hostname;
33
+ }
34
+ if (authToken) {
35
+ fetchOptions.headers['Authorization'] = `Bearer ${authToken}`;
36
+ }
37
+ const response = await fetch(url, fetchOptions);
38
+ if (response.status === 401) {
39
+ throw new Error('Authentication required');
40
+ }
41
+ if (!response.ok) {
42
+ throw new Error(`Failed to fetch blog list: ${response.statusText}`);
43
+ }
44
+ const jsonData = await response.json();
45
+ return jsonData;
46
+ }
47
+ catch (error) {
48
+ console.error('Error fetching blog list:', error);
49
+ throw error;
50
+ }
51
+ }
@@ -0,0 +1,14 @@
1
+ import type { BlogPostData } from './types.js';
2
+ interface FetchBlogPostProps {
3
+ path: string;
4
+ lang?: string;
5
+ astroRequest?: Request;
6
+ authToken?: string;
7
+ backendHost?: string;
8
+ }
9
+ /**
10
+ * Fetches a single blog post from the backend by language and path
11
+ * Corresponds to GET /blog_post/website/{lang}/{path}
12
+ */
13
+ export declare function fetchBlogPost({ path, lang, astroRequest, authToken, backendHost, }: FetchBlogPostProps): Promise<BlogPostData>;
14
+ export {};
@@ -0,0 +1,61 @@
1
+ import { fetchPublicSettings } from '../page/index.js';
2
+ /**
3
+ * Fetches a single blog post from the backend by language and path
4
+ * Corresponds to GET /blog_post/website/{lang}/{path}
5
+ */
6
+ export async function fetchBlogPost({ path, lang = 'default', astroRequest, authToken, backendHost = 'http://localhost:8000/api/v1', }) {
7
+ try {
8
+ const cleanPath = path.startsWith('/') ? path.substring(1) : path;
9
+ let postSlug = cleanPath;
10
+ // rm the blog/ prefix
11
+ if (cleanPath.startsWith('blog/')) {
12
+ postSlug = cleanPath.substring('blog/'.length);
13
+ }
14
+ const url = `${backendHost}/blog_post/single/${lang}/${postSlug}`;
15
+ const fetchOptions = {
16
+ method: 'GET',
17
+ headers: {
18
+ 'Content-Type': 'application/json',
19
+ },
20
+ };
21
+ let hostname = null;
22
+ if (astroRequest) {
23
+ const url = new URL(astroRequest.url);
24
+ hostname = url.hostname;
25
+ }
26
+ else if (typeof window !== 'undefined') {
27
+ hostname = window.location.hostname;
28
+ }
29
+ if (hostname) {
30
+ fetchOptions.headers['X-Original-Host'] = hostname;
31
+ fetchOptions.headers['X-Frontend-Host'] = hostname;
32
+ }
33
+ if (authToken) {
34
+ fetchOptions.headers['Authorization'] = `Bearer ${authToken}`;
35
+ }
36
+ const response = await fetch(url, fetchOptions);
37
+ if (response.status === 401) {
38
+ throw new Error('Authentication required');
39
+ }
40
+ if (response.status === 404) {
41
+ try {
42
+ const { detail } = (await response.json());
43
+ console.warn('404', url, { detail });
44
+ }
45
+ catch {
46
+ console.warn('404', url);
47
+ }
48
+ const siteSettings = await fetchPublicSettings(null, astroRequest, lang, backendHost);
49
+ return {
50
+ notFound: true,
51
+ public_settings: siteSettings,
52
+ };
53
+ }
54
+ const jsonData = await response.json();
55
+ return jsonData;
56
+ }
57
+ catch (error) {
58
+ console.error('Error fetching blog post:', error);
59
+ throw error;
60
+ }
61
+ }
@@ -0,0 +1,3 @@
1
+ export * from './fetchBlogList.js';
2
+ export * from './fetchBlogPost.js';
3
+ export * from './types.js';
@@ -0,0 +1,3 @@
1
+ export * from './fetchBlogList.js';
2
+ export * from './fetchBlogPost.js';
3
+ export * from './types.js';
@@ -0,0 +1,45 @@
1
+ import type { SiteSettings } from '../types.js';
2
+ import type { SeoMetadata, LanguageAlternative } from '../page/types.js';
3
+ export interface BlogPostAuthor {
4
+ id: number;
5
+ display_name?: string;
6
+ username: string;
7
+ image?: string;
8
+ }
9
+ export interface BlogPostListItem {
10
+ id: number;
11
+ title: string;
12
+ slug: string;
13
+ excerpt?: string;
14
+ featured_image_id?: number;
15
+ featured_image_name?: string;
16
+ publish_date?: string;
17
+ author?: BlogPostAuthor;
18
+ lang: string;
19
+ }
20
+ export interface BlogListData {
21
+ lang: string;
22
+ public_settings: SiteSettings;
23
+ blog_posts: BlogPostListItem[];
24
+ page: number;
25
+ page_size: number;
26
+ total_count: number;
27
+ total_pages: number;
28
+ }
29
+ export interface BlogPostData {
30
+ id?: number;
31
+ title?: string;
32
+ content?: string;
33
+ lang?: string;
34
+ public_settings: SiteSettings;
35
+ seo_metadata?: SeoMetadata;
36
+ custom_code?: string | null;
37
+ page_custom_code?: string | null;
38
+ require_login?: boolean | null;
39
+ featured_image_id?: number | null;
40
+ featured_image_name?: string | null;
41
+ publish_date?: string | null;
42
+ author?: BlogPostAuthor | null;
43
+ language_alternatives?: LanguageAlternative[];
44
+ notFound?: boolean;
45
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Sets a cookie with the given name, value, and expiration days
3
+ */
4
+ export declare function setCookie(name: string, value: string, days?: number): void;
5
+ /**
6
+ * Gets a cookie value by name
7
+ */
8
+ export declare function getCookie(name: string): string | null;
9
+ /**
10
+ * Removes a cookie by name
11
+ */
12
+ export declare function removeCookie(name: string): void;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Cookie expiration in days when not specified
3
+ */
4
+ const COOKIE_DEFAULT_EXPIRY_DAYS = 30;
5
+ /**
6
+ * Sets a cookie with the given name, value, and expiration days
7
+ */
8
+ export function setCookie(name, value, days = COOKIE_DEFAULT_EXPIRY_DAYS) {
9
+ if (typeof document !== 'undefined') {
10
+ const expiryDate = new Date();
11
+ expiryDate.setDate(expiryDate.getDate() + days);
12
+ document.cookie = `${name}=${value}; expires=${expiryDate.toUTCString()}; path=/; SameSite=Lax`;
13
+ }
14
+ }
15
+ /**
16
+ * Gets a cookie value by name
17
+ */
18
+ export function getCookie(name) {
19
+ if (typeof document !== 'undefined') {
20
+ const nameEQ = name + '=';
21
+ const ca = document.cookie.split(';');
22
+ for (let i = 0; i < ca.length; i++) {
23
+ let c = ca[i];
24
+ while (c.charAt(0) === ' ')
25
+ c = c.substring(1, c.length);
26
+ if (c.indexOf(nameEQ) === 0)
27
+ return c.substring(nameEQ.length, c.length);
28
+ }
29
+ }
30
+ return null;
31
+ }
32
+ /**
33
+ * Removes a cookie by name
34
+ */
35
+ export function removeCookie(name) {
36
+ if (typeof document !== 'undefined') {
37
+ document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
38
+ }
39
+ }
@@ -0,0 +1,48 @@
1
+ export * from './cookieUtils.js';
2
+ export * from './isObjectOrArray.js';
3
+ /**
4
+ * Builds the full URL for an attachment file
5
+ * @param backendHost - The backend host URL (e.g. "https://api.example.com")
6
+ * @param name - The attachment filename
7
+ * @returns Full URL to serve the attachment, or empty string if name is falsy
8
+ */
9
+ export declare function getAttachmentUrl(backendHost: string, name: string): string;
10
+ /**
11
+ * Builds the relative URL for an attachment file (no host)
12
+ * @param name - The attachment filename
13
+ * @returns Relative URL to serve the attachment, or empty string if name is falsy
14
+ */
15
+ export declare function getAttachmentRelativeUrl(name: string): string;
16
+ /**
17
+ * Extracts the filename from an attachment serve URL
18
+ * @param url - The attachment URL (e.g. "https://api.example.com/attachment/serve/file.png")
19
+ * @returns The filename portion of the URL, or empty string if url is falsy
20
+ */
21
+ export declare function getFileNameFromAttachUrl(url: string): string;
22
+ /**
23
+ * Triggers a browser download for a file from an attachment URL
24
+ * @param url - The attachment URL to download from
25
+ */
26
+ export declare function downloadFromAttachUrl(url: string): void;
27
+ /**
28
+ * Converts a string to a deterministic hex color via a simple hash
29
+ */
30
+ export declare function stringToColor(string: string): string;
31
+ /**
32
+ * Returns a background color and initials string derived from a display name.
33
+ * Suitable for use in avatar components.
34
+ */
35
+ export declare function stringAvatar(name: string): {
36
+ color: string;
37
+ children: string;
38
+ };
39
+ /**
40
+ * Extracts the file extension from a filename or URL
41
+ * @param fileName - The filename or path (e.g. "photo.jpg", "/path/to/file.pdf")
42
+ * @returns The extension without the dot (e.g. "jpg"), or empty string if none
43
+ */
44
+ export declare function getFileExtension(fileName: string): string;
45
+ /**
46
+ * Format file size in human-readable format
47
+ */
48
+ export declare const formatFileSize: (bytes: number) => string;
@@ -0,0 +1,95 @@
1
+ export * from './cookieUtils.js';
2
+ export * from './isObjectOrArray.js';
3
+ /**
4
+ * Builds the full URL for an attachment file
5
+ * @param backendHost - The backend host URL (e.g. "https://api.example.com")
6
+ * @param name - The attachment filename
7
+ * @returns Full URL to serve the attachment, or empty string if name is falsy
8
+ */
9
+ export function getAttachmentUrl(backendHost, name) {
10
+ return name ? `${backendHost}/attachment/serve/${name}` : '';
11
+ }
12
+ /**
13
+ * Builds the relative URL for an attachment file (no host)
14
+ * @param name - The attachment filename
15
+ * @returns Relative URL to serve the attachment, or empty string if name is falsy
16
+ */
17
+ export function getAttachmentRelativeUrl(name) {
18
+ return name ? `/attachment/serve/${name}` : '';
19
+ }
20
+ /**
21
+ * Extracts the filename from an attachment serve URL
22
+ * @param url - The attachment URL (e.g. "https://api.example.com/attachment/serve/file.png")
23
+ * @returns The filename portion of the URL, or empty string if url is falsy
24
+ */
25
+ export function getFileNameFromAttachUrl(url) {
26
+ return url?.substring(url.lastIndexOf('/') + 1) || '';
27
+ }
28
+ /**
29
+ * Triggers a browser download for a file from an attachment URL
30
+ * @param url - The attachment URL to download from
31
+ */
32
+ export function downloadFromAttachUrl(url) {
33
+ const downloadUrl = url.includes('?') ? `${url}&download=true` : `${url}?download=true`;
34
+ fetch(downloadUrl)
35
+ .then((response) => response.blob())
36
+ .then((blob) => {
37
+ const objectUrl = URL.createObjectURL(blob);
38
+ const link = document.createElement('a');
39
+ link.href = objectUrl;
40
+ link.setAttribute('download', getFileNameFromAttachUrl(url));
41
+ document.body.appendChild(link);
42
+ link.click();
43
+ document.body.removeChild(link);
44
+ URL.revokeObjectURL(objectUrl);
45
+ })
46
+ .catch((error) => {
47
+ console.error('Error downloading the file:', error);
48
+ });
49
+ }
50
+ /**
51
+ * Converts a string to a deterministic hex color via a simple hash
52
+ */
53
+ export function stringToColor(string) {
54
+ let hash = 0;
55
+ for (let i = 0; i < string.length; i++) {
56
+ hash = string.charCodeAt(i) + ((hash << 5) - hash);
57
+ }
58
+ let color = '#';
59
+ for (let i = 0; i < 3; i++) {
60
+ const value = (hash >> (i * 8)) & 0xff;
61
+ color += `00${value.toString(16)}`.slice(-2);
62
+ }
63
+ return color;
64
+ }
65
+ /**
66
+ * Returns a background color and initials string derived from a display name.
67
+ * Suitable for use in avatar components.
68
+ */
69
+ export function stringAvatar(name) {
70
+ const children = name.includes(' ')
71
+ ? `${name.split(' ')[0][0]}${name.split(' ')[1][0]}`
72
+ : (name[0] ?? '');
73
+ return {
74
+ color: stringToColor(name),
75
+ children,
76
+ };
77
+ }
78
+ /**
79
+ * Extracts the file extension from a filename or URL
80
+ * @param fileName - The filename or path (e.g. "photo.jpg", "/path/to/file.pdf")
81
+ * @returns The extension without the dot (e.g. "jpg"), or empty string if none
82
+ */
83
+ export function getFileExtension(fileName) {
84
+ return fileName?.split('.').pop() ?? '';
85
+ }
86
+ /**
87
+ * Format file size in human-readable format
88
+ */
89
+ export const formatFileSize = (bytes) => {
90
+ if (!bytes)
91
+ return '';
92
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
93
+ const i = Math.floor(Math.log(bytes) / Math.log(1024));
94
+ return Math.round((bytes / Math.pow(1024, i)) * 100) / 100 + ' ' + sizes[i];
95
+ };
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Returns true if value is a plain object or array (not null)
3
+ */
4
+ export declare function isObjectOrArray(value: unknown): boolean;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Returns true if value is a plain object or array (not null)
3
+ */
4
+ export function isObjectOrArray(value) {
5
+ return (value !== null &&
6
+ (Array.isArray(value) || Object.prototype.toString.call(value) === '[object Object]'));
7
+ }
@@ -0,0 +1,2 @@
1
+ export * from './websiteDataTypes.js';
2
+ export * from './pagingTableParams.js';
@@ -0,0 +1,2 @@
1
+ export * from './websiteDataTypes.js';
2
+ export * from './pagingTableParams.js';
@@ -0,0 +1,8 @@
1
+ /**
2
+ * URL search parameter keys used for paging and searching in list views
3
+ */
4
+ export declare const PagingTableParams: {
5
+ readonly Search: "search";
6
+ readonly Limit: "limit";
7
+ readonly Page: "page";
8
+ };
@@ -0,0 +1,8 @@
1
+ /**
2
+ * URL search parameter keys used for paging and searching in list views
3
+ */
4
+ export const PagingTableParams = {
5
+ Search: 'search',
6
+ Limit: 'limit',
7
+ Page: 'page',
8
+ };
@@ -0,0 +1,7 @@
1
+ export declare const WebsiteDataTypes: {
2
+ readonly Page: "Page";
3
+ readonly BlogList: "BlogList";
4
+ readonly BlogPost: "BlogPost";
5
+ readonly SearchResults: "SearchResults";
6
+ };
7
+ export type WebsiteDataType = (typeof WebsiteDataTypes)[keyof typeof WebsiteDataTypes];
@@ -0,0 +1,6 @@
1
+ export const WebsiteDataTypes = {
2
+ Page: 'Page',
3
+ BlogList: 'BlogList',
4
+ BlogPost: 'BlogPost',
5
+ SearchResults: 'SearchResults',
6
+ };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,6 @@
1
- export * from './page';
2
- export * from './language';
3
- export * from './menus';
4
- export * from './types';
1
+ export * from './blog/index.js';
2
+ export * from './common/utils/index.js';
3
+ export * from './constants/index.js';
4
+ export * from './menus/index.js';
5
+ export * from './page/index.js';
6
+ export * from './types.js';
package/dist/index.js CHANGED
@@ -1,4 +1,6 @@
1
- export * from './page';
2
- export * from './language';
3
- export * from './menus';
4
- export * from './types';
1
+ export * from './blog/index.js';
2
+ export * from './common/utils/index.js';
3
+ export * from './constants/index.js';
4
+ export * from './menus/index.js';
5
+ export * from './page/index.js';
6
+ export * from './types.js';
@@ -1,2 +1,2 @@
1
- export * from './isActiveMenu';
2
- export * from './types';
1
+ export * from './isActiveMenu.js';
2
+ export * from './types.js';
@@ -1,2 +1,2 @@
1
- export * from './isActiveMenu';
2
- export * from './types';
1
+ export * from './isActiveMenu.js';
2
+ export * from './types.js';
@@ -1,3 +1,3 @@
1
- import type { MenuItem } from './types';
2
- import type { PageData } from '../page';
3
- export declare const isActiveMenu: (menuItem: MenuItem, pageData: PageData) => boolean;
1
+ import type { MenuItem } from './types.js';
2
+ import type { WebsiteData } from '../types.js';
3
+ export declare const isActiveMenu: (menuItem: MenuItem, websiteData: WebsiteData) => boolean;
@@ -1,11 +1,11 @@
1
1
  // Check if a menu item should be marked as active
2
- export const isActiveMenu = (menuItem, pageData) => {
2
+ export const isActiveMenu = (menuItem, websiteData) => {
3
3
  // return if not browser
4
4
  if (typeof window === 'undefined') {
5
5
  return false;
6
6
  }
7
7
  const location = window.location;
8
- const currentLang = pageData.lang;
8
+ const currentLang = websiteData.data.lang;
9
9
  let result;
10
10
  if (menuItem.url === '/') {
11
11
  result =
@@ -1,5 +1,13 @@
1
- import type { PageData } from './types';
1
+ import type { PageData } from './types.js';
2
+ interface FetchPageDataProps {
3
+ path: string;
4
+ lang?: string;
5
+ astroRequest?: Request;
6
+ authToken?: string;
7
+ backendHost?: string;
8
+ }
2
9
  /**
3
10
  * Fetches page data from the backend by language and slug
4
11
  */
5
- export declare function fetchPageData(lang: string | null, slug: string, isPreview?: boolean, authToken?: string | null, astroRequest?: Request | null, backendHost?: string): Promise<PageData>;
12
+ export declare function fetchPageData({ path, lang, astroRequest, authToken, backendHost, }: FetchPageDataProps): Promise<PageData>;
13
+ export {};
@@ -1,26 +1,24 @@
1
- import { fetchPublicSettings } from './fetchPublicSettings';
1
+ import { fetchPublicSettings } from './fetchPublicSettings.js';
2
2
  /**
3
3
  * Fetches page data from the backend by language and slug
4
4
  */
5
- export async function fetchPageData(lang, slug, isPreview = false, authToken = null, astroRequest = null, backendHost = 'http://localhost:8000') {
5
+ export async function fetchPageData({ path, lang, astroRequest, authToken, backendHost = 'http://localhost:8000/api/v1', }) {
6
6
  try {
7
- // Format the slug properly, make sure it starts with a slash
8
- let formattedSlug = slug.startsWith('/') ? slug : `/${slug}`;
7
+ // Format the path properly, make sure it starts with a slash
8
+ let formattedPath = path.startsWith('/') ? path : `/${path}`;
9
9
  // Backend will consider 'default' as the home slug
10
- if (formattedSlug === '/') {
11
- formattedSlug = '/default';
10
+ if (formattedPath === '/') {
11
+ formattedPath = '/default';
12
12
  }
13
13
  // Determine the URL based on whether a language is provided
14
- let url;
15
- if (lang && lang !== 'default') {
16
- url = `${backendHost}/page/website/${lang}${formattedSlug}`;
17
- }
18
- else {
19
- url = `${backendHost}/page/website/default${formattedSlug}`;
20
- }
14
+ const langPrefix = lang || 'default';
15
+ let url = `${backendHost}/page/website/${langPrefix}${formattedPath}`;
21
16
  // Add preview parameter if enabled
22
- if (isPreview) {
23
- url += '?preview=true';
17
+ if (astroRequest) {
18
+ const previewParam = new URL(astroRequest.url).searchParams.get('preview');
19
+ if (previewParam === 'true') {
20
+ url += `?preview=true`;
21
+ }
24
22
  }
25
23
  // Prepare fetch options
26
24
  const fetchOptions = {
@@ -33,8 +31,8 @@ export async function fetchPageData(lang, slug, isPreview = false, authToken = n
33
31
  let hostname = null;
34
32
  // Server-side: Extract hostname from Astro request
35
33
  if (astroRequest) {
36
- const url = new URL(astroRequest.url);
37
- hostname = url.hostname;
34
+ const requestUrl = new URL(astroRequest.url);
35
+ hostname = requestUrl.hostname;
38
36
  }
39
37
  // Client-side: Extract hostname from window
40
38
  else if (typeof window !== 'undefined') {
@@ -57,22 +55,24 @@ export async function fetchPageData(lang, slug, isPreview = false, authToken = n
57
55
  }
58
56
  // Only treat actual 404 as not found
59
57
  if (response.status === 404) {
60
- const { detail } = await response.json();
61
- console.warn('404', url, { detail });
58
+ try {
59
+ const { detail } = await response.json();
60
+ console.warn('404', url, { detail });
61
+ }
62
+ catch {
63
+ console.warn('404', url);
64
+ }
62
65
  // When page is not found, still fetch menus and site settings
63
66
  try {
64
67
  const siteSettings = await fetchPublicSettings(null, astroRequest, lang, backendHost);
65
68
  return {
66
69
  notFound: true,
67
- status: 404,
68
- detail,
69
70
  public_settings: siteSettings,
70
- lang: lang || siteSettings.default_language?.iso_code || 'en',
71
71
  };
72
72
  }
73
73
  catch (settingsError) {
74
74
  console.warn('Could not fetch site settings for 404 page:', settingsError);
75
- throw new Error(`Page not found: ${detail}`);
75
+ throw new Error(`Page not found`);
76
76
  }
77
77
  }
78
78
  try {
@@ -1,4 +1,4 @@
1
- import type { SiteSettings } from '../types';
1
+ import type { SiteSettings } from '../types.js';
2
2
  /**
3
3
  * Fetches public settings from the backend
4
4
  */
@@ -0,0 +1,31 @@
1
+ import type { SiteSettings } from '../types.js';
2
+ export interface SearchResultItem {
3
+ id: string;
4
+ title: string;
5
+ url: string;
6
+ publishDate: string | null;
7
+ contentType: string;
8
+ relevanceScore: number;
9
+ }
10
+ export interface SearchResultsData {
11
+ lang: string;
12
+ query: string;
13
+ public_settings: SiteSettings;
14
+ results: SearchResultItem[];
15
+ total: number;
16
+ suggestions: string[];
17
+ }
18
+ interface FetchSearchResultsProps {
19
+ lang?: string;
20
+ q: string;
21
+ limit?: number;
22
+ astroRequest?: Request;
23
+ authToken?: string;
24
+ backendHost?: string;
25
+ }
26
+ /**
27
+ * Fetches search results from the backend.
28
+ * Calls GET /api/v1/page/website_search/{lang}?q=...&limit=...
29
+ */
30
+ export declare function fetchSearchResults({ lang, q, limit, astroRequest, authToken, backendHost, }: FetchSearchResultsProps): Promise<SearchResultsData>;
31
+ export {};
@@ -0,0 +1,73 @@
1
+ import { fetchPublicSettings } from './fetchPublicSettings.js';
2
+ /**
3
+ * Fetches search results from the backend.
4
+ * Calls GET /api/v1/page/website_search/{lang}?q=...&limit=...
5
+ */
6
+ export async function fetchSearchResults({ lang = 'default', q, limit = 100, astroRequest, authToken, backendHost = 'http://localhost:8000/api/v1', }) {
7
+ // Build hostname for headers
8
+ let hostname = null;
9
+ if (astroRequest) {
10
+ const requestUrl = new URL(astroRequest.url);
11
+ hostname = requestUrl.hostname;
12
+ }
13
+ else if (typeof window !== 'undefined') {
14
+ hostname = window.location.hostname;
15
+ }
16
+ const headers = {
17
+ 'Content-Type': 'application/json',
18
+ };
19
+ if (hostname) {
20
+ headers['X-Original-Host'] = hostname;
21
+ headers['X-Frontend-Host'] = hostname;
22
+ }
23
+ if (authToken) {
24
+ headers['Authorization'] = `Bearer ${authToken}`;
25
+ }
26
+ // Always fetch public_settings alongside the search call
27
+ const settingsPromise = fetchPublicSettings(null, astroRequest ?? null, lang, `http://localhost:8000`);
28
+ // If no query, skip the search call and return empty results
29
+ if (!q.trim()) {
30
+ const public_settings = await settingsPromise;
31
+ // Normalize lang: replace 'default' with the actual ISO code from settings
32
+ const resolvedLang = lang === 'default' ? public_settings.default_language?.iso_code || lang : lang;
33
+ return {
34
+ lang: resolvedLang,
35
+ query: q,
36
+ public_settings,
37
+ results: [],
38
+ total: 0,
39
+ suggestions: [],
40
+ };
41
+ }
42
+ const searchParams = new URLSearchParams({ q, limit: String(limit) });
43
+ const searchUrl = `${backendHost}/page/website_search/${lang}?${searchParams.toString()}`;
44
+ const [public_settings, searchResponse] = await Promise.allSettled([
45
+ settingsPromise,
46
+ fetch(searchUrl, { method: 'GET', headers }),
47
+ ]);
48
+ const resolvedSettings = public_settings.status === 'fulfilled' ? public_settings.value : {};
49
+ // Normalize lang: replace 'default' with the actual ISO code from settings
50
+ const resolvedLang = lang === 'default' ? resolvedSettings.default_language?.iso_code || lang : lang;
51
+ if (searchResponse.status === 'rejected' || !searchResponse.value.ok) {
52
+ console.error('Error fetching search results:', searchResponse.status === 'rejected'
53
+ ? searchResponse.reason
54
+ : searchResponse.value.statusText);
55
+ return {
56
+ lang: resolvedLang,
57
+ query: q,
58
+ public_settings: resolvedSettings,
59
+ results: [],
60
+ total: 0,
61
+ suggestions: [],
62
+ };
63
+ }
64
+ const apiData = await searchResponse.value.json();
65
+ return {
66
+ lang: resolvedLang,
67
+ query: q,
68
+ public_settings: resolvedSettings,
69
+ results: apiData.results ?? [],
70
+ total: apiData.total ?? 0,
71
+ suggestions: apiData.suggestions ?? [],
72
+ };
73
+ }
@@ -0,0 +1,9 @@
1
+ import { type WebsiteDataType } from '../constants/index.js';
2
+ export interface Pagination {
3
+ page?: number;
4
+ pageSize?: number;
5
+ }
6
+ export declare function getPathType(path: string): {
7
+ pathType: WebsiteDataType;
8
+ pagination?: Pagination;
9
+ };
@@ -0,0 +1,46 @@
1
+ import { WebsiteDataTypes } from '../constants/index.js';
2
+ export function getPathType(path) {
3
+ let pathType = WebsiteDataTypes.Page;
4
+ let pagination = undefined;
5
+ // delete forward at the beginning
6
+ if (path.startsWith('/')) {
7
+ path = path.slice(1);
8
+ }
9
+ if (path === 'search' || path.startsWith('search?') || path.startsWith('search/')) {
10
+ return { pathType: WebsiteDataTypes.SearchResults };
11
+ }
12
+ if (path.startsWith('blog')) {
13
+ // split
14
+ const parts = path.split('/');
15
+ let pageSize = undefined;
16
+ // extract query parameters if any
17
+ const queryString = path.split('?')[1];
18
+ if (queryString) {
19
+ const params = new URLSearchParams(queryString);
20
+ const size = params.get('pageSize');
21
+ if (size && !isNaN(Number(size))) {
22
+ pageSize = Number(size);
23
+ }
24
+ }
25
+ // check which blog list format, either /blog, /blog/page/2, or /blog/{slug}
26
+ if (parts.length > 1 && parts[1] !== '') {
27
+ // /blog/page/2
28
+ if (parts[1] === 'page' && parts[2] && !isNaN(Number(parts[2]))) {
29
+ pathType = WebsiteDataTypes.BlogList;
30
+ pagination = { page: Number(parts[2]), pageSize };
31
+ }
32
+ // /blog/{slug}
33
+ else {
34
+ pathType = WebsiteDataTypes.BlogPost;
35
+ }
36
+ }
37
+ else {
38
+ // /blog
39
+ pathType = WebsiteDataTypes.BlogList;
40
+ if (pageSize) {
41
+ pagination = { pageSize };
42
+ }
43
+ }
44
+ }
45
+ return { pathType, pagination };
46
+ }
@@ -1,6 +1,7 @@
1
- export * from './fetchFormData';
2
- export * from './fetchPageData';
3
- export * from './fetchPublicSettings';
4
- export * from './getAuthToken';
5
- export * from './parseSlugForLangAndPath';
6
- export * from './types';
1
+ export * from './fetchPageData.js';
2
+ export * from './fetchPublicSettings.js';
3
+ export * from './fetchSearchResults.js';
4
+ export * from './getAuthToken.js';
5
+ export * from './parseSlug.js';
6
+ export * from './types.js';
7
+ export * from './isCrossingTemplateBoundary.js';
@@ -1,6 +1,7 @@
1
- export * from './fetchFormData';
2
- export * from './fetchPageData';
3
- export * from './fetchPublicSettings';
4
- export * from './getAuthToken';
5
- export * from './parseSlugForLangAndPath';
6
- export * from './types';
1
+ export * from './fetchPageData.js';
2
+ export * from './fetchPublicSettings.js';
3
+ export * from './fetchSearchResults.js';
4
+ export * from './getAuthToken.js';
5
+ export * from './parseSlug.js';
6
+ export * from './types.js';
7
+ export * from './isCrossingTemplateBoundary.js';
@@ -0,0 +1 @@
1
+ export declare const isCrossingTemplateBoundary: (fromPath: string, toPath: string) => boolean;
@@ -0,0 +1,6 @@
1
+ import { getPathType } from './getPathType';
2
+ export const isCrossingTemplateBoundary = (fromPath, toPath) => {
3
+ const fromPathType = getPathType(fromPath).pathType;
4
+ const toPathType = getPathType(toPath).pathType;
5
+ return fromPathType !== toPathType;
6
+ };
@@ -0,0 +1,12 @@
1
+ import type { Pagination } from './getPathType.js';
2
+ import type { WebsiteDataType } from '../constants/index.js';
3
+ export interface SlugParseResult {
4
+ lang?: string;
5
+ path: string;
6
+ pathType: WebsiteDataType;
7
+ pagination?: Pagination;
8
+ }
9
+ /**
10
+ * Parses a slug to determine language and path
11
+ */
12
+ export declare function parseSlug(slug: string | null): SlugParseResult;
@@ -1,10 +1,11 @@
1
- import { isValidLanguageCode } from '../language';
1
+ import { isValidLanguageCode } from './isValidLanguageCode.js';
2
+ import { getPathType } from './getPathType.js';
2
3
  /**
3
4
  * Parses a slug to determine language and path
4
5
  */
5
- export function parseSlugForLangAndPath(slug) {
6
+ export function parseSlug(slug) {
6
7
  const slugParts = slug ? slug.split('/').filter(Boolean) : [];
7
- let lang = null;
8
+ let lang;
8
9
  let path = '/';
9
10
  // Check if the first part is a valid language code
10
11
  if (slugParts.length > 0 && isValidLanguageCode(slugParts[0])) {
@@ -15,8 +16,9 @@ export function parseSlugForLangAndPath(slug) {
15
16
  }
16
17
  }
17
18
  else {
18
- // No language in URL, use the path as is with a leading slash
19
- path = slugParts.length > 0 ? '/' + slugParts.join('/') : '/';
19
+ // No language in URL, use the path as is
20
+ path = slugParts.length > 0 ? slugParts.join('/') : '/';
20
21
  }
21
- return { lang, path };
22
+ const { pathType, pagination } = getPathType(path);
23
+ return { lang, path, pathType, pagination };
22
24
  }
@@ -1,9 +1,5 @@
1
- import type { SiteSettings } from '../types';
2
- import type { MenuItem } from '../menus/types';
3
- export interface SlugParseResult {
4
- lang: string | null;
5
- path: string;
6
- }
1
+ import type { SiteSettings } from '../types.js';
2
+ import type { MenuItem } from '../menus/types.js';
7
3
  export interface Language {
8
4
  id: number;
9
5
  name: string;
@@ -35,41 +31,17 @@ export interface LanguageAlternative {
35
31
  slug: string;
36
32
  locale: Language;
37
33
  }
38
- export interface BlogPostAuthor {
39
- id: number;
40
- display_name?: string;
41
- username: string;
42
- image?: string;
43
- }
44
- export interface BlogPostListItem {
45
- id: number;
46
- title: string;
47
- slug: string;
48
- excerpt?: string;
49
- featured_image_id?: number;
50
- publish_date?: string;
51
- author?: BlogPostAuthor;
52
- lang: string;
53
- }
54
34
  export interface PageData {
55
35
  id?: number;
56
36
  title?: string;
57
37
  content?: Content;
58
- lang: string;
38
+ slug?: string;
39
+ lang?: string;
59
40
  public_settings: SiteSettings;
60
41
  seo_metadata?: SeoMetadata;
61
42
  language_alternatives?: LanguageAlternative[];
62
- is_frontend_page?: boolean | null;
63
- string_id?: string | null;
64
- contents?: unknown;
65
43
  page_custom_code?: string | null;
66
44
  custom_code?: string | null;
67
45
  require_login?: boolean;
68
- blog_posts?: BlogPostListItem[];
69
- featured_image_id?: number;
70
- publish_date?: string;
71
- author?: BlogPostAuthor;
72
46
  notFound?: boolean;
73
- status?: number;
74
- detail?: string;
75
47
  }
package/dist/types.d.ts CHANGED
@@ -1,5 +1,13 @@
1
1
  import type { MenuItem } from './menus/types';
2
2
  import type { SpecialTemplate } from './page/types';
3
+ import type { WebsiteDataType } from './constants';
4
+ import type { PageData, SearchResultsData } from './page';
5
+ import type { BlogListData, BlogPostData } from './blog';
6
+ export type WebsiteData = {
7
+ type: WebsiteDataType;
8
+ data: PageData | BlogListData | BlogPostData | SearchResultsData;
9
+ settings?: SiteSettings;
10
+ };
3
11
  export interface SiteSettings {
4
12
  id: number;
5
13
  name: string;
package/package.json CHANGED
@@ -1,7 +1,12 @@
1
1
  {
2
2
  "name": "@deepsel/cms-utils",
3
- "version": "1.1.3",
3
+ "version": "1.1.6",
4
4
  "description": "Helper utilities for Deepsel CMS",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/DeepselSystems/deepsel-cms",
8
+ "directory": "packages/cms-utils"
9
+ },
5
10
  "publishConfig": {
6
11
  "access": "public"
7
12
  },
@@ -21,69 +26,9 @@
21
26
  "import": "./dist/index.js",
22
27
  "types": "./dist/index.d.ts"
23
28
  },
24
- "./types": {
25
- "import": "./dist/types.js",
26
- "types": "./dist/types.d.ts"
27
- },
28
- "./language": {
29
- "import": "./dist/language/index.js",
30
- "types": "./dist/language/index.d.ts"
31
- },
32
- "./language/isValidLanguageCode": {
33
- "import": "./dist/language/isValidLanguageCode.js",
34
- "types": "./dist/language/isValidLanguageCode.d.ts"
35
- },
36
- "./menus": {
37
- "import": "./dist/menus/index.js",
38
- "types": "./dist/menus/index.d.ts"
39
- },
40
- "./menus/isActiveMenu": {
41
- "import": "./dist/menus/isActiveMenu.js",
42
- "types": "./dist/menus/isActiveMenu.d.ts"
43
- },
44
- "./menus/types": {
45
- "import": "./dist/menus/types.js",
46
- "types": "./dist/menus/types.d.ts"
47
- },
48
- "./page": {
49
- "import": "./dist/page/index.js",
50
- "types": "./dist/page/index.d.ts"
51
- },
52
- "./page/constants": {
53
- "import": "./dist/page/constants.js",
54
- "types": "./dist/page/constants.d.ts"
55
- },
56
- "./page/types": {
57
- "import": "./dist/page/types.js",
58
- "types": "./dist/page/types.d.ts"
59
- },
60
- "./page/fetchBlogListData": {
61
- "import": "./dist/page/fetchBlogListData.js",
62
- "types": "./dist/page/fetchBlogListData.d.ts"
63
- },
64
- "./page/fetchBlogPostData": {
65
- "import": "./dist/page/fetchBlogPostData.js",
66
- "types": "./dist/page/fetchBlogPostData.d.ts"
67
- },
68
- "./page/fetchFormData": {
69
- "import": "./dist/page/fetchFormData.js",
70
- "types": "./dist/page/fetchFormData.d.ts"
71
- },
72
- "./page/fetchPageData": {
73
- "import": "./dist/page/fetchPageData.js",
74
- "types": "./dist/page/fetchPageData.d.ts"
75
- },
76
- "./page/fetchPublicSettings": {
77
- "import": "./dist/page/fetchPublicSettings.js",
78
- "types": "./dist/page/fetchPublicSettings.d.ts"
79
- },
80
- "./page/getAuthToken": {
81
- "import": "./dist/page/getAuthToken.js",
82
- "types": "./dist/page/getAuthToken.d.ts"
83
- },
84
- "./page/parseSlugForLangAndPath": {
85
- "import": "./dist/page/parseSlugForLangAndPath.js",
86
- "types": "./dist/page/parseSlugForLangAndPath.d.ts"
29
+ "./common/utils": {
30
+ "import": "./dist/common/utils/index.js",
31
+ "types": "./dist/common/utils/index.d.ts"
87
32
  }
88
33
  },
89
34
  "files": [
@@ -1 +0,0 @@
1
- export * from './isValidLanguageCode';
@@ -1 +0,0 @@
1
- export * from './isValidLanguageCode';
File without changes
@@ -1 +0,0 @@
1
- "use strict";
@@ -1,4 +0,0 @@
1
- /**
2
- * Fetches Form data from the backend by language and slug
3
- */
4
- export declare function fetchFormData(lang: string, slug: string, backendHost?: string): Promise<Record<string, unknown>>;
@@ -1,38 +0,0 @@
1
- /**
2
- * Fetches Form data from the backend by language and slug
3
- */
4
- export async function fetchFormData(lang, slug, backendHost = 'http://localhost:8000') {
5
- try {
6
- // Format the slug properly
7
- const formattedSlug = slug.replace(/^\/forms\//, '');
8
- // Determine the URL based on whether a language is provided
9
- const url = `${backendHost}/form/website/${lang}/${formattedSlug}`;
10
- // Prepare fetch options
11
- const fetchOptions = {
12
- method: 'GET',
13
- headers: {
14
- 'Content-Type': 'application/json',
15
- },
16
- };
17
- // Fetch the page data from the backend
18
- const response = await fetch(url, fetchOptions);
19
- // Only treat actual 404 as not found
20
- if (response.status === 404) {
21
- const { detail } = await response.json();
22
- console.warn('404', url, { detail });
23
- return { notFound: true, status: 404, detail };
24
- }
25
- try {
26
- // Parse the JSON
27
- return await response.json();
28
- }
29
- catch (parseError) {
30
- console.error(`Failed to parse response: ${parseError.message}`);
31
- return { error: true, parseError: parseError.message };
32
- }
33
- }
34
- catch (error) {
35
- console.error('Error fetching page data:', error);
36
- return { error: true, message: error.message };
37
- }
38
- }
@@ -1,5 +0,0 @@
1
- import type { SlugParseResult } from './types';
2
- /**
3
- * Parses a slug to determine language and path
4
- */
5
- export declare function parseSlugForLangAndPath(slug: string | null): SlugParseResult;
File without changes