tinacms
Version:
[](https://github.com/tinacms/tinacms/blob/main/LICENSE) [](https://www.npmjs.com/package/tinacms) [![Bui
253 lines (252 loc) • 7.79 kB
JavaScript
import React from "react";
function useTina(props) {
const stringifiedQuery = JSON.stringify({
query: props.query,
variables: props.variables
});
const id = React.useMemo(
() => hashFromQuery(stringifiedQuery),
[stringifiedQuery]
);
const processedData = React.useMemo(() => {
const dataCopy = JSON.parse(JSON.stringify(props.data));
return addMetadata(id, dataCopy, []);
}, [props.data, id]);
const [data, setData] = React.useState(processedData);
const [isClient, setIsClient] = React.useState(false);
const [quickEditEnabled, setQuickEditEnabled] = React.useState(false);
const [isInTinaIframe, setIsInTinaIframe] = React.useState(false);
React.useEffect(() => {
setIsClient(true);
setData(processedData);
parent.postMessage({
type: "url-changed"
});
}, [id, processedData]);
React.useEffect(() => {
if (quickEditEnabled) {
let mouseDownHandler = function(e) {
const attributeNames = e.target.getAttributeNames();
const tinaAttribute = attributeNames.find(
(name) => name.startsWith("data-tina-field")
);
let fieldName;
if (tinaAttribute) {
e.preventDefault();
e.stopPropagation();
fieldName = e.target.getAttribute(tinaAttribute);
} else {
const ancestor = e.target.closest(
"[data-tina-field], [data-tina-field-overlay]"
);
if (ancestor) {
const attributeNames2 = ancestor.getAttributeNames();
const tinaAttribute2 = attributeNames2.find(
(name) => name.startsWith("data-tina-field")
);
if (tinaAttribute2) {
e.preventDefault();
e.stopPropagation();
fieldName = ancestor.getAttribute(tinaAttribute2);
}
}
}
if (fieldName) {
if (isInTinaIframe) {
parent.postMessage(
{ type: "field:selected", fieldName },
window.location.origin
);
}
}
};
const style = document.createElement("style");
style.type = "text/css";
style.textContent = `
[data-tina-field] {
outline: 2px dashed rgba(34,150,254,0.5);
transition: box-shadow ease-out 150ms;
}
[data-tina-field]:hover {
box-shadow: inset 100vi 100vh rgba(34,150,254,0.3);
outline: 2px solid rgba(34,150,254,1);
cursor: pointer;
}
[data-tina-field-overlay] {
outline: 2px dashed rgba(34,150,254,0.5);
position: relative;
}
[data-tina-field-overlay]:hover {
cursor: pointer;
outline: 2px solid rgba(34,150,254,1);
}
[data-tina-field-overlay]::after {
content: '';
position: absolute;
inset: 0;
z-index: 20;
transition: opacity ease-out 150ms;
background-color: rgba(34,150,254,0.3);
opacity: 0;
}
[data-tina-field-overlay]:hover::after {
opacity: 1;
}
`;
document.head.appendChild(style);
document.body.classList.add("__tina-quick-editing-enabled");
document.addEventListener("click", mouseDownHandler, true);
return () => {
document.removeEventListener("click", mouseDownHandler, true);
document.body.classList.remove("__tina-quick-editing-enabled");
style.remove();
};
}
}, [quickEditEnabled, isInTinaIframe]);
React.useEffect(() => {
if (props == null ? void 0 : props.experimental___selectFormByFormId) {
parent.postMessage({
type: "user-select-form",
formId: props.experimental___selectFormByFormId()
});
}
}, [id]);
React.useEffect(() => {
const { experimental___selectFormByFormId, ...rest } = props;
parent.postMessage({ type: "open", ...rest, id }, window.location.origin);
const handleMessage = (event) => {
if (event.data.type === "quickEditEnabled") {
setQuickEditEnabled(event.data.value);
}
if (event.data.id === id && event.data.type === "updateData") {
const rawData = event.data.data;
const newlyProcessedData = addMetadata(
id,
JSON.parse(JSON.stringify(rawData)),
[]
);
setData(newlyProcessedData);
setIsInTinaIframe(true);
const anyTinaField = document.querySelector("[data-tina-field]");
if (anyTinaField) {
parent.postMessage(
{ type: "quick-edit", value: true },
window.location.origin
);
} else {
parent.postMessage(
{ type: "quick-edit", value: false },
window.location.origin
);
}
}
};
window.addEventListener("message", handleMessage);
return () => {
window.removeEventListener("message", handleMessage);
parent.postMessage({ type: "close", id }, window.location.origin);
};
}, [id, setQuickEditEnabled]);
return { data, isClient };
}
function useEditState() {
const [edit, setEdit] = React.useState(false);
React.useEffect(() => {
if (typeof window !== "undefined") {
parent.postMessage({ type: "isEditMode" }, window.location.origin);
window.addEventListener("message", (event) => {
var _a;
if (((_a = event.data) == null ? void 0 : _a.type) === "tina:editMode") {
setEdit(true);
}
});
}
}, []);
return { edit };
}
const tinaField = (object, property, index) => {
const contentSource = object == null ? void 0 : object._content_source;
if (!contentSource) {
return "";
}
const { queryId, path } = contentSource;
if (!property) {
return `${queryId}---${path.join(".")}`;
}
const fullPath = typeof index === "number" ? [...path, property, index] : [...path, property];
return `${queryId}---${fullPath.join(".")}`;
};
const addMetadata = (id, obj, path = []) => {
if (obj === null) {
return obj;
}
if (isScalarOrUndefined(obj)) {
return obj;
}
if (obj instanceof String) {
return obj.valueOf();
}
if (Array.isArray(obj)) {
return obj.map(
(item, index) => addMetadata(id, item, [...path, index])
);
}
const transformedObj = {};
for (const [key, value] of Object.entries(obj)) {
const currentPath = [...path, key];
if ([
"__typename",
"_sys",
"_internalSys",
"_values",
"_internalValues",
"_content_source",
"_tina_metadata"
].includes(key)) {
transformedObj[key] = value;
} else {
transformedObj[key] = addMetadata(id, value, currentPath);
}
}
if (transformedObj && typeof transformedObj === "object" && "type" in transformedObj && transformedObj.type === "root") {
return transformedObj;
}
return { ...transformedObj, _content_source: { queryId: id, path } };
};
function isScalarOrUndefined(value) {
const type = typeof value;
if (type === "string")
return true;
if (type === "number")
return true;
if (type === "boolean")
return true;
if (type === "undefined")
return true;
if (value == null)
return true;
if (value instanceof String)
return true;
if (value instanceof Number)
return true;
if (value instanceof Boolean)
return true;
return false;
}
const hashFromQuery = (input) => {
let hash = 0;
for (let i = 0; i < input.length; i++) {
const char = input.charCodeAt(i);
hash = (hash << 5) - hash + char & 4294967295;
}
const nonNegativeHash = Math.abs(hash);
const alphanumericHash = nonNegativeHash.toString(36);
return alphanumericHash;
};
export {
addMetadata,
hashFromQuery,
tinaField,
useEditState,
useTina
};