diff --git a/CHANGELOG.md b/CHANGELOG.md
index ab641e111..10c64acb4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@ and this project adheres to
- ✨(helm) redirecting system #1697
- 📱(frontend) add comments for smaller device #1737
+- ✨(project) add custom js support via config #1759
### Changed
diff --git a/docs/env.md b/docs/env.md
index 58e9cd92b..43c1badeb 100644
--- a/docs/env.md
+++ b/docs/env.md
@@ -60,6 +60,7 @@ These are the environment variables you can set for the `impress-backend` contai
| DJANGO_SERVER_TO_SERVER_API_TOKENS | | [] |
| DOCUMENT_IMAGE_MAX_SIZE | Maximum size of document in bytes | 10485760 |
| FRONTEND_CSS_URL | To add a external css file to the app | |
+| FRONTEND_JS_URL | To add a external js file to the app | |
| FRONTEND_HOMEPAGE_FEATURE_ENABLED | Frontend feature flag to display the homepage | false |
| FRONTEND_THEME | Frontend theme to use | |
| LANGUAGE_CODE | Default language | en-us |
diff --git a/docs/theming.md b/docs/theming.md
index 51f56a897..f67024dfe 100644
--- a/docs/theming.md
+++ b/docs/theming.md
@@ -8,7 +8,7 @@ To use this feature, simply set the `FRONTEND_CSS_URL` environment variable to t
FRONTEND_CSS_URL=http://anything/custom-style.css
```
-Once you've set this variable, our application will load your custom CSS file and apply the styles to our frontend application.
+Once you've set this variable, Docs will load your custom CSS file and apply the styles to our frontend application.
### Benefits
@@ -32,6 +32,61 @@ Then, set the `FRONTEND_CSS_URL` environment variable to the URL of your custom
----
+# Runtime JavaScript Injection 🚀
+
+### How to Use
+
+To use this feature, simply set the `FRONTEND_JS_URL` environment variable to the URL of your custom JavaScript file. For example:
+
+```javascript
+FRONTEND_JS_URL=http://anything/custom-script.js
+```
+
+Once you've set this variable, Docs will load your custom JavaScript file and execute it in the browser, allowing you to modify the application's behavior at runtime.
+
+### Benefits
+
+This feature provides several benefits, including:
+
+* **Dynamic customization** 🔄: With this feature, you can dynamically modify the behavior and appearance of our application without requiring any code changes.
+* **Flexibility** 🌈: You can add custom functionality, modify existing features, or integrate third-party services.
+* **Runtime injection** ⏱️: This feature allows you to inject JavaScript into the application at runtime, without requiring a restart or recompilation.
+
+### Example Use Case
+
+Let's say you want to add a custom menu to the application header. You can create a custom JavaScript file with the following contents:
+
+```javascript
+(function() {
+ 'use strict';
+
+ function initCustomMenu() {
+ // Wait for the page to be fully loaded
+ const header = document.querySelector('header');
+ if (!header) return false;
+
+ // Create and inject your custom menu
+ const customMenu = document.createElement('div');
+ customMenu.innerHTML = '';
+ header.appendChild(customMenu);
+
+ console.log('Custom menu added successfully');
+ return true;
+ }
+
+ // Initialize when DOM is ready
+ if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', initCustomMenu);
+ } else {
+ initCustomMenu();
+ }
+})();
+```
+
+Then, set the `FRONTEND_JS_URL` environment variable to the URL of your custom JavaScript file. Once you've done this, our application will load your custom JavaScript file and execute it, adding your custom menu to the header.
+
+----
+
# **Your Docs icon** 📝
You can add your own Docs icon in the header from the theme customization file.
diff --git a/src/backend/core/api/viewsets.py b/src/backend/core/api/viewsets.py
index eccbf4952..6f959009f 100644
--- a/src/backend/core/api/viewsets.py
+++ b/src/backend/core/api/viewsets.py
@@ -2197,6 +2197,7 @@ class ConfigView(drf.views.APIView):
"ENVIRONMENT",
"FRONTEND_CSS_URL",
"FRONTEND_HOMEPAGE_FEATURE_ENABLED",
+ "FRONTEND_JS_URL",
"FRONTEND_THEME",
"MEDIA_BASE_URL",
"POSTHOG_KEY",
diff --git a/src/backend/core/tests/test_api_config.py b/src/backend/core/tests/test_api_config.py
index 0261125e8..e29187b3e 100644
--- a/src/backend/core/tests/test_api_config.py
+++ b/src/backend/core/tests/test_api_config.py
@@ -24,6 +24,7 @@ pytestmark = pytest.mark.django_db
COLLABORATION_WS_NOT_CONNECTED_READY_ONLY=True,
CRISP_WEBSITE_ID="123",
FRONTEND_CSS_URL="http://testcss/",
+ FRONTEND_JS_URL="http://testjs/",
FRONTEND_THEME="test-theme",
MEDIA_BASE_URL="http://testserver/",
POSTHOG_KEY={"id": "132456", "host": "https://eu.i.posthog-test.com"},
@@ -49,6 +50,7 @@ def test_api_config(is_authenticated):
"ENVIRONMENT": "test",
"FRONTEND_CSS_URL": "http://testcss/",
"FRONTEND_HOMEPAGE_FEATURE_ENABLED": True,
+ "FRONTEND_JS_URL": "http://testjs/",
"FRONTEND_THEME": "test-theme",
"LANGUAGES": [
["en-us", "English"],
diff --git a/src/backend/impress/settings.py b/src/backend/impress/settings.py
index 4b456ebbb..5f74d7d57 100755
--- a/src/backend/impress/settings.py
+++ b/src/backend/impress/settings.py
@@ -509,6 +509,9 @@ class Base(Configuration):
FRONTEND_CSS_URL = values.Value(
None, environ_name="FRONTEND_CSS_URL", environ_prefix=None
)
+ FRONTEND_JS_URL = values.Value(
+ None, environ_name="FRONTEND_JS_URL", environ_prefix=None
+ )
THEME_CUSTOMIZATION_FILE_PATH = values.Value(
os.path.join(BASE_DIR, "impress/configuration/theme/default.json"),
diff --git a/src/frontend/apps/e2e/__tests__/app-impress/config.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/config.spec.ts
index c5410c7cf..44470fbb2 100644
--- a/src/frontend/apps/e2e/__tests__/app-impress/config.spec.ts
+++ b/src/frontend/apps/e2e/__tests__/app-impress/config.spec.ts
@@ -126,6 +126,20 @@ test.describe('Config', () => {
).toBeAttached();
});
+ test('it checks FRONTEND_JS_URL config', async ({ page }) => {
+ await overrideConfig(page, {
+ FRONTEND_JS_URL: 'http://localhost:123465/js/script.js',
+ });
+
+ await page.goto('/');
+
+ await expect(
+ page
+ .locator('script[src="http://localhost:123465/js/script.js"]')
+ .first(),
+ ).toBeAttached();
+ });
+
test('it checks theme_customization.translations config', async ({
page,
}) => {
@@ -145,10 +159,6 @@ test.describe('Config', () => {
await expect(page.getByText('MyCustomDocs')).toBeAttached();
});
-});
-
-test.describe('Config: Not logged', () => {
- test.use({ storageState: { cookies: [], origins: [] } });
test('it checks the config api is called', async ({ page }) => {
const responsePromise = page.waitForResponse(
@@ -168,6 +178,10 @@ test.describe('Config: Not logged', () => {
expect(configApi).toStrictEqual(CONFIG_LEFT);
});
+});
+
+test.describe('Config: Not logged', () => {
+ test.use({ storageState: { cookies: [], origins: [] } });
test('it checks that theme is configured from config endpoint', async ({
page,
diff --git a/src/frontend/apps/e2e/__tests__/app-impress/utils-common.ts b/src/frontend/apps/e2e/__tests__/app-impress/utils-common.ts
index b4fda8168..67adf44c7 100644
--- a/src/frontend/apps/e2e/__tests__/app-impress/utils-common.ts
+++ b/src/frontend/apps/e2e/__tests__/app-impress/utils-common.ts
@@ -10,6 +10,7 @@ export const CONFIG = {
COLLABORATION_WS_NOT_CONNECTED_READY_ONLY: true,
ENVIRONMENT: 'development',
FRONTEND_CSS_URL: null,
+ FRONTEND_JS_URL: null,
FRONTEND_HOMEPAGE_FEATURE_ENABLED: true,
FRONTEND_THEME: null,
MEDIA_BASE_URL: 'http://localhost:8083',
diff --git a/src/frontend/apps/impress/src/core/config/ConfigProvider.tsx b/src/frontend/apps/impress/src/core/config/ConfigProvider.tsx
index 6694f6b94..f6e399938 100644
--- a/src/frontend/apps/impress/src/core/config/ConfigProvider.tsx
+++ b/src/frontend/apps/impress/src/core/config/ConfigProvider.tsx
@@ -1,5 +1,6 @@
import { Loader } from '@openfun/cunningham-react';
import Head from 'next/head';
+import Script from 'next/script';
import { PropsWithChildren, useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
@@ -87,6 +88,9 @@ export const ConfigProvider = ({ children }: PropsWithChildren) => {
)}
+ {conf?.FRONTEND_JS_URL && (
+
+ )}
{children}
diff --git a/src/frontend/apps/impress/src/core/config/api/useConfig.tsx b/src/frontend/apps/impress/src/core/config/api/useConfig.tsx
index 990da403a..c1e9c26df 100644
--- a/src/frontend/apps/impress/src/core/config/api/useConfig.tsx
+++ b/src/frontend/apps/impress/src/core/config/api/useConfig.tsx
@@ -21,6 +21,7 @@ export interface ConfigResponse {
ENVIRONMENT: string;
FRONTEND_CSS_URL?: string;
FRONTEND_HOMEPAGE_FEATURE_ENABLED?: boolean;
+ FRONTEND_JS_URL?: string;
FRONTEND_THEME?: Theme;
LANGUAGES: [string, string][];
LANGUAGE_CODE: string;
diff --git a/src/frontend/apps/impress/src/features/header/components/Header.tsx b/src/frontend/apps/impress/src/features/header/components/Header.tsx
index a187590e9..a792cc426 100644
--- a/src/frontend/apps/impress/src/features/header/components/Header.tsx
+++ b/src/frontend/apps/impress/src/features/header/components/Header.tsx
@@ -29,6 +29,7 @@ export const Header = () => {
{
border-bottom: 1px solid
var(--c--contextuals--border--surface--primary);
`}
- className="--docs--header"
>
{!isDesktop && }
{
) : (
-
+