@keystatic/core

Search for an npm package
import { c } from './react-compiler-runtime-0011f46e.js';
import { useOverlayTriggerState } from '@react-stately/overlays';
import React, { useState, useEffect, useContext, createContext, startTransition, useMemo, useSyncExternalStore, useCallback, useRef, cloneElement, forwardRef } from 'react';
import { ReactEditor, useSelected, useSlateStatic } from 'slate-react';
import * as s from 'superstruct';
import { ClearButton, ActionButton, ToggleButton, Button, ButtonGroup } from '@keystar/ui/button';
import { DialogContainer, DialogTrigger, Dialog } from '@keystar/ui/dialog';
import { FileTrigger } from '@keystar/ui/drag-and-drop';
import { Icon } from '@keystar/ui/icon';
import { imageIcon } from '@keystar/ui/icon/icons/imageIcon';
import { link2Icon } from '@keystar/ui/icon/icons/link2Icon';
import { link2OffIcon } from '@keystar/ui/icon/icons/link2OffIcon';
import { pencilIcon } from '@keystar/ui/icon/icons/pencilIcon';
import { trash2Icon } from '@keystar/ui/icon/icons/trash2Icon';
import { undo2Icon } from '@keystar/ui/icon/icons/undo2Icon';
import { Flex, VStack, HStack } from '@keystar/ui/layout';
import { TextLink } from '@keystar/ui/link';
import { NumberField } from '@keystar/ui/number-field';
import { ProgressCircle } from '@keystar/ui/progress';
import { Content } from '@keystar/ui/slots';
import { TextField, TextArea } from '@keystar/ui/text-field';
import { toastQueue } from '@keystar/ui/toast';
import { TooltipTrigger, Tooltip } from '@keystar/ui/tooltip';
import { Text, Heading } from '@keystar/ui/typography';
import { useId } from '@keystar/ui/utils';
import { breakpoints, css, tokenSchema, transition } from '@keystar/ui/style';
import { Transforms, Editor, Node, Element, Text as Text$1, Path } from 'slate';
import { useOverlay, useOverlayPosition } from '@react-aria/overlays';
import { useEffectEvent, useResizeObserver, useLayoutEffect, mergeProps } from '@react-aria/utils';
import { Overlay } from '@keystar/ui/overlays';
import { assert, assertNever, isDefined } from 'emery';
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
import { css as css$1 } from '@emotion/css';
import * as awarenessProtocol from 'y-protocols/awareness';
import { Awareness } from 'y-protocols/awareness';
import * as Y from 'yjs';
import { createIndexedDBProvider } from '@toeverything/y-indexeddb';
import * as bc from 'lib0/broadcastchannel';
import * as time from 'lib0/time';
import * as encoding from 'lib0/encoding';
import * as decoding from 'lib0/decoding';
import * as syncProtocol from 'y-protocols/sync';
import * as authProtocol from 'y-protocols/auth';
import * as mutex from 'lib0/mutex';
import * as math from 'lib0/math';
import weakMemoize from '@emotion/weak-memoize';
import { parse } from 'cookie';
import { gql } from '@ts-gql/tag/no-transform';
import { useQuery } from 'urql';
import { d as bytesToHex, c as base64UrlEncode } from './base64-3538d789.js';
import { useLocale } from '@react-aria/i18n';
import { get, createStore, set, del, clear, keys, delMany, setMany, entries } from 'idb-keyval';
import { LRUCache } from 'lru-cache';
import ReconnectingWebSocket from 'partysocket/ws';
import { createEncoder, writeVarUint, toUint8Array } from 'lib0/encoding.js';
import { Avatar } from '@keystar/ui/avatar';
const RouterContext = /*#__PURE__*/createContext(null);
function RouterProvider(props) {
const $ = c(17);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = () => window.location.href;
$[0] = t0;
} else {
t0 = $[0];
}
const [url, setUrl] = useState(t0);
let t1;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = function navigate(url_0, replace) {
const newUrl = new URL(url_0, window.location.href);
if (newUrl.origin !== window.location.origin || !newUrl.pathname.startsWith("/keystatic")) {
window.location.assign(newUrl);
return;
}
window.history[replace ? "replaceState" : "pushState"](null, "", newUrl);
startTransition(() => {
setUrl(newUrl.toString());
});
};
$[1] = t1;
} else {
t1 = $[1];
}
const navigate = t1;
let t2;
if ($[2] === Symbol.for("react.memo_cache_sentinel")) {
t2 = function replace(path) {
navigate(path, true);
};
$[2] = t2;
} else {
t2 = $[2];
}
const replace_0 = t2;
let t3;
if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
t3 = function push(path_0) {
navigate(path_0, false);
};
$[3] = t3;
} else {
t3 = $[3];
}
const push = t3;
let t4;
let parsedUrl;
if ($[4] !== url) {
parsedUrl = new URL(url);
const replaced = parsedUrl.pathname.replace(/^\/keystatic\/?/, "");
t4 = replaced === "" ? [] : replaced.split("/").map(decodeURIComponent);
$[4] = url;
$[5] = t4;
$[6] = parsedUrl;
} else {
t4 = $[5];
parsedUrl = $[6];
}
const params = t4;
const t5 = parsedUrl.pathname + parsedUrl.search;
let t6;
if ($[7] !== t5 || $[8] !== parsedUrl.pathname || $[9] !== parsedUrl.search || $[10] !== params) {
t6 = {
href: t5,
pathname: parsedUrl.pathname,
search: parsedUrl.search,
replace: replace_0,
push,
params
};
$[7] = t5;
$[8] = parsedUrl.pathname;
$[9] = parsedUrl.search;
$[10] = params;
$[11] = t6;
} else {
t6 = $[11];
}
const router = t6;
let t7;
let t8;
if ($[12] === Symbol.for("react.memo_cache_sentinel")) {
t7 = () => {
const handleNavigate = () => {
startTransition(() => {
setUrl(window.location.href);
});
};
window.addEventListener("popstate", handleNavigate);
return () => {
window.removeEventListener("popstate", handleNavigate);
};
};
t8 = [];
$[12] = t7;
$[13] = t8;
} else {
t7 = $[12];
t8 = $[13];
}
useEffect(t7, t8);
let t9;
if ($[14] !== router || $[15] !== props.children) {
t9 = /*#__PURE__*/jsx(RouterContext.Provider, {
value: router,
children: props.children
});
$[14] = router;
$[15] = props.children;
$[16] = t9;
} else {
t9 = $[16];
}
return t9;
}
function useRouter() {
const router = useContext(RouterContext);
if (router == null) {
throw new Error("useRouter must be used within a RouterProvider");
}
return router;
}
// these are intentionally more restrictive than the types allowed by strong and weak maps
const emptyCacheNode = Symbol('emptyCacheNode');
// weak keys should always come before strong keys in the arguments though that cannot be enforced with types
function memoize(func) {
const cacheNode = {
value: emptyCacheNode,
strong: undefined,
weak: undefined
};
return (...args) => {
let currentCacheNode = cacheNode;
for (const arg of args) {
if (typeof arg === 'string' || typeof arg === 'number') {
if (currentCacheNode.strong === undefined) {
currentCacheNode.strong = new Map();
}
if (!currentCacheNode.strong.has(arg)) {
currentCacheNode.strong.set(arg, {
value: emptyCacheNode,
strong: undefined,
weak: undefined
});
}
currentCacheNode = currentCacheNode.strong.get(arg);
continue;
}
if (typeof arg === 'object') {
if (currentCacheNode.weak === undefined) {
currentCacheNode.weak = new WeakMap();
}
if (!currentCacheNode.weak.has(arg)) {
currentCacheNode.weak.set(arg, {
value: emptyCacheNode,
strong: undefined,
weak: undefined
});
}
currentCacheNode = currentCacheNode.weak.get(arg);
continue;
}
}
if (currentCacheNode.value !== emptyCacheNode) {
return currentCacheNode.value;
}
const result = func(...args);
currentCacheNode.value = result;
return result;
};
}
function fixPath(path) {
return path.replace(/^\.?\/+/, '').replace(/\/*$/, '');
}
const collectionPath = /\/\*\*?(?:$|\/)/;
function getConfiguredCollectionPath(config, collection) {
var _collectionConfig$pat;
const collectionConfig = config.collections[collection];
const path = (_collectionConfig$pat = collectionConfig.path) !== null && _collectionConfig$pat !== void 0 ? _collectionConfig$pat : `${collection}/*/`;
if (!collectionPath.test(path)) {
throw new Error(`Collection path must end with /* or /** or include /*/ or /**/ but ${collection} has ${path}`);
}
return path;
}
function getCollectionPath(config, collection) {
const configuredPath = getConfiguredCollectionPath(config, collection);
const path = fixPath(configuredPath.replace(/\*\*?.*$/, ''));
return path;
}
function getCollectionFormat(config, collection) {
return getFormatInfo(config, 'collections', collection);
}
function getSingletonFormat(config, singleton) {
return getFormatInfo(config, 'singletons', singleton);
}
function getCollectionItemPath(config, collection, slug) {
const basePath = getCollectionPath(config, collection);
const suffix = getCollectionItemSlugSuffix(config, collection);
return `${basePath}/${slug}${suffix}`;
}
function getEntryDataFilepath(dir, formatInfo) {
return `${dir}${formatInfo.dataLocation === 'index' ? '/index' : ''}${getDataFileExtension(formatInfo)}`;
}
function getSlugGlobForCollection(config, collection) {
const collectionPath = getConfiguredCollectionPath(config, collection);
return collectionPath.includes('**') ? '**' : '*';
}
function getCollectionItemSlugSuffix(config, collection) {
const configuredPath = getConfiguredCollectionPath(config, collection);
const path = fixPath(configuredPath.replace(/^[^*]+\*\*?/, ''));
return path ? `/${path}` : '';
}
function getSingletonPath(config, singleton) {
var _singleton$path, _singleton$path2;
if ((_singleton$path = config.singletons[singleton].path) !== null && _singleton$path !== void 0 && _singleton$path.includes('*')) {
throw new Error(`Singleton paths cannot include * but ${singleton} has ${config.singletons[singleton].path}`);
}
return fixPath((_singleton$path2 = config.singletons[singleton].path) !== null && _singleton$path2 !== void 0 ? _singleton$path2 : singleton);
}
function getDataFileExtension(formatInfo) {
return formatInfo.contentField ? formatInfo.contentField.contentExtension : '.' + formatInfo.data;
}
const getFormatInfo = memoize(_getFormatInfo);
function _getFormatInfo(config, type, key) {
var _collectionOrSingleto, _format$data;
const collectionOrSingleton = type === 'collections' ? config.collections[key] : config.singletons[key];
const path = type === 'collections' ? getConfiguredCollectionPath(config, key) : (_collectionOrSingleto = collectionOrSingleton.path) !== null && _collectionOrSingleto !== void 0 ? _collectionOrSingleto : `${key}/`;
const dataLocation = path.endsWith('/') ? 'index' : 'outer';
const {
schema,
format = 'yaml'
} = collectionOrSingleton;
if (typeof format === 'string') {
return {
dataLocation,
contentField: undefined,
data: format
};
}
let contentField;
if (format.contentField) {
let field = {
kind: 'object',
fields: schema
};
let path = Array.isArray(format.contentField) ? format.contentField : [format.contentField];
let contentExtension;
try {
contentExtension = getContentExtension(path, field, () => JSON.stringify(format.contentField));
} catch (err) {
if (err instanceof ContentFieldLocationError) {
throw new Error(`${err.message} (${type}.${key})`);
}
throw err;
}
contentField = {
path,
contentExtension
};
}
return {
data: (_format$data = format.data) !== null && _format$data !== void 0 ? _format$data : 'yaml',
contentField,
dataLocation
};
}
class ContentFieldLocationError extends Error {
constructor(message) {
super(message);
}
}
function getContentExtension(path, schema, debugName) {
if (path.length === 0) {
if (schema.kind !== 'form' || schema.formKind !== 'content') {
throw new ContentFieldLocationError(`Content field for ${debugName()} is not a content field`);
}
return schema.contentExtension;
}
if (schema.kind === 'object') {
const field = schema.fields[path[0]];
if (!field) {
throw new ContentFieldLocationError(`Field ${debugName()} specified in contentField does not exist`);
}
return getContentExtension(path.slice(1), field, debugName);
}
if (schema.kind === 'conditional') {
if (path[0] !== 'value') {
throw new ContentFieldLocationError(`Conditional fields referenced in a contentField path must only reference the value field (${debugName()})`);
}
let contentExtension;
const innerPath = path.slice(1);
for (const value of Object.values(schema.values)) {
const foundContentExtension = getContentExtension(innerPath, value, debugName);
if (!contentExtension) {
contentExtension = foundContentExtension;
continue;
}
if (contentExtension !== foundContentExtension) {
throw new ContentFieldLocationError(`contentField ${debugName()} has conflicting content extensions`);
}
}
if (!contentExtension) {
throw new ContentFieldLocationError(`contentField ${debugName()} does not point to a content field`);
}
return contentExtension;
}
throw new ContentFieldLocationError(`Path specified in contentField ${debugName()} does not point to a content field`);
}
function getPathPrefix(storage) {
if (storage.kind === 'local' || !storage.pathPrefix) {
return undefined;
}
return fixPath(storage.pathPrefix) + '/';
}
async function sha1(content) {
const hashBuffer = await crypto.subtle.digest('SHA-1', content);
return bytesToHex(new Uint8Array(hashBuffer));
}
const textEncoder$1 = new TextEncoder();
const blobShaCache = new WeakMap();
async function blobSha(contents) {
const cached = blobShaCache.get(contents);
if (cached !== undefined) return cached;
const blobPrefix = textEncoder$1.encode('blob ' + contents.length + '\0');
const array = new Uint8Array(blobPrefix.byteLength + contents.byteLength);
array.set(blobPrefix, 0);
array.set(contents, blobPrefix.byteLength);
const digestPromise = sha1(array);
blobShaCache.set(contents, digestPromise);
digestPromise.then(digest => blobShaCache.set(contents, digest));
return digestPromise;
}
function getTreeNodeAtPath(root, path) {
const parts = path.split('/');
let node = root.get(parts[0]);
for (const part of parts.slice(1)) {
if (!node) return undefined;
if (!node.children) return undefined;
node = node.children.get(part);
}
return node;
}
function getNodeAtPath(tree, path) {
if (path === '') return tree;
let node = tree;
for (const part of path.split('/')) {
if (!node.has(part)) {
node.set(part, new Map());
}
const innerNode = node.get(part);
assert(innerNode instanceof Map, 'expected tree');
node = innerNode;
}
return node;
}
function getFilename(path) {
return path.replace(/.*\//, '');
}
function getDirname(path) {
if (!path.includes('/')) return '';
return path.replace(/\/[^/]+$/, '');
}
function toTreeChanges(changes) {
const changesRoot = new Map();
for (const deletion of changes.deletions) {
const parentTree = getNodeAtPath(changesRoot, getDirname(deletion));
parentTree.set(getFilename(deletion), 'delete');
}
for (const addition of changes.additions) {
const parentTree = getNodeAtPath(changesRoot, getDirname(addition.path));
parentTree.set(getFilename(addition.path), addition.contents);
}
return changesRoot;
}
const SPACE_CHAR_CODE = 32;
const space = new Uint8Array([SPACE_CHAR_CODE]);
const nullchar = new Uint8Array([0]);
const tree = textEncoder$1.encode('tree ');
// based on https://github.com/isomorphic-git/isomorphic-git/blob/c09dfa20ffe0ab9e6602e0fa172d72ba8994e443/src/models/GitTree.js#L108-L122
function treeSha(children) {
const entries = [...children].map(([name, node]) => ({
name,
sha: node.entry.sha,
mode: node.entry.mode
}));
entries.sort((a, b) => {
const aName = a.mode === '040000' ? a.name + '/' : a.name;
const bName = b.mode === '040000' ? b.name + '/' : b.name;
return aName === bName ? 0 : aName < bName ? -1 : 1;
});
const treeObject = entries.flatMap(entry => {
const mode = textEncoder$1.encode(entry.mode.replace(/^0/, ''));
const name = textEncoder$1.encode(entry.name);
const sha = hexToBytes(entry.sha);
return [mode, space, name, nullchar, sha];
});
return sha1(concatBytes([tree, textEncoder$1.encode(treeObject.reduce((sum, val) => sum + val.byteLength, 0).toString()), nullchar, ...treeObject]));
}
function concatBytes(byteArrays) {
const totalLength = byteArrays.reduce((sum, arr) => sum + arr.byteLength, 0);
const result = new Uint8Array(totalLength);
let offset = 0;
for (const arr of byteArrays) {
result.set(arr, offset);
offset += arr.byteLength;
}
return result;
}
function hexToBytes(str) {
const bytes = new Uint8Array(str.length / 2);
for (var i = 0; i < bytes.byteLength; i += 1) {
const start = i * 2;
bytes[i] = parseInt(str.slice(start, start + 2), 16);
}
return bytes;
}
async function createTreeNodeEntry(path, children) {
const sha = await treeSha(children);
return {
path,
mode: '040000',
type: 'tree',
sha
};
}
async function createBlobNodeEntry(path, contents) {
const sha = 'sha' in contents ? contents.sha : await blobSha(contents);
return {
path,
mode: '100644',
type: 'blob',
sha
};
}
async function updateTreeWithChanges(tree, changes) {
var _await$updateTree;
const newTree = (_await$updateTree = await updateTree(tree, toTreeChanges(changes), [])) !== null && _await$updateTree !== void 0 ? _await$updateTree : new Map();
return {
entries: treeToEntries(newTree),
sha: await treeSha(newTree !== null && newTree !== void 0 ? newTree : new Map())
};
}
function treeToEntries(tree) {
return [...tree.values()].flatMap(x => x.children ? [x.entry, ...treeToEntries(x.children)] : [x.entry]);
}
async function updateTree(tree, changedTree, path) {
const newTree = new Map(tree);
for (const [key, value] of changedTree) {
if (value === 'delete') {
newTree.delete(key);
}
if (value instanceof Map) {
var _newTree$get$children, _newTree$get;
const existingChildren = (_newTree$get$children = (_newTree$get = newTree.get(key)) === null || _newTree$get === void 0 ? void 0 : _newTree$get.children) !== null && _newTree$get$children !== void 0 ? _newTree$get$children : new Map();
const children = await updateTree(existingChildren, value, path.concat(key));
if (children === undefined) {
newTree.delete(key);
continue;
}
const entry = await createTreeNodeEntry(path.concat(key).join('/'), children);
newTree.set(key, {
entry,
children
});
}
if (value instanceof Uint8Array || typeof value === 'object' && 'sha' in value) {
const entry = await createBlobNodeEntry(path.concat(key).join('/'), value);
newTree.set(key, {
entry
});
}
}
if (newTree.size === 0) {
return undefined;
}
return newTree;
}
function treeEntriesToTreeNodes(entries) {
const root = new Map();
const getChildrenAtPath = parts => {
var _node;
if (parts.length === 0) {
return root;
}
let node = root.get(parts[0]);
for (const part of parts.slice(1)) {
if (!node) return undefined;
if (!node.children) return undefined;
node = node.children.get(part);
}
return (_node = node) === null || _node === void 0 ? void 0 : _node.children;
};
for (const entry of entries) {
const split = entry.path.split('/');
const children = getChildrenAtPath(split.slice(0, -1));
if (children) {
children.set(split[split.length - 1], {
entry,
children: entry.type === 'tree' ? new Map() : undefined
});
}
}
return root;
}
function weakMemoizeN(fn) {
const root = {
inner: new WeakMap()
};
return function (...args) {
let currentCacheNode = root;
for (const arg of args) {
const {
inner
} = currentCacheNode;
if (!inner.has(arg)) {
inner.set(arg, {
inner: new WeakMap()
});
}
currentCacheNode = inner.get(arg);
}
if (!currentCacheNode.hasOwnProperty('value')) {
currentCacheNode.value = fn(...args);
}
return currentCacheNode.value;
};
}
const LOADING = {
then() {}
};
function isThenable(value) {
return value && typeof value.then === 'function';
}
function suspendOnData(state) {
if (state.kind === 'error') {
throw state.error;
}
if (state.kind === 'loaded') {
return state.data;
}
throw state.promise;
}
function useData(func) {
const result_0 = useMemo(() => {
try {
const result = func();
// this avoids unhandled promise rejections
// we actually handle the result in an effect
if (isThenable(result)) {
result.then(() => {}, () => {});
}
return {
kind: 'result',
result
};
} catch (error) {
return {
kind: 'error',
error: error
};
}
}, [func]);
const [state, setState] = useState(() => {
return result_0.kind === 'result' ? isThenable(result_0.result) ? {
kind: 'loading',
promise: result_0.result
} : {
kind: 'loaded',
data: result_0.result
} : result_0;
});
let stateToReturn = state;
const resultState = useMemo(() => {
if (result_0.kind === 'error' && (state.kind !== 'error' || state.error !== result_0.error)) {
return {
kind: 'error',
error: result_0.error
};
}
if (result_0.kind === 'result' && !isThenable(result_0.result) && (state.kind !== 'loaded' || state.data !== result_0.result)) {
return {
kind: 'loaded',
data: result_0.result
};
}
}, [result_0, state]);
if (resultState && resultState !== state) {
stateToReturn = resultState;
setState(resultState);
}
useEffect(() => {
if (result_0.kind === 'result' && isThenable(result_0.result)) {
setState({
kind: 'loading',
promise: result_0.result
});
let isActive = true;
result_0.result.then(result_1 => {
if (!isActive) return;
setState({
kind: 'loaded',
data: result_1
});
}, error_0 => {
if (!isActive) return;
setState({
kind: 'error',
error: error_0
});
});
return () => {
isActive = false;
};
}
}, [result_0]);
return stateToReturn;
}
function mapDataState(state, func) {
if (state.kind === 'error' || state.kind === 'loading') {
return state;
}
return {
kind: 'loaded',
data: func(state.data)
};
}
function mergeDataStates(input) {
const entries = Object.entries(input);
for (const [, value] of entries) {
if (value.kind === 'error') {
return {
kind: 'error',
error: value.error
};
}
}
let promises = [];
for (const [, value] of entries) {
if (value.kind === 'loading') {
promises.push(value.promise);
}
}
if (promises.length) {
return {
kind: 'loading',
promise: promiseAllMemoized(...promises)
};
}
return {
kind: 'loaded',
data: Object.fromEntries(entries.map(([key, val]) => {
return [key, val.data];
}))
};
}
const promiseAllMemoized = weakMemoizeN((...args) => {
return Promise.all(args);
});
function collectDirectoriesUsedInSchemaInner(schema, directories, seenSchemas) {
if (seenSchemas.has(schema)) {
return;
}
seenSchemas.add(schema);
if (schema.kind === 'array') {
return collectDirectoriesUsedInSchemaInner(schema.element, directories, seenSchemas);
}
if (schema.kind === 'child') {
return;
}
if (schema.kind === 'form') {
if (schema.formKind === 'asset' && schema.directory !== undefined) {
directories.add(fixPath(schema.directory));
}
if ((schema.formKind === 'content' || schema.formKind === 'assets') && schema.directories !== undefined) {
for (const directory of schema.directories) {
directories.add(fixPath(directory));
}
}
return;
}
if (schema.kind === 'object') {
for (const field of Object.values(schema.fields)) {
collectDirectoriesUsedInSchemaInner(field, directories, seenSchemas);
}
return;
}
if (schema.kind === 'conditional') {
for (const innerSchema of Object.values(schema.values)) {
collectDirectoriesUsedInSchemaInner(innerSchema, directories, seenSchemas);
}
return;
}
assertNever(schema);
}
function collectDirectoriesUsedInSchema(schema) {
const directories = new Set();
collectDirectoriesUsedInSchemaInner(schema, directories, new Set());
return directories;
}
function getDirectoriesForTreeKey(schema, directory, slug, format) {
const directories = [fixPath(directory)];
if (format.dataLocation === 'outer') {
directories.push(fixPath(directory) + getDataFileExtension(format));
}
const toAdd = slug === undefined ? '' : `/${slug}`;
for (const directory of collectDirectoriesUsedInSchema(schema)) {
directories.push(directory + toAdd);
}
return directories;
}
function getTreeKey(directories, tree) {
return directories.map(d => {
var _getTreeNodeAtPath;
return (_getTreeNodeAtPath = getTreeNodeAtPath(tree, d)) === null || _getTreeNodeAtPath === void 0 ? void 0 : _getTreeNodeAtPath.entry.sha;
}).join('-');
}
var pkgJson = {
name: "@keystatic/core",
version: "0.5.42",
license: "MIT",
repository: {
type: "git",
url: "https://github.com/Thinkmill/keystatic/",
directory: "packages/keystatic"
},
type: "module",
exports: {
"./ui": {
types: "./dist/keystatic-core-ui.js",
node: {
"react-server": "./dist/keystatic-core-ui.node.react-server.js",
"default": "./dist/keystatic-core-ui.node.js"
},
"react-server": "./dist/keystatic-core-ui.react-server.js",
worker: "./dist/keystatic-core-ui.worker.js",
"default": "./dist/keystatic-core-ui.js"
},
".": {
types: "./dist/keystatic-core.js",
node: {
"react-server": "./dist/keystatic-core.node.react-server.js",
"default": "./dist/keystatic-core.node.js"
},
"react-server": "./dist/keystatic-core.react-server.js",
worker: "./dist/keystatic-core.worker.js",
"default": "./dist/keystatic-core.js"
},
"./api/utils": {
types: "./dist/keystatic-core-api-utils.js",
node: {
"react-server": "./dist/keystatic-core-api-utils.node.react-server.js",
"default": "./dist/keystatic-core-api-utils.node.js"
},
"react-server": "./dist/keystatic-core-api-utils.react-server.js",
worker: "./dist/keystatic-core-api-utils.worker.js",
"default": "./dist/keystatic-core-api-utils.js"
},
"./renderer": {
types: "./dist/keystatic-core-renderer.js",
node: {
"react-server": "./dist/keystatic-core-renderer.node.react-server.js",
"default": "./dist/keystatic-core-renderer.node.js"
},
"react-server": "./dist/keystatic-core-renderer.react-server.js",
worker: "./dist/keystatic-core-renderer.worker.js",
"default": "./dist/keystatic-core-renderer.js"
},
"./api/generic": {
types: "./dist/keystatic-core-api-generic.js",
node: {
"react-server": "./dist/keystatic-core-api-generic.node.react-server.js",
"default": "./dist/keystatic-core-api-generic.node.js"
},
"react-server": "./dist/keystatic-core-api-generic.react-server.js",
worker: "./dist/keystatic-core-api-generic.worker.js",
"default": "./dist/keystatic-core-api-generic.js"
},
"./reader": {
types: "./dist/keystatic-core-reader.js",
node: {
"react-server": "./dist/keystatic-core-reader.node.react-server.js",
"default": "./dist/keystatic-core-reader.node.js"
},
"react-server": "./dist/keystatic-core-reader.react-server.js",
worker: "./dist/keystatic-core-reader.worker.js",
"default": "./dist/keystatic-core-reader.js"
},
"./reader/github": {
types: "./dist/keystatic-core-reader-github.js",
node: {
"react-server": "./dist/keystatic-core-reader-github.node.react-server.js",
"default": "./dist/keystatic-core-reader-github.node.js"
},
"react-server": "./dist/keystatic-core-reader-github.react-server.js",
worker: "./dist/keystatic-core-reader-github.worker.js",
"default": "./dist/keystatic-core-reader-github.js"
},
"./content-components": {
types: "./dist/keystatic-core-content-components.js",
node: {
"react-server": "./dist/keystatic-core-content-components.node.react-server.js",
"default": "./dist/keystatic-core-content-components.node.js"
},
"react-server": "./dist/keystatic-core-content-components.react-server.js",
worker: "./dist/keystatic-core-content-components.worker.js",
"default": "./dist/keystatic-core-content-components.js"
},
"./component-blocks": {
types: "./dist/keystatic-core-component-blocks.js",
node: {
"react-server": "./dist/keystatic-core-component-blocks.node.react-server.js",
"default": "./dist/keystatic-core-component-blocks.node.js"
},
"react-server": "./dist/keystatic-core-component-blocks.react-server.js",
worker: "./dist/keystatic-core-component-blocks.worker.js",
"default": "./dist/keystatic-core-component-blocks.js"
},
"./package.json": "./package.json"
},
files: [
"dist"
],
scripts: {
setup: "ts-gql build && tsx scripts/l10n.cts && tsx scripts/build-prism.cts",
build: "pnpm run setup && next build",
dev: "next dev",
start: "next start"
},
dependencies: {
"@babel/runtime": "^7.18.3",
"@braintree/sanitize-url": "^6.0.2",
"@emotion/css": "^11.9.0",
"@emotion/weak-memoize": "^0.3.0",
"@floating-ui/react": "^0.24.0",
"@internationalized/string": "^3.2.3",
"@keystar/ui": "workspace:^",
"@markdoc/markdoc": "^0.4.0",
"@react-aria/focus": "^3.18.1",
"@react-aria/i18n": "^3.12.1",
"@react-aria/interactions": "^3.22.1",
"@react-aria/label": "^3.7.11",
"@react-aria/overlays": "^3.23.2",
"@react-aria/selection": "^3.19.3",
"@react-aria/utils": "^3.25.1",
"@react-aria/visually-hidden": "^3.8.15",
"@react-stately/collections": "^3.10.9",
"@react-stately/list": "^3.10.8",
"@react-stately/overlays": "^3.6.10",
"@react-stately/utils": "^3.10.3",
"@react-types/shared": "^3.24.1",
"@sindresorhus/slugify": "^1.1.2",
"@toeverything/y-indexeddb": "^0.10.0-canary.9",
"@ts-gql/tag": "^0.7.3",
"@types/react": "^18.2.8",
"@urql/core": "^5.0.4",
"@urql/exchange-auth": "^2.2.0",
"@urql/exchange-graphcache": "^7.1.2",
"@urql/exchange-persisted": "^4.3.0",
cookie: "^1.0.0",
emery: "^1.4.1",
"escape-string-regexp": "^4.0.0",
"fast-deep-equal": "^3.1.3",
graphql: "^16.6.0",
"idb-keyval": "^6.2.1",
ignore: "^5.2.4",
"is-hotkey": "^0.2.0",
"js-yaml": "^4.1.0",
lib0: "^0.2.88",
"lru-cache": "^10.2.0",
"match-sorter": "^6.3.1",
"mdast-util-from-markdown": "^2.0.0",
"mdast-util-gfm-autolink-literal": "^2.0.0",
"mdast-util-gfm-strikethrough": "^2.0.0",
"mdast-util-gfm-table": "^2.0.0",
"mdast-util-mdx": "^3.0.0",
"mdast-util-to-markdown": "^2.1.0",
"micromark-extension-gfm-autolink-literal": "^2.0.0",
"micromark-extension-gfm-strikethrough": "^2.0.0",
"micromark-extension-gfm-table": "^2.0.0",
"micromark-extension-mdxjs": "^3.0.0",
minimatch: "^9.0.3",
partysocket: "^0.0.22",
"prosemirror-commands": "^1.5.1",
"prosemirror-history": "^1.3.0",
"prosemirror-keymap": "^1.2.1",
"prosemirror-model": "^1.19.0",
"prosemirror-state": "^1.4.2",
"prosemirror-tables": "^1.3.4",
"prosemirror-transform": "^1.7.1",
"prosemirror-view": "^1.30.2",
"scroll-into-view-if-needed": "^3.0.3",
slate: "^0.91.4",
"slate-history": "^0.86.0",
"slate-react": "^0.91.9",
superstruct: "^1.0.4",
"unist-util-visit": "^5.0.0",
urql: "^4.1.0",
"y-prosemirror": "^1.2.2",
"y-protocols": "^1.0.6",
yjs: "^13.6.11"
},
devDependencies: {
"@internationalized/string-compiler": "^3.2.4",
"@jest/expect": "^29.7.0",
"@jest/globals": "^29.7.0",
"@testing-library/user-event": "^14.4.3",
"@ts-gql/compiler": "^0.16.7",
"@ts-gql/eslint-plugin": "^0.9.1",
"@ts-gql/next": "^17.0.1",
"@types/is-hotkey": "^0.1.7",
"@types/js-yaml": "^4.0.5",
"@types/mdast": "^4.0.3",
"@types/node": "16.11.13",
"@types/prismjs": "^1.26.0",
"@types/react-dom": "^18.0.11",
"@types/signal-exit": "^3.0.1",
eslint: "^8.18.0",
"fast-glob": "^3.2.12",
"jest-diff": "^29.0.1",
outdent: "^0.8.0",
"pretty-format": "^29.0.1",
prismjs: "^1.29.0",
react: "^18.2.0",
"react-dom": "^18.2.0",
"react-element-to-jsx-string": "^15.0.0",
"resize-observer-polyfill": "^1.5.1",
"signal-exit": "^3.0.7",
"slate-hyperscript": "^0.77.0",
tsx: "^4.8.2",
typescript: "^5.5.3"
},
peerDependencies: {
react: "^18.2.0",
"react-dom": "^18.2.0"
},
preconstruct: {
entrypoints: [
"index.ts",
"api/generic.ts",
"api/utils.ts",
"reader/index.ts",
"reader/github.ts",
"renderer.tsx",
"ui.tsx",
"component-blocks/index.tsx",
"content-components.ts"
]
},
"ts-gql": {
schema: "./github.graphql",
mode: "no-transform",
addTypename: false,
scalars: {
GitObjectID: "string"
}
},
imports: {
"#react-cache-in-react-server": {
"react-server": "./src/reader/react-server-cache.ts",
"default": "./src/reader/noop-cache.ts"
},
"#sha1": {
node: "./src/sha1/node.ts",
"default": "./src/sha1/webcrypto.ts"
},
"#webcrypto": {
node: "./src/api/webcrypto/node.ts",
"default": "./src/api/webcrypto/default.ts"
},
"#api-handler": {
node: "./src/api/api-node.ts",
"default": "./src/api/api-noop.ts"
},
"#ui": {
node: "./src/app/ui-empty.tsx",
worker: "./src/app/ui-empty.tsx",
"react-server": "./src/app/ui-empty.tsx",
"default": "./src/app/ui.tsx"
},
"#field-ui/*": {
node: "./src/form/fields/empty-field-ui.tsx",
worker: "./src/form/fields/empty-field-ui.tsx",
"react-server": "./src/form/fields/empty-field-ui.tsx",
"default": "./src/form/fields/*/ui.tsx"
},
"#component-block-primitives": {
node: "./src/form/fields/document/DocumentEditor/primitives/blank-for-react-server.tsx",
worker: "./src/form/fields/document/DocumentEditor/primitives/blank-for-react-server.tsx",
"react-server": "./src/form/fields/document/DocumentEditor/primitives/blank-for-react-server.tsx",
"default": "./src/form/fields/document/DocumentEditor/primitives/index.tsx"
},
"#cloud-image-preview": {
node: "./src/component-blocks/blank-for-react-server.tsx",
worker: "./src/component-blocks/blank-for-react-server.tsx",
"react-server": "./src/component-blocks/blank-for-react-server.tsx",
"default": "./src/component-blocks/cloud-image-preview.tsx"
},
"#markdoc": "./src/markdoc.js",
"#base64": "./src/base64.ts",
"#react-compiler-runtime": "./src/react-compiler-runtime.ts"
}
};
function object(fields, opts) {
return {
...opts,
kind: 'object',
fields
};
}
const units = {
seconds: 60,
minutes: 60,
hours: 24,
days: 7,
weeks: 4,
months: 12,
years: Infinity
};
function formatTimeAgo(targetDate, currentDate, formatter) {
let duration = (targetDate.getTime() - currentDate.getTime()) / 1000;
for (const [name, amount] of Object.entries(units)) {
if (Math.abs(duration) < amount) {
return formatter.format(Math.round(duration), name);
}
duration /= amount;
}
return 'unknown';
}
function RelativeTime(props) {
const $ = c(10);
const {
locale
} = useLocale();
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = () => new Date();
$[0] = t0;
} else {
t0 = $[0];
}
const [now] = useState(t0);
let t1;
let t2;
if ($[1] !== locale || $[2] !== props.date || $[3] !== now) {
const formatter = new Intl.RelativeTimeFormat(locale);
formatter.format(props.date.getTime() - now.getTime(), "second");
t2 = formatTimeAgo(props.date, now, formatter);
$[1] = locale;
$[2] = props.date;
$[3] = now;
$[4] = t2;
} else {
t2 = $[4];
}
t1 = t2;
const formatted = t1;
let t3;
if ($[5] !== props.date) {
t3 = props.date.toISOString();
$[5] = props.date;
$[6] = t3;
} else {
t3 = $[6];
}
let t4;
if ($[7] !== t3 || $[8] !== formatted) {
t4 = /*#__PURE__*/jsx("time", {
dateTime: t3,
children: formatted
});
$[7] = t3;
$[8] = formatted;
$[9] = t4;
} else {
t4 = $[9];
}
return t4;
}
function showDraftRestoredToast(savedAt, hasChangedSince) {
toastQueue.info( /*#__PURE__*/jsxs(Text, {
children: ["Restored draft from ", /*#__PURE__*/jsx(RelativeTime, {
date: savedAt
}), ".", ' ', hasChangedSince && /*#__PURE__*/jsx(Text, {
color: "accent",
children: "Other changes have been made to this entry since the draft. You may want to discard the draft changes."
})]
}), {
timeout: 8000
});
}
let store;
function getStore() {
if (!store) {
store = createStore('keystatic', 'items');
}
return store;
}
// the as anys are because the indexeddb types dont't accept readonly arrays
function setDraft(key, val) {
return set(key, val, getStore());
}
function delDraft(key) {
return del(key, getStore());
}
function getDraft(key) {
return get(key, getStore());
}
async function clearDrafts() {
await clear(getStore());
}
function getCollection(config, collection) {
return config.collections[collection];
}
function getBranchPrefix(config) {
return config.storage.kind !== 'local' ? config.storage.branchPrefix : undefined;
}
function isGitHubConfig(config) {
return config.storage.kind === 'github';
}
function isLocalConfig(config) {
return config.storage.kind === 'local';
}
function isCloudConfig(config) {
var _config$cloud;
if (config.storage.kind !== 'cloud') return false;
if (!((_config$cloud = config.cloud) !== null && _config$cloud !== void 0 && _config$cloud.project) || !config.cloud.project.includes('/')) {
throw new Error(`Keystatic is set to \`storage: { kind: 'cloud' }\` but \`cloud.project\` isn't set.
config({
storage: { kind: 'cloud' },
cloud: { project: 'team/project' },
})`);
}
return true;
}
function getSplitCloudProject(config) {
var _config$cloud2;
if (!((_config$cloud2 = config.cloud) !== null && _config$cloud2 !== void 0 && _config$cloud2.project)) return undefined;
const [team, project] = config.cloud.project.split('/');
return {
team,
project
};
}
function getRepoPath(config) {
return `${config.owner}/${config.name}`;
}
function getRepoUrl(config) {
return `https://github.com/${getRepoPath(config)}`;
}
function getSlugFromState(collectionConfig, state) {
const value = state[collectionConfig.slugField];
const field = collectionConfig.schema[collectionConfig.slugField];
if (field.kind !== 'form' || field.formKind !== 'slug') {
throw new Error(`slugField is not a slug field`);
}
return field.serializeWithSlug(value).slug;
}
function getEntriesInCollectionWithTreeKey(config, collection, rootTree) {
var _getTreeNodeAtPath$ch, _getTreeNodeAtPath;
const collectionConfig = config.collections[collection];
const schema = object(collectionConfig.schema);
const formatInfo = getCollectionFormat(config, collection);
const extension = getDataFileExtension(formatInfo);
const glob = getSlugGlobForCollection(config, collection);
const collectionPath = getCollectionPath(config, collection);
const directory = (_getTreeNodeAtPath$ch = (_getTreeNodeAtPath = getTreeNodeAtPath(rootTree, collectionPath)) === null || _getTreeNodeAtPath === void 0 ? void 0 : _getTreeNodeAtPath.children) !== null && _getTreeNodeAtPath$ch !== void 0 ? _getTreeNodeAtPath$ch : new Map();
const entries = [];
const directoriesUsedInSchema = [...collectDirectoriesUsedInSchema(schema)];
const suffix = getCollectionItemSlugSuffix(config, collection);
const possibleEntries = new Map(directory);
if (glob === '**') {
const handleDirectory = (dir, prefix) => {
for (const [key, entry] of dir) {
if (entry.children) {
possibleEntries.set(`${prefix}${key}`, entry);
handleDirectory(entry.children, `${prefix}${key}/`);
} else {
possibleEntries.set(`${prefix}${key}`, entry);
}
}
};
handleDirectory(directory, '');
}
for (const [key, entry] of possibleEntries) {
if (formatInfo.dataLocation === 'index') {
var _actualEntry$children;
const actualEntry = getTreeNodeAtPath(rootTree, getCollectionItemPath(config, collection, key));
if (!(actualEntry !== null && actualEntry !== void 0 && (_actualEntry$children = actualEntry.children) !== null && _actualEntry$children !== void 0 && _actualEntry$children.has('index' + extension))) continue;
entries.push({
key: getTreeKey([actualEntry.entry.path, ...directoriesUsedInSchema.map(x => `${x}/${key}`)], rootTree),
slug: key,
sha: actualEntry.children.get('index' + extension).entry.sha
});
} else {
if (suffix) {
const newEntry = getTreeNodeAtPath(rootTree, getCollectionItemPath(config, collection, key) + extension);
if (!newEntry || newEntry.children) continue;
entries.push({
key: getTreeKey([entry.entry.path, getCollectionItemPath(config, collection, key), ...directoriesUsedInSchema.map(x => `${x}/${key}`)], rootTree),
slug: key,
sha: newEntry.entry.sha
});
}
if (entry.children || !key.endsWith(extension)) continue;
const slug = key.slice(0, -extension.length);
entries.push({
key: getTreeKey([entry.entry.path, getCollectionItemPath(config, collection, slug), ...directoriesUsedInSchema.map(x => `${x}/${slug}`)], rootTree),
slug,
sha: entry.entry.sha
});
}
}
return entries;
}
const KEYSTATIC_CLOUD_API_URL = 'https://api.keystatic.cloud';
const PKG_VERSION = pkgJson.version;
const KEYSTATIC_CLOUD_HEADERS = {
'x-keystatic-version': PKG_VERSION
};
const textEncoder = new TextEncoder();
async function redirectToCloudAuth(from, config) {
var _config$cloud3;
if (!((_config$cloud3 = config.cloud) !== null && _config$cloud3 !== void 0 && _config$cloud3.project)) {
throw new Error('Not a cloud config');
}
const code_verifier = base64UrlEncode(crypto.getRandomValues(new Uint8Array(32)));
const code_challenge = base64UrlEncode(new Uint8Array(await crypto.subtle.digest('SHA-256', textEncoder.encode(code_verifier))));
const state = base64UrlEncode(crypto.getRandomValues(new Uint8Array(32)));
localStorage.setItem('keystatic-cloud-state', JSON.stringify({
state,
from,
code_verifier
}));
const url = new URL(`${KEYSTATIC_CLOUD_API_URL}/oauth/authorize`);
url.searchParams.set('state', state);
url.searchParams.set('client_id', config.cloud.project);
url.searchParams.set('redirect_uri', `${window.location.origin}/keystatic/cloud/oauth/callback`);
url.searchParams.set('response_type', 'code');
url.searchParams.set('code_challenge_method', 'S256');
url.searchParams.set('code_challenge', code_challenge);
url.searchParams.set('keystatic_version', pkgJson.version);
window.location.href = url.toString();
}
function useShowRestoredDraftMessage(draft, state, localTreeKey) {
const $ = c(8);
let t0;
if ($[0] !== draft || $[1] !== state || $[2] !== localTreeKey) {
t0 = () => {
if (draft && state === draft.state) {
showDraftRestoredToast(draft.savedAt, localTreeKey !== draft.treeKey);
}
};
$[0] = draft;
$[1] = state;
$[2] = localTreeKey;
$[3] = t0;
} else {
t0 = $[3];
}
const show = useEffectEvent(t0);
let t1;
let t2;
if ($[4] !== draft || $[5] !== show) {
t1 = () => {
if (draft) {
show();
}
};
t2 = [draft, show];
$[4] = draft;
$[5] = show;
$[6] = t1;
$[7] = t2;
} else {
t1 = $[6];
t2 = $[7];
}
useEffect(t1, t2);
}
const storedTokenSchema = s.object({
token: s.string(),
project: s.string(),
validUntil: s.coerce(s.date(), s.number(), val => new Date(val))
});
function getSyncAuth(config) {
if (typeof document === 'undefined') {
return null;
}
if (config.storage.kind === 'github') {
const cookies = parse(document.cookie);
const accessToken = cookies['keystatic-gh-access-token'];
if (!accessToken) {
return null;
}
return {
accessToken
};
}
if (config.storage.kind === 'cloud') {
return getCloudAuth(config);
}
return null;
}
function getCloudAuth(config) {
var _config$cloud;
if (!((_config$cloud = config.cloud) !== null && _config$cloud !== void 0 && _config$cloud.project)) return null;
const unparsedTokenData = localStorage.getItem('keystatic-cloud-access-token');
let tokenData;
try {
tokenData = storedTokenSchema.create(JSON.parse(unparsedTokenData));
} catch (err) {
return null;
}
if (!tokenData || tokenData.validUntil < new Date() || tokenData.project !== config.cloud.project) {
return null;
}
return {
accessToken: tokenData.token
};
}
let _refreshTokenPromise;
async function getAuth(config) {
const token = getSyncAuth(config);
if (config.storage.kind === 'github' && !token) {
if (!_refreshTokenPromise) {
_refreshTokenPromise = (async () => {
try {
const res = await fetch('/api/keystatic/github/refresh-token', {
method: 'POST'
});
if (res.status === 200) {
const cookies = parse(document.cookie);
const accessToken = cookies['keystatic-gh-access-token'];
if (accessToken) {
return {
accessToken
};
}
}
} catch {} finally {
_refreshTokenPromise = undefined;
}
return null;
})();
}
return _refreshTokenPromise;
}
return token;
}
const SidebarFooter_viewer = gql`
fragment SidebarFooter_viewer on User {
id
name
login
avatarUrl
databaseId
}
`;
const ViewerContext = /*#__PURE__*/createContext(undefined);
function useViewer() {
return useContext(ViewerContext);
}
function parseRepoConfig(repo) {
if (typeof repo === 'string') {
const [owner, name] = repo.split('/');
return {
owner,
name
};
}
return repo;
}
function serializeRepoConfig(repo) {
if (typeof repo === 'string') {
return repo;
}
return `${repo.owner}/${repo.name}`;
}
function assertValidRepoConfig(repo) {
if (typeof repo === 'string') {
if (!repo.includes('/')) {
throw new Error(`Invalid repo config: ${repo}. It must be in the form owner/name`);
}
}
if (typeof repo === 'object') {
if (!repo.owner && !repo.name) {
throw new Error(`Invalid repo config: owner and name are missing`);
}
if (!repo.owner) {
throw new Error(`Invalid repo config: owner is missing`);
}
if (!repo.name) {
throw new Error(`Invalid repo config: name is missing`);
}
}
}
function scopeEntriesWithPathPrefix(tree, config) {
const prefix = getPathPrefix(config.storage);
if (!prefix) return tree;
const newEntries = [];
for (const entry of tree.entries.values()) {
if (entry.path.startsWith(prefix)) {
newEntries.push({
...entry,
path: entry.path.slice(prefix.length)
});
}
}
return {
entries: new Map(newEntries.map(entry => [entry.path, entry])),
tree: treeEntriesToTreeNodes(newEntries)
};
}
let _treeStore;
function getTreeStore() {
if (!_treeStore) {
_treeStore = createStore('keystatic-trees', 'trees');
}
return _treeStore;
}
let _blobStore;
function getBlobStore() {
if (!_blobStore) {
_blobStore = createStore('keystatic-blobs', 'blobs');
}
return _blobStore;
}
function setBlobToPersistedCache(sha, val) {
return set(sha, val, getBlobStore());
}
async function getBlobFromPersistedCache(sha) {
const stored = await get(sha, getBlobStore());
if (stored instanceof Uint8Array) {
return stored;
}
}
let _storedTreeCache;
const treeSchema = s.array(s.object({
path: s.string(),
mode: s.string(),
sha: s.string()
}));
function getStoredTrees() {
if (_storedTreeCache) {
return _storedTreeCache;
}
const cache = new Map();
return entries(getTreeStore()).then(entries => {
for (const [sha, tree] of entries) {
if (typeof sha !== 'string') continue;
let parsed;
try {
parsed = treeSchema.create(tree);
} catch {
continue;
}
cache.set(sha, parsed);
}
_storedTreeCache = cache;
return cache;
});
}
function constructTreeFromStoredTrees(sha, trees, parentPath = '') {
const tree = new Map();
const storedTree = trees.get(sha);
if (!storedTree) {
return;
}
for (const entry of storedTree) {
const innerPath = (parentPath === '' ? '' : parentPath + '/') + entry.path;
if (entry.mode === '040000') {
const child = constructTreeFromStoredTrees(entry.sha, trees, innerPath);
if (child) {
tree.set(entry.path, child);
continue;
}
return;
}
tree.set(entry.path, {
entry: {
mode: entry.mode,
path: innerPath,
sha: entry.sha,
type: entry.mode === '120000' ? 'symlink' : 'blob'
}
});
}
return {
entry: {
mode: '040000',
path: parentPath,
sha,
type: 'tree'
},
children: tree
};
}
function getTreeFromPersistedCache(sha) {
const stored = getStoredTrees();
if (stored instanceof Map) {
return constructTreeFromStoredTrees(sha, stored);
}
return stored.then(stored => constructTreeFromStoredTrees(sha, stored));
}
async function garbageCollectGitObjects(roots) {
const treesToDelete = new Map();
const invalidTrees = [];
for (const [sha, tree] of await getStoredTrees()) {
if (typeof sha !== 'string') {
invalidTrees.push(sha);
continue;
}
let parsed;
try {
parsed = treeSchema.create(tree);
} catch {
invalidTrees.push(sha);
continue;
}
treesToDelete.set(sha, parsed);
}
const allBlobs = await keys(getBlobStore());
const blobsToDelete = new Set(allBlobs);
const queue = new Set(roots);
for (const sha of queue) {
if (blobsToDelete.has(sha)) {
blobsToDelete.delete(sha);
continue;
}
const tree = treesToDelete.get(sha);
if (tree) {
for (const entry of tree) {
queue.add(entry.sha);
}
treesToDelete.delete(sha);
continue;
}
}
const treeKeysToDelete = [...treesToDelete.keys(), ...invalidTrees];
await Promise.all([delMany([...blobsToDelete], getBlobStore()), delMany([...treesToDelete.keys(), ...invalidTrees], getTreeStore())]);
for (const key of treeKeysToDelete) {
var _storedTreeCache2;
(_storedTreeCache2 = _storedTreeCache) === null || _storedTreeCache2 === void 0 || _storedTreeCache2.delete(key);
}
}
function setTreeToPersistedCache(sha, children) {
const allTrees = [];
collectTrees(sha, children, allTrees);
return setMany(allTrees, getTreeStore());
}
function collectTrees(sha, children, allTrees) {
const entries = [];
for (const [path, entry] of children) {
entries.push({
path: path.replace(/.*\//, ''),
mode: entry.entry.mode,
sha: entry.entry.sha
});
if (entry.children) {
collectTrees(entry.entry.sha, entry.children, allTrees);
}
}
allTrees.push([sha, entries]);
}
async function clearObjectCache() {
await Promise.all([clear(getBlobStore()), clear(getTreeStore())]);
}
const messageSync$1 = 0;
const messageQueryAwareness = 3;
const messageAwareness$1 = 1;
const messageAuth$1 = 2;
const messageSubDocSync = 4;
const messageHandlers = [];
messageHandlers[messageSync$1] = (encoder, decoder, provider, emitSynced) => {
encoding.writeVarUint(encoder, messageSync$1);
const syncMessageType = syncProtocol.readSyncMessage(decoder, encoder, provider.doc, provider);
if (emitSynced && syncMessageType === syncProtocol.messageYjsSyncStep2 && !provider.synced) {
provider.synced = true;
}
};
messageHandlers[messageQueryAwareness] = (encoder, decoder, provider) => {
encoding.writeVarUint(encoder, messageAwareness$1);
encoding.writeVarUint8Array(encoder, awarenessProtocol.encodeAwarenessUpdate(provider.awareness, Array.from(provider.awareness.getStates().keys())));
};
messageHandlers[messageAwareness$1] = (encoder, decoder, provider) => {
awarenessProtocol.applyAwarenessUpdate(provider.awareness, decoding.readVarUint8Array(decoder), provider);
};
messageHandlers[messageAuth$1] = (encoder, decoder, provider) => {
authProtocol.readAuthMessage(decoder, provider.doc, permissionDeniedHandler);
};
messageHandlers[messageSubDocSync] = (encoder, decoder, provider, emitSynced) => {
const subDocID = decoding.readVarString(decoder);
encoding.writeVarUint(encoder, messageSync$1);
const subDoc = provider.getSubDoc(subDocID);
if (subDoc) {
const syncMessageType = syncProtocol.readSyncMessage(decoder, encoder, subDoc, provider);
if (emitSynced && syncMessageType === syncProtocol.messageYjsSyncStep2) {
subDoc.emit('sync', [true]);
}
}
};
const reconnectTimeoutBase = 1200;
const maxReconnectTimeout = 2500;
// @todo - this should depend on awareness.outdatedTime
const messageReconnectTimeout = 30000;
const permissionDeniedHandler = (provider, reason) => console.warn(`Permission denied to access ${provider.url}.\n${reason}`);
const readMessage = (provider, buf, emitSynced) => {
const decoder = decoding.createDecoder(buf);
const encoder = encoding.createEncoder();
const messageType = decoding.readVarUint(decoder);
const messageHandler = messageHandlers[messageType];
if (messageHandler) {
messageHandler(encoder, decoder, provider, emitSynced, messageType);
} else {
console.error('Unable to compute message');
}
return encoder;
};
const setupWS = (provider, WS) => {
if (provider.shouldConnect && provider.ws === null) {
const websocket = new WS(provider.url);
websocket.binaryType = 'arraybuffer';
provider.ws = websocket;
provider.wsconnecting = true;
provider.wsconnected = false;
provider.synced = false;
let authState = {
kind: 'authenticating',
queue: []
};
websocket.onmessage = event => {
provider.wsLastMessageReceived = time.getUnixTime();
const bytes = new Uint8Array(event.data);
if (authState.kind === 'authenticating') {
const decoder = decoding.createDecoder(bytes);
const messageType = decoding.readVarInt(decoder);
if (messageType === messageAuth$1) {
const authMessageType = decoding.readVarInt(decoder);
if (authMessageType === 2) {
const queue = authState.queue;
authState = {
kind: 'authed'
};
provider.onConnect(websocket);
for (const queued of queue) {
const encoder = readMessage(provider, queued, true);
if (encoding.length(encoder) > 1) {
websocket.send(encoding.toUint8Array(encoder));
}
}
return;
}
}
authState.queue.push(bytes);
} else {
const encoder = readMessage(provider, bytes, true);
if (encoding.length(encoder) > 1) {
websocket.send(encoding.toUint8Array(encoder));
}
}
};
websocket.onclose = () => {
provider.ws = null;
provider.wsconnecting = false;
if (provider.wsconnected) {
provider.wsconnected = false;
provider.synced = false;
// update awareness (all users except local left)
awarenessProtocol.removeAwarenessStates(provider.awareness, Array.from(provider.awareness.getStates().keys()).filter(client => client !== provider.doc.clientID), provider);
provider.onStatus({
status: 'disconnected'
});
} else {
provider.wsUnsuccessfulReconnects++;
}
// Start with no reconnect timeout and increase timeout by
// log10(wsUnsuccessfulReconnects).
// The idea is to increase reconnect timeout slowly and have no reconnect
// timeout at the beginning (log(1) = 0)
setTimeout(setupWS, math.min(math.log10(provider.wsUnsuccessfulReconnects + 1) * reconnectTimeoutBase, maxReconnectTimeout), provider);
};
websocket.onopen = async () => {
provider.wsLastMessageReceived = time.getUnixTime();
const encoder = encoding.createEncoder();
encoding.writeVarUint(encoder, messageAuth$1);
encoding.writeVarUint(encoder, 0);
encoding.writeVarString(encoder, await provider.authToken());
websocket.send(encoding.toUint8Array(encoder));
};
provider.onStatus({
status: 'connecting'
});
}
};
const broadcastMessage = (provider, buf) => {
if (provider.ws && provider.wsconnected) {
var _provider$ws;
(_provider$ws = provider.ws) === null || _provider$ws === void 0 || _provider$ws.send(buf);
}
if (provider.bcconnected) {
provider.mux(() => {
bc.publish(provider.bcChannel, buf);
});
}
};
class WebsocketProvider {
#WS;
#synced;
#resyncInterval;
#checkInterval;
constructor(opts) {
var _opts$WebSocketPolyfi, _opts$onStatus, _opts$onSynced;
this.bcChannel = opts.url;
this.url = opts.url;
this.doc = opts.doc;
this.#WS = (_opts$WebSocketPolyfi = opts.WebSocketPolyfill) !== null && _opts$WebSocketPolyfi !== void 0 ? _opts$WebSocketPolyfi : WebSocket;
this.awareness = opts.awareness;
this.wsconnected = false;
this.wsconnecting = false;
this.bcconnected = false;
this.wsUnsuccessfulReconnects = 0;
this.mux = mutex.createMutex();
this.#synced = false;
this.authToken = opts.authToken;
this.ws = null;
this.wsLastMessageReceived = 0;
this.onStatus = (_opts$onStatus = opts.onStatus) !== null && _opts$onStatus !== void 0 ? _opts$onStatus : () => {};
this.onSynced = (_opts$onSynced = opts.onSynced) !== null && _opts$onSynced !== void 0 ? _opts$onSynced : () => {};
this.shouldConnect = false;
this.subdocs = new Map();
this.#resyncInterval = null;
if (opts.resyncInterval !== undefined && opts.resyncInterval > 0) {
this.#resyncInterval = setInterval(() => {
if (this.ws) {
// resend sync step 1
const encoder = encoding.createEncoder();
encoding.writeVarUint(encoder, messageSync$1);
syncProtocol.writeSyncStep1(encoder, opts.doc);
this.ws.send(encoding.toUint8Array(encoder));
}
}, opts.resyncInterval);
}
this.doc.on('subdocs', ({
added,
removed,
loaded
}) => {
added.forEach(subdoc => {
this.subdocs.set(subdoc.guid, subdoc);
});
removed.forEach(subdoc => {
subdoc.off('update', this.#getSubDocUpdateHandler(subdoc));
this.subdocs.delete(subdoc.guid);
});
loaded.forEach(subdoc => {
this.waitForConnection(() => {
// always send sync step 1 when connected
const encoder = encoding.createEncoder();
encoding.writeVarUint(encoder, messageSubDocSync);
encoding.writeVarString(encoder, subdoc.guid);
syncProtocol.writeSyncStep1(encoder, subdoc);
this.send(encoding.toUint8Array(encoder), () => {
subdoc.on('update', this.#getSubDocUpdateHandler(subdoc));
});
}, 1000);
});
});
this.doc.on('update', this.#updateHandler);
if (typeof window !== 'undefined') {
window.addEventListener('beforeunload', this.#beforeUnloadHandler);
}
opts.awareness.on('update', this.#awarenessUpdateHandler);
this.#checkInterval = setInterval(() => {
if (this.wsconnected && messageReconnectTimeout < time.getUnixTime() - this.wsLastMessageReceived) {
var _this$ws;
// no message received in a long time - not even your own awareness
// updates (which are updated every 15 seconds)
(_this$ws = this.ws) === null || _this$ws === void 0 || _this$ws.close();
}
}, messageReconnectTimeout / 10);
}
onConnect(ws) {
this.wsconnecting = false;
this.wsconnected = true;
this.wsUnsuccessfulReconnects = 0;
this.onStatus({
status: 'connected'
});
// always send sync step 1 when connected
const encoder = encoding.createEncoder();
encoding.writeVarUint(encoder, messageSync$1);
syncProtocol.writeSyncStep1(encoder, this.doc);
ws.send(encoding.toUint8Array(encoder));
// broadcast local awareness state
if (this.awareness.getLocalState() !== null) {
const encoderAwarenessState = encoding.createEncoder();
encoding.writeVarUint(encoderAwarenessState, messageAwareness$1);
encoding.writeVarUint8Array(encoderAwarenessState, awarenessProtocol.encodeAwarenessUpdate(this.awareness, [this.doc.clientID]));
ws.send(encoding.toUint8Array(encoderAwarenessState));
}
}
#bcSubscriber = data => {
this.mux(() => {
const encoder = readMessage(this, new Uint8Array(data), false);
if (encoding.length(encoder) > 1) {
bc.publish(this.bcChannel, encoding.toUint8Array(encoder));
}
});
};
#beforeUnloadHandler = () => {
awarenessProtocol.removeAwarenessStates(this.awareness, [this.doc.clientID], 'window unload');
};
waitForConnection = (callback, interval) => {
const ws = this.ws;
if ((ws === null || ws === void 0 ? void 0 : ws.readyState) === 1) {
callback(ws);
} else {
setTimeout(() => {
this.waitForConnection(callback, interval);
}, interval);
}
};
#awarenessUpdateHandler = ({
added,
updated,
removed
}) => {
const changedClients = added.concat(updated).concat(removed);
const encoder = encoding.createEncoder();
encoding.writeVarUint(encoder, messageAwareness$1);
encoding.writeVarUint8Array(encoder, awarenessProtocol.encodeAwarenessUpdate(this.awareness, changedClients));
broadcastMessage(this, encoding.toUint8Array(encoder));
};
send = (message, callback) => {
this.waitForConnection(ws => {
ws.send(message);
if (typeof callback !== 'undefined') {
callback();
}
}, 1000);
};
get synced() {
return this.#synced;
}
getSubDoc(id) {
return this.subdocs.get(id);
}
set synced(state) {
if (this.#synced !== state) {
this.#synced = state;
this.onSynced(state);
this.doc.emit('sync', [state]);
}
}
#getSubDocUpdateHandler = weakMemoize(subDoc => update => {
const encoder = encoding.createEncoder();
encoding.writeVarUint(encoder, messageSubDocSync);
encoding.writeVarString(encoder, subDoc.guid);
syncProtocol.writeUpdate(encoder, update);
broadcastMessage(this, encoding.toUint8Array(encoder));
});
#updateHandler = (update, origin) => {
if (origin !== this) {
const encoder = encoding.createEncoder();
encoding.writeVarUint(encoder, messageSync$1);
syncProtocol.writeUpdate(encoder, update);
broadcastMessage(this, encoding.toUint8Array(encoder));
}
};
destroy() {
if (this.#resyncInterval !== null) {
clearInterval(this.#resyncInterval);
}
clearInterval(this.#checkInterval);
this.disconnect();
if (typeof window !== 'undefined') {
window.removeEventListener('beforeunload', this.#beforeUnloadHandler);
}
this.awareness.off('update', this.#awarenessUpdateHandler);
this.doc.off('update', this.#updateHandler);
}
connectBc() {
if (!this.bcconnected) {
bc.subscribe(this.bcChannel, this.#bcSubscriber);
this.bcconnected = true;
}
// send sync step1 to bc
this.mux(() => {
// write sync step 1
const encoderSync = encoding.createEncoder();
encoding.writeVarUint(encoderSync, messageSync$1);
syncProtocol.writeSyncStep1(encoderSync, this.doc);
bc.publish(this.bcChannel, encoding.toUint8Array(encoderSync));
// broadcast local state
const encoderState = encoding.createEncoder();
encoding.writeVarUint(encoderState, messageSync$1);
syncProtocol.writeSyncStep2(encoderState, this.doc);
bc.publish(this.bcChannel, encoding.toUint8Array(encoderState));
// write queryAwareness
const encoderAwarenessQuery = encoding.createEncoder();
encoding.writeVarUint(encoderAwarenessQuery, messageQueryAwareness);
bc.publish(this.bcChannel, encoding.toUint8Array(encoderAwarenessQuery));
// broadcast local awareness state
const encoderAwarenessState = encoding.createEncoder();
encoding.writeVarUint(encoderAwarenessState, messageAwareness$1);
encoding.writeVarUint8Array(encoderAwarenessState, awarenessProtocol.encodeAwarenessUpdate(this.awareness, [this.doc.clientID]));
bc.publish(this.bcChannel, encoding.toUint8Array(encoderAwarenessState));
});
}
disconnectBc() {
// broadcast message with local awareness state set to null (indicating disconnect)
const encoder = encoding.createEncoder();
encoding.writeVarUint(encoder, messageAwareness$1);
encoding.writeVarUint8Array(encoder, awarenessProtocol.encodeAwarenessUpdate(this.awareness, [this.doc.clientID], new Map()));
broadcastMessage(this, encoding.toUint8Array(encoder));
if (this.bcconnected) {
bc.unsubscribe(this.bcChannel, this.#bcSubscriber);
this.bcconnected = false;
}
}
disconnect() {
this.shouldConnect = false;
this.disconnectBc();
if (this.ws !== null) {
this.ws.close();
}
}
connect() {
this.shouldConnect = true;
if (!this.wsconnected && this.ws === null) {
setupWS(this, this.#WS);
this.connectBc();
}
}
}
const YjsContext = /*#__PURE__*/createContext(null);
const messageSync = 0;
const messageAwareness = 1;
const messageAuth = 2;
const messageSyncSubdoc = 4;
const messageChunkStart = 5;
function decodeSentMessage(message) {
const decoder = decoding.createDecoder(message);
const messageType = decoding.readVarUint(decoder);
switch (messageType) {
case messageSync:
{
return {
kind: 'sync'
};
}
case messageSyncSubdoc:
{
return {
kind: 'sync-subdoc'
};
}
case messageAwareness:
{
const awarenessUpdate = decoding.readVarUint8Array(decoder);
const states = [];
{
const decoder = decoding.createDecoder(awarenessUpdate);
const len = decoding.readVarUint(decoder);
for (let i = 0; i < len; i++) {
const clientID = decoding.readVarUint(decoder);
let clock = decoding.readVarUint(decoder);
const state = JSON.parse(decoding.readVarString(decoder));
states.push({
clientID,
clock,
state
});
}
}
return {
kind: 'awareness',
states
};
}
case messageAuth:
{
return {
kind: 'auth'
};
}
}
}
function decodeMessage(message) {
const decoder = decoding.createDecoder(message);
const messageType = decoding.readVarUint(decoder);
switch (messageType) {
case messageSync:
{
return {
kind: 'sync'
};
}
case messageSyncSubdoc:
{
return {
kind: 'sync-subdoc'
};
}
case messageAwareness:
{
const awarenessUpdate = decoding.readVarUint8Array(decoder);
const states = [];
{
const decoder = decoding.createDecoder(awarenessUpdate);
const len = decoding.readVarUint(decoder);
for (let i = 0; i < len; i++) {
const clientID = decoding.readVarUint(decoder);
let clock = decoding.readVarUint(decoder);
const state = JSON.parse(decoding.readVarString(decoder));
states.push({
clientID,
clock,
state
});
}
}
return {
kind: 'awareness',
states
};
}
case messageAuth:
{
return {
kind: 'auth'
};
}
}
}
function useYjs() {
const yjs = useContext(YjsContext);
if (!yjs) {
throw new Error("CollabProvider not found");
}
if (yjs === "loading") {
throw new Error("CollabProvider is loading");
}
return yjs;
}
function useYjsIfAvailable() {
return useContext(YjsContext);
}
const enableMessageLogging = false;
const emptyMap = new Map();
const AwarenessContext = /*#__PURE__*/createContext(emptyMap);
function useAwarenessStates() {
return useContext(AwarenessContext);
}
const currentAwarenesses = new WeakMap();
function CollabProvider(props) {
var _props$config$cloud;
const router = useRouter();
const cloudInfo = useCloudInfo();
const project = (_props$config$cloud = props.config.cloud) === null || _props$config$cloud === void 0 ? void 0 : _props$config$cloud.project;
const key = `ks-multiplayer-${project}`;
const isMultiplayerEnabled = cloudInfo === null ? localStorage.getItem(key) === 'true' : cloudInfo.team.multiplayer;
const yJsInfo = useMemo(() => {
// we'll optimistically connect to the websocket if multiplayer was enabled last time
if (!isMultiplayerEnabled) {
return;
}
const doc = new Y.Doc();
const data = doc.getMap('data');
const awareness = new Awareness(doc);
const idb = createIndexedDBProvider(doc, `keystatic-2-${project}`);
const provider = new WebsocketProvider({
doc,
url: `wss://live.keystatic.cloud/${project}?v=${PKG_VERSION}`,
WebSocketPolyfill: class extends ReconnectingWebSocket {
constructor(url) {
super(url);
this.addEventListener('message', event => {
if (event.data instanceof ArrayBuffer && enableMessageLogging) {
console.log('recv', decodeMessage(new Uint8Array(event.data)));
}
});
}
send(data) {
if (data instanceof Uint8Array && enableMessageLogging) {
console.log('send', decodeSentMessage(data));
}
const CHUNK_MAX_SIZE = 1000000;
if (data instanceof Uint8Array && data.byteLength > CHUNK_MAX_SIZE) {
const chunks = Math.ceil(data.byteLength / CHUNK_MAX_SIZE);
const encoder = createEncoder();
writeVarUint(encoder, messageChunkStart);
writeVarUint(encoder, chunks);
super.send(toUint8Array(encoder));
for (let i = 0; i < chunks; i++) {
const start = i * CHUNK_MAX_SIZE;
const end = Math.min((i + 1) * CHUNK_MAX_SIZE, data.byteLength);
super.send(data.slice(start, end));
}
return;
}
super.send(data);
}
},
awareness,
authToken: async () => getAuth(props.config).then(auth => {
var _auth$accessToken;
return (_auth$accessToken = auth === null || auth === void 0 ? void 0 : auth.accessToken) !== null && _auth$accessToken !== void 0 ? _auth$accessToken : '';
})
});
return {
doc,
awareness,
provider,
data,
idb
};
}, [isMultiplayerEnabled, project, props.config]);
const currentBranch = useCurrentBranch();
useEffect(() => {
yJsInfo === null || yJsInfo === void 0 || yJsInfo.awareness.setLocalStateField('branch', currentBranch);
yJsInfo === null || yJsInfo === void 0 || yJsInfo.awareness.setLocalStateField('location', router.params.slice(2).join('/'));
}, [currentBranch, router.params, yJsInfo === null || yJsInfo === void 0 ? void 0 : yJsInfo.awareness]);
const hasRepo = !!currentBranch;
useEffect(() => {
if (hasRepo && yJsInfo) {
yJsInfo.idb.connect();
let didConnectToWS = false;
const remove = yJsInfo.idb.subscribeStatusChange(() => {
if (yJsInfo.idb.status.type === 'synced' || yJsInfo.idb.status.type === 'error') {
remove();
yJsInfo.provider.connect();
didConnectToWS = true;
}
});
return () => {
yJsInfo.idb.disconnect();
if (didConnectToWS) {
yJsInfo.provider.disconnect();
} else {
remove();
}
};
}
}, [yJsInfo, hasRepo]);
useEffect(() => {
if (cloudInfo === null) return;
const key_0 = `ks-multiplayer-${project}`;
if (cloudInfo.team.multiplayer) {
localStorage.setItem(key_0, 'true');
} else {
localStorage.removeItem(key_0);
}
}, [cloudInfo, project]);
const awarenessStates = useSyncExternalStore(useCallback(onStoreChange => {
const awareness_0 = yJsInfo === null || yJsInfo === void 0 ? void 0 : yJsInfo.awareness;
if (!awareness_0) return () => {};
const fn = () => {
currentAwarenesses.set(awareness_0, new Map(awareness_0.getStates()));
onStoreChange();
};
awareness_0.on('change', fn);
return () => {
currentAwarenesses.set(awareness_0, new Map(awareness_0.getStates()));
awareness_0.off('change', fn);
};
}, [yJsInfo]), () => {
var _currentAwarenesses$g;
if (!(yJsInfo !== null && yJsInfo !== void 0 && yJsInfo.awareness)) return emptyMap;
return (_currentAwarenesses$g = currentAwarenesses.get(yJsInfo.awareness)) !== null && _currentAwarenesses$g !== void 0 ? _currentAwarenesses$g : emptyMap;
});
return /*#__PURE__*/jsx(AwarenessContext.Provider, {
value: awarenessStates !== null && awarenessStates !== void 0 ? awarenessStates : emptyMap,
children: /*#__PURE__*/jsx(YjsContext.Provider, {
value: yJsInfo === undefined ? cloudInfo === undefined ? 'loading' : null : yJsInfo,
children: props.children
})
});
}
function EmptyRepo(props) {
const $ = c(4);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = /*#__PURE__*/jsx(Flex, {
justifyContent: "center",
children: /*#__PURE__*/jsx(Heading, {
children: "Git repo not initialised"
})
});
$[0] = t0;
} else {
t0 = $[0];
}
const t1 = `https://github.com/${props.repo}`;
let t2;
if ($[1] !== t1 || $[2] !== props.repo) {
t2 = /*#__PURE__*/jsx(Flex, {
alignItems: "center",
justifyContent: "center",
margin: "xxlarge",
children: /*#__PURE__*/jsxs(Flex, {
backgroundColor: "surface",
padding: "large",
border: "color.alias.borderIdle",
borderRadius: "medium",
direction: "column",
justifyContent: "center",
gap: "xlarge",
maxWidth: "scale.4600",
children: [t0, /*#__PURE__*/jsxs(Text, {
children: ["The Keystatic GitHub App is installed in the GitHub repository", " ", /*#__PURE__*/jsx(TextLink, {
href: t1,
children: props.repo
}), " ", "but the Git repo is not initialised. Please initialise the Git repo before using Keystatic."]
})]
})
});
$[1] = t1;
$[2] = props.repo;
$[3] = t2;
} else {
t2 = $[3];
}
return t2;
}
function fetchLocalTree(sha) {
if (treeCache.has(sha)) {
return treeCache.get(sha);
}
const promise = fetch('/api/keystatic/tree', {
headers: {
'no-cors': '1'
}
}).then(x => x.json()).then(async entries => hydrateTreeCacheWithEntries(entries));
treeCache.set(sha, promise);
return promise;
}
function useSetTreeSha() {
return useContext(SetTreeShaContext);
}
const SetTreeShaContext = /*#__PURE__*/createContext(() => {
throw new Error('SetTreeShaContext not set');
});
function LocalAppShellProvider(props) {
const [currentTreeSha, setCurrentTreeSha] = useState('initial');
const tree = useData(useCallback(() => fetchLocalTree(currentTreeSha), [currentTreeSha]));
const allTreeData = useMemo(() => ({
unscopedDefault: tree,
scoped: {
default: tree,
current: tree,
merged: mergeDataStates({
default: tree,
current: tree
})
}
}), [tree]);
const changedData = useMemo(() => {
if (allTreeData.scoped.merged.kind !== 'loaded') {
return {
collections: new Map(),
singletons: new Set()
};
}
return getChangedData(props.config, allTreeData.scoped.merged.data);
}, [allTreeData, props.config]);
return /*#__PURE__*/jsx(SetTreeShaContext.Provider, {
value: setCurrentTreeSha,
children: /*#__PURE__*/jsx(ChangedContext.Provider, {
value: changedData,
children: /*#__PURE__*/jsx(TreeContext.Provider, {
value: allTreeData,
children: props.children
})
})
});
}
const cloudInfoSchema = s.type({
user: s.type({
id: s.string(),
name: s.string(),
email: s.string(),
avatarUrl: s.optional(s.string())
}),
project: s.type({
name: s.string()
}),
team: s.object({
name: s.string(),
slug: s.string(),
images: s.boolean(),
multiplayer: s.boolean()
})
});
const CloudInfo = /*#__PURE__*/createContext(null);
function useCloudInfo() {
const context = useContext(CloudInfo);
return context === 'unauthorized' ? null : context;
}
function useRawCloudInfo() {
return useContext(CloudInfo);
}
function CloudInfoProvider(props) {
const data = useData(useCallback(async () => {
var _props$config$cloud, _getCloudAuth;
if (!((_props$config$cloud = props.config.cloud) !== null && _props$config$cloud !== void 0 && _props$config$cloud.project)) throw new Error('no cloud project set');
const token = (_getCloudAuth = getCloudAuth(props.config)) === null || _getCloudAuth === void 0 ? void 0 : _getCloudAuth.accessToken;
if (!token) {
return 'unauthorized';
}
const res = await fetch(`${KEYSTATIC_CLOUD_API_URL}/v1/info`, {
headers: {
...KEYSTATIC_CLOUD_HEADERS,
Authorization: `Bearer ${token}`
}
});
if (res.status === 401) return 'unauthorized';
return cloudInfoSchema.create(await res.json());
}, [props.config]));
return /*#__PURE__*/jsx(CloudInfo.Provider, {
value: data.kind === 'loaded' ? data.data : null,
children: props.children
});
}
const GitHubAppShellDataContext = /*#__PURE__*/createContext(null);
function GitHubAppShellDataProvider(props) {
var _state$data, _state$data2, _moreRefsState$data, _state$data3;
const repo = props.config.storage.kind === 'github' ? parseRepoConfig(props.config.storage.repo) : {
name: 'repo-name',
owner: 'repo-owner'
};
const [state] = useQuery({
query: props.config.storage.kind === 'github' ? GitHubAppShellQuery : CloudAppShellQuery,
variables: repo
});
const [cursorState, setCursorState] = useState(null);
const [moreRefsState] = useQuery({
query: gql`
query FetchMoreRefs($owner: String!, $name: String!, $after: String) {
repository(owner: $owner, name: $name) {
__typename
id
refs(refPrefix: "refs/heads/", first: 100, after: $after) {
__typename
nodes {
...Ref_base
}
pageInfo {
hasNextPage
endCursor
}
}
}
}
${Ref_base}
`,
pause: !((_state$data = state.data) !== null && _state$data !== void 0 && (_state$data = _state$data.repository) !== null && _state$data !== void 0 && (_state$data = _state$data.refs) !== null && _state$data !== void 0 && _state$data.pageInfo.hasNextPage),
variables: {
...repo,
after: cursorState !== null && cursorState !== void 0 ? cursorState : (_state$data2 = state.data) === null || _state$data2 === void 0 || (_state$data2 = _state$data2.repository) === null || _state$data2 === void 0 || (_state$data2 = _state$data2.refs) === null || _state$data2 === void 0 ? void 0 : _state$data2.pageInfo.endCursor
}
});
const pageInfo = (_moreRefsState$data = moreRefsState.data) === null || _moreRefsState$data === void 0 || (_moreRefsState$data = _moreRefsState$data.repository) === null || _moreRefsState$data === void 0 || (_moreRefsState$data = _moreRefsState$data.refs) === null || _moreRefsState$data === void 0 ? void 0 : _moreRefsState$data.pageInfo;
if (pageInfo !== null && pageInfo !== void 0 && pageInfo.hasNextPage && pageInfo.endCursor !== cursorState && pageInfo.endCursor) {
setCursorState(pageInfo.endCursor);
}
if ((_state$data3 = state.data) !== null && _state$data3 !== void 0 && (_state$data3 = _state$data3.repository) !== null && _state$data3 !== void 0 && _state$data3.owner && !state.data.repository.defaultBranchRef && !state.fetching && !state.error) {
return /*#__PURE__*/jsx(EmptyRepo, {
repo: `${state.data.repository.owner.login}/${state.data.repository.name}`
});
}
return /*#__PURE__*/jsx(GitHubAppShellDataContext.Provider, {
value: state,
children: /*#__PURE__*/jsx(ViewerContext.Provider, {
value: state.data && 'viewer' in state.data ? state.data.viewer : undefined,
children: props.children
})
});
}
const writePermissions = new Set(['WRITE', 'ADMIN', 'MAINTAIN']);
function GitHubAppShellProvider(props) {
var _repo, _repo3, _repo5, _defaultBranchRef$tar, _currentBranchRef$tar, _repo7, _repo9, _repo10, _repo12, _repo13, _repo14, _repo15, _repo16;
const router = useRouter();
const {
data,
error
} = useContext(GitHubAppShellDataContext);
let repo = data === null || data === void 0 ? void 0 : data.repository;
if (repo && 'viewerPermission' in repo && repo.viewerPermission && !writePermissions.has(repo.viewerPermission) && 'forks' in repo) {
var _repo$forks$nodes$, _repo$forks;
repo = (_repo$forks$nodes$ = (_repo$forks = repo.forks) === null || _repo$forks === void 0 || (_repo$forks = _repo$forks.nodes) === null || _repo$forks === void 0 ? void 0 : _repo$forks[0]) !== null && _repo$forks$nodes$ !== void 0 ? _repo$forks$nodes$ : repo;
}
const defaultBranchRef = (_repo = repo) === null || _repo === void 0 || (_repo = _repo.refs) === null || _repo === void 0 || (_repo = _repo.nodes) === null || _repo === void 0 ? void 0 : _repo.find(x => {
var _repo2;
return (x === null || x === void 0 ? void 0 : x.name) === ((_repo2 = repo) === null || _repo2 === void 0 || (_repo2 = _repo2.defaultBranchRef) === null || _repo2 === void 0 ? void 0 : _repo2.name);
});
const currentBranchRef = (_repo3 = repo) === null || _repo3 === void 0 || (_repo3 = _repo3.refs) === null || _repo3 === void 0 || (_repo3 = _repo3.nodes) === null || _repo3 === void 0 ? void 0 : _repo3.find(x_0 => (x_0 === null || x_0 === void 0 ? void 0 : x_0.name) === props.currentBranch);
useEffect(() => {
var _repo4;
if ((_repo4 = repo) !== null && _repo4 !== void 0 && (_repo4 = _repo4.refs) !== null && _repo4 !== void 0 && _repo4.nodes) {
garbageCollectGitObjects(repo.refs.nodes.map(x_1 => {
var _x_1$target;
return (x_1 === null || x_1 === void 0 || (_x_1$target = x_1.target) === null || _x_1$target === void 0 ? void 0 : _x_1$target.__typename) === 'Commit' ? x_1.target.tree.oid : undefined;
}).filter(isDefined));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [(_repo5 = repo) === null || _repo5 === void 0 ? void 0 : _repo5.id]);
const defaultBranchTreeSha = (_defaultBranchRef$tar = defaultBranchRef === null || defaultBranchRef === void 0 ? void 0 : defaultBranchRef.target.tree.oid) !== null && _defaultBranchRef$tar !== void 0 ? _defaultBranchRef$tar : null;
const currentBranchTreeSha = (_currentBranchRef$tar = currentBranchRef === null || currentBranchRef === void 0 ? void 0 : currentBranchRef.target.tree.oid) !== null && _currentBranchRef$tar !== void 0 ? _currentBranchRef$tar : null;
const defaultBranchTree = useGitHubTreeData(defaultBranchTreeSha, props.config);
const currentBranchTree = useGitHubTreeData(currentBranchTreeSha, props.config);
const allTreeData = useMemo(() => {
const scopedDefault = mapDataState(defaultBranchTree, tree => scopeEntriesWithPathPrefix(tree, props.config));
const scopedCurrent = mapDataState(currentBranchTree, tree_0 => scopeEntriesWithPathPrefix(tree_0, props.config));
return {
unscopedDefault: currentBranchTree,
scoped: {
default: scopedDefault,
current: scopedCurrent,
merged: mergeDataStates({
default: scopedDefault,
current: scopedCurrent
})
}
};
}, [currentBranchTree, defaultBranchTree, props.config]);
const changedData = useMemo(() => {
if (allTreeData.scoped.merged.kind !== 'loaded') {
return {
collections: new Map(),
singletons: new Set()
};
}
return getChangedData(props.config, allTreeData.scoped.merged.data);
}, [allTreeData, props.config]);
useEffect(() => {
var _error$response, _repo6;
if ((error === null || error === void 0 || (_error$response = error.response) === null || _error$response === void 0 ? void 0 : _error$response.status) === 401) {
if (isGitHubConfig(props.config)) {
window.location.href = `/api/keystatic/github/login?from=${router.params.map(encodeURIComponent).join('/')}`;
} else {
redirectToCloudAuth(router.params.map(encodeURIComponent).join('/'), props.config);
}
}
if (!((_repo6 = repo) !== null && _repo6 !== void 0 && _repo6.id) && error !== null && error !== void 0 && error.graphQLErrors.some(err => {
var _err$originalError, _err$originalError2;
return (err === null || err === void 0 || (_err$originalError = err.originalError) === null || _err$originalError === void 0 ? void 0 : _err$originalError.type) === 'NOT_FOUND' || (err === null || err === void 0 || (_err$originalError2 = err.originalError) === null || _err$originalError2 === void 0 ? void 0 : _err$originalError2.type) === 'FORBIDDEN';
})) {
window.location.href = `/api/keystatic/github/repo-not-found?from=${router.params.map(encodeURIComponent).join('/')}`;
}
}, [error, router, (_repo7 = repo) === null || _repo7 === void 0 ? void 0 : _repo7.id, props.config]);
const branches = useMemo(() => {
var _repo8;
return new Map((_repo8 = repo) === null || _repo8 === void 0 || (_repo8 = _repo8.refs) === null || _repo8 === void 0 || (_repo8 = _repo8.nodes) === null || _repo8 === void 0 ? void 0 : _repo8.flatMap(x_2 => {
var _x_2$target;
if ((x_2 === null || x_2 === void 0 || (_x_2$target = x_2.target) === null || _x_2$target === void 0 ? void 0 : _x_2$target.__typename) !== 'Commit') {
return [];
}
return [[x_2.name, {
id: x_2.id,
commitSha: x_2.target.oid,
treeSha: x_2.target.tree.oid
}]];
}));
}, [(_repo9 = repo) === null || _repo9 === void 0 || (_repo9 = _repo9.refs) === null || _repo9 === void 0 ? void 0 : _repo9.nodes]);
const hasWritePermission = !!repo && (props.config.storage.kind === 'cloud' || 'viewerPermission' in repo && !!((_repo10 = repo) !== null && _repo10 !== void 0 && _repo10.viewerPermission) && writePermissions.has(repo.viewerPermission));
const repoInfo = useMemo(() => {
var _repo11;
if (!(data !== null && data !== void 0 && data.repository) || !((_repo11 = repo) !== null && _repo11 !== void 0 && (_repo11 = _repo11.defaultBranchRef) !== null && _repo11 !== void 0 && _repo11.name)) return null;
return {
id: repo.id,
defaultBranch: repo.defaultBranchRef.name,
hasWritePermission,
isPrivate: repo.isPrivate,
name: repo.name,
owner: repo.owner.login,
upstream: {
name: repo.name,
owner: repo.owner.login
}
};
}, [data === null || data === void 0 ? void 0 : data.repository, hasWritePermission, (_repo12 = repo) === null || _repo12 === void 0 || (_repo12 = _repo12.defaultBranchRef) === null || _repo12 === void 0 ? void 0 : _repo12.name, (_repo13 = repo) === null || _repo13 === void 0 ? void 0 : _repo13.id, (_repo14 = repo) === null || _repo14 === void 0 ? void 0 : _repo14.isPrivate, (_repo15 = repo) === null || _repo15 === void 0 ? void 0 : _repo15.name, (_repo16 = repo) === null || _repo16 === void 0 ? void 0 : _repo16.owner.login]);
return /*#__PURE__*/jsx(AppShellErrorContext.Provider, {
value: error,
children: /*#__PURE__*/jsx(CurrentBranchContext.Provider, {
value: props.currentBranch,
children: /*#__PURE__*/jsx(BranchesContext.Provider, {
value: branches,
children: /*#__PURE__*/jsx(RepoInfoContext.Provider, {
value: repoInfo,
children: /*#__PURE__*/jsx(ChangedContext.Provider, {
value: changedData,
children: /*#__PURE__*/jsx(TreeContext.Provider, {
value: allTreeData,
children: props.config.storage.kind === 'cloud' ? /*#__PURE__*/jsx(CollabProvider, {
config: props.config,
children: props.children
}) : props.children
})
})
})
})
})
});
}
const AppShellErrorContext = /*#__PURE__*/createContext(undefined);
const CurrentBranchContext = /*#__PURE__*/createContext('');
function useCurrentBranch() {
return useContext(CurrentBranchContext);
}
const BranchesContext = /*#__PURE__*/createContext(new Map());
function useBranches() {
return useContext(BranchesContext);
}
const RepoInfoContext = /*#__PURE__*/createContext(null);
function useRepoInfo() {
return useContext(RepoInfoContext);
}
const ChangedContext = /*#__PURE__*/createContext({
collections: new Map(),
singletons: new Set()
});
const TreeContext = /*#__PURE__*/createContext({
unscopedDefault: {
kind: 'loading',
promise: LOADING
},
scoped: {
current: {
kind: 'loading',
promise: LOADING
},
default: {
kind: 'loading',
promise: LOADING
},
merged: {
kind: 'loading',
promise: LOADING
}
}
});
function useTree() {
return useContext(TreeContext).scoped;
}
function useCurrentUnscopedTree() {
return useContext(TreeContext).unscopedDefault;
}
function useChanged() {
return useContext(ChangedContext);
}
function useBaseCommit() {
var _branchInfo$get$commi, _branchInfo$get;
const branchInfo = useBranches();
const currentBranch = useCurrentBranch();
return (_branchInfo$get$commi = (_branchInfo$get = branchInfo.get(currentBranch)) === null || _branchInfo$get === void 0 ? void 0 : _branchInfo$get.commitSha) !== null && _branchInfo$get$commi !== void 0 ? _branchInfo$get$commi : '';
}
const Ref_base = gql`
fragment Ref_base on Ref {
id
name
target {
__typename
id
oid
... on Commit {
tree {
id
oid
}
}
}
}
`;
const BaseRepo = gql`
fragment Repo_base on Repository {
id
isPrivate
owner {
id
login
}
name
defaultBranchRef {
id
name
}
refs(refPrefix: "refs/heads/", first: 100) {
nodes {
...Ref_base
}
pageInfo {
hasNextPage
endCursor
}
}
}
${Ref_base}
`;
const CloudAppShellQuery = gql`
query CloudAppShell($name: String!, $owner: String!) {
repository(owner: $owner, name: $name) {
id
...Repo_base
}
}
${BaseRepo}
`;
const Repo_ghDirect = gql`
fragment Repo_ghDirect on Repository {
id
...Repo_base
viewerPermission
}
${BaseRepo}
`;
const Repo_primary = gql`
fragment Repo_primary on Repository {
id
...Repo_ghDirect
forks(affiliations: [OWNER], first: 1) {
nodes {
...Repo_ghDirect
}
}
}
${Repo_ghDirect}
`;
const GitHubAppShellQuery = gql`
query GitHubAppShell($name: String!, $owner: String!) {
repository(owner: $owner, name: $name) {
id
...Repo_primary
}
viewer {
...SidebarFooter_viewer
}
}
${Repo_primary}
${SidebarFooter_viewer}
`;
const treeCache = new LRUCache({
max: 40
});
async function hydrateTreeCacheWithEntries(entries) {
const data = {
entries: new Map(entries.map(entry => [entry.path, entry])),
tree: treeEntriesToTreeNodes(entries)
};
const sha = await treeSha(data.tree);
treeCache.set(sha, data);
return data;
}
function fetchGitHubTreeData(sha, config) {
const cached = treeCache.get(sha);
if (cached) return cached;
const cachedFromPersisted = getTreeFromPersistedCache(sha);
if (cachedFromPersisted && !(cachedFromPersisted instanceof Promise)) {
const entries = treeToEntries(cachedFromPersisted.children);
const result = {
entries: new Map(entries.map(entry => [entry.path, entry])),
tree: cachedFromPersisted.children
};
treeCache.set(sha, result);
return result;
}
const promise = (async () => {
const cached = await cachedFromPersisted;
if (cached) {
const entries = treeToEntries(cached.children);
const result = {
entries: new Map(entries.map(entry => [entry.path, entry])),
tree: cached.children
};
treeCache.set(sha, result);
return result;
}
const auth = await getAuth(config);
if (!auth) throw new Error('Not authorized');
const {
tree
} = await fetch(config.storage.kind === 'github' ? `https://api.github.com/repos/${serializeRepoConfig(config.storage.repo)}/git/trees/${sha}?recursive=1` : `${KEYSTATIC_CLOUD_API_URL}/v1/github/trees/${sha}`, {
headers: {
Authorization: `Bearer ${auth.accessToken}`,
...(config.storage.kind === 'cloud' ? KEYSTATIC_CLOUD_HEADERS : {})
}
}).then(x => x.json());
const treeEntries = tree.map(({
url,
size,
...rest
}) => rest);
await setTreeToPersistedCache(sha, treeEntriesToTreeNodes(treeEntries));
return hydrateTreeCacheWithEntries(treeEntries);
})();
treeCache.set(sha, promise);
return promise;
}
function useGitHubTreeData(sha, config) {
return useData(useCallback(() => sha ? fetchGitHubTreeData(sha, config) : LOADING, [sha, config]));
}
function getChangedData(config, trees) {
var _config$collections, _config$singletons;
return {
collections: new Map(Object.keys((_config$collections = config.collections) !== null && _config$collections !== void 0 ? _config$collections : {}).map(collection => {
const currentBranch = new Map(getEntriesInCollectionWithTreeKey(config, collection, trees.current.tree).map(x => [x.slug, x.key]));
const defaultBranch = new Map(getEntriesInCollectionWithTreeKey(config, collection, trees.default.tree).map(x => [x.slug, x.key]));
const changed = new Set();
const added = new Set();
for (const [key, entry] of currentBranch) {
const defaultBranchEntry = defaultBranch.get(key);
if (defaultBranchEntry === undefined) {
added.add(key);
continue;
}
if (entry !== defaultBranchEntry) {
changed.add(key);
}
}
const removed = new Set([...defaultBranch.keys()].filter(key => !currentBranch.has(key)));
return [collection, {
removed,
added,
changed,
totalCount: currentBranch.size
}];
})),
singletons: new Set(Object.keys((_config$singletons = config.singletons) !== null && _config$singletons !== void 0 ? _config$singletons : {}).filter(singleton => {
var _getTreeNodeAtPath, _getTreeNodeAtPath2;
const singletonPath = getSingletonPath(config, singleton);
return ((_getTreeNodeAtPath = getTreeNodeAtPath(trees.current.tree, singletonPath)) === null || _getTreeNodeAtPath === void 0 ? void 0 : _getTreeNodeAtPath.entry.sha) !== ((_getTreeNodeAtPath2 = getTreeNodeAtPath(trees.default.tree, singletonPath)) === null || _getTreeNodeAtPath2 === void 0 ? void 0 : _getTreeNodeAtPath2.entry.sha);
}))
};
}
// Config context
// -----------------------------------------------------------------------------
const ConfigContext = /*#__PURE__*/createContext(null);
function useConfig() {
const config = useContext(ConfigContext);
if (!config) {
throw new Error("ConfigContext.Provider not found");
}
return config;
}
// Meta context
// -----------------------------------------------------------------------------
const AppStateContext = /*#__PURE__*/createContext({
basePath: '/keystatic'
});
function useAppState() {
const appState = useContext(AppStateContext);
if (!appState) {
throw new Error("AppStateContext.Provider not found");
}
return appState;
}
// Page context
// -----------------------------------------------------------------------------
const ContentPanelContext = /*#__PURE__*/createContext('mobile');
const ContentPanelProvider = ContentPanelContext.Provider;
function useContentPanelSize() {
return useContext(ContentPanelContext);
}
function useContentPanelQuery(options) {
const sizes = ["mobile", "tablet", "desktop", "wide"];
const size = useContentPanelSize();
const startIndex = "above" in options ? sizes.indexOf(options.above) + 1 : 0;
const endIndex = "below" in options ? sizes.indexOf(options.below) - 1 : sizes.length - 1;
const range = sizes.slice(startIndex, endIndex + 1);
return range.includes(size);
}
/** @private only used to initialize context */
function useContentPanelState(ref) {
const $ = c(5);
const [contentSize, setContentSize] = useState("mobile");
let t0;
if ($[0] !== ref.current) {
t0 = () => {
setContentSize(size => {
const contentPane = ref.current;
if (!contentPane) {
return size;
}
if (contentPane.offsetWidth >= breakpoints.wide) {
return "wide";
}
if (contentPane.offsetWidth >= breakpoints.desktop) {
return "desktop";
}
if (contentPane.offsetWidth >= breakpoints.tablet) {
return "tablet";
}
return "mobile";
});
};
$[0] = ref.current;
$[1] = t0;
} else {
t0 = $[1];
}
const onResize = t0;
let t1;
if ($[2] !== ref || $[3] !== onResize) {
t1 = {
ref,
onResize
};
$[2] = ref;
$[3] = onResize;
$[4] = t1;
} else {
t1 = $[4];
}
useResizeObserver(t1);
return contentSize;
}
function focusWithPreviousSelection(editor) {
const selection = window.getSelection();
if (selection) {
selection.removeAllRanges();
selection.addRange(ReactEditor.toDOMRange(editor, editor.selection));
}
ReactEditor.focus(editor);
}
const blockElementSpacing = css({
marginBlock: '0.75em',
'&:first-child': {
marginBlockStart: 0
},
'&:last-child': {
marginBlockEnd: 0
}
});
const ForceValidationContext = /*#__PURE__*/React.createContext(false);
ForceValidationContext.Provider;
// this ensures that when changes happen, they are immediately shown
// this stops the problem of a cursor resetting to the end when a change is made
// because the changes are applied asynchronously
function useElementWithSetNodes(editor, element) {
const [state, setState] = useState({
element,
elementWithChanges: element
});
if (state.element !== element) {
setState({
element,
elementWithChanges: element
});
}
const elementRef = useRef(element);
useEffect(() => {
elementRef.current = element;
});
const setNodes = useCallback(changesOrCallback => {
const currentElement = elementRef.current;
const changes = typeof changesOrCallback === 'function' ? changesOrCallback(currentElement) : changesOrCallback;
Transforms.setNodes(editor, changes, {
at: ReactEditor.findPath(editor, currentElement)
});
setState({
element: currentElement,
elementWithChanges: {
...currentElement,
...changes
}
});
}, [editor]);
return [state.elementWithChanges, setNodes];
}
function useEventCallback(callback) {
const callbackRef = useRef(callback);
const cb = useCallback((...args) => {
return callbackRef.current(...args);
}, []);
useEffect(() => {
callbackRef.current = callback;
});
return cb;
}
function insertNodesButReplaceIfSelectionIsAtEmptyParagraphOrHeading(editor, nodes) {
var _pathRefForEmptyNodeA;
let pathRefForEmptyNodeAtCursor;
const entry = Editor.above(editor, {
match: node => node.type === 'heading' || node.type === 'paragraph'
});
if (entry && Node.string(entry[0]) === '') {
pathRefForEmptyNodeAtCursor = Editor.pathRef(editor, entry[1]);
}
Transforms.insertNodes(editor, nodes);
let path = (_pathRefForEmptyNodeA = pathRefForEmptyNodeAtCursor) === null || _pathRefForEmptyNodeA === void 0 ? void 0 : _pathRefForEmptyNodeA.unref();
if (path) {
Transforms.removeNodes(editor, {
at: path
});
// even though the selection is in the right place after the removeNodes
// for some reason the editor blurs so we need to focus it again
ReactEditor.focus(editor);
}
}
const tableCellChildren = ['paragraph', 'code', 'heading', 'ordered-list', 'unordered-list', 'divider', 'image'];
const blockquoteChildren = [...tableCellChildren, 'table'];
const paragraphLike = [...blockquoteChildren, 'blockquote'];
const insideOfLayouts = [...paragraphLike, 'component-block'];
function blockContainer(args) {
return {
kind: 'blocks',
allowedChildren: new Set(args.allowedChildren),
blockToWrapInlinesIn: args.allowedChildren[0],
invalidPositionHandleMode: args.invalidPositionHandleMode
};
}
function inlineContainer(args) {
return {
kind: 'inlines',
invalidPositionHandleMode: args.invalidPositionHandleMode
};
}
const editorSchema = {
editor: blockContainer({
allowedChildren: [...insideOfLayouts, 'layout'],
invalidPositionHandleMode: 'move'
}),
layout: blockContainer({
allowedChildren: ['layout-area'],
invalidPositionHandleMode: 'move'
}),
'layout-area': blockContainer({
allowedChildren: insideOfLayouts,
invalidPositionHandleMode: 'unwrap'
}),
blockquote: blockContainer({
allowedChildren: blockquoteChildren,
invalidPositionHandleMode: 'move'
}),
paragraph: inlineContainer({
invalidPositionHandleMode: 'unwrap'
}),
code: inlineContainer({
invalidPositionHandleMode: 'move'
}),
divider: inlineContainer({
invalidPositionHandleMode: 'move'
}),
heading: inlineContainer({
invalidPositionHandleMode: 'unwrap'
}),
'component-block': blockContainer({
allowedChildren: ['component-block-prop', 'component-inline-prop'],
invalidPositionHandleMode: 'move'
}),
'component-inline-prop': inlineContainer({
invalidPositionHandleMode: 'unwrap'
}),
'component-block-prop': blockContainer({
allowedChildren: insideOfLayouts,
invalidPositionHandleMode: 'unwrap'
}),
'ordered-list': blockContainer({
allowedChildren: ['list-item'],
invalidPositionHandleMode: 'move'
}),
'unordered-list': blockContainer({
allowedChildren: ['list-item'],
invalidPositionHandleMode: 'move'
}),
'list-item': blockContainer({
allowedChildren: ['list-item-content', 'ordered-list', 'unordered-list'],
invalidPositionHandleMode: 'unwrap'
}),
'list-item-content': inlineContainer({
invalidPositionHandleMode: 'unwrap'
}),
image: inlineContainer({
invalidPositionHandleMode: 'move'
}),
table: blockContainer({
invalidPositionHandleMode: 'move',
allowedChildren: ['table-head', 'table-body']
}),
'table-body': blockContainer({
invalidPositionHandleMode: 'move',
allowedChildren: ['table-row']
}),
'table-row': blockContainer({
invalidPositionHandleMode: 'move',
allowedChildren: ['table-cell']
}),
'table-cell': blockContainer({
invalidPositionHandleMode: 'move',
allowedChildren: tableCellChildren
}),
'table-head': blockContainer({
invalidPositionHandleMode: 'move',
allowedChildren: ['table-row']
})
};
const inlineContainerTypes = new Set(Object.entries(editorSchema).filter(([, value]) => value.kind === 'inlines').map(([type]) => type));
function isInlineContainer(node) {
return node.type !== undefined && inlineContainerTypes.has(node.type);
}
const blockTypes = new Set(Object.keys(editorSchema).filter(x => x !== 'editor'));
function isBlock(node) {
return blockTypes.has(node.type);
}
// to print the editor schema in Graphviz if you want to visualize it
// function printEditorSchema(editorSchema: EditorSchema) {
// return `digraph G {
// concentrate=true;
// ${Object.keys(editorSchema)
// .map(key => {
// let val = editorSchema[key];
// if (val.kind === 'inlines') {
// return `"${key}" -> inlines`;
// }
// if (val.kind === 'blocks') {
// return `"${key}" -> {${[...val.allowedChildren].map(x => JSON.stringify(x)).join(' ')}}`;
// }
// })
// .join('\n ')}
// }`;
// }
function getWholeDocumentFeaturesForChildField(editorDocumentFeatures, options) {
var _options$formatting, _options$formatting2, _options$formatting3, _options$formatting4, _options$formatting5, _options$formatting6, _options$formatting7;
const inlineMarksFromOptions = (_options$formatting = options.formatting) === null || _options$formatting === void 0 ? void 0 : _options$formatting.inlineMarks;
const inlineMarks = Object.fromEntries(Object.keys(editorDocumentFeatures.formatting.inlineMarks).map(_mark => {
const mark = _mark;
return [mark, inlineMarksFromOptions === 'inherit' || (inlineMarksFromOptions === null || inlineMarksFromOptions === void 0 ? void 0 : inlineMarksFromOptions[mark]) === 'inherit' ? editorDocumentFeatures.formatting.inlineMarks[mark] : false];
}));
const headingLevels = (_options$formatting2 = options.formatting) === null || _options$formatting2 === void 0 ? void 0 : _options$formatting2.headingLevels;
return {
formatting: {
inlineMarks,
softBreaks: ((_options$formatting3 = options.formatting) === null || _options$formatting3 === void 0 ? void 0 : _options$formatting3.softBreaks) === 'inherit' && editorDocumentFeatures.formatting.softBreaks,
alignment: {
center: editorDocumentFeatures.formatting.alignment.center && ((_options$formatting4 = options.formatting) === null || _options$formatting4 === void 0 ? void 0 : _options$formatting4.alignment) === 'inherit',
end: editorDocumentFeatures.formatting.alignment.end && ((_options$formatting5 = options.formatting) === null || _options$formatting5 === void 0 ? void 0 : _options$formatting5.alignment) === 'inherit'
},
blockTypes: ((_options$formatting6 = options.formatting) === null || _options$formatting6 === void 0 ? void 0 : _options$formatting6.blockTypes) === 'inherit' ? editorDocumentFeatures.formatting.blockTypes : {
blockquote: false,
code: false
},
headings: headingLevels === 'inherit' ? editorDocumentFeatures.formatting.headings : {
levels: headingLevels ? editorDocumentFeatures.formatting.headings.levels.filter(level => headingLevels.includes(level)) : [],
schema: editorDocumentFeatures.formatting.headings.schema
},
listTypes: ((_options$formatting7 = options.formatting) === null || _options$formatting7 === void 0 ? void 0 : _options$formatting7.listTypes) === 'inherit' ? editorDocumentFeatures.formatting.listTypes : {
ordered: false,
unordered: false
}
},
dividers: options.dividers === 'inherit' ? editorDocumentFeatures.dividers : false,
images: options.images === 'inherit' && editorDocumentFeatures.images,
layouts: [],
links: options.links === 'inherit' && editorDocumentFeatures.links,
tables: options.tables === 'inherit' && editorDocumentFeatures.tables
};
}
function getDocumentFeaturesForChildField(editorDocumentFeatures, options) {
var _options$formatting8, _options$formatting10, _options$formatting11, _options$formatting12, _options$formatting13, _options$formatting14;
// an important note for this: normalization based on document features
// is done based on the document features returned here
// and the editor document features
// so the result for any given child prop will be the things that are
// allowed by both these document features
// AND the editor document features
const inlineMarksFromOptions = (_options$formatting8 = options.formatting) === null || _options$formatting8 === void 0 ? void 0 : _options$formatting8.inlineMarks;
const inlineMarks = inlineMarksFromOptions === 'inherit' ? 'inherit' : Object.fromEntries(Object.keys(editorDocumentFeatures.formatting.inlineMarks).map(mark => {
return [mark, !!(inlineMarksFromOptions || {})[mark]];
}));
if (options.kind === 'inline') {
var _options$formatting9;
return {
kind: 'inline',
inlineMarks,
documentFeatures: {
links: options.links === 'inherit'
},
softBreaks: ((_options$formatting9 = options.formatting) === null || _options$formatting9 === void 0 ? void 0 : _options$formatting9.softBreaks) === 'inherit'
};
}
const headingLevels = (_options$formatting10 = options.formatting) === null || _options$formatting10 === void 0 ? void 0 : _options$formatting10.headingLevels;
return {
kind: 'block',
inlineMarks,
softBreaks: ((_options$formatting11 = options.formatting) === null || _options$formatting11 === void 0 ? void 0 : _options$formatting11.softBreaks) === 'inherit',
documentFeatures: {
layouts: [],
dividers: options.dividers === 'inherit' ? editorDocumentFeatures.dividers : false,
formatting: {
alignment: ((_options$formatting12 = options.formatting) === null || _options$formatting12 === void 0 ? void 0 : _options$formatting12.alignment) === 'inherit' ? editorDocumentFeatures.formatting.alignment : {
center: false,
end: false
},
blockTypes: ((_options$formatting13 = options.formatting) === null || _options$formatting13 === void 0 ? void 0 : _options$formatting13.blockTypes) === 'inherit' ? editorDocumentFeatures.formatting.blockTypes : {
blockquote: false,
code: false
},
headings: headingLevels === 'inherit' ? editorDocumentFeatures.formatting.headings : {
levels: headingLevels ? editorDocumentFeatures.formatting.headings.levels.filter(level => headingLevels.includes(level)) : [],
schema: editorDocumentFeatures.formatting.headings.schema
},
listTypes: ((_options$formatting14 = options.formatting) === null || _options$formatting14 === void 0 ? void 0 : _options$formatting14.listTypes) === 'inherit' ? editorDocumentFeatures.formatting.listTypes : {
ordered: false,
unordered: false
}
},
links: options.links === 'inherit',
images: options.images === 'inherit' ? editorDocumentFeatures.images : false,
tables: options.tables === 'inherit'
},
componentBlocks: options.componentBlocks === 'inherit'
};
}
function getSchemaAtPropPathInner(path, value, schema) {
// because we're checking the length here
// the non-null asserts on shift below are fine
if (path.length === 0) {
return schema;
}
if (schema.kind === 'child' || schema.kind === 'form') {
return;
}
if (schema.kind === 'conditional') {
const key = path.shift();
if (key === 'discriminant') {
return getSchemaAtPropPathInner(path, value.discriminant, schema.discriminant);
}
if (key === 'value') {
const propVal = schema.values[value.discriminant];
return getSchemaAtPropPathInner(path, value.value, propVal);
}
return;
}
if (schema.kind === 'object') {
const key = path.shift();
return getSchemaAtPropPathInner(path, value[key], schema.fields[key]);
}
if (schema.kind === 'array') {
const index = path.shift();
return getSchemaAtPropPathInner(path, value[index], schema.element);
}
assertNever(schema);
}
function getSchemaAtPropPath(path, value, props) {
return getSchemaAtPropPathInner([...path], value, {
kind: 'object',
fields: props
});
}
function getAncestorSchemas(rootSchema, path, value) {
const ancestors = [];
const currentPath = [...path];
let currentProp = rootSchema;
let currentValue = value;
while (currentPath.length) {
ancestors.push(currentProp);
const key = currentPath.shift(); // this code only runs when path.length is truthy so this non-null assertion is fine
if (currentProp.kind === 'array') {
currentProp = currentProp.element;
currentValue = currentValue[key];
} else if (currentProp.kind === 'conditional') {
currentProp = currentProp.values[value.discriminant];
currentValue = currentValue.value;
} else if (currentProp.kind === 'object') {
currentValue = currentValue[key];
currentProp = currentProp.fields[key];
} else if (currentProp.kind === 'child' || currentProp.kind === 'form') {
throw new Error(`unexpected prop "${key}"`);
} else {
assertNever(currentProp);
}
}
return ancestors;
}
function getPlaceholderTextForPropPath(propPath, fields, formProps) {
const field = getSchemaAtPropPath(propPath, formProps, fields);
if ((field === null || field === void 0 ? void 0 : field.kind) === 'child' && (field.options.kind === 'block' && field.options.editIn !== 'modal' || field.options.kind === 'inline')) {
return field.options.placeholder;
}
return '';
}
function cloneDescendent(node) {
if (Element.isElement(node)) {
return {
...node,
children: node.children.map(cloneDescendent)
};
}
return {
...node
};
}
const allMarks = ['bold', 'italic', 'underline', 'strikethrough', 'code', 'superscript', 'subscript', 'keyboard'];
const isElementActive = (editor, format) => {
const [match] = Editor.nodes(editor, {
match: n => n.type === format
});
return !!match;
};
function clearFormatting(editor) {
Transforms.unwrapNodes(editor, {
match: node => node.type === 'heading' || node.type === 'blockquote' || node.type === 'code'
});
Transforms.unsetNodes(editor, allMarks, {
match: Text$1.isText
});
}
function moveChildren(editor, parent, to, shouldMoveNode = () => true) {
const parentPath = Path.isPath(parent) ? parent : parent[1];
const parentNode = Path.isPath(parent) ? Node.get(editor, parentPath) : parent[0];
if (!isBlock(parentNode)) return;
for (let i = parentNode.children.length - 1; i >= 0; i--) {
if (shouldMoveNode(parentNode.children[i], i)) {
const childPath = [...parentPath, i];
Transforms.moveNodes(editor, {
at: childPath,
to
});
}
}
}
/**
* This is equivalent to Editor.after except that it ignores points that have no content
* like the point in a void text node, an empty text node and the last point in a text node
*/
// TODO: this would probably break if you were trying to get the last point in the editor?
function EditorAfterButIgnoringingPointsWithNoContent(editor, at, {
distance = 1
} = {}) {
const anchor = Editor.point(editor, at, {
edge: 'end'
});
const focus = Editor.end(editor, []);
const range = {
anchor,
focus
};
let d = 0;
let target;
for (const p of Editor.positions(editor, {
at: range
})) {
if (d > distance) {
break;
}
// this is the important change
const node = Node.get(editor, p.path);
if (node.text.length === p.offset) {
continue;
}
if (d !== 0) {
target = p;
}
d++;
}
return target;
}
function nodeTypeMatcher(...args) {
if (args.length === 1) {
const type = args[0];
return node => node.type === type;
}
const set = new Set(args);
return node => typeof node.type === 'string' && set.has(node.type);
}
function getAncestorComponentChildFieldDocumentFeatures(editor, editorDocumentFeatures, componentBlocks) {
const ancestorComponentProp = Editor.above(editor, {
match: nodeTypeMatcher('component-block-prop', 'component-inline-prop')
});
if (ancestorComponentProp) {
const propPath = ancestorComponentProp[0].propPath;
const ancestorComponent = Editor.parent(editor, ancestorComponentProp[1]);
if (ancestorComponent[0].type === 'component-block') {
const component = ancestorComponent[0].component;
const componentBlock = componentBlocks[component];
if (componentBlock && propPath) {
const childField = getSchemaAtPropPath(propPath, ancestorComponent[0].props, componentBlock.schema);
if ((childField === null || childField === void 0 ? void 0 : childField.kind) === 'child') {
return getDocumentFeaturesForChildField(editorDocumentFeatures, childField.options);
}
}
}
}
}
const BlockPopoverContext = /*#__PURE__*/createContext(null);
function useBlockPopoverContext() {
const context = useContext(BlockPopoverContext);
if (!context) {
throw new Error('useBlockPopoverContext must be used within a BlockPopoverTrigger');
}
return context;
}
const typeMatcher = nodeTypeMatcher('code', 'component-block', 'image', 'layout', 'link', 'table', 'heading');
const ActiveBlockPopoverContext = /*#__PURE__*/createContext(undefined);
function useActiveBlockPopover() {
return useContext(ActiveBlockPopoverContext);
}
function ActiveBlockPopoverProvider(props) {
const nodeWithPopover = Editor.above(props.editor, {
match: typeMatcher
});
return /*#__PURE__*/jsx(ActiveBlockPopoverContext.Provider, {
value: nodeWithPopover === null || nodeWithPopover === void 0 ? void 0 : nodeWithPopover[0],
children: props.children
});
}
const BlockPopoverTrigger = ({
children,
element
}) => {
const [trigger, popover] = children;
const activePopoverElement = useActiveBlockPopover();
const triggerRef = useRef(null);
const state = useOverlayTriggerState({
isOpen: activePopoverElement === element
});
const context = useMemo(() => ({
state,
triggerRef
}), [state, triggerRef]);
return /*#__PURE__*/jsxs(BlockPopoverContext.Provider, {
value: context,
children: [/*#__PURE__*/cloneElement(trigger, {
ref: triggerRef
}), popover]
});
};
function BlockPopover(props) {
const {
state
} = useBlockPopoverContext();
let wrapperRef = useRef(null);
return /*#__PURE__*/jsx(Overlay, {
isOpen: state.isOpen,
nodeRef: wrapperRef,
children: /*#__PURE__*/jsx(BlockPopoverWrapper, {
wrapperRef: wrapperRef,
...props
})
});
}
const BlockPopoverWrapper = ({
children,
placement: preferredPlacement = 'bottom'
}) => {
let popoverRef = useRef(null);
let {
state,
triggerRef
} = useBlockPopoverContext();
let {
placement,
popoverProps
} = useBlockPopover({
isNonModal: true,
isKeyboardDismissDisabled: false,
placement: preferredPlacement,
triggerRef,
popoverRef
}, state);
return /*#__PURE__*/jsx("div", {
ref: popoverRef,
...popoverProps,
"data-open": state.isOpen,
"data-placement": placement,
contentEditable: false,
className: css({
backgroundColor: tokenSchema.color.background.surface,
// TODO: component token?
borderRadius: tokenSchema.size.radius.medium,
// TODO: component token?
border: `${tokenSchema.size.border.regular} solid ${tokenSchema.color.border.emphasis}`,
boxSizing: 'content-box',
// resolves measurement/scroll issues related to border
// boxShadow: `0 0 0 ${tokenSchema.size.border.regular} ${tokenSchema.color.border.emphasis}`,
minHeight: tokenSchema.size.element.regular,
minWidth: tokenSchema.size.element.regular,
opacity: 0,
outline: 0,
pointerEvents: 'auto',
position: 'absolute',
// use filter:drop-shadow instead of box-shadow so the arrow is included
filter: `drop-shadow(0 1px 4px ${tokenSchema.color.shadow.regular})`,
// filter bug in safari: https://stackoverflow.com/questions/56478925/safari-drop-shadow-filter-remains-visible-even-with-hidden-element
willChange: 'filter',
userSelect: 'none',
// placement
'&[data-placement="top"]': {
marginBottom: tokenSchema.size.space.regular,
transform: `translateY(${tokenSchema.size.space.regular})`
},
'&[data-placement="bottom"]': {
marginTop: tokenSchema.size.space.regular,
transform: `translateY(calc(${tokenSchema.size.space.regular} * -1))`
},
'&[data-open="true"]': {
opacity: 1,
transform: `translateX(0) translateY(0)`,
// enter animation
transition: transition(['opacity', 'transform'], {
easing: 'easeOut'
})
}
}),
children: typeof children === 'function' ? children(state.close) : children
});
};
/**
* Provides the behavior and accessibility implementation for a popover component.
* A popover is an overlay element positioned relative to a trigger.
*/
function useBlockPopover(props, state) {
var _triggerRef$current2;
let {
triggerRef,
popoverRef,
isNonModal,
isKeyboardDismissDisabled,
...otherProps
} = props;
let [isSticky, setSticky] = useState(false);
let {
overlayProps,
underlayProps
} = useOverlay({
isOpen: state.isOpen,
onClose: state.close,
shouldCloseOnBlur: true,
isDismissable: !isNonModal,
isKeyboardDismissDisabled: false
}, popoverRef);
// stick the popover to the bottom of the viewport instead of flipping
const containerPadding = 8;
useEffect(() => {
if (state.isOpen) {
const checkForStickiness = () => {
var _popoverRef$current, _triggerRef$current;
const vh = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0);
let popoverRect = (_popoverRef$current = popoverRef.current) === null || _popoverRef$current === void 0 ? void 0 : _popoverRef$current.getBoundingClientRect();
let triggerRect = (_triggerRef$current = triggerRef.current) === null || _triggerRef$current === void 0 ? void 0 : _triggerRef$current.getBoundingClientRect();
if (popoverRect && triggerRect) {
setSticky(triggerRect.bottom + popoverRect.height + containerPadding * 2 > vh && triggerRect.top < vh);
}
};
checkForStickiness();
window.addEventListener('scroll', checkForStickiness);
return () => {
checkForStickiness();
window.removeEventListener('scroll', checkForStickiness);
};
}
}, [popoverRef, triggerRef, state.isOpen]);
let {
overlayProps: positionProps,
arrowProps,
placement,
updatePosition
} = useOverlayPosition({
...otherProps,
containerPadding,
shouldFlip: false,
targetRef: triggerRef,
overlayRef: popoverRef,
isOpen: state.isOpen,
onClose: undefined
});
// force update position when the trigger changes
let previousBoundingRect = usePrevious((_triggerRef$current2 = triggerRef.current) === null || _triggerRef$current2 === void 0 ? void 0 : _triggerRef$current2.getBoundingClientRect());
useLayoutEffect(() => {
if (previousBoundingRect) {
var _triggerRef$current3;
const currentBoundingRect = (_triggerRef$current3 = triggerRef.current) === null || _triggerRef$current3 === void 0 ? void 0 : _triggerRef$current3.getBoundingClientRect();
if (currentBoundingRect) {
const hasChanged = previousBoundingRect.height !== currentBoundingRect.height || previousBoundingRect.width !== currentBoundingRect.width || previousBoundingRect.x !== currentBoundingRect.x || previousBoundingRect.y !== currentBoundingRect.y;
if (hasChanged) {
updatePosition();
}
}
}
}, [previousBoundingRect, triggerRef, updatePosition]);
// make sure popovers are below modal dialogs and their blanket
if (positionProps.style) {
positionProps.style.zIndex = 1;
}
// switching to position: fixed will undoubtedly bite me later, but this hack works for now
if (isSticky) {
positionProps.style = {
...positionProps.style,
// @ts-expect-error
maxHeight: null,
position: 'fixed',
// @ts-expect-error
top: null,
bottom: containerPadding
};
}
return {
arrowProps,
placement,
popoverProps: mergeProps(overlayProps, positionProps),
underlayProps,
updatePosition
};
}
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
const NotEditable = /*#__PURE__*/forwardRef(function NotEditable({
className,
...props
}, ref) {
return /*#__PURE__*/jsx("div", {
...props,
ref: ref,
className: [css({
userSelect: 'none',
whiteSpace: 'initial'
}), className].join(' '),
contentEditable: false
});
});
function slugify(input) {
let slug = input.toLowerCase().trim();
// remove accents from charaters
slug = slug.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
// replace invalid chars with spaces
slug = slug.replace(/[^a-z0-9\s-]/g, ' ').trim();
// replace multiple spaces or hyphens with a single hyphen
slug = slug.replace(/[\s-]+/g, '-');
return slug;
}
const imageUploadResponse = s.type({
src: s.string(),
width: s.number(),
height: s.number()
});
function uploadImage(file, config) {
if (file.size > 10000000) {
throw new Error('Images must be smaller than 10MB');
}
const auth = getCloudAuth(config);
if (!auth) {
throw new Error('You must be signed in to upload images');
}
const filenameMatch = /(.+)\.(png|jpe?g|gif|webp)$/.exec(file.name);
if (!filenameMatch) {
throw new Error('Invalid image type, only PNG, JPEG, GIF, and WebP are supported');
}
const filename = slugify(filenameMatch[1]);
const ext = filenameMatch[2];
const filenameWithExt = `${filename}.${ext}`;
const newFile = new File([file], filenameWithExt, {
type: `image/${filenameWithExt === 'jpg' ? 'jpeg' : filenameWithExt}`
});
const formData = new FormData();
formData.set('image', newFile, filenameWithExt);
return (async () => {
const res = await fetch(`${KEYSTATIC_CLOUD_API_URL}/v1/image`, {
method: 'PUT',
headers: {
Authorization: `Bearer ${auth.accessToken}`,
...KEYSTATIC_CLOUD_HEADERS
},
body: formData
});
if (!res.ok) {
throw new Error(`Failed to upload image: ${await res.text()}`);
}
const data = await res.json();
let parsedData;
try {
parsedData = imageUploadResponse.create(data);
} catch {
throw new Error('Unexpected response from cloud');
}
return parsedData;
})();
}
function parseImageData(data) {
try {
const parsed = JSON.parse(data);
if (typeof parsed === 'object' && parsed !== null && 'src' in parsed && typeof parsed.src === 'string') {
return {
src: parsed.src,
alt: 'alt' in parsed && typeof parsed.alt === 'string' ? parsed.alt : '',
height: 'height' in parsed && typeof parsed.height === 'number' && Number.isInteger(parsed.height) ? parsed.height : undefined,
width: 'width' in parsed && typeof parsed.width === 'number' && Number.isInteger(parsed.width) ? parsed.width : undefined
};
}
} catch (err) {}
const pattern = /^\s*!\[(.*)\]\(([a-z0-9_\-/:.]+)\)\s*$/;
const match = data.match(pattern);
if (match) {
return {
src: match[2],
alt: match[1]
};
}
return {
src: data,
alt: ''
};
}
function useImageDimensions(src) {
const $ = c(4);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = {};
$[0] = t0;
} else {
t0 = $[0];
}
const [dimensions, setDimensions] = useState(t0);
let t1;
let t2;
if ($[1] !== src) {
t1 = () => {
if (!src || !isValidURL(src)) {
setDimensions({});
return;
}
let shouldSet;
shouldSet = true;
loadImageDimensions(src).then(dimensions_0 => {
if (shouldSet) {
setDimensions(dimensions_0);
}
});
return () => {
shouldSet = false;
};
};
t2 = [src];
$[1] = src;
$[2] = t1;
$[3] = t2;
} else {
t1 = $[2];
t2 = $[3];
}
useEffect(t1, t2);
return dimensions;
}
function loadImageDimensions(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => {
resolve({
width: img.width,
height: img.height
});
};
img.onerror = () => {
reject();
};
img.src = url;
});
}
const imageDataSchema = s.type({
src: s.string(),
alt: s.string(),
width: s.number(),
height: s.number()
});
async function loadImageData(url, config) {
const auth = getCloudAuth(config);
if (auth) {
const res = await fetch(`${KEYSTATIC_CLOUD_API_URL}/v1/image?${new URLSearchParams({
url
})}`, {
headers: {
Authorization: `Bearer ${auth.accessToken}`,
...KEYSTATIC_CLOUD_HEADERS
}
});
if (res.ok) {
const data = await res.json();
try {
return imageDataSchema.create(data);
} catch {}
}
}
return loadImageDimensions(url).then(dimensions => ({
src: url,
alt: '',
...dimensions
}));
}
function ImageDimensionsInput(props) {
const $ = c(42);
const dimensions = useImageDimensions(props.src);
const [constrainProportions, setConstrainProportions] = useState(true);
const revertLabel = `Revert to original (${dimensions.width} Ɨ ${dimensions.height})`;
const dimensionsMatchOriginal = dimensions.width === props.image.width && dimensions.height === props.image.height;
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = {
maximumFractionDigits: 0
};
$[0] = t0;
} else {
t0 = $[0];
}
let t1;
if ($[1] !== constrainProportions || $[2] !== props) {
t1 = width => {
if (constrainProportions) {
props.onChange({
width,
height: Math.round(width / getAspectRatio(props.image))
});
} else {
props.onChange({
width
});
}
};
$[1] = constrainProportions;
$[2] = props;
$[3] = t1;
} else {
t1 = $[3];
}
let t2;
if ($[4] !== props.image.width || $[5] !== t1) {
t2 = /*#__PURE__*/jsx(NumberField, {
label: "Width",
width: "scale.1600",
formatOptions: t0,
value: props.image.width,
onChange: t1
});
$[4] = props.image.width;
$[5] = t1;
$[6] = t2;
} else {
t2 = $[6];
}
let t3;
if ($[7] === Symbol.for("react.memo_cache_sentinel")) {
t3 = () => {
setConstrainProportions(state => !state);
};
$[7] = t3;
} else {
t3 = $[7];
}
const t4 = constrainProportions ? link2Icon : link2OffIcon;
let t5;
if ($[8] !== t4) {
t5 = /*#__PURE__*/jsx(Icon, {
src: t4
});
$[8] = t4;
$[9] = t5;
} else {
t5 = $[9];
}
let t6;
if ($[10] !== constrainProportions || $[11] !== t5) {
t6 = /*#__PURE__*/jsx(ToggleButton, {
isSelected: constrainProportions,
"aria-label": "Constrain proportions",
prominence: "low",
onPress: t3,
children: t5
});
$[10] = constrainProportions;
$[11] = t5;
$[12] = t6;
} else {
t6 = $[12];
}
let t7;
if ($[13] === Symbol.for("react.memo_cache_sentinel")) {
t7 = /*#__PURE__*/jsx(Tooltip, {
children: "Constrain proportions"
});
$[13] = t7;
} else {
t7 = $[13];
}
let t8;
if ($[14] !== t6) {
t8 = /*#__PURE__*/jsxs(TooltipTrigger, {
children: [t6, t7]
});
$[14] = t6;
$[15] = t8;
} else {
t8 = $[15];
}
let t9;
if ($[16] === Symbol.for("react.memo_cache_sentinel")) {
t9 = {
maximumFractionDigits: 0
};
$[16] = t9;
} else {
t9 = $[16];
}
let t10;
if ($[17] !== constrainProportions || $[18] !== props) {
t10 = height => {
if (constrainProportions) {
props.onChange({
height,
width: Math.round(height * getAspectRatio(props.image))
});
} else {
props.onChange({
height
});
}
};
$[17] = constrainProportions;
$[18] = props;
$[19] = t10;
} else {
t10 = $[19];
}
let t11;
if ($[20] !== props.image.height || $[21] !== t10) {
t11 = /*#__PURE__*/jsx(NumberField, {
label: "Height",
width: "scale.1600",
formatOptions: t9,
value: props.image.height,
onChange: t10
});
$[20] = props.image.height;
$[21] = t10;
$[22] = t11;
} else {
t11 = $[22];
}
const t12 = dimensionsMatchOriginal || !dimensions.width || !dimensions.height;
let t13;
if ($[23] !== props || $[24] !== dimensions.height || $[25] !== dimensions.width) {
t13 = () => {
props.onChange({
height: dimensions.height,
width: dimensions.width
});
};
$[23] = props;
$[24] = dimensions.height;
$[25] = dimensions.width;
$[26] = t13;
} else {
t13 = $[26];
}
let t14;
if ($[27] === Symbol.for("react.memo_cache_sentinel")) {
t14 = /*#__PURE__*/jsx(Icon, {
src: undo2Icon
});
$[27] = t14;
} else {
t14 = $[27];
}
let t15;
if ($[28] !== revertLabel || $[29] !== t12 || $[30] !== t13) {
t15 = /*#__PURE__*/jsx(ActionButton, {
"aria-label": revertLabel,
isDisabled: t12,
onPress: t13,
children: t14
});
$[28] = revertLabel;
$[29] = t12;
$[30] = t13;
$[31] = t15;
} else {
t15 = $[31];
}
let t16;
if ($[32] !== revertLabel) {
t16 = /*#__PURE__*/jsx(Tooltip, {
maxWidth: "100%",
children: revertLabel
});
$[32] = revertLabel;
$[33] = t16;
} else {
t16 = $[33];
}
let t17;
if ($[34] !== t15 || $[35] !== t16) {
t17 = /*#__PURE__*/jsxs(TooltipTrigger, {
children: [t15, t16]
});
$[34] = t15;
$[35] = t16;
$[36] = t17;
} else {
t17 = $[36];
}
let t18;
if ($[37] !== t2 || $[38] !== t8 || $[39] !== t11 || $[40] !== t17) {
t18 = /*#__PURE__*/jsxs(HStack, {
gap: "regular",
alignItems: "end",
children: [t2, t8, t11, t17]
});
$[37] = t2;
$[38] = t8;
$[39] = t11;
$[40] = t17;
$[41] = t18;
} else {
t18 = $[41];
}
return t18;
}
const emptyImageData = {
src: '',
alt: ''
};
const ALLOWED_IMAGE_EXTENSIONS = ['jpeg', 'jpg', 'png', 'gif', 'webp'];
const ACCEPTED_TYPES = ALLOWED_IMAGE_EXTENSIONS.map(ext => `image/${ext}`);
function UploadImageButton(props) {
var _config$cloud;
const $ = c(12);
let styleProps;
if ($[0] !== props) {
const {
onUploaded,
...t0
} = props;
styleProps = t0;
$[0] = props;
$[1] = styleProps;
} else {
styleProps = $[1];
}
const config = useConfig();
const [isUploading, setIsUploading] = useState(false);
if (!((_config$cloud = config.cloud) !== null && _config$cloud !== void 0 && _config$cloud.project)) {
return null;
}
let t0;
if ($[2] !== config || $[3] !== props) {
t0 = async items => {
const files = Array.from(items || []);
if (files[0]) {
setIsUploading(true);
try {
const result = await uploadImage(files[0], config);
props.onUploaded({
...result,
alt: ""
});
setIsUploading(false);
} catch (t1) {
const err = t1;
setIsUploading(false);
toastQueue.critical(err.message);
}
}
};
$[2] = config;
$[3] = props;
$[4] = t0;
} else {
t0 = $[4];
}
const t1 = isUploading ? "Uploading\u2026" : "Upload";
let t2;
if ($[5] !== isUploading || $[6] !== styleProps || $[7] !== t1) {
t2 = /*#__PURE__*/jsx(ActionButton, {
isDisabled: isUploading,
...styleProps,
children: t1
});
$[5] = isUploading;
$[6] = styleProps;
$[7] = t1;
$[8] = t2;
} else {
t2 = $[8];
}
let t3;
if ($[9] !== t0 || $[10] !== t2) {
t3 = /*#__PURE__*/jsx(FileTrigger, {
acceptedFileTypes: ACCEPTED_TYPES,
onSelect: t0,
children: t2
});
$[9] = t0;
$[10] = t2;
$[11] = t3;
} else {
t3 = $[11];
}
return t3;
}
function ImageDialog(props) {
const $ = c(45);
const {
image,
onCancel,
onChange,
onClose
} = props;
const [state, setState] = useState(image !== null && image !== void 0 ? image : emptyImageData);
const [status, setStatus] = useState(image ? "good" : "");
const formId = useId();
const imageLibraryURL = useImageLibraryURL();
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = event => {
event.preventDefault();
const text = event.clipboardData.getData("text/plain");
setState(parseImageData(text));
};
$[0] = t0;
} else {
t0 = $[0];
}
const onPaste = t0;
const config = useConfig();
const hasSetFields = !!(state.alt || state.width || state.height);
let t1;
let t2;
if ($[1] !== state.src || $[2] !== hasSetFields || $[3] !== config) {
t1 = () => {
if (!state.src) {
setStatus("");
return;
}
if (!isValidURL(state.src)) {
return;
}
if (hasSetFields) {
setStatus("good");
return;
}
setStatus("loading");
loadImageData(state.src, config).then(newData => {
setState(state_0 => ({
...state_0,
...newData
}));
setStatus("good");
}).catch(() => {
setStatus("error");
});
};
t2 = [config, hasSetFields, state.src];
$[1] = state.src;
$[2] = hasSetFields;
$[3] = config;
$[4] = t1;
$[5] = t2;
} else {
t1 = $[4];
t2 = $[5];
}
useEffect(t1, t2);
let t3;
if ($[6] === Symbol.for("react.memo_cache_sentinel")) {
t3 = /*#__PURE__*/jsx(Heading, {
children: "Cloud image"
});
$[6] = t3;
} else {
t3 = $[6];
}
let t4;
if ($[7] !== status || $[8] !== onChange || $[9] !== state || $[10] !== onClose) {
t4 = e => {
e.preventDefault();
if (status !== "good") {
return;
}
onChange(state);
onClose();
};
$[7] = status;
$[8] = onChange;
$[9] = state;
$[10] = onClose;
$[11] = t4;
} else {
t4 = $[11];
}
let t5;
if ($[12] === Symbol.for("react.memo_cache_sentinel")) {
t5 = e_0 => {
if (e_0.code === "Backspace" || e_0.code === "Delete") {
setState(emptyImageData);
} else {
e_0.continuePropagation();
}
};
$[12] = t5;
} else {
t5 = $[12];
}
let t6;
if ($[13] !== imageLibraryURL) {
t6 = /*#__PURE__*/jsxs(Text, {
children: ["Copy an image URL from the", " ", /*#__PURE__*/jsx(TextLink, {
prominence: "high",
href: imageLibraryURL,
target: "_blank",
rel: "noreferrer",
children: "Image Library"
}), " ", "and paste it into this field."]
});
$[13] = imageLibraryURL;
$[14] = t6;
} else {
t6 = $[14];
}
let t7;
if ($[15] !== status || $[16] !== state) {
t7 = status === "loading" ? /*#__PURE__*/jsx(Flex, {
height: "element.regular",
width: "element.regular",
alignItems: "center",
justifyContent: "center",
children: /*#__PURE__*/jsx(ProgressCircle, {
size: "small",
"aria-label": "Checking\u2026",
isIndeterminate: true
})
}) : state.src ? /*#__PURE__*/jsx(ClearButton, {
onPress: () => setState(emptyImageData),
preventFocus: true
}) : null;
$[15] = status;
$[16] = state;
$[17] = t7;
} else {
t7 = $[17];
}
let t8;
if ($[18] !== state.src || $[19] !== t6 || $[20] !== t7) {
t8 = /*#__PURE__*/jsx(TextField, {
label: "Image URL",
flex: true,
autoFocus: true,
onPaste: onPaste,
onKeyDown: t5,
value: state.src,
description: t6,
endElement: t7
});
$[18] = state.src;
$[19] = t6;
$[20] = t7;
$[21] = t8;
} else {
t8 = $[21];
}
let t9;
if ($[22] === Symbol.for("react.memo_cache_sentinel")) {
t9 = /*#__PURE__*/jsx(UploadImageButton, {
onUploaded: data => {
setState(data);
}
});
$[22] = t9;
} else {
t9 = $[22];
}
let t10;
if ($[23] !== t8) {
t10 = /*#__PURE__*/jsxs(HStack, {
alignItems: "end",
gap: "medium",
children: [t8, t9]
});
$[23] = t8;
$[24] = t10;
} else {
t10 = $[24];
}
let t11;
if ($[25] !== status || $[26] !== state) {
t11 = status === "good" ? /*#__PURE__*/jsxs(Fragment, {
children: [/*#__PURE__*/jsx(TextArea, {
label: "Alt text",
value: state.alt,
onChange: alt => setState(state_1 => ({
...state_1,
alt
}))
}), /*#__PURE__*/jsx(ImageDimensionsInput, {
src: state.src,
image: state,
onChange: dimensions => {
setState(state_2 => ({
...state_2,
...dimensions
}));
}
})]
}) : null;
$[25] = status;
$[26] = state;
$[27] = t11;
} else {
t11 = $[27];
}
let t12;
if ($[28] !== formId || $[29] !== t4 || $[30] !== t10 || $[31] !== t11) {
t12 = /*#__PURE__*/jsx(Content, {
children: /*#__PURE__*/jsxs(VStack, {
elementType: "form",
id: formId,
gap: "xlarge",
onSubmit: t4,
children: [t10, t11]
})
});
$[28] = formId;
$[29] = t4;
$[30] = t10;
$[31] = t11;
$[32] = t12;
} else {
t12 = $[32];
}
let t13;
if ($[33] !== onCancel) {
t13 = /*#__PURE__*/jsx(Button, {
onPress: onCancel,
children: "Cancel"
});
$[33] = onCancel;
$[34] = t13;
} else {
t13 = $[34];
}
const t14 = status !== "good";
const t15 = image ? "Done" : "Insert";
let t16;
if ($[35] !== formId || $[36] !== t14 || $[37] !== t15) {
t16 = /*#__PURE__*/jsx(Button, {
prominence: "high",
type: "submit",
form: formId,
isDisabled: t14,
children: t15
});
$[35] = formId;
$[36] = t14;
$[37] = t15;
$[38] = t16;
} else {
t16 = $[38];
}
let t17;
if ($[39] !== t13 || $[40] !== t16) {
t17 = /*#__PURE__*/jsxs(ButtonGroup, {
children: [t13, t16]
});
$[39] = t13;
$[40] = t16;
$[41] = t17;
} else {
t17 = $[41];
}
let t18;
if ($[42] !== t12 || $[43] !== t17) {
t18 = /*#__PURE__*/jsxs(Dialog, {
children: [t3, t12, t17]
});
$[42] = t12;
$[43] = t17;
$[44] = t18;
} else {
t18 = $[44];
}
return t18;
}
function Placeholder(props) {
const $ = c(26);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = {
defaultOpen: false
};
$[0] = t0;
} else {
t0 = $[0];
}
const state = useOverlayTriggerState(t0);
const {
open
} = state;
let t1;
let t2;
if ($[1] !== props.selected || $[2] !== open) {
t1 = () => {
if (props.selected) {
open();
}
};
t2 = [props.selected, open];
$[1] = props.selected;
$[2] = open;
$[3] = t1;
$[4] = t2;
} else {
t1 = $[3];
t2 = $[4];
}
useEffect(t1, t2);
let t3;
if ($[5] !== state || $[6] !== props) {
t3 = () => {
state.close();
props.onRemove();
};
$[5] = state;
$[6] = props;
$[7] = t3;
} else {
t3 = $[7];
}
const closeAndCleanup = t3;
let t4;
if ($[8] !== state) {
t4 = () => state.open();
$[8] = state;
$[9] = t4;
} else {
t4 = $[9];
}
let t5;
if ($[10] === Symbol.for("react.memo_cache_sentinel")) {
t5 = /*#__PURE__*/jsx(Icon, {
src: imageIcon
});
$[10] = t5;
} else {
t5 = $[10];
}
const t6 = state.isOpen ? "" : "(click to configure)";
let t7;
if ($[11] !== t6) {
t7 = /*#__PURE__*/jsxs(Text, {
children: ["Cloud image", t6]
});
$[11] = t6;
$[12] = t7;
} else {
t7 = $[12];
}
let t8;
if ($[13] !== t4 || $[14] !== t7) {
t8 = /*#__PURE__*/jsxs(Flex, {
alignItems: "center",
backgroundColor: "surface",
borderRadius: "regular",
gap: "regular",
height: "element.large",
paddingX: "large",
onClick: t4,
children: [t5, t7]
});
$[13] = t4;
$[14] = t7;
$[15] = t8;
} else {
t8 = $[15];
}
let t9;
if ($[16] !== state || $[17] !== props || $[18] !== closeAndCleanup) {
t9 = state.isOpen && /*#__PURE__*/jsx(ImageDialog, {
onChange: props.onChange,
onCancel: closeAndCleanup,
onClose: state.close
});
$[16] = state;
$[17] = props;
$[18] = closeAndCleanup;
$[19] = t9;
} else {
t9 = $[19];
}
let t10;
if ($[20] !== closeAndCleanup || $[21] !== t9) {
t10 = /*#__PURE__*/jsx(DialogContainer, {
onDismiss: closeAndCleanup,
children: t9
});
$[20] = closeAndCleanup;
$[21] = t9;
$[22] = t10;
} else {
t10 = $[22];
}
let t11;
if ($[23] !== t8 || $[24] !== t10) {
t11 = /*#__PURE__*/jsxs(NotEditable, {
children: [t8, t10]
});
$[23] = t8;
$[24] = t10;
$[25] = t11;
} else {
t11 = $[25];
}
return t11;
}
function ImagePreview(t0) {
const $ = c(38);
const {
image,
onChange,
onRemove,
selected
} = t0;
const t1 = selected ? "accent" : "surface";
const t2 = selected ? "color.alias.borderFocused" : "neutral";
let t3;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t3 = {
maxHeight: 368
};
$[0] = t3;
} else {
t3 = $[0];
}
let t4;
if ($[1] !== image.src) {
t4 = imageWithTransforms({
source: image.src,
height: 736,
width: 1468
});
$[1] = image.src;
$[2] = t4;
} else {
t4 = $[2];
}
let t5;
if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
t5 = {
objectFit: "contain"
};
$[3] = t5;
} else {
t5 = $[3];
}
let t6;
if ($[4] !== image.alt || $[5] !== t4) {
t6 = /*#__PURE__*/jsx(Flex, {
backgroundColor: "canvas",
justifyContent: "center",
UNSAFE_style: t3,
children: /*#__PURE__*/jsx("img", {
alt: image.alt,
src: t4,
style: t5
})
});
$[4] = image.alt;
$[5] = t4;
$[6] = t6;
} else {
t6 = $[6];
}
const t7 = selected ? "color.alias.borderFocused" : "neutral";
let t8;
if ($[7] !== image.alt) {
t8 = image.alt ? /*#__PURE__*/jsx(Text, {
truncate: 2,
children: image.alt
}) : null;
$[7] = image.alt;
$[8] = t8;
} else {
t8 = $[8];
}
let t9;
if ($[9] !== image.width || $[10] !== image.height) {
t9 = /*#__PURE__*/jsxs(Text, {
color: "neutralTertiary",
size: "small",
children: [image.width, " \xD7 ", image.height]
});
$[9] = image.width;
$[10] = image.height;
$[11] = t9;
} else {
t9 = $[11];
}
let t10;
if ($[12] !== t8 || $[13] !== t9) {
t10 = /*#__PURE__*/jsxs(VStack, {
flex: "1",
gap: "medium",
justifyContent: "center",
children: [t8, t9]
});
$[12] = t8;
$[13] = t9;
$[14] = t10;
} else {
t10 = $[14];
}
let t11;
if ($[15] === Symbol.for("react.memo_cache_sentinel")) {
t11 = /*#__PURE__*/jsx(ActionButton, {
children: /*#__PURE__*/jsx(Icon, {
src: pencilIcon
})
});
$[15] = t11;
} else {
t11 = $[15];
}
let t12;
if ($[16] === Symbol.for("react.memo_cache_sentinel")) {
t12 = /*#__PURE__*/jsxs(TooltipTrigger, {
children: [t11, /*#__PURE__*/jsx(Tooltip, {
children: "Edit Image Options"
})]
});
$[16] = t12;
} else {
t12 = $[16];
}
let t13;
if ($[17] !== image || $[18] !== onChange) {
t13 = /*#__PURE__*/jsxs(DialogTrigger, {
children: [t12, onClose => /*#__PURE__*/jsx(ImageDialog, {
image: image,
onChange: onChange,
onCancel: onClose,
onClose: onClose
})]
});
$[17] = image;
$[18] = onChange;
$[19] = t13;
} else {
t13 = $[19];
}
let t14;
if ($[20] === Symbol.for("react.memo_cache_sentinel")) {
t14 = /*#__PURE__*/jsx(Icon, {
src: trash2Icon
});
$[20] = t14;
} else {
t14 = $[20];
}
let t15;
if ($[21] !== onRemove) {
t15 = /*#__PURE__*/jsx(ActionButton, {
onPress: onRemove,
children: t14
});
$[21] = onRemove;
$[22] = t15;
} else {
t15 = $[22];
}
let t16;
if ($[23] === Symbol.for("react.memo_cache_sentinel")) {
t16 = /*#__PURE__*/jsx(Tooltip, {
children: "Remove Image"
});
$[23] = t16;
} else {
t16 = $[23];
}
let t17;
if ($[24] !== t15) {
t17 = /*#__PURE__*/jsxs(TooltipTrigger, {
children: [t15, t16]
});
$[24] = t15;
$[25] = t17;
} else {
t17 = $[25];
}
let t18;
if ($[26] !== t13 || $[27] !== t17) {
t18 = /*#__PURE__*/jsxs(HStack, {
gap: "regular",
children: [t13, t17]
});
$[26] = t13;
$[27] = t17;
$[28] = t18;
} else {
t18 = $[28];
}
let t19;
if ($[29] !== t7 || $[30] !== t10 || $[31] !== t18) {
t19 = /*#__PURE__*/jsxs(HStack, {
padding: "large",
gap: "xlarge",
borderTop: t7,
children: [t10, t18]
});
$[29] = t7;
$[30] = t10;
$[31] = t18;
$[32] = t19;
} else {
t19 = $[32];
}
let t20;
if ($[33] !== t1 || $[34] !== t2 || $[35] !== t6 || $[36] !== t19) {
t20 = /*#__PURE__*/jsx(Fragment, {
children: /*#__PURE__*/jsx(NotEditable, {
children: /*#__PURE__*/jsxs(VStack, {
backgroundColor: t1,
borderRadius: "medium",
border: t2,
overflow: "hidden",
children: [t6, t19]
})
})
});
$[33] = t1;
$[34] = t2;
$[35] = t6;
$[36] = t19;
$[37] = t20;
} else {
t20 = $[37];
}
return t20;
}
function CloudImagePreview(props) {
var _props$fields$width$v, _props$fields$height$;
const $ = c(17);
const selected = useSelected();
const editor = useSlateStatic();
if (!props.fields.src.value) {
let t0;
if ($[0] !== editor || $[1] !== props) {
t0 = () => {
focusWithPreviousSelection(editor);
props.onRemove();
};
$[0] = editor;
$[1] = props;
$[2] = t0;
} else {
t0 = $[2];
}
let t1;
if ($[3] !== props.onChange || $[4] !== t0 || $[5] !== selected) {
t1 = /*#__PURE__*/jsx(Placeholder, {
onChange: props.onChange,
onRemove: t0,
selected: selected
});
$[3] = props.onChange;
$[4] = t0;
$[5] = selected;
$[6] = t1;
} else {
t1 = $[6];
}
return t1;
}
const t0 = (_props$fields$width$v = props.fields.width.value) !== null && _props$fields$width$v !== void 0 ? _props$fields$width$v : undefined;
const t1 = (_props$fields$height$ = props.fields.height.value) !== null && _props$fields$height$ !== void 0 ? _props$fields$height$ : undefined;
let t2;
if ($[7] !== props.fields.src.value || $[8] !== props.fields.alt.value || $[9] !== t0 || $[10] !== t1) {
t2 = {
src: props.fields.src.value,
alt: props.fields.alt.value,
width: t0,
height: t1
};
$[7] = props.fields.src.value;
$[8] = props.fields.alt.value;
$[9] = t0;
$[10] = t1;
$[11] = t2;
} else {
t2 = $[11];
}
let t3;
if ($[12] !== t2 || $[13] !== props.onChange || $[14] !== props.onRemove || $[15] !== selected) {
t3 = /*#__PURE__*/jsx(ImagePreview, {
image: t2,
onChange: props.onChange,
onRemove: props.onRemove,
selected: selected
});
$[12] = t2;
$[13] = props.onChange;
$[14] = props.onRemove;
$[15] = selected;
$[16] = t3;
} else {
t3 = $[16];
}
return t3;
}
function handleFile(file, config) {
try {
const result = uploadImage(file, config);
toastQueue.info('Uploading imageā€¦');
return result.then(data => {
toastQueue.positive('Image uploaded');
return {
...data,
alt: ''
};
});
} catch (err) {
toastQueue.critical(err.message);
return false;
}
}
function CloudImagePreviewForNewEditor(props) {
var _props$value$width, _props$value$height;
const $ = c(14);
if (!props.value.src) {
let t0;
if ($[0] !== props.onChange || $[1] !== props.onRemove || $[2] !== props.isSelected) {
t0 = /*#__PURE__*/jsx(Placeholder, {
onChange: props.onChange,
onRemove: props.onRemove,
selected: props.isSelected
});
$[0] = props.onChange;
$[1] = props.onRemove;
$[2] = props.isSelected;
$[3] = t0;
} else {
t0 = $[3];
}
return t0;
}
const t0 = (_props$value$width = props.value.width) !== null && _props$value$width !== void 0 ? _props$value$width : undefined;
const t1 = (_props$value$height = props.value.height) !== null && _props$value$height !== void 0 ? _props$value$height : undefined;
let t2;
if ($[4] !== props.value.src || $[5] !== props.value.alt || $[6] !== t0 || $[7] !== t1) {
t2 = {
src: props.value.src,
alt: props.value.alt,
width: t0,
height: t1
};
$[4] = props.value.src;
$[5] = props.value.alt;
$[6] = t0;
$[7] = t1;
$[8] = t2;
} else {
t2 = $[8];
}
let t3;
if ($[9] !== t2 || $[10] !== props.onChange || $[11] !== props.onRemove || $[12] !== props.isSelected) {
t3 = /*#__PURE__*/jsx(ImagePreview, {
image: t2,
onChange: props.onChange,
onRemove: props.onRemove,
selected: props.isSelected
});
$[9] = t2;
$[10] = props.onChange;
$[11] = props.onRemove;
$[12] = props.isSelected;
$[13] = t3;
} else {
t3 = $[13];
}
return t3;
}
// Utils
// -----------------------------------------------------------------------------
function imageWithTransforms(options) {
let {
fit = 'scale-down',
source,
height,
width
} = options;
if (!/^https?:\/\/[^\.]+\.keystatic\.net/.test(source)) {
return source;
}
return `${source}?` + new URLSearchParams({
fit,
height: height.toString(),
width: width.toString()
}).toString();
}
function isValidURL(str) {
try {
new URL(str);
return true;
} catch {
return false;
}
}
function useImageLibraryURL() {
const config = useConfig();
const split = getSplitCloudProject(config);
if (!split) {
return "https://keystatic.cloud/";
}
return `https://keystatic.cloud/teams/${split.team}/project/${split.project}/images`;
}
function getAspectRatio(state) {
if (!state.width || !state.height) return 1;
return state.width / state.height;
}
const cloudImageToolbarIcon = imageIcon;
class FieldDataError extends Error {
constructor(message) {
super(message);
this.name = 'FieldDataError';
}
}
function assertRequired(value, validation, label) {
if (value === null && validation !== null && validation !== void 0 && validation.isRequired) {
throw new FieldDataError(`${label} is required`);
}
}
function basicFormFieldWithSimpleReaderParse(config) {
return {
kind: 'form',
Input: config.Input,
defaultValue: config.defaultValue,
parse: config.parse,
serialize: config.serialize,
validate: config.validate,
reader: {
parse(value) {
return config.validate(config.parse(value));
}
},
label: config.label
};
}
function areArraysEqual(a, b) {
return a.length === b.length && a.every((x, i) => x === b[i]);
}
function normalizeTextBasedOnInlineMarksAndSoftBreaks([node, path], editor, inlineMarks, softBreaks) {
const marksToRemove = Object.keys(node).filter(x => x !== 'text' && x !== 'insertMenu' && inlineMarks[x] !== true);
if (marksToRemove.length) {
Transforms.unsetNodes(editor, marksToRemove, {
at: path
});
return true;
}
if (!softBreaks) {
const hasSoftBreaks = node.text.includes('\n');
if (hasSoftBreaks) {
const [parentNode] = Editor.parent(editor, path);
if (parentNode.type !== 'code') {
for (const position of Editor.positions(editor, {
at: path
})) {
const character = Node.get(editor, position.path).text[position.offset];
if (character === '\n') {
Transforms.delete(editor, {
at: position
});
return true;
}
}
}
}
}
return false;
}
function normalizeInlineBasedOnLinks([node, path], editor, links) {
if (node.type === 'link' && !links) {
Transforms.insertText(editor, ` (${node.href})`, {
at: Editor.end(editor, path)
});
Transforms.unwrapNodes(editor, {
at: path
});
return true;
}
return false;
}
function normalizeElementBasedOnDocumentFeatures([node, path], editor, {
formatting,
dividers,
layouts,
links,
images,
tables
}) {
if (node.type === 'heading' && (!formatting.headings.levels.length || !formatting.headings.levels.includes(node.level)) || node.type === 'ordered-list' && !formatting.listTypes.ordered || node.type === 'unordered-list' && !formatting.listTypes.unordered || node.type === 'code' && !formatting.blockTypes.code || node.type === 'blockquote' && !formatting.blockTypes.blockquote || node.type === 'image' && !images || node.type === 'table' && !tables || node.type === 'layout' && (layouts.length === 0 || !layouts.some(layout => areArraysEqual(layout, node.layout)))) {
Transforms.unwrapNodes(editor, {
at: path
});
return true;
}
if ((node.type === 'paragraph' || node.type === 'heading') && (!formatting.alignment.center && node.textAlign === 'center' || !formatting.alignment.end && node.textAlign === 'end' || 'textAlign' in node && node.textAlign !== 'center' && node.textAlign !== 'end')) {
Transforms.unsetNodes(editor, 'textAlign', {
at: path
});
return true;
}
if (node.type === 'divider' && !dividers) {
Transforms.removeNodes(editor, {
at: path
});
return true;
}
return normalizeInlineBasedOnLinks([node, path], editor, links);
}
function withDocumentFeaturesNormalization(documentFeatures, editor) {
const {
normalizeNode
} = editor;
editor.normalizeNode = ([node, path]) => {
if (Text$1.isText(node)) {
normalizeTextBasedOnInlineMarksAndSoftBreaks([node, path], editor, documentFeatures.formatting.inlineMarks, documentFeatures.formatting.softBreaks);
} else if (Element.isElement(node)) {
normalizeElementBasedOnDocumentFeatures([node, path], editor, documentFeatures);
}
normalizeNode([node, path]);
};
return editor;
}
function CollabAddToPathProvider(props) {
const yjsInfo = useYjsIfAvailable();
const cloudInfo = useCloudInfo();
const router = useRouter();
const awarenessStates = useAwarenessStates();
const avatarsAtPath = useMemo(() => {
if (!yjsInfo || yjsInfo === 'loading' || !cloudInfo) {
return [];
}
const avatars = [];
for (const [clientId, val] of awarenessStates) {
if (clientId === yjsInfo.awareness.clientID || !val.user || router.href !== `/keystatic/branch/${val.branch}/${val.location}` || !Array.isArray(val.path) || !areArraysEqual(val.path, props.path)) {
continue;
}
avatars.push(val.user);
}
return avatars;
}, [yjsInfo, cloudInfo, awarenessStates, router.href, props.path]);
return /*#__PURE__*/jsxs("div", {
"data-ks-path": JSON.stringify(props.path),
onFocus: e => {
if (e.target.closest('[data-ks-path]') === e.currentTarget) {
if (yjsInfo && yjsInfo !== 'loading') {
yjsInfo.awareness.setLocalStateField('path', props.path);
}
}
},
children: [!!avatarsAtPath.length && /*#__PURE__*/jsx("div", {
className: css$1({
position: 'relative',
width: '100%',
height: 0
}),
children: /*#__PURE__*/jsx("div", {
className: css$1({
position: 'absolute',
top: 0,
right: 0,
display: 'flex',
gap: '0.5em'
}),
children: avatarsAtPath.map((avatar, i) => /*#__PURE__*/jsx(Avatar, {
size: "xsmall",
src: avatar.avatarUrl,
name: avatar.name
}, i))
})
}), props.children]
});
}
function AddToPathProvider(props) {
const $ = c(9);
const path = useContext(PathContext);
const config = useConfig();
let t0;
let t1;
if ($[0] !== path || $[1] !== props.part) {
t1 = path.concat(props.part);
$[0] = path;
$[1] = props.part;
$[2] = t1;
} else {
t1 = $[2];
}
t0 = t1;
const newPath = t0;
let t2;
if ($[3] !== newPath || $[4] !== props.children) {
t2 = /*#__PURE__*/jsx(PathContext.Provider, {
value: newPath,
children: props.children
});
$[3] = newPath;
$[4] = props.children;
$[5] = t2;
} else {
t2 = $[5];
}
const inner = t2;
if (config.storage.kind === "cloud") {
let t3;
if ($[6] !== newPath || $[7] !== inner) {
t3 = /*#__PURE__*/jsx(CollabAddToPathProvider, {
path: newPath,
children: inner
});
$[6] = newPath;
$[7] = inner;
$[8] = t3;
} else {
t3 = $[8];
}
return t3;
}
return inner;
}
const SlugFieldContext = /*#__PURE__*/createContext(undefined);
const SlugFieldProvider = SlugFieldContext.Provider;
const PathContext = /*#__PURE__*/createContext([]);
const PathContextProvider = PathContext.Provider;
function validateText(val, min, max, fieldLabel, slugInfo, pattern) {
if (val.length < min) {
if (min === 1) {
return `${fieldLabel} must not be empty`;
} else {
return `${fieldLabel} must be at least ${min} characters long`;
}
}
if (val.length > max) {
return `${fieldLabel} must be no longer than ${max} characters`;
}
if (pattern && !pattern.regex.test(val)) {
return pattern.message || `${fieldLabel} must match the pattern ${pattern.regex}`;
}
if (slugInfo) {
if (val === '') {
return `${fieldLabel} must not be empty`;
}
if (val === '..') {
return `${fieldLabel} must not be ..`;
}
if (val === '.') {
return `${fieldLabel} must not be .`;
}
if (slugInfo.glob === '**') {
const split = val.split('/');
if (split.some(s => s === '..')) {
return `${fieldLabel} must not contain ..`;
}
if (split.some(s => s === '.')) {
return `${fieldLabel} must not be .`;
}
}
if ((slugInfo.glob === '*' ? /[\\/]/ : /[\\]/).test(val)) {
return `${fieldLabel} must not contain slashes`;
}
if (/^\s|\s$/.test(val)) {
return `${fieldLabel} must not start or end with spaces`;
}
if (slugInfo.slugs.has(val)) {
return `${fieldLabel} must be unique`;
}
}
}
function TextFieldInput(props) {
const $ = c(15);
const TextFieldComponent = props.multiline ? TextArea : TextField;
const [blurred, setBlurred] = useState(false);
const slugContext = useContext(SlugFieldContext);
const path = useContext(PathContext);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = () => setBlurred(true);
$[0] = t0;
} else {
t0 = $[0];
}
const t1 = props.min > 0;
let t2;
if ($[1] !== props || $[2] !== blurred || $[3] !== path || $[4] !== slugContext) {
t2 = props.forceValidation || blurred ? validateText(props.value, props.min, props.max, props.label, path.length === 1 && (slugContext === null || slugContext === void 0 ? void 0 : slugContext.field) === path[0] ? slugContext : undefined, props.pattern) : undefined;
$[1] = props;
$[2] = blurred;
$[3] = path;
$[4] = slugContext;
$[5] = t2;
} else {
t2 = $[5];
}
let t3;
if ($[6] !== TextFieldComponent || $[7] !== props.label || $[8] !== props.description || $[9] !== props.autoFocus || $[10] !== props.value || $[11] !== props.onChange || $[12] !== t1 || $[13] !== t2) {
t3 = /*#__PURE__*/jsx(TextFieldComponent, {
label: props.label,
description: props.description,
autoFocus: props.autoFocus,
value: props.value,
onChange: props.onChange,
onBlur: t0,
isRequired: t1,
errorMessage: t2
});
$[6] = TextFieldComponent;
$[7] = props.label;
$[8] = props.description;
$[9] = props.autoFocus;
$[10] = props.value;
$[11] = props.onChange;
$[12] = t1;
$[13] = t2;
$[14] = t3;
} else {
t3 = $[14];
}
return t3;
}
function parseAsNormalField(value) {
if (value === undefined) {
return '';
}
if (typeof value !== 'string') {
throw new FieldDataError('Must be a string');
}
return value;
}
const emptySet = new Set();
function text({
label,
defaultValue = '',
validation: {
length: {
max = Infinity,
min = 0
} = {},
pattern,
isRequired
} = {},
description,
multiline = false
}) {
min = Math.max(isRequired ? 1 : 0, min);
function validate(value, slugField) {
const message = validateText(value, min, max, label, slugField, pattern);
if (message !== undefined) {
throw new FieldDataError(message);
}
return value;
}
return {
kind: 'form',
formKind: 'slug',
label,
Input(props) {
return /*#__PURE__*/jsx(TextFieldInput, {
label: label,
description: description,
min: min,
max: max,
multiline: multiline,
pattern: pattern,
...props
});
},
defaultValue() {
return typeof defaultValue === 'string' ? defaultValue : defaultValue();
},
parse(value, args) {
if ((args === null || args === void 0 ? void 0 : args.slug) !== undefined) {
return args.slug;
}
return parseAsNormalField(value);
},
serialize(value) {
return {
value: value === '' ? undefined : value
};
},
serializeWithSlug(value) {
return {
slug: value,
value: undefined
};
},
reader: {
parse(value) {
const parsed = parseAsNormalField(value);
return validate(parsed, undefined);
},
parseWithSlug(_value, args) {
validate(parseAsNormalField(args.slug), {
glob: args.glob,
slugs: emptySet
});
return null;
}
},
validate(value, args) {
return validate(value, args === null || args === void 0 ? void 0 : args.slugField);
}
};
}
export { setDraft as $, useCloudInfo as A, useAwarenessStates as B, getSyncAuth as C, redirectToCloudAuth as D, CloudAppShellQuery as E, useSetTreeSha as F, GitHubAppShellQuery as G, useCurrentUnscopedTree as H, updateTreeWithChanges as I, hydrateTreeCacheWithEntries as J, KEYSTATIC_CLOUD_API_URL as K, LOADING as L, scopeEntriesWithPathPrefix as M, fetchGitHubTreeData as N, treeSha as O, getSlugFromState as P, useYjs as Q, getDraft as R, useYjsIfAvailable as S, getCollection as T, suspendOnData as U, useShowRestoredDraftMessage as V, useEventCallback as W, getBranchPrefix as X, getRepoUrl as Y, getDataFileExtension as Z, isGitHubConfig as _, useTree as a, basicFormFieldWithSimpleReaderParse as a$, delDraft as a0, useViewer as a1, useContentPanelState as a2, ContentPanelProvider as a3, AppShellErrorContext as a4, AppStateContext as a5, ConfigContext as a6, GitHubAppShellProvider as a7, LocalAppShellProvider as a8, useBranches as a9, PathContextProvider as aA, SlugFieldProvider as aB, useElementWithSetNodes as aC, useActiveBlockPopover as aD, BlockPopoverTrigger as aE, BlockPopover as aF, focusWithPreviousSelection as aG, cloneDescendent as aH, areArraysEqual as aI, getSchemaAtPropPath as aJ, NotEditable as aK, getDocumentFeaturesForChildField as aL, getAncestorSchemas as aM, normalizeTextBasedOnInlineMarksAndSoftBreaks as aN, normalizeElementBasedOnDocumentFeatures as aO, normalizeInlineBasedOnLinks as aP, insertNodesButReplaceIfSelectionIsAtEmptyParagraphOrHeading as aQ, clearFormatting as aR, EditorAfterButIgnoringingPointsWithNoContent as aS, isInlineContainer as aT, withDocumentFeaturesNormalization as aU, editorSchema as aV, useContentPanelSize as aW, ActiveBlockPopoverProvider as aX, getPlaceholderTextForPropPath as aY, getWholeDocumentFeaturesForChildField as aZ, fixPath as a_, GitHubAppShellDataContext as aa, getSingletonFormat as ab, getSingletonPath as ac, isCloudConfig as ad, assertValidRepoConfig as ae, RouterProvider as af, CloudInfoProvider as ag, GitHubAppShellDataProvider as ah, useAppState as ai, useChanged as aj, Ref_base as ak, blockElementSpacing as al, useImageLibraryURL as am, useRawCloudInfo as an, clearObjectCache as ao, clearDrafts as ap, getCloudAuth as aq, FieldDataError as ar, AddToPathProvider as as, moveChildren as at, isBlock as au, nodeTypeMatcher as av, getAncestorComponentChildFieldDocumentFeatures as aw, allMarks as ax, isElementActive as ay, useContentPanelQuery as az, useBaseCommit as b, text as b0, collectDirectoriesUsedInSchema as b1, emptyImageData as b2, ImageDimensionsInput as b3, parseImageData as b4, loadImageData as b5, UploadImageButton as b6, assertRequired as b7, SlugFieldContext as b8, PathContext as b9, validateText as ba, treeEntriesToTreeNodes as bb, CloudImagePreviewForNewEditor as bc, cloudImageToolbarIcon as bd, handleFile as be, CloudImagePreview as bf, useRepoInfo as c, getDirectoriesForTreeKey as d, getTreeKey as e, useData as f, getEntryDataFilepath as g, getTreeNodeAtPath as h, getBlobFromPersistedCache as i, blobSha as j, serializeRepoConfig as k, getPathPrefix as l, getAuth as m, KEYSTATIC_CLOUD_HEADERS as n, object as o, getCollectionPath as p, useCurrentBranch as q, isLocalConfig as r, setBlobToPersistedCache as s, getEntriesInCollectionWithTreeKey as t, useRouter as u, getCollectionFormat as v, getCollectionItemPath as w, getSlugGlobForCollection as x, parseRepoConfig as y, useConfig as z };