Add image upload capability
authorAdam Dullage <redacted>
Wed, 15 May 2024 18:40:33 +0000 (19:40 +0100)
committerAdam Dullage <redacted>
Wed, 15 May 2024 18:40:33 +0000 (19:40 +0100)
client/api.js
client/components/toastui/ToastEditor.vue
client/views/Note.vue
vite.config.js

index 2a0ce055dde3cfe60cc8a99151275984f264c2f3..16b4b5b70a308f27e237724d7c9e5d29d004e061 100644 (file)
@@ -128,3 +128,18 @@ export async function getTags() {
     return Promise.reject(response);
   }
 }
+
+export async function createAttachment(file) {
+  try {
+    const formData = new FormData();
+    formData.append("file", file);
+    const response = await api.post("/api/attachments", formData, {
+      headers: {
+        "Content-Type": "multipart/form-data",
+      },
+    });
+    return response.data;
+  } catch (response) {
+    return Promise.reject(response);
+  }
+}
index fe5e9df984cf2cb666939bd3b84fe532e7a5a0e3..4048ea59e940073ae824a86c788ecaf6204b419b 100644 (file)
@@ -14,6 +14,7 @@ const props = defineProps({
     type: String,
     default: "markdown",
   },
+  addImageBlobHook: Function,
 });
 
 const editorElement = ref();
@@ -25,6 +26,9 @@ onMounted(() => {
     el: editorElement.value,
     initialValue: props.initialValue,
     initialEditType: props.initialEditType,
+    hooks: props.addImageBlobHook
+      ? { addImageBlobHook: props.addImageBlobHook }
+      : {},
   });
 });
 
index 2a821212ea415ce0a78f61c557492efd9d544d62..136faa4446acb8e69a63b3c51f1fa5f96b8365de 100644 (file)
@@ -76,6 +76,7 @@
         ref="toastEditor"
         :initialValue="note.content"
         :initialEditType="loadDefaultEditorMode()"
+        :addImageBlobHook="addImageBlobHook"
       />
     </div>
   </LoadingIndicator>
@@ -96,6 +97,7 @@ import { useRouter } from "vue-router";
 
 import {
   apiErrorHandler,
+  createAttachment,
   createNote,
   deleteNote,
   getNote,
@@ -328,6 +330,59 @@ function noteSaveSuccess() {
   toast.add(getToastOptions("Success", "Note saved successfully."));
 }
 
+function addImageBlobHook(file, callback) {
+  const altTextInputValue = document.getElementById(
+    "toastuiAltTextInput",
+  )?.value;
+
+  // Upload the image then use the callback to insert the URL into the editor
+  postAttachment(file).then(function (data) {
+    if (data) {
+      // If the user has entered an alt text, use it. Otherwise, use the filename returned by the API.
+      const altText = altTextInputValue ? altTextInputValue : data.filename;
+      callback(data.url, altText);
+    }
+  });
+}
+
+function postAttachment(file) {
+  // Invalid Character Validation
+  if (reservedFilenameCharacters.test(file.name)) {
+    badFilenameToast("Title");
+    return;
+  }
+
+  // Uploading Toast
+  toast.add(getToastOptions("Uploading", "Uploading attachment..."));
+
+  // Upload the attachment
+  return createAttachment(file)
+    .then((data) => {
+      // Success Toast
+      toast.add(
+        getToastOptions("Success", "Attachment uploaded successfully."),
+      );
+      return data;
+    })
+    .catch((error) => {
+      if (error.response?.status === 409) {
+        // Note: The current implementation will append a datetime to the filename if it already exists.
+        // Error Toast
+        toast.add(
+          getToastOptions(
+            "Duplicate",
+            "An attachment with this filename already exists.",
+            true,
+          ),
+        );
+      } else if (error.response?.status == 413) {
+        entityTooLargeToast("attachment");
+      } else {
+        apiErrorHandler(error, toast);
+      }
+    });
+}
+
 watch(() => props.title, init);
 onMounted(init);
 </script>
index 99645b28553dde127f0bcb63bd1d9629e6404f03..34a0c6561c191142cbf2c70735e0ec8dd1c4087b 100644 (file)
@@ -13,6 +13,10 @@ export default defineConfig({
         target: devApiUrl,\r
         changeOrigin: true,\r
       },\r
+      "/attachments/": {\r
+        target: devApiUrl,\r
+        changeOrigin: true,\r
+      },\r
       "/docs": {\r
         target: devApiUrl,\r
         changeOrigin: true,\r
git clone https://git.99rst.org/PROJECT