Files
contentstack-imagevault/rte/main.tsx
2024-03-26 11:53:20 +01:00

225 lines
6.0 KiB
TypeScript

import React from 'react';
import ContentstackSDK from '@contentstack/app-sdk';
import { ImageElement } from '~/components/ImageElement';
import { Icon } from '@contentstack/venus-components';
import { openImageVault } from '~/utils/imagevault';
import type { Lang } from '~/types/lang';
import type {
ContentstackEmbeddedData,
ContentstackPluginDefinition,
ExtractedContentstackEmbeddedData,
} from '~/types/contentstack';
function findThisPlugin(ext: ContentstackPluginDefinition) {
return ext.type === 'rte_plugin' && ext.title === 'ImageVault';
}
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;
// @ts-expect-error: Added at runtime
const config = await rte.getConfig();
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,
uid: crypto.randomUUID(),
children: [{ text: "" }],
},
{ at: savedSelection }
);
},
});
}
});
return {
ImageVault,
};
} catch (e) {
console.error({ e });
}
}
export default init();