225 lines
6.0 KiB
TypeScript
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();
|