", "body_type": "html"}
-
- response = client.post(
- f"/api/v1.0/templates/{template.id!s}/generate-document/",
- data,
- format="json",
- )
-
- assert response.status_code == 200
- assert response.headers["content-type"] == "application/pdf"
-
-
-def test_api_templates_generate_document_type_markdown():
- """Generate pdf document with the body type markdown."""
- user = factories.UserFactory()
-
- client = APIClient()
- client.force_login(user)
-
- template = factories.TemplateFactory(is_public=True)
- data = {"body": "# Test markdown body", "body_type": "markdown"}
-
- response = client.post(
- f"/api/v1.0/templates/{template.id!s}/generate-document/",
- data,
- format="json",
- )
-
- assert response.status_code == 200
- assert response.headers["content-type"] == "application/pdf"
-
-
-def test_api_templates_generate_document_type_unknown():
- """Generate pdf document with the body type unknown."""
- user = factories.UserFactory()
-
- client = APIClient()
- client.force_login(user)
-
- template = factories.TemplateFactory(is_public=True)
- data = {"body": "# Test markdown body", "body_type": "unknown"}
-
- response = client.post(
- f"/api/v1.0/templates/{template.id!s}/generate-document/",
- data,
- format="json",
- )
-
- assert response.status_code == 400
- assert response.json() == {
- "body_type": [
- '"unknown" is not a valid choice.',
- ]
- }
-
-
-def test_api_templates_generate_document_export_docx():
- """Generate pdf document with the body type html."""
- user = factories.UserFactory()
-
- client = APIClient()
- client.force_login(user)
-
- template = factories.TemplateFactory(is_public=True)
- data = {"body": "
Test body
", "body_type": "html", "format": "docx"}
-
- response = client.post(
- f"/api/v1.0/templates/{template.id!s}/generate-document/",
- data,
- format="json",
- )
-
- assert response.status_code == 200
- assert (
- response.headers["content-type"]
- == "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
- )
diff --git a/src/backend/core/tests/test_models_templates.py b/src/backend/core/tests/test_models_templates.py
index 6e7cba2c1..95f8fbdea 100644
--- a/src/backend/core/tests/test_models_templates.py
+++ b/src/backend/core/tests/test_models_templates.py
@@ -2,10 +2,6 @@
Unit tests for the Template model
"""
-import os
-import time
-from unittest import mock
-
from django.contrib.auth.models import AnonymousUser
from django.core.exceptions import ValidationError
@@ -189,31 +185,3 @@ def test_models_templates_get_abilities_preset_role(django_assert_num_queries):
"partial_update": False,
"generate_document": True,
}
-
-
-def test_models_templates__generate_word():
- """Generate word document and assert no tmp files are left in /tmp folder."""
- template = factories.TemplateFactory()
- response = template.generate_word("
Test body
", {})
-
- assert response.status_code == 200
- assert len([f for f in os.listdir("/tmp") if f.startswith("docx_")]) == 0
-
-
-@mock.patch(
- "pypandoc.convert_text",
- side_effect=RuntimeError("Conversion failed"),
-)
-def test_models_templates__generate_word__raise_error(_mock_pypandoc):
- """
- Generate word document and assert no tmp files are left in /tmp folder
- even when the conversion fails.
- """
- template = factories.TemplateFactory()
-
- try:
- template.generate_word("
Test body
", {})
- except RuntimeError as e:
- assert str(e) == "Conversion failed"
- time.sleep(0.5)
- assert len([f for f in os.listdir("/tmp") if f.startswith("docx_")]) == 0
diff --git a/src/backend/pyproject.toml b/src/backend/pyproject.toml
index 2bc8b1a60..8d8d13d55 100644
--- a/src/backend/pyproject.toml
+++ b/src/backend/pyproject.toml
@@ -50,13 +50,11 @@ dependencies = [
"openai==1.58.1",
"psycopg[binary]==3.2.3",
"PyJWT==2.10.1",
- "pypandoc==1.14",
"python-frontmatter==1.1.0",
"python-magic==0.4.27",
"requests==2.32.3",
"sentry-sdk==2.19.2",
"url-normalize==1.4.3",
- "WeasyPrint>=60.2",
"whitenoise==6.8.2",
"mozilla-django-oidc==4.0.1",
]
From 3a27cc2c2504aa5f83c2b8671a666cd3d4de01ab Mon Sep 17 00:00:00 2001
From: Anthony LC
Date: Mon, 6 Jan 2025 16:31:29 +0100
Subject: [PATCH 3/7] =?UTF-8?q?=F0=9F=94=8A(changelog)=20add=20some=20chan?=
=?UTF-8?q?gelog=20entries?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Add some changelog entries that can be useful to
display in the release notes.
---
CHANGELOG.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index feefc5135..ba33a324f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -18,6 +18,8 @@ and this project adheres to
- ✨(frontend) new share modal ui #489
- ✨(frontend) add favorite feature #515
- ✨(frontend) export pdf docx front side #537
+- 📝(documentation) Documentation about self-hosted installation #530
+- ✨(helm) helm versioning #530
## Changed
From bf46ff3c79c3986f6662ae5e373695a8ccf642fb Mon Sep 17 00:00:00 2001
From: Anthony LC
Date: Tue, 14 Jan 2025 12:23:06 +0100
Subject: [PATCH 4/7] =?UTF-8?q?=F0=9F=8F=B7=EF=B8=8F(backend)=20add=20cont?=
=?UTF-8?q?ent-type=20to=20uploaded=20files?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
All the uploaded files had the content-type set
to `application/octet-stream`. It create issues
when the file is downloaded from the frontend
because the browser doesn't know how to handle
the file.
We improved the mimetype database and we
now determine the content-type of the file
and set it to the file object.
---
.github/workflows/impress.yml | 5 +--
CHANGELOG.md | 1 +
Dockerfile | 2 ++
src/backend/core/api/viewsets.py | 14 ++++++++-
.../test_api_documents_attachment_upload.py | 31 ++++++++++++++-----
5 files changed, 42 insertions(+), 11 deletions(-)
diff --git a/.github/workflows/impress.yml b/.github/workflows/impress.yml
index 896f07efb..385aa633c 100644
--- a/.github/workflows/impress.yml
+++ b/.github/workflows/impress.yml
@@ -206,10 +206,11 @@ jobs:
- name: Install development dependencies
run: pip install --user .[dev]
- - name: Install gettext (required to compile messages)
+ - name: Install gettext (required to compile messages) and MIME support
run: |
sudo apt-get update
- sudo apt-get install -y gettext pandoc
+ sudo apt-get install -y gettext pandoc shared-mime-info
+ sudo wget https://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types -O /etc/mime.types
- name: Generate a MO file from strings extracted from the project
run: python manage.py compilemessages
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ba33a324f..b8e55e73c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,7 @@ and this project adheres to
- 💄(frontend) add filtering to left panel #475
- ✨(frontend) new share modal ui #489
- ✨(frontend) add favorite feature #515
+- 🏷️(backend) add content-type to uploaded files #552
- ✨(frontend) export pdf docx front side #537
- 📝(documentation) Documentation about self-hosted installation #530
- ✨(helm) helm versioning #530
diff --git a/Dockerfile b/Dockerfile
index d5b56209a..501db0d53 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -75,6 +75,8 @@ RUN apk add \
pango \
shared-mime-info
+RUN wget https://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types -O /etc/mime.types
+
# Copy entrypoint
COPY ./docker/files/usr/local/bin/entrypoint /usr/local/bin/entrypoint
diff --git a/src/backend/core/api/viewsets.py b/src/backend/core/api/viewsets.py
index 56623520d..6066c7c29 100644
--- a/src/backend/core/api/viewsets.py
+++ b/src/backend/core/api/viewsets.py
@@ -2,6 +2,7 @@
# pylint: disable=too-many-lines
import logging
+import mimetypes
import re
import uuid
from urllib.parse import urlparse
@@ -604,8 +605,19 @@ def attachment_upload(self, request, *args, **kwargs):
extension = serializer.validated_data["expected_extension"]
key = f"{document.key_base}/{ATTACHMENTS_FOLDER:s}/{file_id!s}.{extension:s}"
+ # Determine the content type of the file
+ file = serializer.validated_data["file"]
+ content_type, _ = mimetypes.guess_type(file.name)
+
+ # Fallback if MIME type cannot be determined
+ if not content_type:
+ content_type = "application/octet-stream"
+
# Prepare metadata for storage
- extra_args = {"Metadata": {"owner": str(request.user.id)}}
+ extra_args = {
+ "Metadata": {"owner": str(request.user.id)},
+ "ContentType": content_type,
+ }
if serializer.validated_data["is_unsafe"]:
extra_args["Metadata"]["is_unsafe"] = "true"
diff --git a/src/backend/core/tests/documents/test_api_documents_attachment_upload.py b/src/backend/core/tests/documents/test_api_documents_attachment_upload.py
index 1288f8ca6..3b0574fa0 100644
--- a/src/backend/core/tests/documents/test_api_documents_attachment_upload.py
+++ b/src/backend/core/tests/documents/test_api_documents_attachment_upload.py
@@ -64,12 +64,22 @@ def test_api_documents_attachment_upload_anonymous_success():
assert response.status_code == 201
pattern = re.compile(rf"^/media/{document.id!s}/attachments/(.*)\.png")
- match = pattern.search(response.json()["file"])
+ file_path = response.json()["file"]
+ match = pattern.search(file_path)
file_id = match.group(1)
# Validate that file_id is a valid UUID
uuid.UUID(file_id)
+ # Now, check the metadata of the uploaded file
+ key = file_path.replace("/media", "")
+ file_head = default_storage.connection.meta.client.head_object(
+ Bucket=default_storage.bucket_name, Key=key
+ )
+
+ assert file_head["Metadata"] == {"owner": "None"}
+ assert file_head["ContentType"] == "image/png"
+
@pytest.mark.parametrize(
"reach, role",
@@ -206,6 +216,7 @@ def test_api_documents_attachment_upload_success(via, role, mock_user_teams):
Bucket=default_storage.bucket_name, Key=key
)
assert file_head["Metadata"] == {"owner": str(user.id)}
+ assert file_head["ContentType"] == "image/png"
def test_api_documents_attachment_upload_invalid(client):
@@ -247,16 +258,18 @@ def test_api_documents_attachment_upload_size_limit_exceeded(settings):
@pytest.mark.parametrize(
- "name,content,extension",
+ "name,content,extension,content_type",
[
- ("test.exe", b"text", "exe"),
- ("test", b"text", "txt"),
- ("test.aaaaaa", b"test", "txt"),
- ("test.txt", PIXEL, "txt"),
- ("test.py", b"#!/usr/bin/python", "py"),
+ ("test.exe", b"text", "exe", "application/x-msdownload"),
+ ("test", b"text", "txt", "application/octet-stream"),
+ ("test.aaaaaa", b"test", "txt", "application/octet-stream"),
+ ("test.txt", PIXEL, "txt", "text/plain"),
+ ("test.py", b"#!/usr/bin/python", "py", "text/x-python"),
],
)
-def test_api_documents_attachment_upload_fix_extension(name, content, extension):
+def test_api_documents_attachment_upload_fix_extension(
+ name, content, extension, content_type
+):
"""
A file with no extension or a wrong extension is accepted and the extension
is corrected in storage.
@@ -287,6 +300,7 @@ def test_api_documents_attachment_upload_fix_extension(name, content, extension)
Bucket=default_storage.bucket_name, Key=key
)
assert file_head["Metadata"] == {"owner": str(user.id), "is_unsafe": "true"}
+ assert file_head["ContentType"] == content_type
def test_api_documents_attachment_upload_empty_file():
@@ -335,3 +349,4 @@ def test_api_documents_attachment_upload_unsafe():
Bucket=default_storage.bucket_name, Key=key
)
assert file_head["Metadata"] == {"owner": str(user.id), "is_unsafe": "true"}
+ assert file_head["ContentType"] == "application/x-msdownload"
From 143c6c1c07cc6e24271daffe674e4ed0a618529c Mon Sep 17 00:00:00 2001
From: Anthony LC
Date: Tue, 14 Jan 2025 15:57:43 +0100
Subject: [PATCH 5/7] =?UTF-8?q?fixup!=20=E2=9C=A8(frontend)=20export=20pdf?=
=?UTF-8?q?=20docx=20front=20side?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../__tests__/app-impress/doc-export.spec.ts | 85 +++++++++-
src/frontend/apps/e2e/package.json | 4 +-
.../doc-header/components/ModalExport.tsx | 61 ++++++--
.../src/features/docs/doc-header/utils.ts | 146 ++----------------
4 files changed, 146 insertions(+), 150 deletions(-)
diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-export.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-export.spec.ts
index 2141cc5a4..eff9592a6 100644
--- a/src/frontend/apps/e2e/__tests__/app-impress/doc-export.spec.ts
+++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-export.spec.ts
@@ -1,3 +1,5 @@
+import path from 'path';
+
import { expect, test } from '@playwright/test';
import cs from 'convert-stream';
import pdf from 'pdf-parse';
@@ -40,10 +42,8 @@ test.describe('Doc Export', () => {
).toBeVisible();
await expect(page.getByRole('button', { name: 'Download' })).toBeVisible();
});
- test('it converts the doc to pdf with a template integrated', async ({
- page,
- browserName,
- }) => {
+
+ test('it exports the doc to pdf', async ({ page, browserName }) => {
const [randomDoc] = await createDoc(page, 'doc-editor', browserName, 1);
const downloadPromise = page.waitForEvent('download', (download) => {
@@ -76,10 +76,7 @@ test.describe('Doc Export', () => {
expect(pdfText).toContain('Hello World'); // This is the doc text
});
- test('it converts the doc to docx with a template integrated', async ({
- page,
- browserName,
- }) => {
+ test('it exports the doc to docx', async ({ page, browserName }) => {
const [randomDoc] = await createDoc(page, 'doc-editor', browserName, 1);
const downloadPromise = page.waitForEvent('download', (download) => {
@@ -109,4 +106,76 @@ test.describe('Doc Export', () => {
const download = await downloadPromise;
expect(download.suggestedFilename()).toBe(`${randomDoc}.docx`);
});
+
+ /**
+ * This test tell us that the export to pdf is working with images
+ * but it does not tell us if the images are beeing displayed correctly
+ * in the pdf.
+ *
+ * TODO: Check if the images are displayed correctly in the pdf
+ */
+ test('it exports the docs with images', async ({ page, browserName }) => {
+ const [randomDoc] = await createDoc(page, 'doc-editor', browserName, 1);
+
+ const fileChooserPromise = page.waitForEvent('filechooser');
+ const downloadPromise = page.waitForEvent('download', (download) => {
+ return download.suggestedFilename().includes(`${randomDoc}.pdf`);
+ });
+
+ await verifyDocName(page, randomDoc);
+
+ await page.locator('.ProseMirror.bn-editor').click();
+ await page.locator('.ProseMirror.bn-editor').fill('Hello World');
+
+ await page.keyboard.press('Enter');
+ await page.locator('.bn-block-outer').last().fill('/');
+ await page.getByText('Resizable image with caption').click();
+ await page.getByText('Upload image').click();
+
+ const fileChooser = await fileChooserPromise;
+ await fileChooser.setFiles(
+ path.join(__dirname, 'assets/logo-suite-numerique.png'),
+ );
+
+ const image = page.getByRole('img', { name: 'logo-suite-numerique.png' });
+
+ await expect(image).toBeVisible();
+
+ await page
+ .getByRole('button', {
+ name: 'download',
+ })
+ .click();
+
+ await page
+ .getByRole('combobox', {
+ name: 'Template',
+ })
+ .click();
+
+ await page
+ .getByRole('option', {
+ name: 'Demo Template',
+ })
+ .click({
+ delay: 100,
+ });
+
+ await new Promise((resolve) => setTimeout(resolve, 1000));
+
+ await page
+ .getByRole('button', {
+ name: 'Download',
+ })
+ .click();
+
+ const download = await downloadPromise;
+ expect(download.suggestedFilename()).toBe(`${randomDoc}.pdf`);
+
+ const pdfBuffer = await cs.toBuffer(await download.createReadStream());
+ const pdfExport = await pdf(pdfBuffer);
+ const pdfText = pdfExport.text;
+
+ expect(pdfText).toContain('Hello World');
+ });
});
diff --git a/src/frontend/apps/e2e/package.json b/src/frontend/apps/e2e/package.json
index 715230c0d..cefbb6bc8 100644
--- a/src/frontend/apps/e2e/package.json
+++ b/src/frontend/apps/e2e/package.json
@@ -13,12 +13,12 @@
},
"devDependencies": {
"@playwright/test": "1.49.1",
+ "@types/luxon": "3.4.2",
"@types/node": "*",
"@types/pdf-parse": "1.1.4",
"eslint-config-impress": "*",
- "typescript": "*",
"luxon": "3.5.0",
- "@types/luxon": "3.4.2"
+ "typescript": "*"
},
"dependencies": {
"convert-stream": "1.0.2",
diff --git a/src/frontend/apps/impress/src/features/docs/doc-header/components/ModalExport.tsx b/src/frontend/apps/impress/src/features/docs/doc-header/components/ModalExport.tsx
index 2dace32f0..4e72a6eb2 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-header/components/ModalExport.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-header/components/ModalExport.tsx
@@ -8,6 +8,7 @@ import {
} from '@blocknote/xl-pdf-exporter';
import {
Button,
+ Loader,
Modal,
ModalSize,
Select,
@@ -17,15 +18,16 @@ import {
import { pdf } from '@react-pdf/renderer';
import { useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
+import { css } from 'styled-components';
import { Box, Text } from '@/components';
import { useEditorStore } from '@/features/docs/doc-editor';
import { Doc } from '@/features/docs/doc-management';
import { TemplatesOrdering, useTemplates } from '../api/useTemplates';
-import { downloadFile } from '../utils';
+import { downloadFile, exportResolveFileUrl } from '../utils';
-export enum DocDownloadFormat {
+enum DocDownloadFormat {
PDF = 'pdf',
DOCX = 'docx',
}
@@ -43,6 +45,7 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => {
const { toast } = useToastProvider();
const { editor } = useEditorStore();
const [templateSelected, setTemplateSelected] = useState('');
+ const [isExporting, setIsExporting] = useState(false);
const [format, setFormat] = useState(
DocDownloadFormat.PDF,
);
@@ -66,15 +69,13 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => {
}, [t, templates?.pages]);
async function onSubmit() {
- if (!format) {
- return;
- }
-
if (!editor) {
- toast(t('No editor found'), VariantType.ERROR);
+ toast(t('The export failed'), VariantType.ERROR);
return;
}
+ setIsExporting(true);
+
const title = doc.title
.toLowerCase()
.normalize('NFD')
@@ -88,15 +89,36 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => {
exportDocument = [...blockTemplate, ...editor.document];
}
- let blobExport;
- if (format === 'pdf') {
- const exporter = new PDFExporter(editor.schema, pdfDefaultSchemaMappings);
+ let blobExport: Blob;
+ if (format === DocDownloadFormat.PDF) {
+ const defaultExporter = new PDFExporter(
+ editor.schema,
+ pdfDefaultSchemaMappings,
+ );
+
+ const exporter = new PDFExporter(
+ editor.schema,
+ pdfDefaultSchemaMappings,
+ {
+ resolveFileUrl: async (url) =>
+ exportResolveFileUrl(url, defaultExporter.options.resolveFileUrl),
+ },
+ );
const pdfDocument = await exporter.toReactPDFDocument(exportDocument);
blobExport = await pdf(pdfDocument).toBlob();
} else {
+ const defaultExporter = new DOCXExporter(
+ editor.schema,
+ docxDefaultSchemaMappings,
+ );
+
const exporter = new DOCXExporter(
editor.schema,
docxDefaultSchemaMappings,
+ {
+ resolveFileUrl: async (url) =>
+ exportResolveFileUrl(url, defaultExporter.options.resolveFileUrl),
+ },
);
blobExport = await exporter.toBlob(exportDocument);
@@ -111,6 +133,8 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => {
VariantType.SUCCESS,
);
+ setIsExporting(false);
+
onClose();
}
@@ -127,6 +151,7 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => {
color="secondary"
fullWidth
onClick={() => onClose()}
+ disabled={isExporting}
>
{t('Cancel')}
@@ -135,6 +160,7 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => {
color="primary"
fullWidth
onClick={() => void onSubmit()}
+ disabled={isExporting}
>
{t('Download')}
@@ -179,6 +205,21 @@ export const ModalExport = ({ onClose, doc }: ModalExportProps) => {
setFormat(options.target.value as DocDownloadFormat)
}
/>
+
+ {isExporting && (
+
+
+
+ )}
);
diff --git a/src/frontend/apps/impress/src/features/docs/doc-header/utils.ts b/src/frontend/apps/impress/src/features/docs/doc-header/utils.ts
index de15b30ef..d4a6165d7 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-header/utils.ts
+++ b/src/frontend/apps/impress/src/features/docs/doc-header/utils.ts
@@ -10,137 +10,23 @@ export function downloadFile(blob: Blob, filename: string) {
window.URL.revokeObjectURL(url);
}
-const convertToLi = (html: string) => {
- const parser = new DOMParser();
- const doc = parser.parseFromString(html, 'text/html');
- const divs = doc.querySelectorAll(
- 'div[data-content-type="bulletListItem"] , div[data-content-type="numberedListItem"]',
- );
-
- // Loop through each div and replace it with a li
- divs.forEach((div) => {
- // Create a new li element
- const li = document.createElement('li');
-
- // Copy the attributes from the div to the li
- for (let i = 0; i < div.attributes.length; i++) {
- li.setAttribute(div.attributes[i].name, div.attributes[i].value);
- }
-
- // Move all child elements of the div to the li
- while (div.firstChild) {
- li.appendChild(div.firstChild);
- }
-
- // Replace the div with the li in the DOM
- if (div.parentNode) {
- div.parentNode.replaceChild(li, div);
- }
- });
-
- /**
- * Convert the blocknote content to a simplified version to be
- * correctly parsed by our pdf and docx parser
- */
- const newContent: string[] = [];
- let currentList: HTMLUListElement | HTMLOListElement | null = null;
-
- // Iterate over all the children of the bn-block-group
- doc.body
- .querySelectorAll('.bn-block-group .bn-block-outer')
- .forEach((outerDiv) => {
- const blockContent = outerDiv.querySelector('.bn-block-content');
-
- if (blockContent) {
- const contentType = blockContent.getAttribute('data-content-type');
-
- if (contentType === 'bulletListItem') {
- // If a list is not started, start a new one
- if (!currentList) {
- currentList = document.createElement('ul');
- }
-
- currentList.appendChild(blockContent);
- } else if (contentType === 'numberedListItem') {
- // If a numbered list is not started, start a new one
- if (!currentList) {
- currentList = document.createElement('ol');
- }
-
- currentList.appendChild(blockContent);
- } else {
- /***
- * If there is a current list, add it to the new content
- * It means that the current list has ended
- */
- if (currentList) {
- newContent.push(currentList.outerHTML);
- }
-
- currentList = null;
- newContent.push(outerDiv.outerHTML);
- }
- } else {
- // In case there is no content-type, add the outerDiv as is
- newContent.push(outerDiv.outerHTML);
- }
+export const exportResolveFileUrl = async (
+ url: string,
+ resolveFileUrl: ((url: string) => Promise) | undefined,
+) => {
+ if (!url.includes(window.location.hostname) && resolveFileUrl) {
+ return resolveFileUrl(url);
+ }
+
+ try {
+ const response = await fetch(url, {
+ credentials: 'include',
});
- return newContent.join('');
-};
-
-const convertToImg = (html: string) => {
- const parser = new DOMParser();
- const doc = parser.parseFromString(html, 'text/html');
- const divs = doc.querySelectorAll('div[data-content-type="image"]');
-
- // Loop through each div and replace it with a img
- divs.forEach((div) => {
- const img = document.createElement('img');
-
- // Copy the attributes from the div to the img
- for (let i = 0; i < div.attributes.length; i++) {
- img.setAttribute(div.attributes[i].name, div.attributes[i].value);
-
- if (div.attributes[i].name === 'data-url') {
- img.setAttribute('src', div.attributes[i].value);
- }
-
- if (div.attributes[i].name === 'data-preview-width') {
- img.setAttribute('width', div.attributes[i].value);
- }
- }
-
- // Move all child elements of the div to the img
- while (div.firstChild) {
- img.appendChild(div.firstChild);
- }
-
- // Replace the div with the img in the DOM
- if (div.parentNode) {
- div.parentNode.replaceChild(img, div);
- }
- });
-
- return doc.body.innerHTML;
-};
-
-export const adaptBlockNoteHTML = (html: string) => {
- html = html.replaceAll('', ' ');
-
- // custom-style is used by pandoc to convert the style
- html = html.replaceAll(
- /data-text-alignment=\"([a-z]+)\"/g,
- 'custom-style="$1"',
- );
- html = html.replaceAll(/data-text-color=\"([a-z]+)\"/g, 'style="color: $1;"');
- html = html.replaceAll(
- /data-background-color=\"([a-z]+)\"/g,
- 'style="background-color: $1;"',
- );
-
- html = convertToLi(html);
- html = convertToImg(html);
+ return response.blob();
+ } catch {
+ console.error(`Failed to fetch image: ${url}`);
+ }
- return html;
+ return url;
};
From 06660c7be99243a74933c56f78b0966f995f72c7 Mon Sep 17 00:00:00 2001
From: Anthony LC
Date: Tue, 14 Jan 2025 15:58:01 +0100
Subject: [PATCH 6/7] =?UTF-8?q?fixup!=20=F0=9F=94=A5(backend)=20remove=20c?=
=?UTF-8?q?ode=20related=20to=20export=20pdf=20docx?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/backend/demo/data/template/code.txt | 12 ++----------
src/backend/demo/data/template/css.txt | 20 --------------------
src/backend/pyproject.toml | 1 -
3 files changed, 2 insertions(+), 31 deletions(-)
diff --git a/src/backend/demo/data/template/code.txt b/src/backend/demo/data/template/code.txt
index 0ab83f606..56f6736b9 100644
--- a/src/backend/demo/data/template/code.txt
+++ b/src/backend/demo/data/template/code.txt
@@ -1,10 +1,2 @@
-
-