@contentstorage/core 1.0.1 → 1.1.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.
@@ -1,3 +1,4 @@
1
1
  export declare const CONTENTSTORAGE_CONFIG: {
2
2
  BASE_URL: string;
3
+ API_URL: string;
3
4
  };
@@ -1,3 +1,4 @@
1
1
  export const CONTENTSTORAGE_CONFIG = {
2
2
  BASE_URL: 'https://cdn.contentstorage.app',
3
+ API_URL: 'https://api.contentstorage.app',
3
4
  };
package/dist/index.d.ts CHANGED
@@ -2,5 +2,5 @@ import { AppConfig, LanguageCode, ContentStructure } from './types.js';
2
2
  import { setContentLanguage, getText, getImage, getVariation, initContentStorage } from './lib/contentManagement.js';
3
3
  import { fetchContent } from './lib/functions/fetchContent.js';
4
4
  export { AppConfig, LanguageCode, ContentStructure };
5
- export { initContentStorage, fetchContent, setContentLanguage, getText, getImage, getVariation, };
6
- export declare let isInContentstorageIframe: boolean;
5
+ export { initContentStorage, fetchContent, setContentLanguage, getText, getImage, getVariation, liveEditorReady, };
6
+ declare function liveEditorReady(retries?: number, delay?: number): Promise<boolean>;
package/dist/index.js CHANGED
@@ -1,7 +1,8 @@
1
+ import { CONTENTSTORAGE_CONFIG } from './contentstorage-config.js';
1
2
  import { setContentLanguage, getText, getImage, getVariation, initContentStorage, } from './lib/contentManagement.js';
2
3
  import { fetchContent } from './lib/functions/fetchContent.js';
3
- export { initContentStorage, fetchContent, setContentLanguage, getText, getImage, getVariation, };
4
- export let isInContentstorageIframe = false;
4
+ export { initContentStorage, fetchContent, setContentLanguage, getText, getImage, getVariation, liveEditorReady, };
5
+ let liveEditorReadyPromise = null;
5
6
  async function isLiveEditorMode() {
6
7
  try {
7
8
  const inIframe = window.self !== window.top;
@@ -10,44 +11,52 @@ async function isLiveEditorMode() {
10
11
  return !!(inIframe && iframeMarkerFromParent);
11
12
  }
12
13
  catch (e) {
13
- // This catch block is for rare edge cases or highly sandboxed environments
14
- // where accessing window.top might throw an error.
15
- // If an error occurs, it's safer to assume it might be in an iframe.
16
- console.warn('Error accessing window.top, assuming iframe context:', e);
14
+ console.warn('Error accessing window.top:', e);
17
15
  return false;
18
16
  }
19
17
  }
20
- isLiveEditorMode().then(async (isLiveMode) => {
21
- console.log('isLiveMode', isLiveMode);
22
- if (!isLiveMode) {
23
- return;
18
+ function liveEditorReady(retries = 2, delay = 3000) {
19
+ if (liveEditorReadyPromise) {
20
+ return liveEditorReadyPromise;
24
21
  }
25
- isInContentstorageIframe = true;
26
- const cdnScriptUrl = `https://cdn.contentstorage.app/live-editor.js?contentstorage-live-editor=true`;
27
- return new Promise((resolve, reject) => {
28
- console.log(`Attempting to load script from: ${cdnScriptUrl}`);
29
- // 1. Create a new <script> element
30
- const scriptElement = document.createElement('script');
31
- scriptElement.type = 'text/javascript';
32
- // 2. Set the src attribute to your script's URL
33
- // The browser will fetch and execute it.
34
- scriptElement.src = cdnScriptUrl;
35
- // 3. Handle successful loading
36
- scriptElement.onload = () => {
37
- console.log(`Script loaded successfully from: ${cdnScriptUrl}`);
38
- // The script has been fetched and executed by the browser.
39
- // If it's an IIFE, it has already run.
40
- resolve(true); // Resolve the promise indicating success
22
+ liveEditorReadyPromise = new Promise(async (resolve) => {
23
+ const isLiveMode = await isLiveEditorMode();
24
+ if (!isLiveMode) {
25
+ resolve(false);
26
+ return;
27
+ }
28
+ const cdnScriptUrl = `${CONTENTSTORAGE_CONFIG.BASE_URL}/live-editor.js?contentstorage-live-editor=true`;
29
+ const loadScript = (attempt = 1) => {
30
+ console.log(`Attempting to load Contentstorage live editor script (attempt ${attempt}/${retries})`);
31
+ const scriptElement = document.createElement('script');
32
+ scriptElement.type = 'text/javascript';
33
+ scriptElement.src = cdnScriptUrl;
34
+ scriptElement.onload = () => {
35
+ console.log(`Script loaded successfully from: ${cdnScriptUrl}`);
36
+ resolve(true);
37
+ };
38
+ scriptElement.onerror = (error) => {
39
+ // Clean up the failed script element to avoid clutter
40
+ scriptElement.remove();
41
+ console.error(`Failed to load script (attempt ${attempt}/${retries})`, error);
42
+ if (attempt < retries) {
43
+ setTimeout(() => loadScript(attempt + 1), delay);
44
+ }
45
+ else {
46
+ console.error(`All ${retries} attempts to load the script failed.`);
47
+ resolve(false);
48
+ }
49
+ };
50
+ document.head.appendChild(scriptElement);
41
51
  };
42
- // 4. Handle errors during loading (e.g., network error, 404)
43
- scriptElement.onerror = (error) => {
44
- console.error(`Failed to load script from: ${cdnScriptUrl}`, error);
45
- reject(new Error(`Failed to load script: ${cdnScriptUrl}`)); // Reject the promise
46
- };
47
- // 5. Append the script element to the document's head (or body)
48
- // This triggers the browser to start loading the script.
49
- document.head.appendChild(scriptElement);
50
- // Or: document.body.appendChild(scriptElement);
52
+ loadScript();
53
+ });
54
+ return liveEditorReadyPromise;
55
+ }
56
+ if (typeof window !== 'undefined') {
57
+ liveEditorReady().then((result) => {
58
+ if (result === true) {
59
+ console.log('Contentstorage live editor script loaded!');
60
+ }
51
61
  });
52
- // fetch script to handle iframe communication
53
- });
62
+ }
@@ -1,6 +1,6 @@
1
1
  import { AppConfig, ContentStructure, GetImageReturn, GetTextReturn, GetVariationReturn, LanguageCode } from '../types.js';
2
2
  export declare let activeContent: object | null;
3
- export declare let appConfig: Pick<AppConfig, 'contentKey' | 'languageCodes'> | null;
3
+ export declare let appConfig: Pick<AppConfig, 'contentKey' | 'languageCodes' | 'pendingChanges'> | null;
4
4
  /**
5
5
  * Loads and sets the content for a specific language.
6
6
  * It will internally ensure the application configuration (for contentDir) is loaded.
@@ -1,2 +1,7 @@
1
1
  import { LanguageCode } from '../../types.js';
2
- export declare function fetchContent(language?: LanguageCode): Promise<void>;
2
+ interface FetchContentOptions {
3
+ withPendingChanges?: boolean;
4
+ contentKey?: string;
5
+ }
6
+ export declare function fetchContent(language?: LanguageCode, options?: FetchContentOptions): Promise<void>;
7
+ export {};
@@ -1,8 +1,10 @@
1
1
  import axios from 'axios';
2
2
  import { CONTENTSTORAGE_CONFIG } from '../../contentstorage-config.js';
3
3
  import { appConfig, setContentLanguage } from '../contentManagement.js';
4
- export async function fetchContent(language) {
4
+ export async function fetchContent(language, options = {}) {
5
+ const { withPendingChanges, contentKey } = options;
5
6
  const languageToFetch = language || appConfig?.languageCodes?.[0];
7
+ const usePendingChangesToFetch = withPendingChanges || appConfig?.pendingChanges;
6
8
  console.log(`Starting content fetch for language ${language}...`);
7
9
  if (!languageToFetch) {
8
10
  throw Error(`No language found`);
@@ -10,10 +12,24 @@ export async function fetchContent(language) {
10
12
  if (!appConfig) {
11
13
  throw Error(`No app config found`);
12
14
  }
13
- const fileUrl = `${CONTENTSTORAGE_CONFIG.BASE_URL}/${appConfig.contentKey}/content/${languageToFetch}.json`;
15
+ const effectiveContentKey = contentKey || appConfig.contentKey;
16
+ let fileUrl;
17
+ let requestConfig = {};
18
+ if (usePendingChangesToFetch) {
19
+ if (!effectiveContentKey) {
20
+ throw Error(`No contentKey found`);
21
+ }
22
+ fileUrl = `${CONTENTSTORAGE_CONFIG.API_URL}/pending-changes/get-json?languageCode=${languageToFetch}`;
23
+ requestConfig.headers = {
24
+ 'X-Content-Key': effectiveContentKey,
25
+ };
26
+ }
27
+ else {
28
+ fileUrl = `${CONTENTSTORAGE_CONFIG.BASE_URL}/${effectiveContentKey}/content/${languageToFetch}.json`;
29
+ }
14
30
  try {
15
31
  // Fetch data for the current language
16
- const response = await axios.get(fileUrl);
32
+ const response = await axios.get(fileUrl, requestConfig);
17
33
  const jsonData = response.data;
18
34
  if (jsonData === undefined || jsonData === null) {
19
35
  throw new Error(`No data received from ${fileUrl} for language ${languageToFetch}.`);
@@ -17,7 +17,10 @@ export async function generateTypes() {
17
17
  if (arg.startsWith('--')) {
18
18
  const key = arg.substring(2);
19
19
  const value = args[i + 1];
20
- if (value && !value.startsWith('--')) {
20
+ if (key === 'pending-changes') {
21
+ cliConfig.pendingChanges = true;
22
+ }
23
+ else if (value && !value.startsWith('--')) {
21
24
  if (key === 'lang') {
22
25
  cliConfig.languageCodes = [value.toUpperCase()];
23
26
  }
@@ -103,11 +106,21 @@ export async function generateTypes() {
103
106
  if (!config.contentKey) {
104
107
  throw new Error('Cannot generate types: contentKey is missing');
105
108
  }
106
- const fileUrl = `${CONTENTSTORAGE_CONFIG.BASE_URL}/${config.contentKey}/content/${firstLanguageCode}.json`; // Adjust URL construction if necessary
109
+ let fileUrl;
110
+ const requestConfig = { responseType: 'json' };
111
+ if (config.pendingChanges) {
112
+ fileUrl = `${CONTENTSTORAGE_CONFIG.API_URL}/pending-changes/get-json?languageCode=${firstLanguageCode}`;
113
+ requestConfig.headers = {
114
+ 'X-Content-Key': config.contentKey,
115
+ };
116
+ }
117
+ else {
118
+ fileUrl = `${CONTENTSTORAGE_CONFIG.BASE_URL}/${config.contentKey}/content/${firstLanguageCode}.json`;
119
+ }
107
120
  dataSourceDescription = `remote URL (${fileUrl})`;
108
121
  console.log(chalk.blue(`Attempting to fetch JSON from: ${fileUrl}`));
109
122
  try {
110
- const response = await axios.get(fileUrl, { responseType: 'json' });
123
+ const response = await axios.get(fileUrl, requestConfig);
111
124
  const jsonResponse = response.data;
112
125
  console.log(chalk.blue('Flattening JSON for type generation'));
113
126
  jsonObject = flattenJson(jsonResponse);
@@ -7,13 +7,48 @@ import { loadConfig } from '../lib/configLoader.js';
7
7
  import { CONTENTSTORAGE_CONFIG } from '../contentstorage-config.js';
8
8
  export async function pullContent() {
9
9
  console.log(chalk.blue('Starting content pull...'));
10
- // Load configuration (assuming this function is defined elsewhere and works)
11
- const config = await loadConfig();
10
+ const args = process.argv.slice(2);
11
+ const cliConfig = {};
12
+ for (let i = 0; i < args.length; i++) {
13
+ const arg = args[i];
14
+ if (arg.startsWith('--')) {
15
+ const key = arg.substring(2);
16
+ const value = args[i + 1];
17
+ if (key === 'pending-changes') {
18
+ cliConfig.pendingChanges = true;
19
+ }
20
+ else if (value && !value.startsWith('--')) {
21
+ if (key === 'lang') {
22
+ cliConfig.languageCodes = [value.toUpperCase()];
23
+ }
24
+ else if (key === 'content-key') {
25
+ cliConfig.contentKey = value;
26
+ }
27
+ else if (key === 'content-dir') {
28
+ cliConfig.contentDir = value;
29
+ }
30
+ // Skip the value in the next iteration
31
+ i++;
32
+ }
33
+ }
34
+ }
35
+ let fileConfig = {};
36
+ try {
37
+ fileConfig = await loadConfig();
38
+ }
39
+ catch {
40
+ console.log(chalk.yellow('Could not load a configuration file. Proceeding with CLI arguments.'));
41
+ }
42
+ const config = { ...fileConfig, ...cliConfig };
12
43
  // Validate required fields
13
44
  if (!config.contentKey) {
14
45
  console.error(chalk.red('Error: Configuration is missing the required "contentKey" property.'));
15
46
  process.exit(1);
16
47
  }
48
+ if (!config.contentDir) {
49
+ console.error(chalk.red('Error: Configuration is missing the required "contentDir" property.'));
50
+ process.exit(1);
51
+ }
17
52
  console.log(chalk.blue(`Content key: ${config.contentKey}`));
18
53
  console.log(chalk.blue(`Saving content to: ${config.contentDir}`));
19
54
  try {
@@ -30,14 +65,24 @@ export async function pullContent() {
30
65
  await fs.mkdir(config.contentDir, { recursive: true });
31
66
  // Process each language code
32
67
  for (const languageCode of config.languageCodes) {
33
- const fileUrl = `${CONTENTSTORAGE_CONFIG.BASE_URL}/${config.contentKey}/content/${languageCode}.json`;
68
+ let fileUrl;
69
+ const requestConfig = {};
70
+ if (config.pendingChanges) {
71
+ fileUrl = `${CONTENTSTORAGE_CONFIG.API_URL}/pending-changes/get-json?languageCode=${languageCode}`;
72
+ requestConfig.headers = {
73
+ 'X-Content-Key': config.contentKey
74
+ };
75
+ }
76
+ else {
77
+ fileUrl = `${CONTENTSTORAGE_CONFIG.BASE_URL}/${config.contentKey}/content/${languageCode}.json`;
78
+ }
34
79
  const filename = `${languageCode}.json`;
35
80
  const outputPath = path.join(config.contentDir, filename);
36
81
  console.log(chalk.blue(`\nProcessing language: ${languageCode}`));
37
82
  console.log(chalk.blue(`Using following contentKey to fetch json: ${config.contentKey}`));
38
83
  try {
39
84
  // Fetch data for the current language
40
- const response = await axios.get(fileUrl);
85
+ const response = await axios.get(fileUrl, requestConfig);
41
86
  const jsonData = response.data;
42
87
  // Basic check for data existence, although axios usually throws for non-2xx responses
43
88
  if (jsonData === undefined || jsonData === null) {
package/dist/types.d.ts CHANGED
@@ -3,6 +3,7 @@ export type AppConfig = {
3
3
  contentKey: string;
4
4
  contentDir: string;
5
5
  typesOutputFile: string;
6
+ pendingChanges?: boolean;
6
7
  };
7
8
  export type LanguageCode = 'SQ' | 'BE' | 'BS' | 'BG' | 'HR' | 'CS' | 'DA' | 'NL' | 'EN' | 'ET' | 'FI' | 'FR' | 'DE' | 'EL' | 'HU' | 'GA' | 'IT' | 'LV' | 'LT' | 'MK' | 'NO' | 'PL' | 'PT' | 'RO' | 'RU' | 'SR' | 'SK' | 'SL' | 'ES' | 'SV' | 'TR' | 'UK';
8
9
  /**
@@ -11,14 +12,6 @@ export type LanguageCode = 'SQ' | 'BE' | 'BS' | 'BG' | 'HR' | 'CS' | 'DA' | 'NL'
11
12
  * consumers enable type-safe autocompletion for localization path strings
12
13
  * used with functions like `getText`.
13
14
  *
14
- * @example
15
- * // In consumer's project (e.g., in a .d.ts file):
16
- * import 'your-library-name'; // Your library's package name
17
- * import type { RootContentItem as AppSpecificRootItem } from './generated_content_types';
18
- *
19
- * declare module 'your-library-name' {
20
- * export interface ContentStructure extends AppSpecificRootItem {}
21
- * }
22
15
  */
23
16
  export interface ContentStructure {
24
17
  }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@contentstorage/core",
3
3
  "author": "Kaido Hussar <kaidohus@gmail.com>",
4
4
  "homepage": "https://contentstorage.app",
5
- "version": "1.0.1",
5
+ "version": "1.1.0",
6
6
  "type": "module",
7
7
  "description": "Fetch content from contentstorage and generate TypeScript types",
8
8
  "module": "dist/index.js",
@@ -45,6 +45,9 @@
45
45
  "engines": {
46
46
  "node": ">=18.0.0"
47
47
  },
48
+ "sideEffects": [
49
+ "./dist/index.js"
50
+ ],
48
51
  "publishConfig": {
49
52
  "access": "public"
50
53
  },