Radish alpha
r
rad:z4D5UCArafTzTQpDZNQRuqswh3ury
Radicle desktop app
Radicle
Git
Infer file type for embeds for previews
Merged did:key:z6MkkfM3...sVz5 opened 1 year ago
9 files changed +91 -21 01416e8a 153331f2
modified Cargo.lock
@@ -2546,6 +2546,12 @@ dependencies = [

[[package]]
name = "infer"
+
version = "0.3.7"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "865e8a58ae8e24d2c4412c31344afa1d302a3740ad67528c10f50d6876cdcf55"
+

+
[[package]]
+
name = "infer"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc150e5ce2330295b8616ce0e3f53250e53af31759a9dbedad1621ba29151847"
@@ -2955,6 +2961,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"

[[package]]
+
name = "mime-infer"
+
version = "3.0.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "91caed19dd472bc88bcd063571df18153529d49301a1918f4cf37f42332bee2e"
+
dependencies = [
+
 "mime",
+
 "unicase",
+
]
+

+
[[package]]
name = "miniz_oxide"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4120,7 +4136,9 @@ dependencies = [
 "anyhow",
 "axum",
 "base64 0.22.1",
+
 "infer 0.3.7",
 "localtime",
+
 "mime-infer",
 "radicle",
 "radicle-surf",
 "serde",
@@ -5657,7 +5675,7 @@ dependencies = [
 "glob",
 "html5ever",
 "http",
-
 "infer",
+
 "infer 0.16.0",
 "json-patch",
 "kuchikiki",
 "log",
@@ -6358,6 +6376,12 @@ dependencies = [
]

[[package]]
+
name = "unicase"
+
version = "2.8.0"
+
source = "registry+https://github.com/rust-lang/crates.io-index"
+
checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df"
+

+
[[package]]
name = "unicode-id-start"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
modified crates/radicle-tauri/src/commands/cob.rs
@@ -22,9 +22,10 @@ pub mod patch;
pub async fn get_embed(
    ctx: tauri::State<'_, AppState>,
    rid: identity::RepoId,
+
    name: Option<String>,
    oid: git::Oid,
-
) -> Result<Vec<u8>, Error> {
-
    ctx.get_embed(rid, oid)
+
) -> Result<types::cobs::EmbedWithMimeType, Error> {
+
    ctx.get_embed(rid, name, oid)
}

#[tauri::command]
modified crates/radicle-types/Cargo.toml
@@ -7,6 +7,8 @@ edition = "2021"
anyhow = { version = "1.0.90" }
axum = { version = "0.7.5", default-features = false, features = ["json"] }
base64 = { version = "0.22.1" }
+
infer = { version = "0.3" }
+
mime-infer = { version = "3.0.0" }
radicle = { version = "0.14.0", features = ["test"] }
radicle-surf = { version = "0.22.1", features = ["serde"] }
serde = { version = "1.0.210", features = ["derive"] }
added crates/radicle-types/bindings/cob/Embed.ts
@@ -0,0 +1,3 @@
+
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+

+
export type Embed = { content: Array<number>; mimeType: string | null };
added crates/radicle-types/bindings/cob/EmbedWithMimeType.ts
@@ -0,0 +1,6 @@
+
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
+

+
export type EmbedWithMimeType = {
+
  content: Array<number>;
+
  mimeType: string | null;
+
};
modified crates/radicle-types/src/cobs.rs
@@ -30,6 +30,15 @@ impl Author {
    }
}

+
#[derive(Serialize, TS)]
+
#[serde(rename_all = "camelCase")]
+
#[ts(export)]
+
#[ts(export_to = "cob/")]
+
pub struct EmbedWithMimeType {
+
    pub content: Vec<u8>,
+
    pub mime_type: Option<String>,
+
}
+

#[derive(TS, Serialize)]
#[doc = "A type alias for the TS type `never`."]
#[ts(export)]
modified crates/radicle-types/src/traits/thread.rs
@@ -15,12 +15,31 @@ use crate::error::Error;
use crate::traits::Profile;

pub trait Thread: Profile {
-
    fn get_embed(&self, rid: identity::RepoId, oid: git::Oid) -> Result<Vec<u8>, Error> {
+
    fn get_embed(
+
        &self,
+
        rid: identity::RepoId,
+
        name: Option<String>,
+
        oid: git::Oid,
+
    ) -> Result<cobs::EmbedWithMimeType, Error> {
        let profile = self.profile();
        let repo = profile.storage.repository(rid)?;
        let blob = repo.blob(oid)?;
+
        let content = blob.content();
+
        let mime_type = match infer::get(content).map(|i| i.mime_type().to_string()) {
+
            Some(mime_type) => Some(mime_type),
+
            None if name.is_some() => {
+
                let filename = name.unwrap();
+
                mime_infer::from_path(&filename)
+
                    .first()
+
                    .map(|m| m.as_ref().to_string())
+
            }
+
            _ => None,
+
        };

-
        Ok::<_, Error>(blob.content().to_vec())
+
        Ok::<_, Error>(cobs::EmbedWithMimeType {
+
            content: content.to_vec(),
+
            mime_type,
+
        })
    }

    fn save_embed_to_disk(
modified crates/test-http-api/src/api.rs
@@ -244,14 +244,15 @@ async fn activity_handler(
#[derive(Serialize, Deserialize)]
struct EmbedBody {
    pub rid: identity::RepoId,
+
    pub name: Option<String>,
    pub oid: git::Oid,
}

async fn get_embeds_handler(
    State(ctx): State<Context>,
-
    Json(EmbedBody { rid, oid }): Json<EmbedBody>,
+
    Json(EmbedBody { rid, name, oid }): Json<EmbedBody>,
) -> impl IntoResponse {
-
    let embed = ctx.get_embed(rid, oid)?;
+
    let embed = ctx.get_embed(rid, name, oid)?;

    Ok::<_, Error>(Json(embed))
}
modified src/components/Markdown.svelte
@@ -1,4 +1,6 @@
<script lang="ts">
+
  import type { Embed } from "@bindings/cob/Embed";
+

  import dompurify from "dompurify";
  import matter from "@radicle/gray-matter";
  import { toDom } from "hast-util-to-dom";
@@ -68,7 +70,6 @@

        // If the markdown link is an oid embed
        if (href && isCommit(href)) {
-
          e.style.display = "block";
          e.onclick = event => {
            event.preventDefault();
            invoke("save_embed_to_disk", {
@@ -77,47 +78,51 @@
              name: e.innerText,
            }).catch(console.error);
          };
-
          void invoke<Uint8Array>("get_embed", {
+
          void invoke<Embed>("get_embed", {
            rid,
+
            name: e.innerText,
            oid: href,
          })
-
            .then(byteArray => {
-
              const buffer = Buffer.from(byteArray);
+
            .then(({ mimeType, content }) => {
+
              const buffer = Buffer.from(content);
              const blob = new Blob([buffer]);
              const url = URL.createObjectURL(blob);
-
              const ext = e.innerHTML.split(".").at(-1);
              // Embed an img element below the link
-
              if (ext?.match(/(gif|jpe?g|tiff?|png|webp|bmp)/)) {
+
              if (mimeType?.startsWith("image")) {
                const element = document.createElement("img");
                element.setAttribute("src", url);
+
                element.style.display = "block";
+
                e.style.display = "block";
                e.insertAdjacentElement("afterend", element);
                // Embed an iframe to display pdf correctly element below the link
-
              } else if (ext?.match(/(pdf)/)) {
+
              } else if (mimeType?.startsWith("application")) {
                const element = document.createElement("embed");
                element.setAttribute("src", url);
-
                element.type = "application/pdf";
+
                element.type = mimeType;
                element.style.overflow = "scroll";
                element.style.height = "40rem";
                element.style.overscrollBehavior = "contain";
+
                e.style.display = "block";
                e.insertAdjacentElement("afterend", element);
-
              } else if (ext?.match(/(mp4|mov)/)) {
+
              } else if (mimeType?.startsWith("video")) {
                const element = document.createElement("video");
                const node = document.createElement("source");
                node.src = url;
                element.controls = true;
-
                node.type = `video/mp4`;
+
                node.type = mimeType;
                element.style.width = "100%";
+
                e.style.display = "block";
                element.appendChild(node);
                e.insertAdjacentElement("afterend", element);
-
              } else if (ext?.match(/(mp3)/)) {
+
              } else if (mimeType?.startsWith("audio")) {
                const element = document.createElement("audio");
+
                element.style.display = "block";
                element.src = url;
                element.controls = true;
+
                e.style.display = "block";
                e.insertAdjacentElement("afterend", element);
              } else {
-
                console.warn(
-
                  `Not able to provide a preview for ${e.innerHTML}`,
-
                );
+
                console.warn(`Not able to provide a preview for this file.`);
              }
            })
            .catch(console.error);