Add image upload to easymde widget

This commit is contained in:
Antoine Bartuccio 2025-02-27 00:33:37 +01:00
parent 6ec79966b1
commit d65cabe4f3
5 changed files with 91 additions and 14 deletions

View File

@ -42,11 +42,11 @@ class MarkdownController(ControllerBase):
@api_controller("/upload")
class UploadController(ControllerBase):
@route.post("/images", response=UploadedFileSchema, permissions=[IsOldSubscriber])
def upload_assets(self, file: UploadedFile):
@route.post("/image", response=UploadedFileSchema, permissions=[IsOldSubscriber])
def upload_image(self, file: UploadedFile):
if file.content_type.split("/")[0] != "image":
return self.create_response(
message=f"{file.name} isn't a file image", status_code=400
message=f"{file.name} isn't a file image", status_code=415
)
def convert_image(file: UploadedFile) -> ContentFile:
@ -60,7 +60,7 @@ class UploadController(ControllerBase):
converted = convert_image(file)
except UnidentifiedImageError:
return self.create_response(
message=f"{file.name} can't be processed", status_code=400
message=f"{file.name} can't be processed", status_code=415
)
with transaction.atomic():

View File

@ -6,13 +6,43 @@ import { inheritHtmlElement, registerComponent } from "#core:utils/web-component
import type CodeMirror from "codemirror";
// biome-ignore lint/style/useNamingConvention: This is how they called their namespace
import EasyMDE from "easymde";
import { markdownRenderMarkdown } from "#openapi";
import { markdownRenderMarkdown, uploadUploadImage } from "#openapi";
const loadEasyMde = (textarea: HTMLTextAreaElement) => {
new EasyMDE({
const easymde = new EasyMDE({
element: textarea,
spellChecker: false,
autoDownloadFontAwesome: false,
uploadImage: true,
imagePathAbsolute: false,
imageUploadFunction: async (file, onSuccess, onError) => {
const response = await uploadUploadImage({
body: {
file: file,
},
});
if (response.response.status !== 200) {
onError(gettext("Invalid file"));
return;
}
onSuccess(response.data.href);
// Workaround function to add ! and image name to uploaded image
// Without this, you get [](url) instead of ![name](url)
let cursor = easymde.codemirror.getCursor();
easymde.codemirror.setSelection({ line: cursor.line, ch: cursor.ch - 1 });
easymde.codemirror.replaceSelection("!");
easymde.codemirror.setSelection({ line: cursor.line, ch: cursor.ch + 1 });
easymde.codemirror.replaceSelection(file.name.split(".").slice(0, -1).join("."));
// Move cursor at the end of the url and add a new line
cursor = easymde.codemirror.getCursor();
easymde.codemirror.setSelection({
line: cursor.line,
ch: cursor.ch + response.data.href.length + 3,
});
easymde.codemirror.replaceSelection("\n");
},
previewRender: (plainText: string, preview: MarkdownInput) => {
/* This is wrapped this way to allow time for Alpine to be loaded on the page */
return Alpine.debounce((plainText: string, preview: MarkdownInput) => {
@ -30,6 +60,14 @@ const loadEasyMde = (textarea: HTMLTextAreaElement) => {
}, 300)(plainText, preview);
},
forceSync: true, // Avoid validation error on generic create view
imageTexts: {
sbInit: gettext("Attach files by drag and dropping or pasting from clipboard."),
sbOnDragEnter: gettext("Drop image to upload it."),
sbOnDrop: gettext("Uploading image #images_names# …"),
sbProgress: gettext("Uploading #file_name#: #progress#%"),
sbOnUploaded: gettext("Uploaded #image_name#"),
sizeUnits: gettext(" B, KB, MB"),
},
toolbar: [
{
name: "heading-smaller",
@ -120,6 +158,12 @@ const loadEasyMde = (textarea: HTMLTextAreaElement) => {
className: "fa-regular fa-image",
title: gettext("Insert image"),
},
{
name: "upload-image",
action: EasyMDE.drawUploadedImage,
className: "fa-solid fa-file-arrow-up",
title: gettext("Upload image"),
},
{
name: "table",
action: EasyMDE.drawTable,

View File

@ -7,7 +7,7 @@
msgid ""
msgstr ""
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-02-25 16:10+0100\n"
"POT-Creation-Date: 2025-02-27 00:27+0100\n"
"PO-Revision-Date: 2024-09-17 11:54+0200\n"
"Last-Translator: Sli <antoine@bartuccio.fr>\n"
"Language-Team: AE info <ae.info@utbm.fr>\n"
@ -34,6 +34,7 @@ msgid "Delete"
msgstr "Supprimer"
#: com/static/bundled/com/components/moderation-alert-index.ts
#, javascript-format
msgid ""
"This event will take place every week for %s weeks. If you publish or delete "
"this event, it will also be published (or deleted) for the following weeks."
@ -54,6 +55,34 @@ msgstr "Vous devez taper %(number)s caractères de plus"
msgid "No results found"
msgstr "Aucun résultat trouvé"
#: core/static/bundled/core/components/easymde-index.ts
msgid "Invalid file"
msgstr "Fichier invalide"
#: core/static/bundled/core/components/easymde-index.ts
msgid "Attach files by drag and dropping or pasting from clipboard."
msgstr "Ajoutez des fichiez en glissant déposant ou collant depuis votre presse papier."
#: core/static/bundled/core/components/easymde-index.ts
msgid "Drop image to upload it."
msgstr "Glissez une image pour la téléverser."
#: core/static/bundled/core/components/easymde-index.ts
msgid "Uploading image #images_names# …"
msgstr "Téléversement de l'image #images_names# …"
#: core/static/bundled/core/components/easymde-index.ts
msgid "Uploading #file_name#: #progress#%"
msgstr "Téléversement de #file_name#: #progress#%"
#: core/static/bundled/core/components/easymde-index.ts
msgid "Uploaded #image_name#"
msgstr "#image_name# téléversé"
#: core/static/bundled/core/components/easymde-index.ts
msgid " B, KB, MB"
msgstr " B, KB, MB"
#: core/static/bundled/core/components/easymde-index.ts
msgid "Heading"
msgstr "Titre"
@ -106,6 +135,10 @@ msgstr "Insérer lien"
msgid "Insert image"
msgstr "Insérer image"
#: core/static/bundled/core/components/easymde-index.ts
msgid "Upload image"
msgstr "Téléverser une image"
#: core/static/bundled/core/components/easymde-index.ts
msgid "Insert table"
msgstr "Insérer tableau"

12
package-lock.json generated
View File

@ -26,7 +26,7 @@
"cytoscape-cxtmenu": "^3.5.0",
"cytoscape-klay": "^3.1.4",
"d3-force-3d": "^3.0.5",
"easymde": "^2.18.0",
"easymde": "^2.19.0",
"glob": "^11.0.0",
"htmx.org": "^2.0.3",
"jquery": "^3.7.1",
@ -3655,14 +3655,14 @@
"license": "MIT"
},
"node_modules/easymde": {
"version": "2.18.0",
"resolved": "https://registry.npmjs.org/easymde/-/easymde-2.18.0.tgz",
"integrity": "sha512-IxVVUxNWIoXLeqtBU4BLc+eS/ScYhT1Dcb6yF5Wchoj1iXAV+TIIDWx+NCaZhY7RcSHqDPKllbYq7nwGKILnoA==",
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/easymde/-/easymde-2.19.0.tgz",
"integrity": "sha512-4F1aNImqse+9xIjLh9ttfpOVenecjFPxUmKbl1tGp72Z+OyIqLZPE/SgNyy88c/xU0mOy0WC3+tfbZDQ5PDWhg==",
"license": "MIT",
"dependencies": {
"@types/codemirror": "^5.60.4",
"@types/codemirror": "^5.60.10",
"@types/marked": "^4.0.7",
"codemirror": "^5.63.1",
"codemirror": "^5.65.15",
"codemirror-spell-checker": "1.1.2",
"marked": "^4.1.0"
}

View File

@ -52,7 +52,7 @@
"cytoscape-cxtmenu": "^3.5.0",
"cytoscape-klay": "^3.1.4",
"d3-force-3d": "^3.0.5",
"easymde": "^2.18.0",
"easymde": "^2.19.0",
"glob": "^11.0.0",
"htmx.org": "^2.0.3",
"jquery": "^3.7.1",