Compare commits

..

16 Commits

Author SHA1 Message Date
Sylvain Boissel
270b374a17 📝(doc) fix publiccode.yml syntax
This fixes publiccode.yml according to the 0.5.0 syntax: remove or rename
non-existing fields, add the missing mandatory ones, fix a few typos.
2026-01-05 17:46:05 +01:00
Anthony LC
ea3a4a6da3 (project) add custom js support via config
From the config, we can add custom JS file URL
to be included in the frontend.
2026-01-05 15:06:53 +01:00
Anthony LC
b78ad27a71 🐛(frontend) fix children not display when first resize
When we resize the window for the first time, then
open the panel, the children were not displayed.
This fix this issue.
2026-01-05 13:21:54 +01:00
Anthony LC
e4b8ffb304 ✈️(frontend) pause Posthog when offline
Posthog keeps trying to send events when the user
is offline, causing the network request queue to fill up
and slowing down the app. This commit pauses Posthog
when the user is offline and resumes it when back online.
2026-01-05 12:07:47 +01:00
Anthony LC
78c7ab247b 🦺(frontend) check content type pdf on PdfBlock
Pdfblock was quite permissive on the content type
it was accepting. Now it checks that the content
type is exactly 'application/pdf' before rendering
the PDF viewer.
2026-01-05 11:47:55 +01:00
Anthony LC
b0bd6e2c01 🥅(frontend) intercept 401 error on GET threads
We intercept 401 errors on GET /threads to avoid
spamming Sentry with authentication errors
when users are not logged in.
2026-01-05 11:23:43 +01:00
Anthony LC
37527416f2 🩹(frontend) small ui improvement
- center initial loader before app load
- add name on input to remove warning
- fix hover on interlinking link
2026-01-05 11:02:30 +01:00
Anthony LC
30bc959340 ⬆️(dependency) fix CVE by bumping qs dependency
Fix CVE by bumping qs from 6.14.0 to 6.14.1
2026-01-05 10:20:17 +01:00
Anthony LC
a73d9c1c78 📱(frontend) add comments for smaller device
Add comments support for mobile devices by
removing the desktop-only restriction and
ensuring the UI adapts well to smaller screens.
2026-01-05 10:04:37 +01:00
Anthony LC
a920daf05b ⬆️(dependencies) bump to blocknote 0.45.0
Bump to BlockNote 0.45.0 to get the latest
features and fixes.
This release includes the fix for the table
deletion that breaks the editor when
deleting tables.
2025-12-22 10:53:12 +01:00
Anthony LC
ff88465398 ⬇️(dependencies) downgrade next from 16.0.10 to 15.5.9
Passing the Next 16 will need more work to be compatible
with our application. We will do this upgrade later
in a dedicated PR.
We add it in the renovate.json to avoid having Renovate
trying to upgrade it again.
2025-12-22 10:52:03 +01:00
renovate[bot]
3617e4f7b8 ⬆️(dependencies) update js dependencies 2025-12-22 10:52:02 +01:00
Anthony LC
efaec45bfd (helm) create ingress-redirects template
Create a new Helm template for ingress redirects
and update the values.yaml file accordingly.
We will be able to manage ingress redirects
through Helm charts easily.
2025-12-22 10:11:54 +01:00
Anthony LC
715d88ba3c ♻️(frontend) replace auth redirect logic for home
To be intercepted by ingress redirects, we need
to redirect using window.location instead of
using Next.js router. The Next.js router does not
trigger a full page reload, so the ingress
redirect logic is not executed.
2025-12-22 10:09:13 +01:00
Anthony LC
7d64d79eeb 🐛(helm) fix OIDC authentication with standard scopes
"usual_name" does not seem to be standard,
it gives error during login.
We replace "usual_name" by "family_name".
2025-12-22 09:24:44 +01:00
Anthony LC
2e66b87dab 🔧(helm) add OIDC_REDIRECT_ALLOWED_HOSTS to fix authentication flow
Add OIDC_REDIRECT_ALLOWED_HOSTS setting to dev and
feature environments to properly allow Keycloak
redirect callbacks after authentication.
2025-12-22 09:24:44 +01:00
46 changed files with 1381 additions and 1517 deletions

View File

@@ -6,6 +6,24 @@ and this project adheres to
## [Unreleased]
### Added
- ✨(helm) redirecting system #1697
- 📱(frontend) add comments for smaller device #1737
- ✨(project) add custom js support via config #1759
### Changed
- 🥅(frontend) intercept 401 error on GET threads #1754
- 🦺(frontend) check content type pdf on PdfBlock #1756
- ✈️(frontend) pause Posthog when offline #1755
### Fixed
- 🐛(frontend) fix tables deletion #1752
- 🐛(frontend) fix children not display when first resize #1753
- 📝(doc) fix publiccode.yml syntax #1770
## [4.2.0] - 2025-12-17
### Added

View File

@@ -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 |

View File

@@ -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 = '<button>Custom Menu</button>';
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.

View File

@@ -1,16 +1,18 @@
publiccodeYmlVersion: "2.4.0"
publiccodeYmlVersion: "0.5.0"
name: Docs
url: https://github.com/suitenumerique/docs
landingURL: https://github.com/suitenumerique/docs
creationDate: 2023-12-10
logo: https://raw.githubusercontent.com/suitenumerique/docs/main/docs/assets/docs-logo.png
usedBy:
- Direction interministériel du numérique (DINUM)
- Direction interministérielle du numérique (DINUM)
fundedBy:
- name: Direction interministériel du numérique (DINUM)
url: https://www.numerique.gouv.fr
- name: Direction interministérielle du numérique (DINUM)
uri: https://www.numerique.gouv.fr
roadmap: "https://github.com/orgs/suitenumerique/projects/2/views/1"
softwareType: "standalone/other"
platforms:
- "web"
developmentStatus: "stable"
description:
en:
shortDescription: "The open source document editor where your notes can become knowledge through live collaboration"
@@ -18,10 +20,18 @@ description:
shortDescription: "L'éditeur de documents open source où vos notes peuvent devenir des connaissances grâce à la collaboration en direct."
legal:
license: MIT
localisation:
localisationReady: true
availableLanguages:
- de
- en
- es
- fr
- nl
maintenance:
type: internal
contacts:
- name: "Virgile Deville"
email: "virgile.deville@numerique.gouv.fr"
- name: "samuel.paccoud"
- name: "Samuel Paccoud"
email: "samuel.paccoud@numerique.gouv.fr"

View File

@@ -30,8 +30,11 @@
"groupName": "ignored js dependencies",
"matchManagers": ["npm"],
"matchPackageNames": [
"@next/eslint-plugin-next",
"docx",
"eslint-config-next",
"fetch-mock",
"next",
"node",
"node-fetch",
"workbox-webpack-plugin"

View File

@@ -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",

View File

@@ -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"],

View File

@@ -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"),

View File

@@ -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,

View File

@@ -153,7 +153,7 @@ test.describe('Doc Comments', () => {
await thread.getByRole('menuitem', { name: 'Edit comment' }).click();
const commentEditor = thread.getByText('This is a comment').first();
await commentEditor.fill('This is an edited comment');
const saveBtn = thread.getByRole('button', { name: 'Save' });
const saveBtn = thread.locator('button[data-test="save"]').first();
await saveBtn.click();
await expect(saveBtn).toBeHidden();
await expect(
@@ -163,7 +163,8 @@ test.describe('Doc Comments', () => {
// Add second comment
await thread.getByRole('paragraph').last().fill('This is a second comment');
await thread.getByRole('button', { name: 'Save' }).click();
await saveBtn.click();
await expect(saveBtn).toBeHidden();
await expect(
thread.getByText('This is an edited comment').first(),
).toBeVisible();
@@ -371,7 +372,7 @@ test.describe('Doc Comments', () => {
test.describe('Doc Comments mobile', () => {
test.use({ viewport: { width: 500, height: 1200 } });
test('Comments are not visible on mobile', async ({ page, browserName }) => {
test('Can comments on mobile', async ({ page, browserName }) => {
const [title] = await createDoc(
page,
'comment-mobile',
@@ -387,7 +388,16 @@ test.describe('Doc Comments mobile', () => {
// Checks add react reaction
const editor = await writeInEditor({ page, text: 'Hello' });
await editor.getByText('Hello').selectText();
await expect(page.getByRole('button', { name: 'Comment' })).toBeHidden();
await expect(page.getByRole('button', { name: 'Paragraph' })).toBeVisible();
await page.getByRole('button', { name: 'Comment' }).click();
const thread = page.locator('.bn-thread');
await thread.getByRole('paragraph').first().fill('This is a comment');
await thread.locator('[data-test="save"]').click();
await expect(thread.getByText('This is a comment').first()).toBeHidden();
await editor.first().click();
await editor.getByText('Hello').click();
await expect(thread.getByText('This is a comment').first()).toBeVisible();
});
});

View File

@@ -880,14 +880,15 @@ test.describe('Doc Editor', () => {
// Wait for the interlink to be created and rendered
const editor = await getEditor({ page });
const interlinkChild2 = editor.getByRole('button', {
name: docChild2,
});
const interlinkChild = editor
.locator('.--docs--interlinking-link-inline-content')
.first();
await expect(interlinkChild2).toBeVisible({ timeout: 10000 });
await expect(interlinkChild2).toContainText('😀');
await expect(interlinkChild2.locator('svg').first()).toBeHidden();
await interlinkChild2.click();
await expect(interlinkChild).toBeVisible({ timeout: 10000 });
await expect(interlinkChild).toContainText('😀');
await expect(interlinkChild).toContainText(docChild2);
await expect(interlinkChild.locator('svg').first()).toBeHidden();
await interlinkChild.click();
await verifyDocName(page, docChild2);
@@ -897,11 +898,9 @@ test.describe('Doc Editor', () => {
await input.fill(docChild1);
await searchContainer.getByText(docChild1).click();
const interlinkChild1 = editor.getByRole('button', {
name: docChild1,
});
await expect(interlinkChild1).toBeVisible({ timeout: 10000 });
await expect(interlinkChild1.locator('svg').first()).toBeVisible();
await expect(interlinkChild).toContainText(docChild1);
await expect(interlinkChild).toBeVisible({ timeout: 10000 });
await expect(interlinkChild.locator('svg').first()).toBeVisible();
await page.keyboard.press('@');
@@ -961,13 +960,35 @@ test.describe('Doc Editor', () => {
test('it embeds PDF', async ({ page, browserName }) => {
await createDoc(page, 'doc-toolbar', browserName, 1);
await page.getByRole('button', { name: 'Share' }).click();
await updateShareLink(page, 'Public', 'Reading');
await page.getByRole('button', { name: 'Close the share modal' }).click();
await openSuggestionMenu({ page });
await page.getByText('Embed a PDF file').click();
const pdfBlock = page.locator('div[data-content-type="pdf"]').first();
const pdfBlock = page.locator('div[data-content-type="pdf"]').last();
await expect(pdfBlock).toBeVisible();
// Try with invalid PDF first
await page.getByText(/Add (PDF|file)/).click();
await page.locator('[data-test="embed-tab"]').click();
await page
.locator('[data-test="embed-input"]')
.fill('https://example.test/test.test');
await page.locator('[data-test="embed-input-button"]').click();
await expect(page.getByText('Invalid or missing PDF file')).toBeVisible();
await openSuggestionMenu({ page });
await page.getByText('Embed a PDF file').click();
// Now with a valid PDF
await page.getByText(/Add (PDF|file)/).click();
const fileChooserPromise = page.waitForEvent('filechooser');
const downloadPromise = page.waitForEvent('download');
@@ -992,7 +1013,7 @@ test.describe('Doc Editor', () => {
await expect(pdfEmbed).toHaveAttribute('role', 'presentation');
// Check download with original filename
await page.locator('.bn-block-content[data-content-type="pdf"]').click();
await pdfBlock.click();
await page.locator('[data-test="downloadfile"]').click();
const download = await downloadPromise;

View File

@@ -527,7 +527,7 @@ test.describe('Doc Export', () => {
await verifyDocName(page, docChild);
await page.locator('.bn-block-outer').last().fill('/');
const editor = await openSuggestionMenu({ page });
await page.getByText('Link a doc').first().click();
const input = page.locator(
@@ -544,12 +544,11 @@ test.describe('Doc Export', () => {
await searchContainer.getByText(randomDoc).click();
// Search the interlinking link in the editor (not in the document tree)
const editor = page.locator('.ProseMirror.bn-editor');
const interlink = editor.getByRole('button', {
name: randomDoc,
});
const interlink = editor
.locator('.--docs--interlinking-link-inline-content')
.first();
await expect(interlink).toBeVisible();
await expect(interlink).toContainText(randomDoc);
const downloadPromise = page.waitForEvent('download', (download) => {
return download.suggestedFilename().includes(`${docChild}.pdf`);
@@ -593,7 +592,7 @@ test.describe('Doc Export', () => {
await verifyDocName(page, docChild);
await page.locator('.bn-block-outer').last().fill('/');
const editor = await openSuggestionMenu({ page });
await page.getByText('Link a doc').first().click();
const input = page.locator(
@@ -610,12 +609,11 @@ test.describe('Doc Export', () => {
await searchContainer.getByText(randomDoc).click();
// Search the interlinking link in the editor (not in the document tree)
const editor = page.locator('.ProseMirror.bn-editor');
const interlink = editor.getByRole('button', {
name: randomDoc,
});
const interlink = editor
.locator('.--docs--interlinking-link-inline-content')
.first();
await expect(interlink).toBeVisible();
await expect(interlink).toContainText(randomDoc);
await page
.getByRole('button', {

View File

@@ -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',

View File

@@ -15,7 +15,7 @@
"test:ui::chromium": "yarn test:ui --project=chromium"
},
"devDependencies": {
"@playwright/test": "1.56.1",
"@playwright/test": "1.57.0",
"@types/node": "*",
"@types/pdf-parse": "1.1.5",
"eslint-plugin-docs": "*",

View File

@@ -19,14 +19,14 @@
},
"dependencies": {
"@ag-media/react-pdf-table": "2.0.3",
"@blocknote/code-block": "0.44.2",
"@blocknote/core": "0.44.2",
"@blocknote/mantine": "0.44.2",
"@blocknote/react": "0.44.2",
"@blocknote/xl-docx-exporter": "0.44.2",
"@blocknote/xl-multi-column": "0.44.2",
"@blocknote/xl-odt-exporter": "0.44.2",
"@blocknote/xl-pdf-exporter": "0.44.2",
"@blocknote/code-block": "0.45.0",
"@blocknote/core": "0.45.0",
"@blocknote/mantine": "0.45.0",
"@blocknote/react": "0.45.0",
"@blocknote/xl-docx-exporter": "0.45.0",
"@blocknote/xl-multi-column": "0.45.0",
"@blocknote/xl-odt-exporter": "0.45.0",
"@blocknote/xl-pdf-exporter": "0.45.0",
"@dnd-kit/core": "6.3.1",
"@dnd-kit/modifiers": "9.0.0",
"@emoji-mart/data": "1.2.1",
@@ -35,14 +35,14 @@
"@fontsource-variable/material-symbols-outlined": "5.2.30",
"@fontsource/material-icons": "5.2.7",
"@gouvfr-lasuite/integration": "1.0.3",
"@gouvfr-lasuite/ui-kit": "0.18.0",
"@hocuspocus/provider": "3.4.0",
"@mantine/core": "8.3.9",
"@mantine/hooks": "8.3.9",
"@gouvfr-lasuite/ui-kit": "0.18.4",
"@hocuspocus/provider": "3.4.3",
"@mantine/core": "8.3.10",
"@mantine/hooks": "8.3.10",
"@openfun/cunningham-react": "4.0.0",
"@react-pdf/renderer": "4.3.1",
"@sentry/nextjs": "10.27.0",
"@tanstack/react-query": "5.90.10",
"@sentry/nextjs": "10.30.0",
"@tanstack/react-query": "5.90.12",
"@tiptap/extensions": "*",
"canvg": "4.0.3",
"clsx": "2.1.1",
@@ -52,17 +52,17 @@
"emoji-datasource-apple": "16.0.0",
"emoji-mart": "5.6.0",
"emoji-regex": "10.6.0",
"i18next": "25.6.3",
"i18next": "25.7.2",
"i18next-browser-languagedetector": "8.2.0",
"idb": "8.0.3",
"lodash": "4.17.21",
"luxon": "3.7.2",
"next": "15.5.9",
"posthog-js": "1.298.0",
"posthog-js": "1.306.1",
"react": "*",
"react-aria-components": "1.13.0",
"react-dom": "*",
"react-i18next": "16.3.5",
"react-i18next": "16.5.0",
"react-intersection-observer": "10.0.0",
"react-resizable-panels": "3.0.6",
"react-select": "5.10.2",
@@ -70,11 +70,11 @@
"use-debounce": "10.0.6",
"y-protocols": "1.0.6",
"yjs": "*",
"zustand": "5.0.8"
"zustand": "5.0.9"
},
"devDependencies": {
"@svgr/webpack": "8.1.0",
"@tanstack/react-query-devtools": "5.91.0",
"@tanstack/react-query-devtools": "5.91.1",
"@testing-library/dom": "10.4.1",
"@testing-library/jest-dom": "6.9.1",
"@testing-library/react": "16.3.0",
@@ -84,21 +84,21 @@
"@types/node": "*",
"@types/react": "*",
"@types/react-dom": "*",
"@vitejs/plugin-react": "5.1.1",
"@vitejs/plugin-react": "5.1.2",
"copy-webpack-plugin": "13.0.1",
"cross-env": "10.1.0",
"dotenv": "17.2.3",
"eslint-plugin-docs": "*",
"fetch-mock": "9.11.0",
"jsdom": "27.2.0",
"jsdom": "27.3.0",
"node-fetch": "2.7.0",
"prettier": "3.6.2",
"stylelint": "16.26.0",
"prettier": "3.7.4",
"stylelint": "16.26.1",
"stylelint-config-standard": "39.0.1",
"stylelint-prettier": "5.0.3",
"typescript": "*",
"vite-tsconfig-paths": "5.1.4",
"vitest": "4.0.13",
"vite-tsconfig-paths": "6.0.1",
"vitest": "4.0.15",
"webpack": "5.103.0",
"workbox-webpack-plugin": "7.1.0"
},

View File

@@ -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) => {
<link rel="stylesheet" href={conf?.FRONTEND_CSS_URL} />
</Head>
)}
{conf?.FRONTEND_JS_URL && (
<Script src={conf?.FRONTEND_JS_URL} strategy="afterInteractive" />
)}
<AnalyticsProvider>
<CrispProvider websiteId={conf?.CRISP_WEBSITE_ID}>
{children}

View File

@@ -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;

View File

@@ -44,7 +44,7 @@ export const Auth = ({ children }: PropsWithChildren) => {
if (config?.FRONTEND_HOMEPAGE_FEATURE_ENABLED) {
if (pathname !== HOME_URL) {
setIsRedirecting(true);
void replace(HOME_URL).then(() => setIsRedirecting(false));
window.location.replace(HOME_URL);
}
return;

View File

@@ -1,6 +1,6 @@
import { baseApiUrl } from '@/api';
export const HOME_URL = '/home';
export const HOME_URL = '/home/';
export const LOGIN_URL = `${baseApiUrl()}authenticate/`;
export const LOGOUT_URL = `${baseApiUrl()}logout/`;
export const PATH_AUTH_LOCAL_STORAGE = 'docs-path-auth';

View File

@@ -16,13 +16,13 @@ import { HocuspocusProvider } from '@hocuspocus/provider';
import { useEffect, useMemo, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { css } from 'styled-components';
import type { Awareness } from 'y-protocols/awareness';
import * as Y from 'yjs';
import { Box, TextErrors } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { Doc, useProviderStore } from '@/docs/doc-management';
import { avatarUrlFromName, useAuth } from '@/features/auth';
import { useResponsiveStore } from '@/stores';
import {
useHeadings,
@@ -85,12 +85,11 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
const { setEditor } = useEditorStore();
const { t } = useTranslation();
const { themeTokens } = useCunninghamTheme();
const { isDesktop } = useResponsiveStore();
const { isSynced: isConnectedToCollabServer } = useProviderStore();
const refEditorContainer = useRef<HTMLDivElement>(null);
const canSeeComment = doc.abilities.comment;
// Determine if comments should be visible in the UI
const showComments = canSeeComment && isDesktop;
const showComments = canSeeComment;
useSaveDoc(doc.id, provider.document, isConnectedToCollabServer);
const { i18n } = useTranslation();
@@ -117,7 +116,7 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
const editor: DocsBlockNoteEditor = useCreateBlockNote(
{
collaboration: {
provider: provider,
provider: provider as { awareness?: Awareness | undefined },
fragment: provider.document.getXmlFragment('document-store'),
user: {
name: cursorName,
@@ -163,8 +162,10 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
},
dictionary: {
...locales[lang as keyof typeof locales],
multi_column:
multiColumnLocales?.[lang as keyof typeof multiColumnLocales],
...(multiColumnLocales && {
multi_column:
multiColumnLocales[lang as keyof typeof multiColumnLocales],
}),
},
pasteHandler: ({ event, defaultPasteHandler }) => {
// Get clipboard data

View File

@@ -18,7 +18,6 @@ import { css } from 'styled-components';
import { Box, Icon } from '@/components';
import { useCunninghamTheme } from '@/cunningham';
import { useDocStore } from '@/features/docs/doc-management';
import { useResponsiveStore } from '@/stores';
import {
DocsBlockSchema,
@@ -31,7 +30,6 @@ export const CommentToolbarButton = () => {
const { currentDoc } = useDocStore();
const { t } = useTranslation();
const { spacingsTokens, colorsTokens } = useCunninghamTheme();
const { isDesktop } = useResponsiveStore();
const comments = useExtension('comments') as unknown as ReturnType<
ReturnType<typeof CommentsExtension>
>;
@@ -61,7 +59,6 @@ export const CommentToolbarButton = () => {
if (
!comments ||
!isDesktop ||
!show ||
!editor.isEditable ||
!Components ||

View File

@@ -37,7 +37,8 @@ export class DocsThreadStore extends ThreadStore {
if (docAuth.canSee) {
this.awareness = awareness;
this.awareness?.on('update', this.onAwarenessUpdate);
void this.refreshThreads();
this.refreshThreads();
}
}
@@ -98,9 +99,9 @@ export class DocsThreadStore extends ThreadStore {
// If we know the threadId, schedule a targeted refresh. Otherwise, fall back to full refresh.
if (ping.threadId) {
await this.refreshThread(ping.threadId);
void this.refreshThread(ping.threadId);
} else {
await this.refreshThreads();
this.refreshThreads();
}
}
};
@@ -280,7 +281,7 @@ export class DocsThreadStore extends ThreadStore {
}),
);
await this.refreshThreads();
this.refreshThreads();
return;
}
@@ -298,7 +299,17 @@ export class DocsThreadStore extends ThreadStore {
this.notifySubscribers();
}
public async refreshThreads(): Promise<void> {
public refreshThreads() {
this.initThreads().catch((e) => {
if (!(e instanceof APIError) || e.status !== 401) {
throw e;
}
return;
});
}
public async initThreads(): Promise<void> {
const response = await fetchAPI(`documents/${this.docId}/threads/`, {
method: 'GET',
});
@@ -484,7 +495,7 @@ export class DocsThreadStore extends ThreadStore {
);
}
await this.refreshThreads();
this.refreshThreads();
this.ping(threadId);
};

View File

@@ -26,13 +26,16 @@ export const cssComments = (
// Thread modal
.bn-thread {
width: 400px;
width: 100%;
min-width: calc(min(400px, 90vw));
max-width: calc(min(400px, 90vw));
max-height: calc(min(500px, 60vh));
padding: 8px;
box-shadow: 0px 6px 18px 0px #00001229;
margin-left: 20px;
margin-right: 20px;
gap: 0;
overflow: auto;
max-height: 500px;
.bn-default-styles {
font-family: var(--c--globals--font--families--base);

View File

@@ -13,12 +13,13 @@ import {
createReactBlockSpec,
} from '@blocknote/react';
import { TFunction } from 'i18next';
import { useEffect } from 'react';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { createGlobalStyle } from 'styled-components';
import { createGlobalStyle, css } from 'styled-components';
import { Box, Icon } from '@/components';
import { Box, Icon, Loading } from '@/components';
import { ANALYZE_URL } from '../../conf';
import { DocsBlockNoteEditor } from '../../types';
const PDFBlockStyle = createGlobalStyle`
@@ -66,6 +67,9 @@ const PdfBlockComponent = ({
const pdfUrl = block.props.url;
const { i18n, t } = useTranslation();
const lang = i18n.resolvedLanguage;
const [isPDFContent, setIsPDFContent] = useState<boolean | null>(null);
const [isPDFContentLoading, setIsPDFContentLoading] =
useState<boolean>(false);
useEffect(() => {
if (lang && locales[lang as keyof typeof locales]) {
@@ -82,9 +86,55 @@ const PdfBlockComponent = ({
}
}, [lang, t]);
useEffect(() => {
if (!pdfUrl || pdfUrl.includes(ANALYZE_URL)) {
return;
}
const validatePDFContent = async () => {
setIsPDFContentLoading(true);
try {
const response = await fetch(pdfUrl, {
credentials: 'include',
});
const contentType = response.headers.get('content-type');
if (response.ok && contentType?.includes('application/pdf')) {
setIsPDFContent(true);
} else {
setIsPDFContent(false);
}
} catch {
setIsPDFContent(false);
} finally {
setIsPDFContentLoading(false);
}
};
void validatePDFContent();
}, [pdfUrl]);
return (
<Box ref={contentRef} className="bn-file-block-content-wrapper">
<PDFBlockStyle />
{isPDFContentLoading && <Loading />}
{!isPDFContentLoading && isPDFContent !== null && !isPDFContent && (
<Box
$align="center"
$justify="center"
$color="#666"
$background="#f5f5f5"
$border="1px solid #ddd"
$height="300px"
$css={css`
text-align: center;
`}
contentEditable={false}
onClick={() => editor.setTextCursorPosition(block)}
>
{t('Invalid or missing PDF file.')}
</Box>
)}
<ResizableFileBlockWrapper
buttonIcon={
<Icon iconName="upload" $size="24px" $css="line-height: normal;" />
@@ -92,18 +142,21 @@ const PdfBlockComponent = ({
block={block as unknown as FileBlockBlock}
editor={editor as unknown as FileBlockEditor}
>
<Box
className="bn-visual-media"
role="presentation"
as="embed"
$width="100%"
$height="450px"
type="application/pdf"
src={pdfUrl}
contentEditable={false}
draggable={false}
onClick={() => editor.setTextCursorPosition(block)}
/>
{!isPDFContentLoading && isPDFContent && (
<Box
as="embed"
className="bn-visual-media"
role="presentation"
$width="100%"
$height="450px"
type="application/pdf"
src={pdfUrl}
aria-label={block.props.name || t('PDF document')}
contentEditable={false}
draggable={false}
onClick={() => editor.setTextCursorPosition(block)}
/>
)}
</ResizableFileBlockWrapper>
</Box>
);

View File

@@ -79,10 +79,12 @@ const LinkSelected = ({ url, title }: LinkSelectedProps) => {
return (
<BoxButton
as="span"
className="--docs--interlinking-link-inline-content"
onClick={handleClick}
draggable="false"
$css={css`
display: contents;
display: inline;
padding: 0.1rem 0.4rem;
border-radius: 4px;
& svg {
@@ -91,7 +93,9 @@ const LinkSelected = ({ url, title }: LinkSelectedProps) => {
margin-right: 0.2rem;
}
&:hover {
background-color: ${colorsTokens['gray-100']};
background-color: var(
--c--contextuals--background--semantic--contextual--primary
);
}
transition: background-color var(--c--globals--transitions--duration)
var(--c--globals--transitions--ease-out);

View File

@@ -171,6 +171,7 @@ export const SearchPage = ({
{trigger}
<Box
as="input"
name="doc-search-input"
$padding={{ left: '3px' }}
$css={inputStyle}
ref={inputRef}

View File

@@ -0,0 +1 @@
export const ANALYZE_URL = 'media-check';

View File

@@ -6,6 +6,7 @@ import { useTranslation } from 'react-i18next';
import { backendUrl } from '@/api';
import { useCreateDocAttachment } from '../api';
import { ANALYZE_URL } from '../conf';
import { DocsBlockNoteEditor } from '../types';
export const useUploadFile = (docId: string) => {
@@ -46,7 +47,6 @@ export const useUploadFile = (docId: string) => {
* @param editor
*/
export const useUploadStatus = (editor: DocsBlockNoteEditor) => {
const ANALYZE_URL = 'media-check';
const { t } = useTranslation();
/**

View File

@@ -172,7 +172,10 @@ export const DocSubPageItem = (props: TreeViewNodeProps<Doc>) => {
emoji={emoji}
withEmojiPicker={doc.abilities.partial_update}
defaultIcon={
<SubPageIcon color="var(--c--contextuals--content--semantic--info--tertiary)" />
<SubPageIcon
color="var(--c--contextuals--content--semantic--info--tertiary)"
style={{ flexShrink: 0 }}
/>
}
$size="sm"
docId={doc.id}

View File

@@ -63,13 +63,11 @@ export const DocTree = ({ currentDoc }: DocTreeProps) => {
});
treeContext?.treeData.handleMove(result);
};
/**
* This function resets the tree states.
*/
const resetStateTree = useCallback(() => {
if (!treeContext?.root?.id) {
return;
}
treeContext?.setRoot(null);
setInitialOpenState(undefined);
}, [treeContext]);

View File

@@ -29,6 +29,7 @@ export const Header = () => {
<SkipToContent />
<Box
as="header"
className="--docs--header"
role="banner"
$css={css`
position: fixed;
@@ -45,7 +46,6 @@ export const Header = () => {
border-bottom: 1px solid
var(--c--contextuals--border--surface--primary);
`}
className="--docs--header"
>
{!isDesktop && <ButtonTogglePanel />}
<StyledLink
@@ -88,7 +88,12 @@ export const Header = () => {
<LaGaufre />
</Box>
) : (
<Box $align="center" $gap={spacingsTokens['sm']} $direction="row">
<Box
className="--docs--header-block-right"
$align="center"
$gap={spacingsTokens['sm']}
$direction="row"
>
<ButtonLogin />
<LanguagePicker />
<LaGaufre />

View File

@@ -10,6 +10,12 @@ body {
padding: 0;
}
/* stylelint-disable-next-line selector-id-pattern */
body > #__next > .c__app > div:has(> .c__loader) {
min-height: 100vh;
width: 100vw;
}
* {
box-sizing: border-box;
}

View File

@@ -1,10 +1,7 @@
import { useRouter } from 'next/router';
import { HOME_URL } from '@/features/auth';
const Page = () => {
const { replace } = useRouter();
void replace(HOME_URL);
window.location.replace(HOME_URL);
};
export default Page;

View File

@@ -3,6 +3,7 @@ import posthog from 'posthog-js';
import { PostHogProvider as PHProvider } from 'posthog-js/react';
import { JSX, PropsWithChildren, ReactNode, useEffect } from 'react';
import { useIsOffline } from '@/features/service-worker/hooks/useOffline';
import { AbstractAnalytic, AnalyticEvent } from '@/libs/';
export class PostHogAnalytic extends AbstractAnalytic {
@@ -45,6 +46,8 @@ export function PostHogProvider({
children,
conf,
}: PropsWithChildren<PostHogProviderProps>) {
const isOffline = useIsOffline((state) => state.isOffline);
useEffect(() => {
if (!conf?.id || !conf?.host || posthog.__loaded) {
return;
@@ -53,9 +56,9 @@ export function PostHogProvider({
posthog.init(conf.id, {
api_host: conf.host,
person_profiles: 'always',
loaded: (posthog) => {
loaded: (posthogInstance) => {
if (process.env.NODE_ENV === 'development') {
posthog.debug();
posthogInstance.debug();
}
},
capture_pageview: false,
@@ -71,5 +74,14 @@ export function PostHogProvider({
};
}, [conf?.host, conf?.id]);
// Disable PostHog when offline to prevent retry requests
useEffect(() => {
if (isOffline) {
posthog.opt_out_capturing();
} else {
posthog.opt_in_capturing();
}
}, [isOffline]);
return <PHProvider client={posthog}>{children}</PHProvider>;
}

View File

@@ -31,14 +31,15 @@
"server:test": "yarn COLLABORATION_SERVER run test"
},
"resolutions": {
"@tiptap/extensions": "3.11.0",
"@types/node": "24.10.1",
"@types/react": "19.2.6",
"@tiptap/extensions": "3.13.0",
"@types/node": "24.10.4",
"@types/react": "19.2.7",
"@types/react-dom": "19.2.3",
"docx": "9.5.0",
"eslint": "9.39.1",
"react": "19.2.0",
"react-dom": "19.2.0",
"eslint": "9.39.2",
"prosemirror-view": "1.41.3",
"react": "19.2.3",
"react-dom": "19.2.3",
"typescript": "5.9.3",
"wrap-ansi": "9.0.2",
"yjs": "13.6.27"

View File

@@ -17,21 +17,21 @@
"eslint": ">=9.0.0"
},
"dependencies": {
"@next/eslint-plugin-next": "16.0.3",
"@next/eslint-plugin-next": "15.5.9",
"@tanstack/eslint-plugin-query": "5.91.2",
"@typescript-eslint/eslint-plugin": "8.47.0",
"@typescript-eslint/parser": "8.47.0",
"@vitest/eslint-plugin": "1.4.3",
"eslint-config-next": "16.0.3",
"@typescript-eslint/eslint-plugin": "8.49.0",
"@typescript-eslint/parser": "8.49.0",
"@vitest/eslint-plugin": "1.5.2",
"eslint-config-next": "15.5.9",
"eslint-config-prettier": "10.1.8",
"eslint-plugin-import": "2.32.0",
"eslint-plugin-jest": "29.2.1",
"eslint-plugin-jest": "29.5.0",
"eslint-plugin-jsx-a11y": "6.10.2",
"eslint-plugin-playwright": "2.3.0",
"eslint-plugin-playwright": "2.4.0",
"eslint-plugin-prettier": "5.5.4",
"eslint-plugin-react": "7.37.5",
"eslint-plugin-testing-library": "7.13.5",
"prettier": "3.6.2"
"eslint-plugin-testing-library": "7.13.6",
"prettier": "3.7.4"
},
"packageManager": "yarn@1.22.22"
}

View File

@@ -21,7 +21,7 @@
"eslint-plugin-import": "2.32.0",
"i18next-parser": "9.3.0",
"jest": "30.2.0",
"ts-jest": "29.4.5",
"ts-jest": "29.4.6",
"typescript": "*",
"yargs": "18.0.0"
},

View File

@@ -16,24 +16,24 @@
"node": ">=22"
},
"dependencies": {
"@blocknote/server-util": "0.44.2",
"@hocuspocus/server": "3.4.0",
"@sentry/node": "10.26.0",
"@sentry/profiling-node": "10.26.0",
"@blocknote/server-util": "0.45.0",
"@hocuspocus/server": "3.4.3",
"@sentry/node": "10.30.0",
"@sentry/profiling-node": "10.30.0",
"@tiptap/extensions": "*",
"axios": "1.13.2",
"cors": "2.8.5",
"express": "5.1.0",
"express": "5.2.1",
"express-ws": "5.0.2",
"uuid": "13.0.0",
"y-protocols": "1.0.6",
"yjs": "*"
},
"devDependencies": {
"@blocknote/core": "0.44.2",
"@hocuspocus/provider": "3.4.0",
"@blocknote/core": "0.45.0",
"@hocuspocus/provider": "3.4.3",
"@types/cors": "2.8.19",
"@types/express": "5.0.5",
"@types/express": "5.0.6",
"@types/express-ws": "3.0.6",
"@types/node": "*",
"@types/supertest": "6.0.3",
@@ -45,7 +45,7 @@
"ts-node": "10.9.2",
"tsc-alias": "1.8.16",
"typescript": "*",
"vitest": "4.0.13",
"vitest": "4.0.15",
"vitest-mock-extended": "3.1.0",
"ws": "8.18.3"
},

File diff suppressed because it is too large Load Diff

View File

@@ -31,17 +31,18 @@ backend:
LOGGING_LEVEL_HANDLERS_CONSOLE: ERROR
LOGGING_LEVEL_LOGGERS_ROOT: INFO
LOGGING_LEVEL_LOGGERS_APP: INFO
OIDC_USERINFO_SHORTNAME_FIELD: "given_name"
OIDC_USERINFO_FULLNAME_FIELDS: "given_name,usual_name"
OIDC_USERINFO_SHORTNAME_FIELD: "first_name"
OIDC_USERINFO_FULLNAME_FIELDS: "name"
OIDC_OP_JWKS_ENDPOINT: https://docs-keycloak.127.0.0.1.nip.io/realms/docs/protocol/openid-connect/certs
OIDC_OP_AUTHORIZATION_ENDPOINT: https://docs-keycloak.127.0.0.1.nip.io/realms/docs/protocol/openid-connect/auth
OIDC_OP_TOKEN_ENDPOINT: https://docs-keycloak.127.0.0.1.nip.io/realms/docs/protocol/openid-connect/token
OIDC_OP_USER_ENDPOINT: https://docs-keycloak.127.0.0.1.nip.io/realms/docs/protocol/openid-connect/userinfo
OIDC_OP_LOGOUT_ENDPOINT: https://docs-keycloak.127.0.0.1.nip.io/realms/docs/protocol/openid-connect/logout
OIDC_REDIRECT_ALLOWED_HOSTS: "docs.127.0.0.1.nip.io"
OIDC_RP_CLIENT_ID: docs
OIDC_RP_CLIENT_SECRET: ThisIsAnExampleKeyForDevPurposeOnly
OIDC_RP_SIGN_ALGO: RS256
OIDC_RP_SCOPES: "openid email given_name usual_name"
OIDC_RP_SCOPES: "openid email profile"
LOGIN_REDIRECT_URL: https://docs.127.0.0.1.nip.io
LOGIN_REDIRECT_URL_FAILURE: https://docs.127.0.0.1.nip.io
LOGOUT_REDIRECT_URL: https://docs.127.0.0.1.nip.io

View File

@@ -32,17 +32,18 @@ backend:
LOGGING_LEVEL_HANDLERS_CONSOLE: ERROR
LOGGING_LEVEL_LOGGERS_ROOT: INFO
LOGGING_LEVEL_LOGGERS_APP: INFO
OIDC_USERINFO_SHORTNAME_FIELD: "given_name"
OIDC_USERINFO_FULLNAME_FIELDS: "given_name,usual_name"
OIDC_USERINFO_SHORTNAME_FIELD: "first_name"
OIDC_USERINFO_FULLNAME_FIELDS: "name"
OIDC_OP_JWKS_ENDPOINT: https://{{ .Values.feature }}-docs-keycloak.{{ .Values.domain }}/realms/docs/protocol/openid-connect/certs
OIDC_OP_AUTHORIZATION_ENDPOINT: https://{{ .Values.feature }}-docs-keycloak.{{ .Values.domain }}/realms/docs/protocol/openid-connect/auth
OIDC_OP_TOKEN_ENDPOINT: https://{{ .Values.feature }}-docs-keycloak.{{ .Values.domain }}/realms/docs/protocol/openid-connect/token
OIDC_OP_USER_ENDPOINT: https://{{ .Values.feature }}-docs-keycloak.{{ .Values.domain }}/realms/docs/protocol/openid-connect/userinfo
OIDC_OP_LOGOUT_ENDPOINT: https://{{ .Values.feature }}-docs-keycloak.{{ .Values.domain }}/realms/docs/protocol/openid-connect/logout
OIDC_REDIRECT_ALLOWED_HOSTS: "{{ .Values.feature }}-docs.{{ .Values.domain }}"
OIDC_RP_CLIENT_ID: docs
OIDC_RP_CLIENT_SECRET: ThisIsAnExampleKeyForDevPurposeOnly
OIDC_RP_SIGN_ALGO: RS256
OIDC_RP_SCOPES: "openid email given_name usual_name"
OIDC_RP_SCOPES: "openid email profile"
LOGIN_REDIRECT_URL: https://{{ .Values.feature }}-docs.{{ .Values.domain }}
LOGIN_REDIRECT_URL_FAILURE: https://{{ .Values.feature }}-docs.{{ .Values.domain }}
LOGOUT_REDIRECT_URL: https://{{ .Values.feature }}-docs.{{ .Values.domain }}

View File

@@ -39,6 +39,14 @@
| `ingressCollaborationWS.annotations.nginx.ingress.kubernetes.io/proxy-read-timeout` | | `86400` |
| `ingressCollaborationWS.annotations.nginx.ingress.kubernetes.io/proxy-send-timeout` | | `86400` |
| `ingressCollaborationWS.annotations.nginx.ingress.kubernetes.io/upstream-hash-by` | | `$arg_room` |
| `ingressRedirects.enabled` | whether to enable the Ingress Redirects or not | `false` |
| `ingressRedirects.className` | IngressClass to use for the Ingress Redirects | `nil` |
| `ingressRedirects.host` | Host for the Ingress Redirects | `impress.example.com` |
| `ingressRedirects.tls.enabled` | Weather to enable TLS for the Ingress Redirects | `true` |
| `ingressRedirects.tls.secretName` | Secret name for TLS config | `nil` |
| `ingressRedirects.tls.additional[].secretName` | Secret name for additional TLS config | |
| `ingressRedirects.tls.additional[].hosts[]` | Hosts for additional TLS config | |
| `ingressRedirects.rules` | Rules for the Ingress Redirects | `[]` |
| `ingressCollaborationApi.enabled` | whether to enable the Ingress or not | `false` |
| `ingressCollaborationApi.className` | IngressClass to use for the Ingress | `nil` |
| `ingressCollaborationApi.host` | Host for the Ingress | `impress.example.com` |
@@ -113,15 +121,15 @@
| `backend.job.annotations` | Annotations to add to the job [default: argocd.argoproj.io/hook: PostSync] | |
| `backend.cronjobs` | Cronjob name, schedule, command | `[]` |
| `backend.probes.liveness.path` | Configure path for backend HTTP liveness probe | `/__heartbeat__` |
| `backend.probes.liveness.targetPort` | Configure port for backend HTTP liveness probe | `undefined` |
| `backend.probes.liveness.targetPort` | Configure port for backend HTTP liveness probe | `nil` |
| `backend.probes.liveness.initialDelaySeconds` | Configure initial delay for backend liveness probe | `10` |
| `backend.probes.liveness.initialDelaySeconds` | Configure timeout for backend liveness probe | `10` |
| `backend.probes.startup.path` | Configure path for backend HTTP startup probe | `undefined` |
| `backend.probes.startup.targetPort` | Configure port for backend HTTP startup probe | `undefined` |
| `backend.probes.startup.initialDelaySeconds` | Configure initial delay for backend startup probe | `undefined` |
| `backend.probes.startup.initialDelaySeconds` | Configure timeout for backend startup probe | `undefined` |
| `backend.probes.startup.path` | Configure path for backend HTTP startup probe | `nil` |
| `backend.probes.startup.targetPort` | Configure port for backend HTTP startup probe | `nil` |
| `backend.probes.startup.initialDelaySeconds` | Configure initial delay for backend startup probe | `nil` |
| `backend.probes.startup.initialDelaySeconds` | Configure timeout for backend startup probe | `nil` |
| `backend.probes.readiness.path` | Configure path for backend HTTP readiness probe | `/__lbheartbeat__` |
| `backend.probes.readiness.targetPort` | Configure port for backend HTTP readiness probe | `undefined` |
| `backend.probes.readiness.targetPort` | Configure port for backend HTTP readiness probe | `nil` |
| `backend.probes.readiness.initialDelaySeconds` | Configure initial delay for backend readiness probe | `10` |
| `backend.probes.readiness.initialDelaySeconds` | Configure timeout for backend readiness probe | `10` |
| `backend.resources` | Resource requirements for the backend container | `{}` |

View File

@@ -0,0 +1,63 @@
{{- if .Values.ingressRedirects.enabled }}
{{- $fullName := include "impress.fullname" . -}}
{{- $ns := .Release.Namespace -}}
{{- range $i, $r := .Values.ingressRedirects.rules }}
{{- $host := $r.host | default $.Values.ingressRedirects.host -}}
{{- $from := $r.from | default "/home" -}}
{{- $to := required (printf "ingressRedirects.rules[%d].to is required" $i) $r.to -}}
{{- $name := printf "%s-redirect-%s" $fullName (replace "/" "-" (trimAll "/" $from)) | trunc 63 | trimSuffix "-" -}}
{{- if $i }}
---
{{- end }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ $name }}
namespace: {{ $ns }}
annotations:
{{- if or (not $r.code) (eq (toString $r.code) "301") }}
nginx.ingress.kubernetes.io/permanent-redirect: "{{ $to }}"
{{- else }}
nginx.ingress.kubernetes.io/temporal-redirect: "{{ $to }}"
nginx.ingress.kubernetes.io/temporal-redirect-code: "{{ $r.code }}"
{{- end }}
spec:
{{- if $.Values.ingressRedirects.className }}
ingressClassName: {{ $.Values.ingressRedirects.className }}
{{- end }}
{{- if $.Values.ingressRedirects.tls.enabled }}
tls:
{{- if $host }}
- secretName: {{ $.Values.ingressRedirects.tls.secretName | default (printf "%s-tls" $fullName) | quote }}
hosts:
- {{ $host | quote }}
{{- end }}
{{- range $.Values.ingressRedirects.tls.additional }}
- hosts:
{{- range .hosts }}
- {{ . | quote }}
{{- end }}
secretName: {{ .secretName }}
{{- end }}
{{- end }}
rules:
- host: {{ $host }}
http:
paths:
- path: {{ $from }}
pathType: Exact
backend:
service:
name: {{ include "impress.frontend.fullname" $ }}
port:
number: {{ $.Values.frontend.service.port }}
- path: {{ printf "%s/" (trimSuffix "/" $from) }}
pathType: Exact
backend:
service:
name: {{ include "impress.frontend.fullname" $ }}
port:
number: {{ $.Values.frontend.service.port }}
{{- end }}
{{- end }}

View File

@@ -85,6 +85,25 @@ ingressCollaborationWS:
nginx.ingress.kubernetes.io/proxy-send-timeout: "86400"
nginx.ingress.kubernetes.io/upstream-hash-by: $arg_room
## @param ingressRedirects.enabled whether to enable the Ingress Redirects or not
## @param ingressRedirects.className IngressClass to use for the Ingress Redirects
## @param ingressRedirects.host Host for the Ingress Redirects
ingressRedirects:
enabled: false
className: null
host: impress.example.com
## @param ingressRedirects.tls.enabled Weather to enable TLS for the Ingress Redirects
## @param ingressRedirects.tls.secretName Secret name for TLS config
## @skip ingressRedirects.tls.additional
## @extra ingressRedirects.tls.additional[].secretName Secret name for additional TLS config
## @extra ingressRedirects.tls.additional[].hosts[] Hosts for additional TLS config
tls:
enabled: true
secretName: null
additional: []
## @param ingressRedirects.rules Rules for the Ingress Redirects
rules: []
## @param ingressCollaborationApi.enabled whether to enable the Ingress or not
## @param ingressCollaborationApi.className IngressClass to use for the Ingress
## @param ingressCollaborationApi.host Host for the Ingress

View File

@@ -5,7 +5,7 @@
"type": "module",
"dependencies": {
"@html-to/text-cli": "0.5.4",
"mjml": "4.17.1"
"mjml": "4.18.0"
},
"private": true,
"scripts": {

View File

@@ -581,46 +581,46 @@ minimatch@^9.0.3, minimatch@^9.0.4:
resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707"
integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==
mjml-accordion@4.17.1:
version "4.17.1"
resolved "https://registry.yarnpkg.com/mjml-accordion/-/mjml-accordion-4.17.1.tgz#6ee3c016ea78a5a0ed29f3ec28c17d181028994f"
integrity sha512-xl9oUbMp8aju6b1OZYqv3orE287ofGNEv09h2mFmzRTJxug7nJBFXs2I9v7dUVuWIBRk940PjnIVSuW+9bPvCA==
mjml-accordion@4.18.0:
version "4.18.0"
resolved "https://registry.yarnpkg.com/mjml-accordion/-/mjml-accordion-4.18.0.tgz#4695289161fc631bd3f4c9546fedb84b42712ef8"
integrity sha512-9PUmy2JxIOGgAaVHvgVYX21nVAo3o/+wJckTTF/YTLGAqB+nm+44buxRzaXxVk7qXRwbCNfE8c8mlGVNh7vB1g==
dependencies:
"@babel/runtime" "^7.28.4"
lodash "^4.17.21"
mjml-core "4.17.1"
mjml-core "4.18.0"
mjml-body@4.17.1:
version "4.17.1"
resolved "https://registry.yarnpkg.com/mjml-body/-/mjml-body-4.17.1.tgz#a4e4d2ee34abfbb45b74999ee49356b35830a0dc"
integrity sha512-EfvVVfutARRjJ1jsOxxf2DY/ufqWswv9JKjbwu/Fu8h4havAcdmw2BDmX3vwXEzatqpL1l//YWOKlqUe9ZEs+A==
mjml-body@4.18.0:
version "4.18.0"
resolved "https://registry.yarnpkg.com/mjml-body/-/mjml-body-4.18.0.tgz#20731dbbc4a92813b4c4f6ce417fc79e7a0b307f"
integrity sha512-34AwX70/7NkRIajPsa5j6NySRiNrlLatTKhiLwTVFiVtrEFlfCcbeMNmdVixI3Ldvs8209ZC6euaAnXDRyR1zw==
dependencies:
"@babel/runtime" "^7.28.4"
lodash "^4.17.21"
mjml-core "4.17.1"
mjml-core "4.18.0"
mjml-button@4.17.1:
version "4.17.1"
resolved "https://registry.yarnpkg.com/mjml-button/-/mjml-button-4.17.1.tgz#1cbdf444802690329ea59ea524d088607666fa5f"
integrity sha512-A9xQwgccPzrwr11izorBsA92THpkrviWkCwlYMxL9V3wgt5YJYDrt4r023HCveqN7b6iTkvqkXeDoIPX/kEDDQ==
mjml-button@4.18.0:
version "4.18.0"
resolved "https://registry.yarnpkg.com/mjml-button/-/mjml-button-4.18.0.tgz#654a1fb6ade98c32f8c7c8e90c729edde81eeefb"
integrity sha512-ZsWMI0j7EcFCMqbqdVwMWhmsVc03FhmypWXokKopGhwySn4IAB4AOURonRmFrO7k6sDeQ+iJ9QtTu7jA+S8wmg==
dependencies:
"@babel/runtime" "^7.28.4"
lodash "^4.17.21"
mjml-core "4.17.1"
mjml-core "4.18.0"
mjml-carousel@4.17.1:
version "4.17.1"
resolved "https://registry.yarnpkg.com/mjml-carousel/-/mjml-carousel-4.17.1.tgz#c395842741f55420dd7a3c08a76cd3d3a73e49ba"
integrity sha512-pWo/aIgRL3XduckOBVEvbpph3vR4f9maRrbJ8Jfu4NVI6Ws3PQ6wt7HPXHmJlzJlK0gTiAF9f4+I076RVHPG7A==
mjml-carousel@4.18.0:
version "4.18.0"
resolved "https://registry.yarnpkg.com/mjml-carousel/-/mjml-carousel-4.18.0.tgz#56e61a7873b91d9549d9992f517a471cdb433030"
integrity sha512-wY4g1CHCOoVSZuar7CLFon/qkPbICu71IT+6pa4BDwkAiaAMAemZPyy+a+iIUgdc8kHgSuHGsGf6PQzBSMWRZA==
dependencies:
"@babel/runtime" "^7.28.4"
lodash "^4.17.21"
mjml-core "4.17.1"
mjml-core "4.18.0"
mjml-cli@4.17.1:
version "4.17.1"
resolved "https://registry.yarnpkg.com/mjml-cli/-/mjml-cli-4.17.1.tgz#0bc278762bc2391b6c61d16784156ee429cd104f"
integrity sha512-1cMWP+yDDBUIjDYnjiKhIPW3NYJrt/W5rqOiB3zOTZQBT722Uh055S3BoLUikKxc+1sQPww4d9dH371zX2HaXA==
mjml-cli@4.18.0:
version "4.18.0"
resolved "https://registry.yarnpkg.com/mjml-cli/-/mjml-cli-4.18.0.tgz#779902569f609ca6c1f26fadced10770a286b6fd"
integrity sha512-N6CnA4o/q/VRnGPxTzvVnjAEcF7WUVVQGYfS9SPAp0qwyf7RysMmewdS9yN8GwXwZV6L2sKdn+3ANNi2FNsJ7w==
dependencies:
"@babel/runtime" "^7.28.4"
chokidar "^3.0.0"
@@ -629,25 +629,25 @@ mjml-cli@4.17.1:
js-beautify "^1.6.14"
lodash "^4.17.21"
minimatch "^9.0.3"
mjml-core "4.17.1"
mjml-migrate "4.17.1"
mjml-parser-xml "4.17.1"
mjml-validator "4.17.1"
mjml-core "4.18.0"
mjml-migrate "4.18.0"
mjml-parser-xml "4.18.0"
mjml-validator "4.18.0"
yargs "^17.7.2"
mjml-column@4.17.1:
version "4.17.1"
resolved "https://registry.yarnpkg.com/mjml-column/-/mjml-column-4.17.1.tgz#3aa64972bb827fd96b596c71ce583e9ef720aa41"
integrity sha512-S+oNZaWFP1/TCEgVwVcwqYIyHwwVZWSKLKj4fcWIMUCaHWKuojYrexOGfULDAwTjYEDhZaRDrrq96ulF12wJeQ==
mjml-column@4.18.0:
version "4.18.0"
resolved "https://registry.yarnpkg.com/mjml-column/-/mjml-column-4.18.0.tgz#d8357449af5804e6eb506b8cf87691787e959cf5"
integrity sha512-0QZ1whxbHUmJaRT8tW+wmr3fWZ/kpsHKAd24c7Z/N1Otm/U2G0T/FFEFJ6cB25X6ZN0K40QZ8L9gdLfiSVuRbA==
dependencies:
"@babel/runtime" "^7.28.4"
lodash "^4.17.21"
mjml-core "4.17.1"
mjml-core "4.18.0"
mjml-core@4.17.1:
version "4.17.1"
resolved "https://registry.yarnpkg.com/mjml-core/-/mjml-core-4.17.1.tgz#a303e8e4f94f5124284843bebd70c1d91a9085a6"
integrity sha512-u2aHbBxFA2uJdS6T1A1ZTGYryPNebHMByRrMPCbe5W8Os+sGiC5gKLhZgjavZteKiMS+09swkvfneNLGYwzBKg==
mjml-core@4.18.0:
version "4.18.0"
resolved "https://registry.yarnpkg.com/mjml-core/-/mjml-core-4.18.0.tgz#36d9ef9ff26cb010f81bef5342cf643b4fdf8043"
integrity sha512-yey72LszXvIo5p0R6DB+YU8er/nP2wPsqpLKQCB0H8vG0WRT1sbSUvnCUOkKGn7subuyWDTdzHKbQO3XYIOmvg==
dependencies:
"@babel/runtime" "^7.28.4"
cheerio "1.0.0-rc.12"
@@ -656,263 +656,263 @@ mjml-core@4.17.1:
js-beautify "^1.6.14"
juice "^10.0.0"
lodash "^4.17.21"
mjml-migrate "4.17.1"
mjml-parser-xml "4.17.1"
mjml-validator "4.17.1"
mjml-migrate "4.18.0"
mjml-parser-xml "4.18.0"
mjml-validator "4.18.0"
mjml-divider@4.17.1:
version "4.17.1"
resolved "https://registry.yarnpkg.com/mjml-divider/-/mjml-divider-4.17.1.tgz#1219c4f25d9e6f963de9438a5adf85064c7e9f01"
integrity sha512-KUWvcx1cIDwkN/gDuY37e9Vv+0U5U+xOVOfXRGloSnapYcP0IvmLtLTJeBwvGhwoN30wBiHDGwO8p/7B6CyxqQ==
mjml-divider@4.18.0:
version "4.18.0"
resolved "https://registry.yarnpkg.com/mjml-divider/-/mjml-divider-4.18.0.tgz#36877b43836d7d05b190c109df5af73a781b6a80"
integrity sha512-FmGUVJqi4RYroh7y85vDx0aUKZgECkxHtMQ4pkLGQbZ2g93/Qt0Ek88DVCNJ5XwUAQQkE/TvrGMLHp3CIqpQ9Q==
dependencies:
"@babel/runtime" "^7.28.4"
lodash "^4.17.21"
mjml-core "4.17.1"
mjml-core "4.18.0"
mjml-group@4.17.1:
version "4.17.1"
resolved "https://registry.yarnpkg.com/mjml-group/-/mjml-group-4.17.1.tgz#38b4da7e67151c2c8c84378ef176432a135e11e2"
integrity sha512-0vOcLm7l4ptLM5rqC6DhCafxIw5+WUrSYLcdUSJxO3AZMGJMxU7fkWeWGowE+PQdgqh6ee1/4RYc2qJDWtHW5A==
mjml-group@4.18.0:
version "4.18.0"
resolved "https://registry.yarnpkg.com/mjml-group/-/mjml-group-4.18.0.tgz#09ec6c78e36c5c074260d888505efde645bc3da9"
integrity sha512-28ABkXsKljBqj7XCC8GkQ94xz8HEU2XTyD+9LTlkDafzGp/MGJb8DcLh/7IkxCwqkQWyeMiDNLf1djsQ909Vxw==
dependencies:
"@babel/runtime" "^7.28.4"
lodash "^4.17.21"
mjml-core "4.17.1"
mjml-core "4.18.0"
mjml-head-attributes@4.17.1:
version "4.17.1"
resolved "https://registry.yarnpkg.com/mjml-head-attributes/-/mjml-head-attributes-4.17.1.tgz#198ca06a6a9b9148af1b6508aab38a41c2cf9b30"
integrity sha512-p+g33eI4xyHb9Yv28zIXnNdsXQYvoGex/GvoGumwyxu6O8OOvPk1mIV87SjDISQHosJJMcTiZVd/RfBlwnZpGA==
mjml-head-attributes@4.18.0:
version "4.18.0"
resolved "https://registry.yarnpkg.com/mjml-head-attributes/-/mjml-head-attributes-4.18.0.tgz#bf311924af6d2e0784df9bb95629bdc02583c0a7"
integrity sha512-nLzix1wrMnojE0RPGhk4iKqSRwHKjie2EPzgKT7CDzfqN+Ref03E5Q19x3cQTLgxvq3C3CnvCQBfnhoS3Eakug==
dependencies:
"@babel/runtime" "^7.28.4"
lodash "^4.17.21"
mjml-core "4.17.1"
mjml-core "4.18.0"
mjml-head-breakpoint@4.17.1:
version "4.17.1"
resolved "https://registry.yarnpkg.com/mjml-head-breakpoint/-/mjml-head-breakpoint-4.17.1.tgz#4fafa9cc176c052a2796d904ff84172290826adb"
integrity sha512-vjsNAgdLnwqmkVlIENbH6odK6ZARiNQvsm+1o8CLo9ymw82WhIEbOnAeCfoddumZ6h2ywbZuBZzS23jJi13MUQ==
mjml-head-breakpoint@4.18.0:
version "4.18.0"
resolved "https://registry.yarnpkg.com/mjml-head-breakpoint/-/mjml-head-breakpoint-4.18.0.tgz#4d9d2e35ec0bdb2fd2c530241c38df9c19106dfb"
integrity sha512-k6rwff+7i+vTQYJ/CjBfE20qNqPaW60IRH2x2oEPuCzmwDmoVWOcplJIuotSqIAdfwF9hLkICknisp1BpczVlQ==
dependencies:
"@babel/runtime" "^7.28.4"
lodash "^4.17.21"
mjml-core "4.17.1"
mjml-core "4.18.0"
mjml-head-font@4.17.1:
version "4.17.1"
resolved "https://registry.yarnpkg.com/mjml-head-font/-/mjml-head-font-4.17.1.tgz#0984862ebae07fbf2427e61c3d997ac155d56bd2"
integrity sha512-Xeih/vqocR1BoBLbh8Sn67kNkfLsyHeZ7Z/3nyNz7TriZ//TGAR/PGTFFghQlXyX1BCtSx/eFoxMkKKswLYReA==
mjml-head-font@4.18.0:
version "4.18.0"
resolved "https://registry.yarnpkg.com/mjml-head-font/-/mjml-head-font-4.18.0.tgz#f153a8b81275a3f25fa28142e3536e3dd4606c51"
integrity sha512-ao8HB5nf+Dmxw4GO6lMMOlnj1lNZONai0GC9RobrZgPlghZw6hpURWGpkON7pQcy6XnOHwYwkV7Go/npzA2i7w==
dependencies:
"@babel/runtime" "^7.28.4"
lodash "^4.17.21"
mjml-core "4.17.1"
mjml-core "4.18.0"
mjml-head-html-attributes@4.17.1:
version "4.17.1"
resolved "https://registry.yarnpkg.com/mjml-head-html-attributes/-/mjml-head-html-attributes-4.17.1.tgz#cb4b74210257d9bb7ba9b23ca5cc409516456b8b"
integrity sha512-O7YzEAFtSELB7wVYV808g6JcxXrzHk5glDdzzCEhDR4bjPHewSUpkrYOqvt0BdfdFsvqH4zm4vsJImUMW692HQ==
mjml-head-html-attributes@4.18.0:
version "4.18.0"
resolved "https://registry.yarnpkg.com/mjml-head-html-attributes/-/mjml-head-html-attributes-4.18.0.tgz#167527f926919ff0cffdc8456e5db17953c81a11"
integrity sha512-xaQE1rthe0RrNotwEr71X1tE+QQ489Yc0ynMm3oNMrohDI/TaCeazx8GAHPMM7VLduDA8D4A5wkZ6PuEvlJu4w==
dependencies:
"@babel/runtime" "^7.28.4"
lodash "^4.17.21"
mjml-core "4.17.1"
mjml-core "4.18.0"
mjml-head-preview@4.17.1:
version "4.17.1"
resolved "https://registry.yarnpkg.com/mjml-head-preview/-/mjml-head-preview-4.17.1.tgz#63e52bae35b43bdc43da838a0c3e85f3131439ef"
integrity sha512-XL+8N9yrADJSw4gX9lvDcp31ghGy8WavenVO8UhxPyhLu/sMJ9lFXLbTB4z5JU1z4t/HPEp/GtgMGxAbr+QrQQ==
mjml-head-preview@4.18.0:
version "4.18.0"
resolved "https://registry.yarnpkg.com/mjml-head-preview/-/mjml-head-preview-4.18.0.tgz#6469215ee85c15ffb58801d5aea3a9f1db337d2c"
integrity sha512-2JvYqhbLyU/+Te6/1AXxzTNoHYCDYhXOVZP7wMvU4t7K34pXqyRUNO405atyHUY1MRafrl6RJ8cIx0x5vUX7PA==
dependencies:
"@babel/runtime" "^7.28.4"
lodash "^4.17.21"
mjml-core "4.17.1"
mjml-core "4.18.0"
mjml-head-style@4.17.1:
version "4.17.1"
resolved "https://registry.yarnpkg.com/mjml-head-style/-/mjml-head-style-4.17.1.tgz#29a1cb440ae36e0029af6cf1adc9eeb181ada09f"
integrity sha512-YTjtqZAG0hD0aYwk02/8hS1W+T4nDUhVCBFmcxL/aTSrRbJQew0dSVtCvqNpAsbIJCUg/mUxx6pKKzRPdN+FtA==
mjml-head-style@4.18.0:
version "4.18.0"
resolved "https://registry.yarnpkg.com/mjml-head-style/-/mjml-head-style-4.18.0.tgz#21746637547d8a2b9b1f9337349be4f1f95b3b20"
integrity sha512-nEwDHkAqY3Fm7QWeAZc/a7MakZpXh6THfrE8/AWrfpgzTHrD/wihNUc09ztNpr6z/K1+JWgQfSF2BRc+X3P46g==
dependencies:
"@babel/runtime" "^7.28.4"
lodash "^4.17.21"
mjml-core "4.17.1"
mjml-core "4.18.0"
mjml-head-title@4.17.1:
version "4.17.1"
resolved "https://registry.yarnpkg.com/mjml-head-title/-/mjml-head-title-4.17.1.tgz#def969e9ac58e975bc686fc95273ddc2d23925f1"
integrity sha512-cUO4b7tDuX1BLu6XYnPgG40o3pBUCkT+Yzu5DGsvRxvCWougJFN68ocF6zcc7OOanmLgBYlJevQKUyT6W5Rp0g==
mjml-head-title@4.18.0:
version "4.18.0"
resolved "https://registry.yarnpkg.com/mjml-head-title/-/mjml-head-title-4.18.0.tgz#82f43cf4ca30f31756ce6fc4c7b71aba14d59041"
integrity sha512-0Hm8o50rPMUQLSCOOa4D4pz9NajmCDccLvBYE4fwKdeUXjSJ6bwAYeMpveel8oNZMDUVJ4Hx+PskisEGHMHM2w==
dependencies:
"@babel/runtime" "^7.28.4"
lodash "^4.17.21"
mjml-core "4.17.1"
mjml-core "4.18.0"
mjml-head@4.17.1:
version "4.17.1"
resolved "https://registry.yarnpkg.com/mjml-head/-/mjml-head-4.17.1.tgz#516d6039e103424d05ec5b55202b79a2b9a440f4"
integrity sha512-+DBJ6UvkpYkKJGJKqo8luucDGbg9+rQZKytl/4VOGTE8bmbrKFixY3lkfmBrSkQ7/t6L4dDVSXywl6H91JsL+g==
mjml-head@4.18.0:
version "4.18.0"
resolved "https://registry.yarnpkg.com/mjml-head/-/mjml-head-4.18.0.tgz#93dd1194fea0f51f23454f24835e7b3428b40872"
integrity sha512-DS0adpIAsVMDIk2DOsHzjg+RNjQU0fF8jiVP9BmdRHVGrLPmpL9wIHZk2KvsKvZe7VaXXBijFt3DZ5/CQ/+D7Q==
dependencies:
"@babel/runtime" "^7.28.4"
lodash "^4.17.21"
mjml-core "4.17.1"
mjml-core "4.18.0"
mjml-hero@4.17.1:
version "4.17.1"
resolved "https://registry.yarnpkg.com/mjml-hero/-/mjml-hero-4.17.1.tgz#d4f7ad9e29cb11107843f68a906f9389acb6a230"
integrity sha512-WDmNVJ4+xHLrkYOrGrq23hUYDVG3iFSyk/vIC/KlcG5Kebu5vVWbe6n3ZEucatPuYn/EUVV1ofIJM6dnXXfkGQ==
mjml-hero@4.18.0:
version "4.18.0"
resolved "https://registry.yarnpkg.com/mjml-hero/-/mjml-hero-4.18.0.tgz#13f6cb1763c3cdbafbbf5b7b1cbd6e4d76e25f61"
integrity sha512-rujm0ROM4QGWw77vnl3NaVaCKXrT4xTSHeAnkHKiY5AuRf6HPTgEtutq5pdel/y6Q9GrmxvN3HRESum7tpJCJw==
dependencies:
"@babel/runtime" "^7.28.4"
lodash "^4.17.21"
mjml-core "4.17.1"
mjml-core "4.18.0"
mjml-image@4.17.1:
version "4.17.1"
resolved "https://registry.yarnpkg.com/mjml-image/-/mjml-image-4.17.1.tgz#9a427d719caf664b3a60b8f6cfb10e91dabdcb5d"
integrity sha512-ZIFXmP2Fb77vvX8SBQYbrAPPvkqx5GqJ7AqVWteQk4iz6nJf8GspZiotWyL4LvgZzf0B81aQCB11y7+RvAfVvw==
mjml-image@4.18.0:
version "4.18.0"
resolved "https://registry.yarnpkg.com/mjml-image/-/mjml-image-4.18.0.tgz#c909279b7742c8e75e2b146b8f1c565e1a0ad503"
integrity sha512-e09NkoYwvzMcTv7V6H5doWD6Te2E1y2EvOLQJoXKVdQpDwyBWGdfnZke0scJGdA58HLAB+0mLYogpLwmfLaP5Q==
dependencies:
"@babel/runtime" "^7.28.4"
lodash "^4.17.21"
mjml-core "4.17.1"
mjml-core "4.18.0"
mjml-migrate@4.17.1:
version "4.17.1"
resolved "https://registry.yarnpkg.com/mjml-migrate/-/mjml-migrate-4.17.1.tgz#d50dd85f5f964d2e860741e657da03078209eeb4"
integrity sha512-Rb66BdvuV8fGYdQJzvLK0naWGI8G9smzm1OJDjdhcCrQU3BfTW/BiTS9FP5G0W73kFJe//vlHCDZ3uBIr6REAA==
mjml-migrate@4.18.0:
version "4.18.0"
resolved "https://registry.yarnpkg.com/mjml-migrate/-/mjml-migrate-4.18.0.tgz#1b08c93a9358b45f5b607043872fad216ef0ff9e"
integrity sha512-qfNCgW9zhJIsbPyXFA5RT/WY4mlje3N0WhHHOsHc0nY89Q01DenyslUy9nLLGXwi4K5FHS58oCjwWbMhwDcj1w==
dependencies:
"@babel/runtime" "^7.28.4"
js-beautify "^1.6.14"
lodash "^4.17.21"
mjml-core "4.17.1"
mjml-parser-xml "4.17.1"
mjml-core "4.18.0"
mjml-parser-xml "4.18.0"
yargs "^17.7.2"
mjml-navbar@4.17.1:
version "4.17.1"
resolved "https://registry.yarnpkg.com/mjml-navbar/-/mjml-navbar-4.17.1.tgz#215e1dc8546dc9658af59770113ac0f9b3eae47e"
integrity sha512-SWtovALlb+tM2lu2stlsKItrM/Tc/YxWiCm+UtLuOvkBmouBX/vASufaFab3VPAq/pGJKF9nFGX2eWoJCGA4rA==
mjml-navbar@4.18.0:
version "4.18.0"
resolved "https://registry.yarnpkg.com/mjml-navbar/-/mjml-navbar-4.18.0.tgz#b367eac34f22aa05e5506c07db6a0273b6255c67"
integrity sha512-uho/MS2tfNAe+V9u2X7NoCco34MDbdp30ETA8009Qo1VCP/D8lZ+s69WGRPu6hvN/Y2pzBgZly++CMg3qFZqBQ==
dependencies:
"@babel/runtime" "^7.28.4"
lodash "^4.17.21"
mjml-core "4.17.1"
mjml-core "4.18.0"
mjml-parser-xml@4.17.1:
version "4.17.1"
resolved "https://registry.yarnpkg.com/mjml-parser-xml/-/mjml-parser-xml-4.17.1.tgz#7b497c20bf1bb343fe49e2c79b24aa5ae926a4a8"
integrity sha512-8cc1+cI1+ymeKmiaioZMaIzg8K9SmCErr0WOdS0n90pnt5eLqGQEh3RQJv7VoucO5aoJXgAnCSGeCstVXvZykg==
mjml-parser-xml@4.18.0:
version "4.18.0"
resolved "https://registry.yarnpkg.com/mjml-parser-xml/-/mjml-parser-xml-4.18.0.tgz#53f78f6d5710eee9fcf4bf110d19cd4b300d66bf"
integrity sha512-sHSsZg4afY1heThuJzxa1Kvfh/QzB7/9P5fFUHeVnnxb07ZTXnhXWA6YbobdND5/l9+5yjN5/UgqDZm3tIT4Uw==
dependencies:
"@babel/runtime" "^7.28.4"
detect-node "2.1.0"
htmlparser2 "^9.1.0"
lodash "^4.17.21"
mjml-preset-core@4.17.1:
version "4.17.1"
resolved "https://registry.yarnpkg.com/mjml-preset-core/-/mjml-preset-core-4.17.1.tgz#7826184b7ca57383e47597c1593e492a9a5b4102"
integrity sha512-cFfelKeRJNG+WZv+kGWjjHrQam5PiHH8JaC3vvjl1eEwLcR2nbaYArlnLTIzgG+M3+cBlIl0Ru3Say5ZqWAcxw==
mjml-preset-core@4.18.0:
version "4.18.0"
resolved "https://registry.yarnpkg.com/mjml-preset-core/-/mjml-preset-core-4.18.0.tgz#183b79cf51847e898c6e0c70601328665e85e2ef"
integrity sha512-x3l8vMVtsaqM/jauMeZIN7HFD2t5A28J4U0o4849yIlRxiWguLFV5l3BL8Byol+YLkoLuT9PjaZs9RYv+FGfeg==
dependencies:
"@babel/runtime" "^7.28.4"
mjml-accordion "4.17.1"
mjml-body "4.17.1"
mjml-button "4.17.1"
mjml-carousel "4.17.1"
mjml-column "4.17.1"
mjml-divider "4.17.1"
mjml-group "4.17.1"
mjml-head "4.17.1"
mjml-head-attributes "4.17.1"
mjml-head-breakpoint "4.17.1"
mjml-head-font "4.17.1"
mjml-head-html-attributes "4.17.1"
mjml-head-preview "4.17.1"
mjml-head-style "4.17.1"
mjml-head-title "4.17.1"
mjml-hero "4.17.1"
mjml-image "4.17.1"
mjml-navbar "4.17.1"
mjml-raw "4.17.1"
mjml-section "4.17.1"
mjml-social "4.17.1"
mjml-spacer "4.17.1"
mjml-table "4.17.1"
mjml-text "4.17.1"
mjml-wrapper "4.17.1"
mjml-accordion "4.18.0"
mjml-body "4.18.0"
mjml-button "4.18.0"
mjml-carousel "4.18.0"
mjml-column "4.18.0"
mjml-divider "4.18.0"
mjml-group "4.18.0"
mjml-head "4.18.0"
mjml-head-attributes "4.18.0"
mjml-head-breakpoint "4.18.0"
mjml-head-font "4.18.0"
mjml-head-html-attributes "4.18.0"
mjml-head-preview "4.18.0"
mjml-head-style "4.18.0"
mjml-head-title "4.18.0"
mjml-hero "4.18.0"
mjml-image "4.18.0"
mjml-navbar "4.18.0"
mjml-raw "4.18.0"
mjml-section "4.18.0"
mjml-social "4.18.0"
mjml-spacer "4.18.0"
mjml-table "4.18.0"
mjml-text "4.18.0"
mjml-wrapper "4.18.0"
mjml-raw@4.17.1:
version "4.17.1"
resolved "https://registry.yarnpkg.com/mjml-raw/-/mjml-raw-4.17.1.tgz#0422013a4b8c6f35afdc624e56b47039c6c174f2"
integrity sha512-CnfgXh+c8u/jOuVjmv9N6Hxal5U4PPJFVY1JFRRJr/7Tcxl8aJUF03mBjqW9zAzoYO1bRcgyG3clchyEwwXQ8g==
mjml-raw@4.18.0:
version "4.18.0"
resolved "https://registry.yarnpkg.com/mjml-raw/-/mjml-raw-4.18.0.tgz#8683b716c592a12560175697407e5130373cf8ac"
integrity sha512-F/kViAwXm3ccPP52kw++/mHQbcYbYYxC8JH15TZxH8GLVZkX5CGKgcBrHhDK7WoIlfEIsVRZ6IZdlHjH8vgyxw==
dependencies:
"@babel/runtime" "^7.28.4"
lodash "^4.17.21"
mjml-core "4.17.1"
mjml-core "4.18.0"
mjml-section@4.17.1:
version "4.17.1"
resolved "https://registry.yarnpkg.com/mjml-section/-/mjml-section-4.17.1.tgz#10339354719e7e2c02911e56510811fb5bf9fa5b"
integrity sha512-YrkvcBgJw2NBnPirjuVU4AoqwySZzOovm5sfryID9I59EmmG+lbBJOnv/v/5wXQSlw2a4n1+VX2sCUcH5/O5sA==
mjml-section@4.18.0:
version "4.18.0"
resolved "https://registry.yarnpkg.com/mjml-section/-/mjml-section-4.18.0.tgz#ca4257bdd83c6202b03ace84f7a6246f453f9669"
integrity sha512-bB8My9zvIEkTOxej+TrjEeaeRT0lsypGeRADtdrRZXeqUClkkuCnCXlsNKSLGT8ZRqjUqWRc5z8ubDOvGk2+Gg==
dependencies:
"@babel/runtime" "^7.28.4"
lodash "^4.17.21"
mjml-core "4.17.1"
mjml-core "4.18.0"
mjml-social@4.17.1:
version "4.17.1"
resolved "https://registry.yarnpkg.com/mjml-social/-/mjml-social-4.17.1.tgz#52e9f28799a1992ae291b1b7000b7c2b58cb23be"
integrity sha512-Agp6CHJn7SwD+cckCxibZ/32luTzAiDJDlKH0SjQ+9NvSoGskkhii3yOqtYnJ+t3NmQkxpRkXOnUN4GEbupghA==
mjml-social@4.18.0:
version "4.18.0"
resolved "https://registry.yarnpkg.com/mjml-social/-/mjml-social-4.18.0.tgz#c9eeb1116f77120431fa27e583e3513852e0c487"
integrity sha512-iAQc9g59L6L3VHDd55BxeIvk/zHkxflxmvuyYyOOvpmmKAvUBC//ULfpxiiM4yupofsThqFfrO+wc8d4kTRkbQ==
dependencies:
"@babel/runtime" "^7.28.4"
lodash "^4.17.21"
mjml-core "4.17.1"
mjml-core "4.18.0"
mjml-spacer@4.17.1:
version "4.17.1"
resolved "https://registry.yarnpkg.com/mjml-spacer/-/mjml-spacer-4.17.1.tgz#5a81281872f3f2556c1828bf24e8df475ac71463"
integrity sha512-TxXDosuRzuoQNdceG47TKy+NWbwIGZmVDV/4XRtkcPHEvlsHpIIzn2+zzj+xrA6qh5Z+zlXL+x8ZpWMqrUoKfQ==
mjml-spacer@4.18.0:
version "4.18.0"
resolved "https://registry.yarnpkg.com/mjml-spacer/-/mjml-spacer-4.18.0.tgz#ab55416f943ca1b3e515bba6203d81b88eab8337"
integrity sha512-FK/0f5IBiONgaRpwNBs7G8EbLdAbmYqcIfHR8O8tP4LipAChLQKHO9vX3vrRMGLBZZNTESLObcFSVWmA40Mfpw==
dependencies:
"@babel/runtime" "^7.28.4"
lodash "^4.17.21"
mjml-core "4.17.1"
mjml-core "4.18.0"
mjml-table@4.17.1:
version "4.17.1"
resolved "https://registry.yarnpkg.com/mjml-table/-/mjml-table-4.17.1.tgz#0955b75ff86eb80a511cbcd7a37befca8c41101c"
integrity sha512-AcAcsNrpzTOsNc0X0i0+5+iNNGEnYjwn9qodF/413yuWDSH9p7SL8vFuI3Snmgv9s1dR+BKDiF8uPt4XTOMlzA==
mjml-table@4.18.0:
version "4.18.0"
resolved "https://registry.yarnpkg.com/mjml-table/-/mjml-table-4.18.0.tgz#3c57741f3aff14428921157d63912709aa005adc"
integrity sha512-vJysCPUL3CHcsQDAFpW+skzBtY0RYsmMBYswI4WX0B05GLKlOjXqpYOwcmAupWeGoBVL5r/t28ynu2PqnOlN3w==
dependencies:
"@babel/runtime" "^7.28.4"
lodash "^4.17.21"
mjml-core "4.17.1"
mjml-core "4.18.0"
mjml-text@4.17.1:
version "4.17.1"
resolved "https://registry.yarnpkg.com/mjml-text/-/mjml-text-4.17.1.tgz#77e1598c1e4d98c10490d242c9928ec3aa6e3663"
integrity sha512-pOrz8tRU3hReKd+K69dJmiVndC0+gB5IfVKIK3fdvYMb9laZBAstkXW0j5wn/0Af4FZSlJkDRLM7Ylxbh1+fqQ==
mjml-text@4.18.0:
version "4.18.0"
resolved "https://registry.yarnpkg.com/mjml-text/-/mjml-text-4.18.0.tgz#f4855611975643cef33bf1b334e1734956994d8e"
integrity sha512-hBLmF3JgveUKktKQFWHqHAr7qr92j1CxAvq7mtpDUgiWgyPFzqRX8mUsFYgZ7DmRxG4UE+Kzpt8/YFd9+E98lw==
dependencies:
"@babel/runtime" "^7.28.4"
lodash "^4.17.21"
mjml-core "4.17.1"
mjml-core "4.18.0"
mjml-validator@4.17.1:
version "4.17.1"
resolved "https://registry.yarnpkg.com/mjml-validator/-/mjml-validator-4.17.1.tgz#d73fb08bc368763f6bf0898a88b6b8452573b2d2"
integrity sha512-0Au5L5fIfAzOJpQG4PkpFeV0mbzCgjCTu5XbG7pJX4Wup72TGYwrA6Aq2yAdlx17kFPWThSZxeB3Xpd3/kwqOg==
mjml-validator@4.18.0:
version "4.18.0"
resolved "https://registry.yarnpkg.com/mjml-validator/-/mjml-validator-4.18.0.tgz#6f5fb9804a6bb5910aa9cf0b64896fb6f9ae0132"
integrity sha512-JmpWAsNTUlAxJOz2zHYfF8Vod8OzM3Qp5JXtrVw5tivZQzq88ZfqVGuqsas51z0pp1/ilfD4lC17YGfGwKGyhA==
dependencies:
"@babel/runtime" "^7.28.4"
mjml-wrapper@4.17.1:
version "4.17.1"
resolved "https://registry.yarnpkg.com/mjml-wrapper/-/mjml-wrapper-4.17.1.tgz#8832cd7da08a32478189041b8b1c6538f204022a"
integrity sha512-c0bCgXCwffI4krnQYU0Zp8ifGkYMgE7a65NAWXlV3AWEfVmjDlhCcD8LBfZ8UfY8zR3Che8pnunowPZfwh0Nxg==
mjml-wrapper@4.18.0:
version "4.18.0"
resolved "https://registry.yarnpkg.com/mjml-wrapper/-/mjml-wrapper-4.18.0.tgz#73e62fa05f307c6b763233284e8f27a16c3c03cd"
integrity sha512-TZeOvLjIhXEK60rjWNiYhEYNlv5GKYahE+96ifcT5OGkWkRA0DsQDfp+6VI32OS5VxsfKq2h/UdERPlQijjpAQ==
dependencies:
"@babel/runtime" "^7.28.4"
lodash "^4.17.21"
mjml-core "4.17.1"
mjml-section "4.17.1"
mjml-core "4.18.0"
mjml-section "4.18.0"
mjml@4.17.1:
version "4.17.1"
resolved "https://registry.yarnpkg.com/mjml/-/mjml-4.17.1.tgz#fe77de5258f31b42532f601ccb10058a2d95bdee"
integrity sha512-aqy5EVZuwXIINl+d7vC1Fn+MzMfIU4qxCx2TUHnGJxYONrtNIgSQEDlgB2ns2oK8a8WgPuEJCZBYwRE+5ZFcng==
mjml@4.18.0:
version "4.18.0"
resolved "https://registry.yarnpkg.com/mjml/-/mjml-4.18.0.tgz#9e4b37d26c1ad689ca26f889048b884e47e26fff"
integrity sha512-rQM4aqFRrNvV1k733e8hJSopBjZvoSdBpRYzNTMAN+As0jqJsO5eN0wTT2IFtfe4PREzzu5b06RkPiUQdd0IIg==
dependencies:
"@babel/runtime" "^7.28.4"
mjml-cli "4.17.1"
mjml-core "4.17.1"
mjml-migrate "4.17.1"
mjml-preset-core "4.17.1"
mjml-validator "4.17.1"
mjml-cli "4.18.0"
mjml-core "4.18.0"
mjml-migrate "4.18.0"
mjml-preset-core "4.18.0"
mjml-validator "4.18.0"
no-case@^2.2.0:
version "2.3.2"