Files
contentstack-imagevault/rte/main.tsx
2024-10-25 07:53:58 +02:00

237 lines
6.4 KiB
TypeScript

import ContentstackSDK from "@contentstack/app-sdk"
import React from "react"
import { ImageElement } from "~/components/ImageElement"
import { Icon } from "@contentstack/venus-components"
import { openImageVault } from "~/utils/imagevault"
import type {
ContentstackEmbeddedData,
ContentstackPluginDefinition,
ExtractedContentstackEmbeddedData,
} from "~/types/contentstack"
import type { Lang } from "~/types/lang"
function findThisPlugin(ext: ContentstackPluginDefinition) {
return ext.type === "rte_plugin" && /imagevault/i.test(ext.title)
}
async function loadScript(url: string) {
return new Promise((resolve, reject) => {
if (!document) {
throw new Error("Run in browser only")
}
const head = document.head || document.getElementsByTagName("head")[0]
const script = document.createElement("script")
script.type = "text/javascript"
script.async = true
script.src = url
script.onload = function () {
this.onerror = this.onload = null
resolve(window.ImageVault)
}
script.onerror = function () {
this.onerror = this.onload = null
reject(`Failed to load ${this.src}`)
}
head.appendChild(script)
})
}
function extractContentstackEmbeddedData(
jwtLike: string
): ExtractedContentstackEmbeddedData | null {
try {
const base64str = jwtLike.replace(/-/g, "+").replace(/_/g, "/")
const jsonStr = decodeURIComponent(
window
.atob(base64str)
.split("")
.map(function (c) {
return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2)
})
.join("")
)
const json = JSON.parse(jsonStr)
json.exports = JSON.parse(json.exports)
json.props.value = JSON.parse(json.props.value)
if (IS_DEV) {
console.log(`Contentstack Embedded Data`, json)
}
const {
entryMetadata,
utilis: {
content_type: { schema },
extensions,
},
requestProps: {
stack: { api_key },
branch,
},
}: ContentstackEmbeddedData = json.props.value
const titleField = schema.find((f) => f.uid === "title")
// We force this value with ! because we know it exists.
// Otherwise this code would not run.
const plugin = extensions.find(findThisPlugin)!
return {
stack: {
apiKey: api_key,
},
branch,
contentType: {
uid: entryMetadata.contentTypeUid,
},
entry: {
locale: entryMetadata.locale,
title:
titleField && titleField.data_type === "text"
? titleField.value
: "Untitled",
uid: entryMetadata.entryUid,
},
plugin,
}
} catch (e) {
console.log(`Unable to parse JWT like: ${jwtLike}`)
}
return null
}
let ivloaded = false
function loadIV(plugin: ContentstackPluginDefinition) {
if (plugin.src) {
const url = new URL(plugin.src)
url.pathname = "/scripts/imagevault-insert-media/insertmediawindow.min.js"
if (IS_DEV) {
console.log(`Loading script: ${url.toString()}`)
}
loadScript(url.toString())
ivloaded = true
}
}
async function init() {
try {
const sdk = await ContentstackSDK.init()
const extensionObj = sdk?.location
const RTE = extensionObj?.RTEPlugin
let embeddedData: ExtractedContentstackEmbeddedData | null = null
const jwtLike = window.name.split("__").find((str) => str.startsWith("ey"))
if (jwtLike) {
embeddedData = extractContentstackEmbeddedData(jwtLike)
if (embeddedData?.plugin) {
loadIV(embeddedData.plugin)
}
}
if (!RTE) return
const ImageVault = RTE("ImageVault", (rte) => {
if (rte) {
if (!ivloaded) {
// Loading failed via embedded data above, try again with data inside RTE
// @ts-expect-error: incorrect typings, requestProps is available at runtime
const extensions = rte._adv.editor.requestProps
.extensions as ContentstackPluginDefinition[]
const plugin = extensions.find(findThisPlugin)
if (plugin) {
loadIV(plugin)
}
}
}
return {
title: "Choose assets from ImageVault",
icon: <Icon icon="Image" version="v2" height={24} width={24} />,
render: (props) => {
if (rte) {
return (
<ImageElement element={props.element} rte={rte}>
{props.children}
</ImageElement>
)
} else {
console.error("No instance of RTE found")
return (
<p>An unexpected error occured, see console for more info.</p>
)
}
},
display: ["toolbar"],
elementType: ["void"],
}
})
ImageVault.on("exec", async (rte) => {
if (rte) {
const savedSelection = rte?.selection?.get() ?? undefined
const appConfig = await rte.getConfig()
// This is the configuration for this instance of the plugin.
// You edit this in the content types settings RTE plugin section.
// @ts-expect-error: Added at runtime
const pluginConfig = await rte.getFieldConfig()
const config = {
...appConfig,
...pluginConfig,
}
openImageVault({
config,
entryData: {
//TODO: Add support for branches
branch: embeddedData ? embeddedData.contentType.uid : "main",
contentTypeUid: embeddedData
? embeddedData.contentType.uid
: "Unknown",
locale: embeddedData
? embeddedData.entry.locale
: ("unknown" as Lang),
stackApiKey: embeddedData ? embeddedData.stack.apiKey : "unknown",
title: embeddedData ? embeddedData.entry.title : "Untitled",
uid: embeddedData ? embeddedData.entry.uid : "unknown",
},
onSuccess(result) {
rte.insertNode(
{
// @ts-expect-error: incorrect typings
type: "ImageVault",
attrs: {
...result,
FocalPoint: result.FocalPoint || { x: 50, y: 50 },
},
uid: crypto.randomUUID(),
children: [{ text: "" }],
},
{ at: savedSelection }
)
},
})
}
})
return {
ImageVault,
}
} catch (e) {
console.error({ e })
}
}
export default init()