tinacms 3.6.3 → 3.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1092 -11
- package/dist/rich-text/index.d.ts +4 -0
- package/dist/rich-text/index.js +14 -0
- package/dist/toolkit/components/dashboard/dashboard-ui.d.ts +16 -0
- package/dist/toolkit/components/dashboard/media-usage-dashboard/index.d.ts +6 -0
- package/dist/toolkit/components/dashboard/media-usage-dashboard/media-lightbox.d.ts +6 -0
- package/dist/toolkit/components/dashboard/media-usage-dashboard/media-usage-scanner.d.ts +21 -0
- package/dist/toolkit/components/dashboard/media-usage-dashboard/media-usage-table.d.ts +7 -0
- package/dist/toolkit/components/dashboard/media-usage-dashboard/media-usage-thumbnails.d.ts +5 -0
- package/dist/toolkit/components/dashboard/media-usage-dashboard/useMediaUsageScanner.d.ts +8 -0
- package/dist/toolkit/components/ui/dialog.d.ts +16 -0
- package/dist/toolkit/fields/plugins/mdx-field-plugin/plate/components/plate-ui/icons.d.ts +1 -0
- package/dist/toolkit/fields/plugins/mdx-field-plugin/plate/components/plate-ui/mark-toolbar-button.d.ts +1 -0
- package/dist/toolkit/fields/plugins/mdx-field-plugin/plate/plugins/editor-plugins.d.ts +3 -3
- package/dist/toolkit/fields/plugins/mdx-field-plugin/plate/plugins/ui/components.d.ts +2 -0
- package/dist/toolkit/fields/plugins/mdx-field-plugin/plate/toolbar/toolbar-overrides.d.ts +1 -1
- package/dist/toolkit/form-builder/create-branch-modal.d.ts +3 -1
- package/dist/toolkit/plugin-screens/media-usage-dashboard-screen.d.ts +1 -0
- package/dist/toolkit/react-screens/screen-plugin.d.ts +2 -2
- package/package.json +5 -4
package/dist/index.js
CHANGED
|
@@ -16,7 +16,7 @@ import { HEADING_KEYS as HEADING_KEYS$1, HEADING_LEVELS as HEADING_LEVELS$1 } fr
|
|
|
16
16
|
import { isHotkey } from "is-hotkey";
|
|
17
17
|
import { Slot } from "@radix-ui/react-slot";
|
|
18
18
|
import { isLangSupported, formatCodeBlock, insertEmptyCodeBlock, unwrapCodeBlock, isCodeBlockEmpty, isSelectionAtCodeBlockStart } from "@udecode/plate-code-block";
|
|
19
|
-
import { X, Search, ChevronDown, Check, AlertTriangle, BracesIcon, Plus, AlignCenter as AlignCenter$1, AlignJustify, AlignLeft as AlignLeft$1, AlignRight as AlignRight$1, PaintBucket, Quote, ChevronRight, ChevronsUpDown, FileCode, Baseline, RectangleVertical, Combine, Ungroup, MessageSquare, MessageSquarePlus, Trash, GripVertical, Edit2, Smile, ExternalLink, Heading1, Heading2, Heading3, Heading4, Heading5, Heading6, Indent, Keyboard, WrapText, Minus, MoreHorizontal, Outdent, Pilcrow, RotateCcw, RectangleHorizontal, Settings, Strikethrough, Subscript, Superscript, Table as Table$1, Text as Text$2, Underline, Link2Off, Eye, SeparatorHorizontal, Moon, SunMedium, Twitter, PaintBucketIcon, CombineIcon, SquareSplitHorizontalIcon, Grid2X2Icon, Trash2Icon, ArrowUp, ArrowDown, XIcon, ArrowLeft, ArrowRight, EraserIcon, ChevronDownIcon as ChevronDownIcon$1, ChevronUp, Clock, CalendarCheck, Calendar as Calendar$1, CalendarDays, RotateCw, ChevronLeft, ArrowUpDown, LoaderCircle, TriangleAlert, FileStack, History, GitBranchIcon, List as List$1, ListOrdered, Grid3x3Icon, CircleX, Link, Unlink } from "lucide-react";
|
|
19
|
+
import { X, Search, ChevronDown, Check, AlertTriangle, BracesIcon, Plus, AlignCenter as AlignCenter$1, AlignJustify, AlignLeft as AlignLeft$1, AlignRight as AlignRight$1, PaintBucket, Quote, ChevronRight, ChevronsUpDown, FileCode, Baseline, RectangleVertical, Combine, Ungroup, MessageSquare, MessageSquarePlus, Trash, GripVertical, Edit2, Smile, ExternalLink, Heading1, Heading2, Heading3, Heading4, Heading5, Heading6, Indent, Keyboard, WrapText, Minus, MoreHorizontal, Outdent, Pilcrow, RotateCcw, RectangleHorizontal, Settings, Highlighter, Strikethrough, Subscript, Superscript, Table as Table$1, Text as Text$2, Underline, Link2Off, Eye, SeparatorHorizontal, Moon, SunMedium, Twitter, PaintBucketIcon, CombineIcon, SquareSplitHorizontalIcon, Grid2X2Icon, Trash2Icon, ArrowUp, ArrowDown, XIcon, ArrowLeft, ArrowRight, EraserIcon, ChevronDownIcon as ChevronDownIcon$1, ChevronUp, Clock, CalendarCheck, Calendar as Calendar$1, CalendarDays, RotateCw, ChevronLeft, ArrowUpDown, AlertCircle, Image, RefreshCw, Database, CheckCircle2, ImageOff, LoaderCircle, TriangleAlert, FileStack, History, GitBranchIcon, List as List$1, ListOrdered, Grid3x3Icon, CircleX, Link, Unlink } from "lucide-react";
|
|
20
20
|
import mermaid from "mermaid";
|
|
21
21
|
import { cva } from "class-variance-authority";
|
|
22
22
|
import { Command as Command$2 } from "cmdk";
|
|
@@ -35,6 +35,7 @@ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
|
|
|
35
35
|
import * as ToolbarPrimitive from "@radix-ui/react-toolbar";
|
|
36
36
|
import * as SeparatorPrimitive from "@radix-ui/react-separator";
|
|
37
37
|
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
|
38
|
+
import colorString, { get as get$6, to as to$1 } from "color-string";
|
|
38
39
|
import { createForm, FORM_ERROR, getIn } from "final-form";
|
|
39
40
|
import arrayMutators from "final-form-arrays";
|
|
40
41
|
import setFieldData from "final-form-set-field-data";
|
|
@@ -46,7 +47,6 @@ import { CSS } from "@dnd-kit/utilities";
|
|
|
46
47
|
import { buildSchema, print, getIntrospectionQuery, buildClientSchema, parse as parse$4 } from "graphql";
|
|
47
48
|
import { diff as diff$1 } from "@graphql-inspector/core";
|
|
48
49
|
import posthog from "posthog-js";
|
|
49
|
-
import { get as get$6, to as to$1 } from "color-string";
|
|
50
50
|
import { HexColorPicker } from "react-colorful";
|
|
51
51
|
import * as dropzone from "react-dropzone";
|
|
52
52
|
import { Command as Command$3 } from "@udecode/cmdk";
|
|
@@ -63,7 +63,7 @@ import "moment-timezone";
|
|
|
63
63
|
import { DayPicker } from "react-day-picker";
|
|
64
64
|
import * as SelectPrimitive from "@radix-ui/react-select";
|
|
65
65
|
import { formatDistanceToNow } from "date-fns";
|
|
66
|
-
import { useReactTable, getCoreRowModel, getSortedRowModel, flexRender } from "@tanstack/react-table";
|
|
66
|
+
import { useReactTable, getCoreRowModel, getSortedRowModel, flexRender, getFilteredRowModel, getExpandedRowModel } from "@tanstack/react-table";
|
|
67
67
|
import { TinaSchema, addNamespaceToSchema, parseURL, resolveForm, normalizePath, canonicalPath, validateSchema } from "@tinacms/schema-tools";
|
|
68
68
|
import { NAMER, resolveField } from "@tinacms/schema-tools";
|
|
69
69
|
import gql from "graphql-tag";
|
|
@@ -27490,6 +27490,35 @@ var CodeBlockPlugin = toPlatePlugin(BaseCodeBlockPlugin, {
|
|
|
27490
27490
|
}
|
|
27491
27491
|
}
|
|
27492
27492
|
}));
|
|
27493
|
+
var BaseHighlightPlugin = createSlatePlugin$1({
|
|
27494
|
+
key: "highlight",
|
|
27495
|
+
node: { isLeaf: true },
|
|
27496
|
+
parsers: {
|
|
27497
|
+
html: {
|
|
27498
|
+
deserializer: {
|
|
27499
|
+
rules: [
|
|
27500
|
+
{
|
|
27501
|
+
validNodeName: ["MARK"]
|
|
27502
|
+
}
|
|
27503
|
+
]
|
|
27504
|
+
}
|
|
27505
|
+
}
|
|
27506
|
+
}
|
|
27507
|
+
});
|
|
27508
|
+
var HighlightPlugin = toPlatePlugin(
|
|
27509
|
+
BaseHighlightPlugin,
|
|
27510
|
+
({ editor, type: type2 }) => ({
|
|
27511
|
+
shortcuts: {
|
|
27512
|
+
toggleHighlight: {
|
|
27513
|
+
keys: [[Key2.Mod, Key2.Shift, "h"]],
|
|
27514
|
+
preventDefault: true,
|
|
27515
|
+
handler: () => {
|
|
27516
|
+
editor.tf.toggleMark(type2);
|
|
27517
|
+
}
|
|
27518
|
+
}
|
|
27519
|
+
}
|
|
27520
|
+
})
|
|
27521
|
+
);
|
|
27493
27522
|
var BaseHorizontalRulePlugin = createSlatePlugin$1({
|
|
27494
27523
|
key: "hr",
|
|
27495
27524
|
node: { isElement: true, isVoid: true },
|
|
@@ -33620,13 +33649,13 @@ const Button$3 = withRef$1(({ asChild = false, className, isMenu, size: size2, v
|
|
|
33620
33649
|
}
|
|
33621
33650
|
);
|
|
33622
33651
|
});
|
|
33623
|
-
const DialogPortal = DialogPrimitive.Portal;
|
|
33624
|
-
const DialogOverlay = withCn(
|
|
33652
|
+
const DialogPortal$1 = DialogPrimitive.Portal;
|
|
33653
|
+
const DialogOverlay$1 = withCn(
|
|
33625
33654
|
DialogPrimitive.Overlay,
|
|
33626
33655
|
"fixed inset-0 z-50 bg-black/80 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0"
|
|
33627
33656
|
);
|
|
33628
33657
|
withRef$1(
|
|
33629
|
-
({ children, className, ...props }, ref) => /* @__PURE__ */ React.createElement(DialogPortal, null, /* @__PURE__ */ React.createElement(DialogOverlay, null), /* @__PURE__ */ React.createElement(
|
|
33658
|
+
({ children, className, ...props }, ref) => /* @__PURE__ */ React.createElement(DialogPortal$1, null, /* @__PURE__ */ React.createElement(DialogOverlay$1, null), /* @__PURE__ */ React.createElement(
|
|
33630
33659
|
DialogPrimitive.Content,
|
|
33631
33660
|
{
|
|
33632
33661
|
ref,
|
|
@@ -34458,6 +34487,7 @@ const Icons = {
|
|
|
34458
34487
|
row: RectangleHorizontal,
|
|
34459
34488
|
search: Search,
|
|
34460
34489
|
settings: Settings,
|
|
34490
|
+
highlight: Highlighter,
|
|
34461
34491
|
strikethrough: Strikethrough,
|
|
34462
34492
|
subscript: Subscript,
|
|
34463
34493
|
superscript: Superscript,
|
|
@@ -37561,6 +37591,30 @@ const uuid = () => {
|
|
|
37561
37591
|
};
|
|
37562
37592
|
const blockClasses = "mt-0.5";
|
|
37563
37593
|
const headerClasses = "font-normal";
|
|
37594
|
+
function getContrastColor(color) {
|
|
37595
|
+
const parsed = colorString.get.rgb(color);
|
|
37596
|
+
if (!parsed)
|
|
37597
|
+
return "#000000";
|
|
37598
|
+
const [r2, g, b] = parsed;
|
|
37599
|
+
const luminance = (0.299 * r2 + 0.587 * g + 0.114 * b) / 255;
|
|
37600
|
+
return luminance > 0.5 ? "#000000" : "#ffffff";
|
|
37601
|
+
}
|
|
37602
|
+
const HighlightLeaf = ({
|
|
37603
|
+
leaf: leaf3,
|
|
37604
|
+
...props
|
|
37605
|
+
}) => {
|
|
37606
|
+
const backgroundColor = leaf3.highlightColor || "#FEF08A";
|
|
37607
|
+
return /* @__PURE__ */ React__default.createElement(
|
|
37608
|
+
PlateLeaf,
|
|
37609
|
+
{
|
|
37610
|
+
as: "mark",
|
|
37611
|
+
className: "rounded-sm",
|
|
37612
|
+
style: { backgroundColor, color: getContrastColor(backgroundColor) },
|
|
37613
|
+
leaf: leaf3,
|
|
37614
|
+
...props
|
|
37615
|
+
}
|
|
37616
|
+
);
|
|
37617
|
+
};
|
|
37564
37618
|
const Components = () => {
|
|
37565
37619
|
return {
|
|
37566
37620
|
[SlashInputPlugin.key]: SlashInputElement,
|
|
@@ -37719,6 +37773,7 @@ const Components = () => {
|
|
|
37719
37773
|
[ListItemPlugin.key]: withProps(PlateElement, { as: "li" }),
|
|
37720
37774
|
[LinkPlugin.key]: LinkElement,
|
|
37721
37775
|
[CodePlugin.key]: CodeLeaf,
|
|
37776
|
+
[HighlightPlugin.key]: HighlightLeaf,
|
|
37722
37777
|
[UnderlinePlugin.key]: withProps(PlateLeaf, { as: "u" }),
|
|
37723
37778
|
[StrikethroughPlugin.key]: withProps(PlateLeaf, { as: "s" }),
|
|
37724
37779
|
[ItalicPlugin.key]: withProps(PlateLeaf, { as: "em" }),
|
|
@@ -39939,6 +39994,9 @@ function MdAccessTime(props) {
|
|
|
39939
39994
|
function MdKeyboardArrowDown(props) {
|
|
39940
39995
|
return GenIcon({ "tag": "svg", "attr": { "viewBox": "0 0 24 24" }, "child": [{ "tag": "path", "attr": { "fill": "none", "d": "M0 0h24v24H0V0z" }, "child": [] }, { "tag": "path", "attr": { "d": "M7.41 8.59 12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z" }, "child": [] }] })(props);
|
|
39941
39996
|
}
|
|
39997
|
+
function MdImage(props) {
|
|
39998
|
+
return GenIcon({ "tag": "svg", "attr": { "viewBox": "0 0 24 24" }, "child": [{ "tag": "path", "attr": { "fill": "none", "d": "M0 0h24v24H0z" }, "child": [] }, { "tag": "path", "attr": { "d": "M21 19V5c0-1.1-.9-2-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2zM8.5 13.5l2.5 3.01L14.5 12l4.5 6H5l3.5-4.5z" }, "child": [] }] })(props);
|
|
39999
|
+
}
|
|
39942
40000
|
function MdArrowForward(props) {
|
|
39943
40001
|
return GenIcon({ "tag": "svg", "attr": { "viewBox": "0 0 24 24" }, "child": [{ "tag": "path", "attr": { "fill": "none", "d": "M0 0h24v24H0z" }, "child": [] }, { "tag": "path", "attr": { "d": "m12 4-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z" }, "child": [] }] })(props);
|
|
39944
40002
|
}
|
|
@@ -46796,6 +46854,882 @@ const PasswordScreenPlugin = createScreen({
|
|
|
46796
46854
|
layout: "fullscreen",
|
|
46797
46855
|
navCategory: "Account"
|
|
46798
46856
|
});
|
|
46857
|
+
const DashboardLoadingState = ({
|
|
46858
|
+
message,
|
|
46859
|
+
progress
|
|
46860
|
+
}) => /* @__PURE__ */ React__default.createElement("div", { className: "flex items-center justify-center p-12" }, /* @__PURE__ */ React__default.createElement("div", { className: "text-center text-gray-500" }, /* @__PURE__ */ React__default.createElement("h3", { className: "text-lg font-semibold text-gray-700 animate-pulse" }, "Loading Dashboard"), /* @__PURE__ */ React__default.createElement("p", { className: "mt-2 text-sm" }, message), progress ? /* @__PURE__ */ React__default.createElement("div", { className: "mt-4 w-72 max-w-full mx-auto" }, /* @__PURE__ */ React__default.createElement("div", { className: "h-2 rounded-full bg-gray-200 overflow-hidden" }, /* @__PURE__ */ React__default.createElement(
|
|
46861
|
+
"div",
|
|
46862
|
+
{
|
|
46863
|
+
className: "h-full bg-tina-orange transition-[width] duration-300 ease-out",
|
|
46864
|
+
style: {
|
|
46865
|
+
width: `${Math.max(0, Math.min(100, progress.value))}%`
|
|
46866
|
+
}
|
|
46867
|
+
}
|
|
46868
|
+
)), /* @__PURE__ */ React__default.createElement("div", { className: "mt-2 flex items-center justify-between text-xs text-gray-500" }, /* @__PURE__ */ React__default.createElement("span", null, progress.label || "In progress"), /* @__PURE__ */ React__default.createElement("span", null, Math.round(progress.value), "%"))) : /* @__PURE__ */ React__default.createElement("div", { className: "mt-4 flex justify-center" }, /* @__PURE__ */ React__default.createElement(LoadingDots, { color: "var(--tina-color-primary)" }))));
|
|
46869
|
+
const DashboardErrorState = ({ message }) => /* @__PURE__ */ React__default.createElement("div", { className: "p-8 text-red-500 bg-red-50 rounded-lg m-8" }, /* @__PURE__ */ React__default.createElement("h3", { className: "text-lg font-bold mb-2 flex items-center gap-2" }, /* @__PURE__ */ React__default.createElement(AlertCircle, { className: "text-xl" }), " Dashboard Error"), /* @__PURE__ */ React__default.createElement("p", null, message));
|
|
46870
|
+
const DashboardTitleBar = ({
|
|
46871
|
+
title,
|
|
46872
|
+
icon,
|
|
46873
|
+
controls
|
|
46874
|
+
}) => /* @__PURE__ */ React__default.createElement("div", { className: "mb-6 flex items-center justify-between gap-4" }, /* @__PURE__ */ React__default.createElement("div", { className: "flex items-center gap-3" }, /* @__PURE__ */ React__default.createElement("span", { className: "text-3xl text-tina-orange" }, icon), /* @__PURE__ */ React__default.createElement("h2", { className: "text-2xl font-bold text-gray-800" }, title)), controls);
|
|
46875
|
+
const MEDIA_USAGE_THUMBNAIL_SIZE = { w: 75, h: 75 };
|
|
46876
|
+
const MEDIA_USAGE_THUMBNAIL_KEY = `${MEDIA_USAGE_THUMBNAIL_SIZE.w}x${MEDIA_USAGE_THUMBNAIL_SIZE.h}`;
|
|
46877
|
+
const getDocumentEditUrl = async (cms, document2) => {
|
|
46878
|
+
var _a2;
|
|
46879
|
+
const collectionName = document2._sys.collection.name;
|
|
46880
|
+
const breadcrumbsDerivedPath = document2._sys.breadcrumbs.join("/");
|
|
46881
|
+
let editUrl = `#/collections/edit/${collectionName}/~/${breadcrumbsDerivedPath}`;
|
|
46882
|
+
try {
|
|
46883
|
+
const collection = cms.api.tina.schema.getCollection(collectionName);
|
|
46884
|
+
if ((_a2 = collection == null ? void 0 : collection.ui) == null ? void 0 : _a2.router) {
|
|
46885
|
+
let customPath = await collection.ui.router({
|
|
46886
|
+
document: document2,
|
|
46887
|
+
collection
|
|
46888
|
+
});
|
|
46889
|
+
if (customPath) {
|
|
46890
|
+
customPath = customPath.startsWith("/") ? customPath.slice(1) : customPath;
|
|
46891
|
+
const tinaBasePath = cms.flags.get("tina-basepath");
|
|
46892
|
+
const tinaPreviewIsSet = cms.flags.get("tina-preview");
|
|
46893
|
+
if (tinaPreviewIsSet) {
|
|
46894
|
+
if (tinaBasePath) {
|
|
46895
|
+
editUrl = `#/~/${tinaBasePath}/${customPath}`;
|
|
46896
|
+
} else {
|
|
46897
|
+
editUrl = `#/~/${customPath}`;
|
|
46898
|
+
}
|
|
46899
|
+
}
|
|
46900
|
+
}
|
|
46901
|
+
}
|
|
46902
|
+
} catch (e3) {
|
|
46903
|
+
console.error(
|
|
46904
|
+
"Unable to determine custom edit URL for document. Falling back to default edit URL.",
|
|
46905
|
+
e3
|
|
46906
|
+
);
|
|
46907
|
+
}
|
|
46908
|
+
return editUrl;
|
|
46909
|
+
};
|
|
46910
|
+
const scanDocumentForMedia = (documentContent, mediaUsages) => {
|
|
46911
|
+
const matchedIds = /* @__PURE__ */ new Set();
|
|
46912
|
+
for (const mediaUsage of mediaUsages) {
|
|
46913
|
+
const src = JSON.stringify(mediaUsage.media.src).slice(1, -1);
|
|
46914
|
+
let index = documentContent.indexOf(src);
|
|
46915
|
+
while (index !== -1) {
|
|
46916
|
+
const prevChar = documentContent[index - 1];
|
|
46917
|
+
const nextChar = documentContent[index + src.length];
|
|
46918
|
+
const isPrevValid = !prevChar || !/[a-zA-Z0-9_.%~+-]/.test(prevChar);
|
|
46919
|
+
const isNextValid = !nextChar || !/[a-zA-Z0-9_.%~+-]/.test(nextChar);
|
|
46920
|
+
if (isPrevValid && isNextValid) {
|
|
46921
|
+
matchedIds.add(mediaUsage.media.id);
|
|
46922
|
+
break;
|
|
46923
|
+
}
|
|
46924
|
+
index = documentContent.indexOf(src, index + 1);
|
|
46925
|
+
}
|
|
46926
|
+
}
|
|
46927
|
+
return matchedIds;
|
|
46928
|
+
};
|
|
46929
|
+
const THUMBNAIL_SIZES = [MEDIA_USAGE_THUMBNAIL_SIZE];
|
|
46930
|
+
const BATCH_SIZE = 100;
|
|
46931
|
+
const UI_YIELD_INTERVAL = 50;
|
|
46932
|
+
const collectAllMedia = async (cms, mediaIdToUsageMap, currentDirectory) => {
|
|
46933
|
+
const subDirectories = [];
|
|
46934
|
+
let currentOffset = void 0;
|
|
46935
|
+
let moreOffsetsAvailable = true;
|
|
46936
|
+
while (moreOffsetsAvailable) {
|
|
46937
|
+
const list = await cms.media.list({
|
|
46938
|
+
directory: currentDirectory,
|
|
46939
|
+
offset: currentOffset,
|
|
46940
|
+
thumbnailSizes: THUMBNAIL_SIZES
|
|
46941
|
+
});
|
|
46942
|
+
for (const item of list.items) {
|
|
46943
|
+
if (item.type === "file") {
|
|
46944
|
+
if (item.src) {
|
|
46945
|
+
const isImg = isImage(item.filename);
|
|
46946
|
+
const isVid = isVideo(item.filename);
|
|
46947
|
+
const type2 = isImg ? "image" : isVid ? "video" : "other";
|
|
46948
|
+
mediaIdToUsageMap[item.id] = {
|
|
46949
|
+
media: item,
|
|
46950
|
+
type: type2,
|
|
46951
|
+
usedIn: []
|
|
46952
|
+
};
|
|
46953
|
+
}
|
|
46954
|
+
} else if (item.type === "dir") {
|
|
46955
|
+
const directorySegment = item.filename.replace(/^\/+|\/+$/g, "");
|
|
46956
|
+
subDirectories.push(
|
|
46957
|
+
currentDirectory ? `${currentDirectory}/${directorySegment}` : directorySegment
|
|
46958
|
+
);
|
|
46959
|
+
}
|
|
46960
|
+
}
|
|
46961
|
+
currentOffset = list.nextOffset;
|
|
46962
|
+
if (!currentOffset) {
|
|
46963
|
+
moreOffsetsAvailable = false;
|
|
46964
|
+
}
|
|
46965
|
+
}
|
|
46966
|
+
if (subDirectories.length > 0) {
|
|
46967
|
+
await Promise.all(
|
|
46968
|
+
subDirectories.map((dir) => collectAllMedia(cms, mediaIdToUsageMap, dir))
|
|
46969
|
+
);
|
|
46970
|
+
}
|
|
46971
|
+
};
|
|
46972
|
+
const scanCollectionForMediaUsage = async (cms, collectionMeta, mediaIdToUsageMap) => {
|
|
46973
|
+
var _a2, _b;
|
|
46974
|
+
let hasNextPage = true;
|
|
46975
|
+
let after3 = void 0;
|
|
46976
|
+
const mediaUsages = Object.values(mediaIdToUsageMap);
|
|
46977
|
+
const listQuery = `
|
|
46978
|
+
query($after: String, $first: Float) {
|
|
46979
|
+
collectionConnection: ${collectionMeta.name}Connection(first: $first, after: $after) {
|
|
46980
|
+
pageInfo { hasNextPage, endCursor }
|
|
46981
|
+
edges {
|
|
46982
|
+
node {
|
|
46983
|
+
... on Document {
|
|
46984
|
+
_sys {
|
|
46985
|
+
relativePath
|
|
46986
|
+
filename
|
|
46987
|
+
basename
|
|
46988
|
+
path
|
|
46989
|
+
breadcrumbs
|
|
46990
|
+
extension
|
|
46991
|
+
template
|
|
46992
|
+
title
|
|
46993
|
+
hasReferences
|
|
46994
|
+
collection {
|
|
46995
|
+
name
|
|
46996
|
+
label
|
|
46997
|
+
path
|
|
46998
|
+
format
|
|
46999
|
+
}
|
|
47000
|
+
}
|
|
47001
|
+
_values
|
|
47002
|
+
}
|
|
47003
|
+
}
|
|
47004
|
+
}
|
|
47005
|
+
}
|
|
47006
|
+
}
|
|
47007
|
+
`;
|
|
47008
|
+
while (hasNextPage) {
|
|
47009
|
+
try {
|
|
47010
|
+
const res = await cms.api.tina.request(listQuery, {
|
|
47011
|
+
variables: { after: after3, first: BATCH_SIZE }
|
|
47012
|
+
});
|
|
47013
|
+
const connection = res.collectionConnection;
|
|
47014
|
+
const edges2 = (connection == null ? void 0 : connection.edges) || [];
|
|
47015
|
+
hasNextPage = ((_a2 = connection == null ? void 0 : connection.pageInfo) == null ? void 0 : _a2.hasNextPage) || false;
|
|
47016
|
+
after3 = (_b = connection == null ? void 0 : connection.pageInfo) == null ? void 0 : _b.endCursor;
|
|
47017
|
+
for (let i2 = 0; i2 < edges2.length; i2++) {
|
|
47018
|
+
if (i2 > 0 && i2 % UI_YIELD_INTERVAL === 0) {
|
|
47019
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
47020
|
+
}
|
|
47021
|
+
const edge = edges2[i2];
|
|
47022
|
+
const matchedIds = scanDocumentForMedia(
|
|
47023
|
+
JSON.stringify(edge.node._values),
|
|
47024
|
+
mediaUsages
|
|
47025
|
+
);
|
|
47026
|
+
if (matchedIds.size === 0)
|
|
47027
|
+
continue;
|
|
47028
|
+
const editUrl = await getDocumentEditUrl(cms, edge.node);
|
|
47029
|
+
matchedIds.forEach((mediaId) => {
|
|
47030
|
+
const usage = mediaIdToUsageMap[mediaId];
|
|
47031
|
+
if (usage) {
|
|
47032
|
+
usage.usedIn.push({
|
|
47033
|
+
collectionName: collectionMeta.name,
|
|
47034
|
+
breadcrumbs: edge.node._sys.breadcrumbs,
|
|
47035
|
+
collectionLabel: collectionMeta.label || collectionMeta.name,
|
|
47036
|
+
// label is optional in the schema
|
|
47037
|
+
editUrl
|
|
47038
|
+
});
|
|
47039
|
+
}
|
|
47040
|
+
});
|
|
47041
|
+
}
|
|
47042
|
+
} catch (e3) {
|
|
47043
|
+
console.error(`Error processing collection ${collectionMeta.name}:`, e3);
|
|
47044
|
+
throw e3;
|
|
47045
|
+
}
|
|
47046
|
+
}
|
|
47047
|
+
};
|
|
47048
|
+
const scanAllMedia = async (cms, onProgress) => {
|
|
47049
|
+
const mediaIdToUsageMap = {};
|
|
47050
|
+
await collectAllMedia(cms, mediaIdToUsageMap, "");
|
|
47051
|
+
const collectionMetas = cms.api.tina.schema.getCollections();
|
|
47052
|
+
for (let i2 = 0; i2 < collectionMetas.length; i2++) {
|
|
47053
|
+
await scanCollectionForMediaUsage(
|
|
47054
|
+
cms,
|
|
47055
|
+
collectionMetas[i2],
|
|
47056
|
+
mediaIdToUsageMap
|
|
47057
|
+
);
|
|
47058
|
+
onProgress == null ? void 0 : onProgress((i2 + 1) / collectionMetas.length * 100);
|
|
47059
|
+
}
|
|
47060
|
+
return Object.values(mediaIdToUsageMap);
|
|
47061
|
+
};
|
|
47062
|
+
const useMediaUsageScanner = () => {
|
|
47063
|
+
const cms = useCMS$1();
|
|
47064
|
+
const [mediaItems, setMediaItems] = useState([]);
|
|
47065
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
47066
|
+
const [errorOccurred, setErrorOccurred] = useState(false);
|
|
47067
|
+
const [progress, setProgress] = useState(0);
|
|
47068
|
+
const activeRef = useRef(true);
|
|
47069
|
+
const scanMedia = useCallback(async () => {
|
|
47070
|
+
setIsLoading(true);
|
|
47071
|
+
setErrorOccurred(false);
|
|
47072
|
+
setProgress(0);
|
|
47073
|
+
try {
|
|
47074
|
+
const updatedMediaItems = await scanAllMedia(cms, setProgress);
|
|
47075
|
+
if (activeRef.current)
|
|
47076
|
+
setMediaItems(updatedMediaItems);
|
|
47077
|
+
} catch (e3) {
|
|
47078
|
+
console.error("Error scanning media usage:", e3);
|
|
47079
|
+
if (activeRef.current)
|
|
47080
|
+
setErrorOccurred(true);
|
|
47081
|
+
} finally {
|
|
47082
|
+
if (activeRef.current)
|
|
47083
|
+
setIsLoading(false);
|
|
47084
|
+
}
|
|
47085
|
+
}, [cms]);
|
|
47086
|
+
useEffect(() => {
|
|
47087
|
+
activeRef.current = true;
|
|
47088
|
+
scanMedia().catch((e3) => {
|
|
47089
|
+
console.error("Unhandled rejection from scanMedia:", e3);
|
|
47090
|
+
});
|
|
47091
|
+
return () => {
|
|
47092
|
+
activeRef.current = false;
|
|
47093
|
+
};
|
|
47094
|
+
}, [scanMedia]);
|
|
47095
|
+
return {
|
|
47096
|
+
mediaItems,
|
|
47097
|
+
isLoading,
|
|
47098
|
+
errorOccurred,
|
|
47099
|
+
progress,
|
|
47100
|
+
refresh: scanMedia
|
|
47101
|
+
};
|
|
47102
|
+
};
|
|
47103
|
+
function Dialog({
|
|
47104
|
+
...props
|
|
47105
|
+
}) {
|
|
47106
|
+
return /* @__PURE__ */ React.createElement(DialogPrimitive.Root, { "data-slot": "dialog", ...props });
|
|
47107
|
+
}
|
|
47108
|
+
function DialogPortal({
|
|
47109
|
+
...props
|
|
47110
|
+
}) {
|
|
47111
|
+
return /* @__PURE__ */ React.createElement(DialogPrimitive.Portal, { "data-slot": "dialog-portal", ...props });
|
|
47112
|
+
}
|
|
47113
|
+
const DialogOverlay = React.forwardRef(({ className, ...props }, ref) => /* @__PURE__ */ React.createElement(
|
|
47114
|
+
DialogPrimitive.Overlay,
|
|
47115
|
+
{
|
|
47116
|
+
ref,
|
|
47117
|
+
"data-slot": "dialog-overlay",
|
|
47118
|
+
className: cn(
|
|
47119
|
+
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 bg-black/10 duration-100 supports-backdrop-filter:backdrop-blur-xs fixed inset-0 isolate z-[100000]",
|
|
47120
|
+
className
|
|
47121
|
+
),
|
|
47122
|
+
...props
|
|
47123
|
+
}
|
|
47124
|
+
));
|
|
47125
|
+
function DialogContent({
|
|
47126
|
+
className,
|
|
47127
|
+
children,
|
|
47128
|
+
showCloseButton = true,
|
|
47129
|
+
...props
|
|
47130
|
+
}) {
|
|
47131
|
+
return /* @__PURE__ */ React.createElement(DialogPortal, null, /* @__PURE__ */ React.createElement(DialogOverlay, null), /* @__PURE__ */ React.createElement(
|
|
47132
|
+
DialogPrimitive.Content,
|
|
47133
|
+
{
|
|
47134
|
+
"data-slot": "dialog-content",
|
|
47135
|
+
className: cn(
|
|
47136
|
+
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 ring-foreground/10 grid max-w-[calc(100%-2rem)] gap-4 rounded-xl p-4 text-sm ring-1 duration-100 sm:max-w-sm fixed top-1/2 left-1/2 z-[100000] w-full -translate-x-1/2 -translate-y-1/2 outline-none",
|
|
47137
|
+
className
|
|
47138
|
+
),
|
|
47139
|
+
...props
|
|
47140
|
+
},
|
|
47141
|
+
children,
|
|
47142
|
+
showCloseButton && /* @__PURE__ */ React.createElement(DialogPrimitive.Close, { "data-slot": "dialog-close", asChild: true }, /* @__PURE__ */ React.createElement(
|
|
47143
|
+
Button,
|
|
47144
|
+
{
|
|
47145
|
+
variant: "ghost",
|
|
47146
|
+
size: "icon",
|
|
47147
|
+
className: "absolute top-2 right-2 h-8 w-8"
|
|
47148
|
+
},
|
|
47149
|
+
/* @__PURE__ */ React.createElement(XIcon, null),
|
|
47150
|
+
/* @__PURE__ */ React.createElement("span", { className: "sr-only" }, "Close")
|
|
47151
|
+
))
|
|
47152
|
+
));
|
|
47153
|
+
}
|
|
47154
|
+
function DialogTitle({
|
|
47155
|
+
className,
|
|
47156
|
+
...props
|
|
47157
|
+
}) {
|
|
47158
|
+
return /* @__PURE__ */ React.createElement(
|
|
47159
|
+
DialogPrimitive.Title,
|
|
47160
|
+
{
|
|
47161
|
+
"data-slot": "dialog-title",
|
|
47162
|
+
className: cn("text-base leading-none font-medium", className),
|
|
47163
|
+
...props
|
|
47164
|
+
}
|
|
47165
|
+
);
|
|
47166
|
+
}
|
|
47167
|
+
DialogTitle.displayName = DialogPrimitive.Title.displayName;
|
|
47168
|
+
function DialogDescription({
|
|
47169
|
+
className,
|
|
47170
|
+
...props
|
|
47171
|
+
}) {
|
|
47172
|
+
return /* @__PURE__ */ React.createElement(
|
|
47173
|
+
DialogPrimitive.Description,
|
|
47174
|
+
{
|
|
47175
|
+
"data-slot": "dialog-description",
|
|
47176
|
+
className: cn(
|
|
47177
|
+
"text-muted-foreground *:[a]:hover:text-foreground text-sm *:[a]:underline *:[a]:underline-offset-3",
|
|
47178
|
+
className
|
|
47179
|
+
),
|
|
47180
|
+
...props
|
|
47181
|
+
}
|
|
47182
|
+
);
|
|
47183
|
+
}
|
|
47184
|
+
DialogDescription.displayName = DialogPrimitive.Description.displayName;
|
|
47185
|
+
const MediaLightbox = ({
|
|
47186
|
+
item,
|
|
47187
|
+
onClose
|
|
47188
|
+
}) => {
|
|
47189
|
+
if (!item)
|
|
47190
|
+
return null;
|
|
47191
|
+
const usageCount = item.usedIn.length;
|
|
47192
|
+
const mediaSrc = item.media.src;
|
|
47193
|
+
const directory = item.media.directory || "/";
|
|
47194
|
+
return /* @__PURE__ */ React__default.createElement(Dialog, { open: true, onOpenChange: (isOpen) => !isOpen && onClose() }, /* @__PURE__ */ React__default.createElement(DialogContent, { className: "w-auto max-w-[95vw] border border-gray-200 bg-white px-4 pt-12 pb-4 shadow-xl sm:max-w-fit" }, /* @__PURE__ */ React__default.createElement(DialogTitle, { className: "sr-only" }, "Preview: ", item.media.filename), /* @__PURE__ */ React__default.createElement(DialogDescription, { className: "sr-only" }, usageCount > 0 ? `Used in ${usageCount} ${usageCount === 1 ? "document" : "documents"}` : "Unused media file"), /* @__PURE__ */ React__default.createElement("div", { className: "mx-auto w-fit max-w-full rounded-lg border border-gray-200 bg-gray-50 p-2 sm:p-3" }, item.type === "video" ? /* @__PURE__ */ React__default.createElement(
|
|
47195
|
+
VideoLightboxContent,
|
|
47196
|
+
{
|
|
47197
|
+
src: mediaSrc,
|
|
47198
|
+
filename: item.media.filename
|
|
47199
|
+
}
|
|
47200
|
+
) : /* @__PURE__ */ React__default.createElement(
|
|
47201
|
+
ImageLightboxContent,
|
|
47202
|
+
{
|
|
47203
|
+
src: mediaSrc,
|
|
47204
|
+
filename: item.media.filename
|
|
47205
|
+
}
|
|
47206
|
+
)), /* @__PURE__ */ React__default.createElement("div", { className: "mt-3 space-y-1 text-center" }, /* @__PURE__ */ React__default.createElement("div", { className: "text-sm font-medium text-gray-700" }, /* @__PURE__ */ React__default.createElement("span", { className: "break-all" }, item.media.filename)), /* @__PURE__ */ React__default.createElement("div", { className: "text-xs text-gray-500" }, "Directory:", " ", /* @__PURE__ */ React__default.createElement("span", { className: "break-all font-medium" }, directory)), /* @__PURE__ */ React__default.createElement("div", { className: "text-sm font-medium text-gray-700" }, usageCount > 0 ? /* @__PURE__ */ React__default.createElement("span", { className: "text-tina-orange font-semibold" }, "Used in ", usageCount, " ", usageCount === 1 ? "doc" : "docs") : /* @__PURE__ */ React__default.createElement("span", { className: "font-semibold text-gray-500" }, "Unused")))));
|
|
47207
|
+
};
|
|
47208
|
+
const ImageLightboxContent = ({
|
|
47209
|
+
src,
|
|
47210
|
+
filename
|
|
47211
|
+
}) => /* @__PURE__ */ React__default.createElement(
|
|
47212
|
+
"img",
|
|
47213
|
+
{
|
|
47214
|
+
src,
|
|
47215
|
+
alt: filename,
|
|
47216
|
+
className: "mx-auto block max-w-full max-h-[75vh] object-contain rounded-md shadow-sm"
|
|
47217
|
+
}
|
|
47218
|
+
);
|
|
47219
|
+
const VideoLightboxContent = ({
|
|
47220
|
+
src,
|
|
47221
|
+
filename
|
|
47222
|
+
}) => {
|
|
47223
|
+
const [playbackFailed, setPlaybackFailed] = React__default.useState(false);
|
|
47224
|
+
React__default.useEffect(() => {
|
|
47225
|
+
setPlaybackFailed(false);
|
|
47226
|
+
return () => {
|
|
47227
|
+
setPlaybackFailed(false);
|
|
47228
|
+
};
|
|
47229
|
+
}, [src]);
|
|
47230
|
+
if (playbackFailed) {
|
|
47231
|
+
return /* @__PURE__ */ React__default.createElement("div", { className: "flex min-h-[12rem] min-w-[16rem] flex-col items-center justify-center gap-3 rounded-md border border-dashed border-gray-300 bg-white px-6 py-8 text-center" }, /* @__PURE__ */ React__default.createElement(BiMovie, { className: "h-10 w-10 text-gray-400" }), /* @__PURE__ */ React__default.createElement("div", { className: "text-sm font-medium text-gray-700" }, filename), /* @__PURE__ */ React__default.createElement("div", { className: "max-w-sm text-sm text-gray-500" }, "This video format is recognized, but this browser could not preview it."));
|
|
47232
|
+
}
|
|
47233
|
+
return /* @__PURE__ */ React__default.createElement(
|
|
47234
|
+
"video",
|
|
47235
|
+
{
|
|
47236
|
+
src,
|
|
47237
|
+
controls: true,
|
|
47238
|
+
playsInline: true,
|
|
47239
|
+
preload: "metadata",
|
|
47240
|
+
className: "mx-auto block h-auto w-[min(80vw,720px)] max-w-full max-h-[75vh] rounded-md bg-black shadow-sm",
|
|
47241
|
+
onError: () => setPlaybackFailed(true)
|
|
47242
|
+
}
|
|
47243
|
+
);
|
|
47244
|
+
};
|
|
47245
|
+
const INFINITE_SCROLL_PAGE_SIZE = 10;
|
|
47246
|
+
const DEFAULT_COLUMN_FILTERS = [
|
|
47247
|
+
{ id: "type", value: "all" },
|
|
47248
|
+
{ id: "usage", value: "all" }
|
|
47249
|
+
];
|
|
47250
|
+
const getUsageCount = (item) => item.usedIn.length;
|
|
47251
|
+
const SortIcon = ({ sorted }) => {
|
|
47252
|
+
if (sorted === "asc")
|
|
47253
|
+
return /* @__PURE__ */ React__default.createElement(ArrowUp, { className: "ml-2 h-4 w-4" });
|
|
47254
|
+
if (sorted === "desc")
|
|
47255
|
+
return /* @__PURE__ */ React__default.createElement(ArrowDown, { className: "ml-2 h-4 w-4" });
|
|
47256
|
+
return /* @__PURE__ */ React__default.createElement(ArrowUpDown, { className: "ml-2 h-4 w-4" });
|
|
47257
|
+
};
|
|
47258
|
+
const mediaTypeFilterFn = (row, _columnId, filterValue) => {
|
|
47259
|
+
if (filterValue === "all")
|
|
47260
|
+
return true;
|
|
47261
|
+
return row.original.type === filterValue;
|
|
47262
|
+
};
|
|
47263
|
+
const usageFilterFn = (row, _columnId, filterValue) => {
|
|
47264
|
+
if (filterValue === "all")
|
|
47265
|
+
return true;
|
|
47266
|
+
const count = getUsageCount(row.original);
|
|
47267
|
+
if (filterValue === "used")
|
|
47268
|
+
return count > 0;
|
|
47269
|
+
if (filterValue === "unused")
|
|
47270
|
+
return count === 0;
|
|
47271
|
+
return true;
|
|
47272
|
+
};
|
|
47273
|
+
const getMediaColumns = (onPreview) => [
|
|
47274
|
+
{
|
|
47275
|
+
// Hidden behavior-only column so TanStack can filter by media type.
|
|
47276
|
+
id: "type",
|
|
47277
|
+
accessorFn: (row) => row.type,
|
|
47278
|
+
filterFn: mediaTypeFilterFn,
|
|
47279
|
+
enableSorting: false,
|
|
47280
|
+
header: () => null,
|
|
47281
|
+
cell: () => null
|
|
47282
|
+
},
|
|
47283
|
+
{
|
|
47284
|
+
id: "preview",
|
|
47285
|
+
header: () => /* @__PURE__ */ React__default.createElement("span", { className: "font-medium text-muted-foreground" }, "Preview"),
|
|
47286
|
+
cell: ({ row }) => {
|
|
47287
|
+
var _a2;
|
|
47288
|
+
const media = row.original.media;
|
|
47289
|
+
const type2 = row.original.type;
|
|
47290
|
+
const usageCount = getUsageCount(row.original);
|
|
47291
|
+
const isExpanded = row.getIsExpanded();
|
|
47292
|
+
const thumbnailSrc = (_a2 = media.thumbnails) == null ? void 0 : _a2[MEDIA_USAGE_THUMBNAIL_KEY];
|
|
47293
|
+
return /* @__PURE__ */ React__default.createElement("div", { className: "flex items-center gap-2" }, usageCount > 0 ? /* @__PURE__ */ React__default.createElement(
|
|
47294
|
+
"button",
|
|
47295
|
+
{
|
|
47296
|
+
type: "button",
|
|
47297
|
+
onClick: (event) => {
|
|
47298
|
+
event.stopPropagation();
|
|
47299
|
+
row.toggleExpanded();
|
|
47300
|
+
},
|
|
47301
|
+
className: `flex h-5 w-5 shrink-0 items-center justify-center rounded-full border transition-colors ${isExpanded ? "border-tina-orange bg-white text-tina-orange" : "border-tina-orange bg-tina-orange text-white"}`,
|
|
47302
|
+
title: "View where this file is used",
|
|
47303
|
+
"aria-label": `Toggle usage references for ${row.original.media.filename}`
|
|
47304
|
+
},
|
|
47305
|
+
/* @__PURE__ */ React__default.createElement(
|
|
47306
|
+
ChevronDown,
|
|
47307
|
+
{
|
|
47308
|
+
className: `h-3.5 w-3.5 transition-transform ${isExpanded ? "rotate-180" : ""}`
|
|
47309
|
+
}
|
|
47310
|
+
)
|
|
47311
|
+
) : /* @__PURE__ */ React__default.createElement("span", { className: "block h-5 w-5 shrink-0", "aria-hidden": "true" }), /* @__PURE__ */ React__default.createElement("div", { className: "flex h-10 w-10 shrink-0 items-center justify-center overflow-hidden rounded border border-gray-200 bg-gray-100 text-gray-400" }, type2 === "image" && thumbnailSrc ? /* @__PURE__ */ React__default.createElement(
|
|
47312
|
+
"img",
|
|
47313
|
+
{
|
|
47314
|
+
src: thumbnailSrc,
|
|
47315
|
+
alt: media.filename,
|
|
47316
|
+
loading: "lazy",
|
|
47317
|
+
decoding: "async",
|
|
47318
|
+
className: "h-full w-full cursor-pointer object-cover transition-opacity hover:opacity-80",
|
|
47319
|
+
onClick: (event) => {
|
|
47320
|
+
event.stopPropagation();
|
|
47321
|
+
onPreview == null ? void 0 : onPreview(row.original);
|
|
47322
|
+
}
|
|
47323
|
+
}
|
|
47324
|
+
) : type2 === "video" ? /* @__PURE__ */ React__default.createElement(
|
|
47325
|
+
"button",
|
|
47326
|
+
{
|
|
47327
|
+
type: "button",
|
|
47328
|
+
title: "Preview video",
|
|
47329
|
+
className: "flex h-full w-full cursor-pointer items-center justify-center text-gray-400 transition-opacity hover:opacity-80",
|
|
47330
|
+
onClick: (event) => {
|
|
47331
|
+
event.stopPropagation();
|
|
47332
|
+
onPreview == null ? void 0 : onPreview(row.original);
|
|
47333
|
+
}
|
|
47334
|
+
},
|
|
47335
|
+
/* @__PURE__ */ React__default.createElement(BiMovie, { className: "text-3xl" })
|
|
47336
|
+
) : /* @__PURE__ */ React__default.createElement(BiFile, { className: "text-3xl" })));
|
|
47337
|
+
},
|
|
47338
|
+
enableSorting: false
|
|
47339
|
+
},
|
|
47340
|
+
{
|
|
47341
|
+
id: "filename",
|
|
47342
|
+
accessorFn: (row) => row.media.filename,
|
|
47343
|
+
header: ({ column }) => {
|
|
47344
|
+
const sorted = column.getIsSorted();
|
|
47345
|
+
return /* @__PURE__ */ React__default.createElement(
|
|
47346
|
+
"button",
|
|
47347
|
+
{
|
|
47348
|
+
type: "button",
|
|
47349
|
+
className: "font-medium text-muted-foreground flex items-center gap-1",
|
|
47350
|
+
onClick: column.getToggleSortingHandler()
|
|
47351
|
+
},
|
|
47352
|
+
"Filename",
|
|
47353
|
+
/* @__PURE__ */ React__default.createElement(SortIcon, { sorted })
|
|
47354
|
+
);
|
|
47355
|
+
},
|
|
47356
|
+
cell: ({ row }) => /* @__PURE__ */ React__default.createElement(TooltipProvider$1, null, /* @__PURE__ */ React__default.createElement(Tooltip$1, { delayDuration: 300 }, /* @__PURE__ */ React__default.createElement(TooltipTrigger$1, { asChild: true }, /* @__PURE__ */ React__default.createElement("span", { className: "block w-full truncate font-medium text-gray-800" }, row.original.media.filename)), /* @__PURE__ */ React__default.createElement(TooltipPortal, null, /* @__PURE__ */ React__default.createElement(TooltipContent$1, { side: "top", className: "max-w-sm break-all shadow-md" }, row.original.media.filename))))
|
|
47357
|
+
},
|
|
47358
|
+
{
|
|
47359
|
+
id: "directory",
|
|
47360
|
+
accessorFn: (row) => row.media.directory || "/",
|
|
47361
|
+
header: ({ column }) => {
|
|
47362
|
+
const sorted = column.getIsSorted();
|
|
47363
|
+
return /* @__PURE__ */ React__default.createElement(
|
|
47364
|
+
"button",
|
|
47365
|
+
{
|
|
47366
|
+
type: "button",
|
|
47367
|
+
className: "font-medium text-muted-foreground flex items-center gap-1",
|
|
47368
|
+
onClick: column.getToggleSortingHandler()
|
|
47369
|
+
},
|
|
47370
|
+
"Directory",
|
|
47371
|
+
/* @__PURE__ */ React__default.createElement(SortIcon, { sorted })
|
|
47372
|
+
);
|
|
47373
|
+
},
|
|
47374
|
+
cell: ({ getValue: getValue2 }) => {
|
|
47375
|
+
const directory = getValue2();
|
|
47376
|
+
return /* @__PURE__ */ React__default.createElement(TooltipProvider$1, null, /* @__PURE__ */ React__default.createElement(Tooltip$1, { delayDuration: 300 }, /* @__PURE__ */ React__default.createElement(TooltipTrigger$1, { asChild: true }, /* @__PURE__ */ React__default.createElement("span", { className: "block w-full truncate text-sm text-gray-500" }, directory)), /* @__PURE__ */ React__default.createElement(TooltipPortal, null, /* @__PURE__ */ React__default.createElement(
|
|
47377
|
+
TooltipContent$1,
|
|
47378
|
+
{
|
|
47379
|
+
side: "top",
|
|
47380
|
+
className: "max-w-sm break-all shadow-md"
|
|
47381
|
+
},
|
|
47382
|
+
directory
|
|
47383
|
+
))));
|
|
47384
|
+
}
|
|
47385
|
+
},
|
|
47386
|
+
{
|
|
47387
|
+
id: "usage",
|
|
47388
|
+
accessorFn: getUsageCount,
|
|
47389
|
+
header: ({ column }) => {
|
|
47390
|
+
const sorted = column.getIsSorted();
|
|
47391
|
+
return /* @__PURE__ */ React__default.createElement(
|
|
47392
|
+
"button",
|
|
47393
|
+
{
|
|
47394
|
+
type: "button",
|
|
47395
|
+
className: "font-medium text-muted-foreground ml-auto flex items-center gap-1",
|
|
47396
|
+
onClick: column.getToggleSortingHandler()
|
|
47397
|
+
},
|
|
47398
|
+
"Usage",
|
|
47399
|
+
/* @__PURE__ */ React__default.createElement(SortIcon, { sorted })
|
|
47400
|
+
);
|
|
47401
|
+
},
|
|
47402
|
+
cell: ({ row }) => {
|
|
47403
|
+
const count = getUsageCount(row.original);
|
|
47404
|
+
return /* @__PURE__ */ React__default.createElement(
|
|
47405
|
+
"span",
|
|
47406
|
+
{
|
|
47407
|
+
className: `inline-block min-w-[3rem] text-right ${count > 0 ? "font-medium text-gray-800" : "text-gray-500"}`
|
|
47408
|
+
},
|
|
47409
|
+
count
|
|
47410
|
+
);
|
|
47411
|
+
},
|
|
47412
|
+
filterFn: usageFilterFn
|
|
47413
|
+
}
|
|
47414
|
+
];
|
|
47415
|
+
const MediaUsageTable = ({
|
|
47416
|
+
mediaItems,
|
|
47417
|
+
onClose,
|
|
47418
|
+
onPreview
|
|
47419
|
+
}) => {
|
|
47420
|
+
var _a2, _b;
|
|
47421
|
+
const [columnFilters, setColumnFilters] = useState(
|
|
47422
|
+
DEFAULT_COLUMN_FILTERS
|
|
47423
|
+
);
|
|
47424
|
+
const [sorting, setSorting] = useState([]);
|
|
47425
|
+
const [expanded, setExpanded] = useState({});
|
|
47426
|
+
const [visibleCount, setVisibleCount] = useState(INFINITE_SCROLL_PAGE_SIZE);
|
|
47427
|
+
const sentinelRef = useRef(null);
|
|
47428
|
+
const scrollContainerRef = useRef(null);
|
|
47429
|
+
const columns = useMemo(() => getMediaColumns(onPreview), [onPreview]);
|
|
47430
|
+
const typeFilter = ((_a2 = columnFilters.find((filter2) => filter2.id === "type")) == null ? void 0 : _a2.value) ?? "all";
|
|
47431
|
+
const usageFilter = ((_b = columnFilters.find((filter2) => filter2.id === "usage")) == null ? void 0 : _b.value) ?? "all";
|
|
47432
|
+
const table = useReactTable({
|
|
47433
|
+
data: mediaItems,
|
|
47434
|
+
columns,
|
|
47435
|
+
getRowId: (row) => row.media.id,
|
|
47436
|
+
initialState: { columnVisibility: { type: false } },
|
|
47437
|
+
state: { sorting, columnFilters, expanded },
|
|
47438
|
+
onColumnFiltersChange: setColumnFilters,
|
|
47439
|
+
onSortingChange: setSorting,
|
|
47440
|
+
onExpandedChange: setExpanded,
|
|
47441
|
+
getCoreRowModel: getCoreRowModel(),
|
|
47442
|
+
getSortedRowModel: getSortedRowModel(),
|
|
47443
|
+
getFilteredRowModel: getFilteredRowModel(),
|
|
47444
|
+
getExpandedRowModel: getExpandedRowModel(),
|
|
47445
|
+
getRowCanExpand: (row) => getUsageCount(row.original) > 0
|
|
47446
|
+
});
|
|
47447
|
+
const handleTypeFilterChange = (value) => {
|
|
47448
|
+
var _a3;
|
|
47449
|
+
setExpanded({});
|
|
47450
|
+
setVisibleCount(INFINITE_SCROLL_PAGE_SIZE);
|
|
47451
|
+
(_a3 = table.getColumn("type")) == null ? void 0 : _a3.setFilterValue(value);
|
|
47452
|
+
};
|
|
47453
|
+
const handleUsageFilterChange = (value) => {
|
|
47454
|
+
var _a3;
|
|
47455
|
+
setExpanded({});
|
|
47456
|
+
setVisibleCount(INFINITE_SCROLL_PAGE_SIZE);
|
|
47457
|
+
(_a3 = table.getColumn("usage")) == null ? void 0 : _a3.setFilterValue(value);
|
|
47458
|
+
};
|
|
47459
|
+
useEffect(() => {
|
|
47460
|
+
setVisibleCount(INFINITE_SCROLL_PAGE_SIZE);
|
|
47461
|
+
}, [sorting]);
|
|
47462
|
+
const allRows = table.getRowModel().rows;
|
|
47463
|
+
const visibleRows = allRows.slice(0, visibleCount);
|
|
47464
|
+
const hasMore = visibleCount < allRows.length;
|
|
47465
|
+
const loadMore = useCallback(() => {
|
|
47466
|
+
setVisibleCount((prev) => prev + INFINITE_SCROLL_PAGE_SIZE);
|
|
47467
|
+
}, []);
|
|
47468
|
+
useEffect(() => {
|
|
47469
|
+
const sentinel = sentinelRef.current;
|
|
47470
|
+
const scrollContainer = scrollContainerRef.current;
|
|
47471
|
+
if (!sentinel || !scrollContainer)
|
|
47472
|
+
return;
|
|
47473
|
+
const observer = new IntersectionObserver(
|
|
47474
|
+
(entries) => {
|
|
47475
|
+
var _a3;
|
|
47476
|
+
if ((_a3 = entries[0]) == null ? void 0 : _a3.isIntersecting) {
|
|
47477
|
+
loadMore();
|
|
47478
|
+
}
|
|
47479
|
+
},
|
|
47480
|
+
{ root: scrollContainer, rootMargin: "200px" }
|
|
47481
|
+
);
|
|
47482
|
+
observer.observe(sentinel);
|
|
47483
|
+
return () => observer.disconnect();
|
|
47484
|
+
}, [loadMore, hasMore]);
|
|
47485
|
+
const filteredRowCount = table.getFilteredRowModel().rows.length;
|
|
47486
|
+
return /* @__PURE__ */ React__default.createElement("div", { className: "bg-white rounded-xl border border-gray-200 shadow-sm overflow-hidden" }, /* @__PURE__ */ React__default.createElement("div", { className: "flex items-center gap-3 border-b border-gray-200 bg-gray-50/50 px-6 py-4" }, /* @__PURE__ */ React__default.createElement("h3", { className: "whitespace-nowrap text-lg font-semibold text-gray-800" }, "Media Inventory"), /* @__PURE__ */ React__default.createElement(
|
|
47487
|
+
MediaFilters,
|
|
47488
|
+
{
|
|
47489
|
+
filteredRowCount,
|
|
47490
|
+
typeFilter,
|
|
47491
|
+
usageFilter,
|
|
47492
|
+
setTypeFilter: handleTypeFilterChange,
|
|
47493
|
+
setUsageFilter: handleUsageFilterChange,
|
|
47494
|
+
className: "ml-auto"
|
|
47495
|
+
}
|
|
47496
|
+
)), /* @__PURE__ */ React__default.createElement("div", { ref: scrollContainerRef, className: "max-h-[45vh] overflow-auto" }, /* @__PURE__ */ React__default.createElement(
|
|
47497
|
+
Table,
|
|
47498
|
+
{
|
|
47499
|
+
className: "border-separate border-spacing-0",
|
|
47500
|
+
wrapperClassName: "overflow-visible"
|
|
47501
|
+
},
|
|
47502
|
+
/* @__PURE__ */ React__default.createElement(TableHeader, null, table.getHeaderGroups().map((headerGroup) => /* @__PURE__ */ React__default.createElement(TableRow, { key: headerGroup.id }, headerGroup.headers.map((header) => /* @__PURE__ */ React__default.createElement(
|
|
47503
|
+
TableHead,
|
|
47504
|
+
{
|
|
47505
|
+
key: header.id,
|
|
47506
|
+
className: `sticky top-0 z-20 border-b border-gray-200 bg-white ${header.column.id === "preview" ? "w-[4rem] pl-9" : header.column.id === "filename" ? "w-[20rem]" : header.column.id === "directory" ? "w-[14rem]" : header.column.id === "usage" ? "w-[5.5rem]" : ""}`
|
|
47507
|
+
},
|
|
47508
|
+
header.isPlaceholder ? null : flexRender(
|
|
47509
|
+
header.column.columnDef.header,
|
|
47510
|
+
header.getContext()
|
|
47511
|
+
)
|
|
47512
|
+
))))),
|
|
47513
|
+
/* @__PURE__ */ React__default.createElement(TableBody, null, allRows.length === 0 ? /* @__PURE__ */ React__default.createElement(TableRow, null, /* @__PURE__ */ React__default.createElement(
|
|
47514
|
+
TableCell,
|
|
47515
|
+
{
|
|
47516
|
+
colSpan: columns.length,
|
|
47517
|
+
className: "!py-8 text-center text-gray-500"
|
|
47518
|
+
},
|
|
47519
|
+
mediaItems.length === 0 ? "No media files found." : "No media files match the current filters."
|
|
47520
|
+
)) : visibleRows.map((row) => /* @__PURE__ */ React__default.createElement(React__default.Fragment, { key: row.id }, /* @__PURE__ */ React__default.createElement(
|
|
47521
|
+
TableRow,
|
|
47522
|
+
{
|
|
47523
|
+
style: { contentVisibility: "auto" },
|
|
47524
|
+
onClick: getUsageCount(row.original) > 0 ? row.getToggleExpandedHandler() : void 0,
|
|
47525
|
+
className: getUsageCount(row.original) > 0 ? row.getIsExpanded() ? "cursor-pointer bg-[#FFF8F6] hover:bg-[#FFF8F6]" : "cursor-pointer" : ""
|
|
47526
|
+
},
|
|
47527
|
+
row.getVisibleCells().map((cell) => /* @__PURE__ */ React__default.createElement(
|
|
47528
|
+
TableCell,
|
|
47529
|
+
{
|
|
47530
|
+
key: cell.id,
|
|
47531
|
+
className: cell.column.id === "preview" ? "!px-2 !py-1.5 w-[4rem]" : cell.column.id === "filename" ? "!px-3 !py-1.5 w-[20rem] max-w-0 overflow-hidden" : cell.column.id === "directory" ? "!px-3 !py-1.5 w-[14rem] max-w-0 overflow-hidden" : cell.column.id === "usage" ? "!pl-2 !pr-4 !py-1.5 w-[5.5rem] text-sm font-medium text-right whitespace-nowrap" : "!px-3 !py-1.5"
|
|
47532
|
+
},
|
|
47533
|
+
flexRender(
|
|
47534
|
+
cell.column.columnDef.cell,
|
|
47535
|
+
cell.getContext()
|
|
47536
|
+
)
|
|
47537
|
+
))
|
|
47538
|
+
), row.getIsExpanded() && /* @__PURE__ */ React__default.createElement(TableRow, null, /* @__PURE__ */ React__default.createElement(
|
|
47539
|
+
TableCell,
|
|
47540
|
+
{
|
|
47541
|
+
colSpan: columns.length,
|
|
47542
|
+
className: "border-b border-gray-150 bg-[#FFEBE5]"
|
|
47543
|
+
},
|
|
47544
|
+
/* @__PURE__ */ React__default.createElement(
|
|
47545
|
+
ExpandedRowContent,
|
|
47546
|
+
{
|
|
47547
|
+
usedIn: row.original.usedIn,
|
|
47548
|
+
onClose
|
|
47549
|
+
}
|
|
47550
|
+
)
|
|
47551
|
+
)))))
|
|
47552
|
+
), hasMore && /* @__PURE__ */ React__default.createElement("div", { ref: sentinelRef, className: "h-px", "aria-hidden": "true" })));
|
|
47553
|
+
};
|
|
47554
|
+
const MediaFilters = ({
|
|
47555
|
+
filteredRowCount,
|
|
47556
|
+
typeFilter,
|
|
47557
|
+
usageFilter,
|
|
47558
|
+
setTypeFilter,
|
|
47559
|
+
setUsageFilter,
|
|
47560
|
+
className
|
|
47561
|
+
}) => /* @__PURE__ */ React__default.createElement("div", { className: `flex items-center gap-3 ${className ?? ""}` }, /* @__PURE__ */ React__default.createElement("span", { className: "text-xs text-gray-500 whitespace-nowrap" }, filteredRowCount, " ", filteredRowCount === 1 ? "result" : "results"), /* @__PURE__ */ React__default.createElement(
|
|
47562
|
+
Select,
|
|
47563
|
+
{
|
|
47564
|
+
value: typeFilter,
|
|
47565
|
+
onValueChange: (value) => setTypeFilter(value)
|
|
47566
|
+
},
|
|
47567
|
+
/* @__PURE__ */ React__default.createElement(
|
|
47568
|
+
SelectTrigger,
|
|
47569
|
+
{
|
|
47570
|
+
"aria-label": "Filter by media type",
|
|
47571
|
+
className: "w-[130px] bg-white border-gray-200 text-gray-700 h-9"
|
|
47572
|
+
},
|
|
47573
|
+
/* @__PURE__ */ React__default.createElement(SelectValue, { placeholder: "All Types" })
|
|
47574
|
+
),
|
|
47575
|
+
/* @__PURE__ */ React__default.createElement(SelectContent, null, /* @__PURE__ */ React__default.createElement(SelectItem, { value: "all" }, "All Types"), /* @__PURE__ */ React__default.createElement(SelectItem, { value: "image" }, "Images"), /* @__PURE__ */ React__default.createElement(SelectItem, { value: "video" }, "Videos"), /* @__PURE__ */ React__default.createElement(SelectItem, { value: "other" }, "Other"))
|
|
47576
|
+
), /* @__PURE__ */ React__default.createElement(
|
|
47577
|
+
Select,
|
|
47578
|
+
{
|
|
47579
|
+
value: usageFilter,
|
|
47580
|
+
onValueChange: (value) => setUsageFilter(value)
|
|
47581
|
+
},
|
|
47582
|
+
/* @__PURE__ */ React__default.createElement(
|
|
47583
|
+
SelectTrigger,
|
|
47584
|
+
{
|
|
47585
|
+
"aria-label": "Filter by usage status",
|
|
47586
|
+
className: "w-[130px] bg-white border-gray-200 text-gray-700 h-9"
|
|
47587
|
+
},
|
|
47588
|
+
/* @__PURE__ */ React__default.createElement(SelectValue, { placeholder: "All Usage" })
|
|
47589
|
+
),
|
|
47590
|
+
/* @__PURE__ */ React__default.createElement(SelectContent, null, /* @__PURE__ */ React__default.createElement(SelectItem, { value: "all" }, "All Usage"), /* @__PURE__ */ React__default.createElement(SelectItem, { value: "used" }, "Used"), /* @__PURE__ */ React__default.createElement(SelectItem, { value: "unused" }, "Unused"))
|
|
47591
|
+
));
|
|
47592
|
+
const ExpandedRowContent = ({
|
|
47593
|
+
usedIn,
|
|
47594
|
+
onClose
|
|
47595
|
+
}) => {
|
|
47596
|
+
const documentCount = usedIn.length;
|
|
47597
|
+
const sortedDocs = [...usedIn].sort(
|
|
47598
|
+
(a2, b) => a2.collectionLabel.localeCompare(b.collectionLabel)
|
|
47599
|
+
);
|
|
47600
|
+
return /* @__PURE__ */ React__default.createElement(React__default.Fragment, null, /* @__PURE__ */ React__default.createElement("p", { className: "text-xs font-semibold text-gray-500 uppercase tracking-wider mb-3" }, "Used in ", documentCount, " document", documentCount !== 1 ? "s" : "", ":"), /* @__PURE__ */ React__default.createElement("table", { className: "w-full text-xs border-collapse" }, /* @__PURE__ */ React__default.createElement("thead", null, /* @__PURE__ */ React__default.createElement("tr", { className: "text-left text-[10px] font-bold uppercase tracking-tight text-gray-400" }, /* @__PURE__ */ React__default.createElement("th", { className: "pb-1.5 pr-6 w-40" }, "Collection"), /* @__PURE__ */ React__default.createElement("th", { className: "pb-1.5" }, "Document"))), /* @__PURE__ */ React__default.createElement("tbody", null, sortedDocs.map((doc) => {
|
|
47601
|
+
const breadcrumb = doc.breadcrumbs.join("/");
|
|
47602
|
+
return /* @__PURE__ */ React__default.createElement(
|
|
47603
|
+
"tr",
|
|
47604
|
+
{
|
|
47605
|
+
key: doc.editUrl || breadcrumb,
|
|
47606
|
+
className: "border-t border-gray-150"
|
|
47607
|
+
},
|
|
47608
|
+
/* @__PURE__ */ React__default.createElement("td", { className: "py-1.5 pr-6 text-gray-500" }, /* @__PURE__ */ React__default.createElement(
|
|
47609
|
+
"a",
|
|
47610
|
+
{
|
|
47611
|
+
href: `#/collections/${doc.collectionName}/~`,
|
|
47612
|
+
onClick: () => onClose == null ? void 0 : onClose(),
|
|
47613
|
+
className: "underline hover:text-tina-orange-dark transition-colors"
|
|
47614
|
+
},
|
|
47615
|
+
doc.collectionLabel
|
|
47616
|
+
)),
|
|
47617
|
+
/* @__PURE__ */ React__default.createElement("td", { className: "py-1.5 text-gray-700" }, /* @__PURE__ */ React__default.createElement("span", { className: "flex items-center gap-1.5" }, /* @__PURE__ */ React__default.createElement(BiFile, { className: "text-gray-400 flex-shrink-0" }), doc.editUrl ? /* @__PURE__ */ React__default.createElement(
|
|
47618
|
+
"a",
|
|
47619
|
+
{
|
|
47620
|
+
href: doc.editUrl,
|
|
47621
|
+
onClick: () => onClose == null ? void 0 : onClose(),
|
|
47622
|
+
className: "underline hover:text-tina-orange-dark transition-colors break-all"
|
|
47623
|
+
},
|
|
47624
|
+
breadcrumb
|
|
47625
|
+
) : /* @__PURE__ */ React__default.createElement("span", { className: "break-all" }, breadcrumb)))
|
|
47626
|
+
);
|
|
47627
|
+
}))));
|
|
47628
|
+
};
|
|
47629
|
+
const MediaUsageDashboard = ({
|
|
47630
|
+
close: onClose
|
|
47631
|
+
}) => {
|
|
47632
|
+
const { mediaItems, isLoading, errorOccurred, progress, refresh } = useMediaUsageScanner();
|
|
47633
|
+
const [lightboxImage, setLightboxImage] = useState(null);
|
|
47634
|
+
const stats = useMemo(() => {
|
|
47635
|
+
const unusedCount = mediaItems.filter(
|
|
47636
|
+
(usage) => usage.usedIn.length === 0
|
|
47637
|
+
).length;
|
|
47638
|
+
const usedCount = mediaItems.length - unusedCount;
|
|
47639
|
+
return {
|
|
47640
|
+
totalFiles: mediaItems.length,
|
|
47641
|
+
unusedCount,
|
|
47642
|
+
usedCount
|
|
47643
|
+
};
|
|
47644
|
+
}, [mediaItems]);
|
|
47645
|
+
if (isLoading) {
|
|
47646
|
+
return /* @__PURE__ */ React__default.createElement(
|
|
47647
|
+
DashboardLoadingState,
|
|
47648
|
+
{
|
|
47649
|
+
message: "Scanning Media Usage...",
|
|
47650
|
+
progress: { value: progress, label: "Scanning collections" }
|
|
47651
|
+
}
|
|
47652
|
+
);
|
|
47653
|
+
}
|
|
47654
|
+
if (errorOccurred) {
|
|
47655
|
+
return /* @__PURE__ */ React__default.createElement(DashboardErrorState, { message: "Something went wrong, unable to collect media usage statistics" });
|
|
47656
|
+
}
|
|
47657
|
+
return /* @__PURE__ */ React__default.createElement("div", { className: "p-8 w-full max-w-6xl mx-auto font-sans" }, /* @__PURE__ */ React__default.createElement(
|
|
47658
|
+
DashboardTitleBar,
|
|
47659
|
+
{
|
|
47660
|
+
title: "Media Usage Dashboard",
|
|
47661
|
+
icon: /* @__PURE__ */ React__default.createElement(Image, null),
|
|
47662
|
+
controls: /* @__PURE__ */ React__default.createElement(
|
|
47663
|
+
Button,
|
|
47664
|
+
{
|
|
47665
|
+
variant: "outline",
|
|
47666
|
+
onClick: refresh,
|
|
47667
|
+
disabled: isLoading,
|
|
47668
|
+
className: "flex items-center gap-2 shadow-sm font-medium transition-colors"
|
|
47669
|
+
},
|
|
47670
|
+
/* @__PURE__ */ React__default.createElement(RefreshCw, { className: "w-4 h-4" }),
|
|
47671
|
+
"Refresh"
|
|
47672
|
+
)
|
|
47673
|
+
}
|
|
47674
|
+
), /* @__PURE__ */ React__default.createElement("div", { className: "grid grid-cols-1 md:grid-cols-3 gap-6 mb-8" }, /* @__PURE__ */ React__default.createElement(
|
|
47675
|
+
StatCard,
|
|
47676
|
+
{
|
|
47677
|
+
label: "Total Media Files",
|
|
47678
|
+
icon: /* @__PURE__ */ React__default.createElement(Database, { className: "text-2xl text-gray-700" }),
|
|
47679
|
+
iconClassName: "bg-gray-100",
|
|
47680
|
+
value: stats.totalFiles
|
|
47681
|
+
}
|
|
47682
|
+
), /* @__PURE__ */ React__default.createElement(
|
|
47683
|
+
StatCard,
|
|
47684
|
+
{
|
|
47685
|
+
label: "In Use",
|
|
47686
|
+
icon: /* @__PURE__ */ React__default.createElement(CheckCircle2, { className: "text-2xl text-tina-orange-dark" }),
|
|
47687
|
+
iconClassName: "bg-tina-orange/10",
|
|
47688
|
+
value: stats.usedCount
|
|
47689
|
+
}
|
|
47690
|
+
), /* @__PURE__ */ React__default.createElement(
|
|
47691
|
+
StatCard,
|
|
47692
|
+
{
|
|
47693
|
+
label: "Unused",
|
|
47694
|
+
icon: /* @__PURE__ */ React__default.createElement(
|
|
47695
|
+
ImageOff,
|
|
47696
|
+
{
|
|
47697
|
+
className: `text-2xl ${stats.unusedCount > 0 ? "text-orange-600" : "text-gray-400"}`
|
|
47698
|
+
}
|
|
47699
|
+
),
|
|
47700
|
+
iconClassName: stats.unusedCount > 0 ? "bg-orange-100" : "bg-gray-100",
|
|
47701
|
+
value: stats.unusedCount,
|
|
47702
|
+
valueClassName: stats.unusedCount > 0 ? "text-orange-600" : "text-gray-800"
|
|
47703
|
+
}
|
|
47704
|
+
)), /* @__PURE__ */ React__default.createElement(
|
|
47705
|
+
MediaUsageTable,
|
|
47706
|
+
{
|
|
47707
|
+
mediaItems,
|
|
47708
|
+
onClose,
|
|
47709
|
+
onPreview: setLightboxImage
|
|
47710
|
+
}
|
|
47711
|
+
), /* @__PURE__ */ React__default.createElement(
|
|
47712
|
+
MediaLightbox,
|
|
47713
|
+
{
|
|
47714
|
+
item: lightboxImage,
|
|
47715
|
+
onClose: () => setLightboxImage(null)
|
|
47716
|
+
}
|
|
47717
|
+
));
|
|
47718
|
+
};
|
|
47719
|
+
const StatCard = ({
|
|
47720
|
+
label,
|
|
47721
|
+
icon,
|
|
47722
|
+
iconClassName,
|
|
47723
|
+
value,
|
|
47724
|
+
valueClassName = "text-gray-800"
|
|
47725
|
+
}) => /* @__PURE__ */ React__default.createElement("div", { className: "bg-white rounded-xl border border-gray-200 shadow-sm flex items-center gap-4 p-6" }, /* @__PURE__ */ React__default.createElement("div", { className: `${iconClassName} p-3 rounded-lg flex-shrink-0` }, icon), /* @__PURE__ */ React__default.createElement("div", { className: "min-w-0" }, /* @__PURE__ */ React__default.createElement("p", { className: "text-sm text-gray-500 font-medium" }, label), /* @__PURE__ */ React__default.createElement("p", { className: `text-3xl font-bold ${valueClassName}` }, value)));
|
|
47726
|
+
const MediaUsageDashboardScreenPlugin = createScreen({
|
|
47727
|
+
name: "Media Usage",
|
|
47728
|
+
Component: MediaUsageDashboard,
|
|
47729
|
+
Icon: MdImage,
|
|
47730
|
+
layout: "popup",
|
|
47731
|
+
navCategory: "Dashboard"
|
|
47732
|
+
});
|
|
46799
47733
|
function createCloudConfig({
|
|
46800
47734
|
...options
|
|
46801
47735
|
}) {
|
|
@@ -46999,7 +47933,7 @@ const NavProvider = ({
|
|
|
46999
47933
|
const name = "tinacms";
|
|
47000
47934
|
const type = "module";
|
|
47001
47935
|
const typings = "dist/index.d.ts";
|
|
47002
|
-
const version$1 = "3.
|
|
47936
|
+
const version$1 = "3.7.0";
|
|
47003
47937
|
const main = "dist/index.js";
|
|
47004
47938
|
const module = "./dist/index.js";
|
|
47005
47939
|
const exports = {
|
|
@@ -47071,6 +48005,7 @@ const dependencies = {
|
|
|
47071
48005
|
"@udecode/plate-dnd": "catalog:",
|
|
47072
48006
|
"@udecode/plate-floating": "catalog:",
|
|
47073
48007
|
"@udecode/plate-heading": "catalog:",
|
|
48008
|
+
"@udecode/plate-highlight": "catalog:",
|
|
47074
48009
|
"@udecode/plate-horizontal-rule": "catalog:",
|
|
47075
48010
|
"@udecode/plate-indent-list": "catalog:",
|
|
47076
48011
|
"@udecode/plate-link": "catalog:",
|
|
@@ -48821,6 +49756,9 @@ class TinaCMS extends CMS {
|
|
|
48821
49756
|
});
|
|
48822
49757
|
this.plugins.add(MediaManagerScreenPlugin);
|
|
48823
49758
|
this.plugins.add(PasswordScreenPlugin);
|
|
49759
|
+
if (isLocalClient) {
|
|
49760
|
+
this.plugins.add(MediaUsageDashboardScreenPlugin);
|
|
49761
|
+
}
|
|
48824
49762
|
if (isLocalClient !== true) {
|
|
48825
49763
|
if (clientId) {
|
|
48826
49764
|
this.plugins.add(
|
|
@@ -65044,7 +65982,8 @@ const CreateBranchModal = ({
|
|
|
65044
65982
|
safeSubmit,
|
|
65045
65983
|
path: path3,
|
|
65046
65984
|
values,
|
|
65047
|
-
crudType
|
|
65985
|
+
crudType,
|
|
65986
|
+
tinaForm
|
|
65048
65987
|
}) => {
|
|
65049
65988
|
const cms = useCMS$1();
|
|
65050
65989
|
const tinaApi = cms.api.tina;
|
|
@@ -65094,6 +66033,7 @@ const CreateBranchModal = ({
|
|
|
65094
66033
|
}
|
|
65095
66034
|
];
|
|
65096
66035
|
const executeEditorialWorkflow = async () => {
|
|
66036
|
+
var _a2;
|
|
65097
66037
|
try {
|
|
65098
66038
|
const branchName = `tina/${newBranchName}`;
|
|
65099
66039
|
setDisabled(true);
|
|
@@ -65108,7 +66048,21 @@ const CreateBranchModal = ({
|
|
|
65108
66048
|
graphql2 = UPDATE_DOCUMENT_GQL;
|
|
65109
66049
|
}
|
|
65110
66050
|
const collection = tinaApi.schema.getCollectionByFullPath(path3);
|
|
65111
|
-
|
|
66051
|
+
let submittedValues = values;
|
|
66052
|
+
if ((_a2 = collection == null ? void 0 : collection.ui) == null ? void 0 : _a2.beforeSubmit) {
|
|
66053
|
+
const valOverride = await collection.ui.beforeSubmit({
|
|
66054
|
+
cms,
|
|
66055
|
+
values,
|
|
66056
|
+
form: tinaForm
|
|
66057
|
+
});
|
|
66058
|
+
if (valOverride) {
|
|
66059
|
+
submittedValues = valOverride;
|
|
66060
|
+
}
|
|
66061
|
+
}
|
|
66062
|
+
const params = tinaApi.schema.transformPayload(
|
|
66063
|
+
collection.name,
|
|
66064
|
+
submittedValues
|
|
66065
|
+
);
|
|
65112
66066
|
const relativePath = pathRelativeToCollection(collection.path, path3);
|
|
65113
66067
|
const result = await tinaApi.executeEditorialWorkflow({
|
|
65114
66068
|
branchName,
|
|
@@ -65242,7 +66196,7 @@ const CreateBranchModal = ({
|
|
|
65242
66196
|
),
|
|
65243
66197
|
/* @__PURE__ */ React.createElement("div", { className: "text-center max-w-24" }, /* @__PURE__ */ React.createElement("div", { className: "text-sm font-semibold leading-tight" }, step.name), /* @__PURE__ */ React.createElement("div", { className: "text-xs text-gray-400 mt-1 leading-tight" }, step.description))
|
|
65244
66198
|
);
|
|
65245
|
-
})), /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React.createElement("div", { className: "text-xs text-gray-500" }, "Estimated time: 1-2 min "), isExecuting && currentStep > 0 && /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-1 text-sm text-gray-500" }, /* @__PURE__ */ React.createElement("svg", { className: "w-4 h-4", fill: "currentColor", viewBox: "0 0 20 20" }, /* @__PURE__ */ React.createElement(
|
|
66199
|
+
})), /* @__PURE__ */ React.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React.createElement("div", { className: "text-xs text-gray-500" }, "Estimated time: 1-2 min "), isExecuting && currentStep > 0 && /* @__PURE__ */ React.createElement("div", { className: "flex items-center gap-1 text-sm text-gray-500 tabular-nums" }, /* @__PURE__ */ React.createElement("svg", { className: "w-4 h-4", fill: "currentColor", viewBox: "0 0 20 20" }, /* @__PURE__ */ React.createElement(
|
|
65246
66200
|
"path",
|
|
65247
66201
|
{
|
|
65248
66202
|
fillRule: "evenodd",
|
|
@@ -65481,6 +66435,7 @@ const FormBuilder = ({
|
|
|
65481
66435
|
crudType: tinaForm.crudType,
|
|
65482
66436
|
path: tinaForm.path,
|
|
65483
66437
|
values: tinaForm.values,
|
|
66438
|
+
tinaForm,
|
|
65484
66439
|
close: () => setCreateBranchModalOpen(false)
|
|
65485
66440
|
}
|
|
65486
66441
|
), /* @__PURE__ */ React.createElement(DragDropContext, { onDragEnd: moveArrayItem }, /* @__PURE__ */ React.createElement(FormKeyBindings, { onSubmit: safeHandleSubmit }), /* @__PURE__ */ React.createElement(FormPortalProvider, null, /* @__PURE__ */ React.createElement(FormWrapper, { id: tinaForm.id }, (tinaForm == null ? void 0 : tinaForm.fields.length) ? /* @__PURE__ */ React.createElement(
|
|
@@ -67379,10 +68334,105 @@ const MarkToolbarButton = withRef$1(({ clear, nodeType, ...rest }, ref) => {
|
|
|
67379
68334
|
const { props } = useMarkToolbarButton(state);
|
|
67380
68335
|
return /* @__PURE__ */ React__default.createElement(ToolbarButton, { ref, ...props, ...rest });
|
|
67381
68336
|
});
|
|
68337
|
+
const highlightColors = [
|
|
68338
|
+
{ label: "Yellow", value: "#FEF08A" },
|
|
68339
|
+
{ label: "Green", value: "#BBF7D0" },
|
|
68340
|
+
{ label: "Blue", value: "#BFDBFE" },
|
|
68341
|
+
{ label: "Red", value: "#CC4141" }
|
|
68342
|
+
];
|
|
67382
68343
|
const BoldToolbarButton = () => /* @__PURE__ */ React__default.createElement(MarkToolbarButton, { tooltip: "Bold (⌘+B)", nodeType: BoldPlugin.key }, /* @__PURE__ */ React__default.createElement(Icons.bold, null));
|
|
67383
68344
|
const StrikethroughToolbarButton = () => /* @__PURE__ */ React__default.createElement(MarkToolbarButton, { tooltip: "Strikethrough", nodeType: StrikethroughPlugin.key }, /* @__PURE__ */ React__default.createElement(Icons.strikethrough, null));
|
|
67384
68345
|
const ItalicToolbarButton = () => /* @__PURE__ */ React__default.createElement(MarkToolbarButton, { tooltip: "Italic (⌘+I)", nodeType: ItalicPlugin.key }, /* @__PURE__ */ React__default.createElement(Icons.italic, null));
|
|
67385
68346
|
const CodeToolbarButton = () => /* @__PURE__ */ React__default.createElement(MarkToolbarButton, { tooltip: "Code (⌘+E)", nodeType: CodePlugin.key }, /* @__PURE__ */ React__default.createElement(Icons.code, null));
|
|
68347
|
+
const HighlightToolbarButton = () => /* @__PURE__ */ React__default.createElement(HighlightColorToolbarButton, null);
|
|
68348
|
+
const useHighlightToolbar = () => {
|
|
68349
|
+
const editor = useEditorRef();
|
|
68350
|
+
const openState = useOpenState();
|
|
68351
|
+
const savedSelection = React__default.useRef(editor.selection);
|
|
68352
|
+
const inlineCodeActive = useMarkToolbarButtonState({
|
|
68353
|
+
nodeType: CodePlugin.key
|
|
68354
|
+
}).pressed;
|
|
68355
|
+
const rememberSelection = React__default.useCallback(() => {
|
|
68356
|
+
if (editor.selection) {
|
|
68357
|
+
savedSelection.current = structuredClone(editor.selection);
|
|
68358
|
+
}
|
|
68359
|
+
}, [editor]);
|
|
68360
|
+
React__default.useEffect(() => {
|
|
68361
|
+
if (openState.open) {
|
|
68362
|
+
rememberSelection();
|
|
68363
|
+
}
|
|
68364
|
+
}, [openState.open, rememberSelection]);
|
|
68365
|
+
const applyHighlight = React__default.useCallback(
|
|
68366
|
+
(highlightColor) => {
|
|
68367
|
+
if (inlineCodeActive) {
|
|
68368
|
+
openState.onOpenChange(false);
|
|
68369
|
+
return;
|
|
68370
|
+
}
|
|
68371
|
+
if (savedSelection.current) {
|
|
68372
|
+
editor.tf.select(structuredClone(savedSelection.current));
|
|
68373
|
+
}
|
|
68374
|
+
if (highlightColor) {
|
|
68375
|
+
editor.tf.addMark("highlight", true);
|
|
68376
|
+
editor.tf.addMark("highlightColor", highlightColor);
|
|
68377
|
+
} else {
|
|
68378
|
+
editor.tf.removeMark("highlight");
|
|
68379
|
+
editor.tf.removeMark("highlightColor");
|
|
68380
|
+
}
|
|
68381
|
+
editor.tf.setNodes(
|
|
68382
|
+
highlightColor ? {
|
|
68383
|
+
highlight: true,
|
|
68384
|
+
highlightColor
|
|
68385
|
+
} : {
|
|
68386
|
+
highlight: void 0,
|
|
68387
|
+
highlightColor: void 0
|
|
68388
|
+
},
|
|
68389
|
+
{
|
|
68390
|
+
at: editor.selection ?? void 0,
|
|
68391
|
+
match: (node3) => editor.api.isText(node3),
|
|
68392
|
+
split: true
|
|
68393
|
+
}
|
|
68394
|
+
);
|
|
68395
|
+
editor.tf.focus();
|
|
68396
|
+
openState.onOpenChange(false);
|
|
68397
|
+
},
|
|
68398
|
+
[editor, inlineCodeActive, openState]
|
|
68399
|
+
);
|
|
68400
|
+
return {
|
|
68401
|
+
applyHighlight,
|
|
68402
|
+
inlineCodeActive,
|
|
68403
|
+
openState,
|
|
68404
|
+
rememberSelection
|
|
68405
|
+
};
|
|
68406
|
+
};
|
|
68407
|
+
const HighlightColorToolbarButton = () => {
|
|
68408
|
+
const { applyHighlight, inlineCodeActive, openState, rememberSelection } = useHighlightToolbar();
|
|
68409
|
+
return /* @__PURE__ */ React__default.createElement(DropdownMenu$1, { modal: false, ...openState }, /* @__PURE__ */ React__default.createElement(DropdownMenuTrigger$1, { asChild: true }, /* @__PURE__ */ React__default.createElement(
|
|
68410
|
+
ToolbarButton,
|
|
68411
|
+
{
|
|
68412
|
+
isDropdown: true,
|
|
68413
|
+
showArrow: true,
|
|
68414
|
+
pressed: openState.open,
|
|
68415
|
+
tooltip: "Highlight color",
|
|
68416
|
+
disabled: inlineCodeActive,
|
|
68417
|
+
onMouseDown: rememberSelection
|
|
68418
|
+
},
|
|
68419
|
+
/* @__PURE__ */ React__default.createElement("div", { className: "flex items-center gap-1.5" }, /* @__PURE__ */ React__default.createElement(Icons.highlight, null), /* @__PURE__ */ React__default.createElement("span", { className: "sr-only" }, "Highlight color"))
|
|
68420
|
+
)), /* @__PURE__ */ React__default.createElement(DropdownMenuContent$1, { align: "start", className: "min-w-[180px]" }, /* @__PURE__ */ React__default.createElement(DropdownMenuItem$1, { onSelect: () => applyHighlight() }, "Clear highlight"), highlightColors.map((color) => /* @__PURE__ */ React__default.createElement(
|
|
68421
|
+
DropdownMenuItem$1,
|
|
68422
|
+
{
|
|
68423
|
+
key: color.value,
|
|
68424
|
+
onSelect: () => applyHighlight(color.value)
|
|
68425
|
+
},
|
|
68426
|
+
/* @__PURE__ */ React__default.createElement(
|
|
68427
|
+
"span",
|
|
68428
|
+
{
|
|
68429
|
+
className: "mr-2 inline-block size-4 rounded border border-gray-300",
|
|
68430
|
+
style: { backgroundColor: color.value }
|
|
68431
|
+
}
|
|
68432
|
+
),
|
|
68433
|
+
color.label
|
|
68434
|
+
))));
|
|
68435
|
+
};
|
|
67386
68436
|
const ListToolbarButton = withRef$1(({ nodeType = BulletedListPlugin.key, ...rest }, ref) => {
|
|
67387
68437
|
const state = useListToolbarButtonState({ nodeType });
|
|
67388
68438
|
const { props } = useListToolbarButton(state);
|
|
@@ -67616,6 +68666,11 @@ const toolbarItems = {
|
|
|
67616
68666
|
width: () => STANDARD_ICON_WIDTH,
|
|
67617
68667
|
Component: /* @__PURE__ */ React__default.createElement(StrikethroughToolbarButton, null)
|
|
67618
68668
|
},
|
|
68669
|
+
highlight: {
|
|
68670
|
+
label: "Highlight",
|
|
68671
|
+
width: () => STANDARD_ICON_WIDTH,
|
|
68672
|
+
Component: /* @__PURE__ */ React__default.createElement(HighlightToolbarButton, null)
|
|
68673
|
+
},
|
|
67619
68674
|
italic: {
|
|
67620
68675
|
label: "Italic",
|
|
67621
68676
|
width: () => STANDARD_ICON_WIDTH,
|
|
@@ -68803,7 +69858,7 @@ const FloatingToolbar = withRef$1(({ children, state, ...props }, propRef) => {
|
|
|
68803
69858
|
Toolbar,
|
|
68804
69859
|
{
|
|
68805
69860
|
className: cn$1(
|
|
68806
|
-
"absolute z-[
|
|
69861
|
+
"absolute z-[10799] whitespace-nowrap border bg-popover px-1 opacity-100 shadow-md print:hidden rounded-md"
|
|
68807
69862
|
),
|
|
68808
69863
|
...props,
|
|
68809
69864
|
...rootProps,
|
|
@@ -119698,6 +120753,7 @@ const resetBlockTypesCodeBlockRule = {
|
|
|
119698
120753
|
const viewPlugins = [
|
|
119699
120754
|
BasicMarksPlugin,
|
|
119700
120755
|
UnderlinePlugin,
|
|
120756
|
+
HighlightPlugin,
|
|
119701
120757
|
HeadingPlugin.configure({ options: { levels: 6 } }),
|
|
119702
120758
|
ParagraphPlugin,
|
|
119703
120759
|
CodeBlockPlugin.configure({
|
|
@@ -119708,6 +120764,30 @@ const viewPlugins = [
|
|
|
119708
120764
|
const CorrectNodeBehaviorPlugin = createSlatePlugin$1({
|
|
119709
120765
|
key: "WITH_CORRECT_NODE_BEHAVIOR"
|
|
119710
120766
|
});
|
|
120767
|
+
const ClearHighlightOnEnterPlugin = createSlatePlugin$1({
|
|
120768
|
+
key: "CLEAR_HIGHLIGHT_ON_ENTER"
|
|
120769
|
+
}).overrideEditor(({ editor, tf: { insertBreak: insertBreak2 } }) => ({
|
|
120770
|
+
transforms: {
|
|
120771
|
+
insertBreak() {
|
|
120772
|
+
const keyboardEvent = editor.currentKeyboardEvent;
|
|
120773
|
+
const isPlainEnter = (keyboardEvent == null ? void 0 : keyboardEvent.key) === "Enter" && !keyboardEvent.shiftKey && !keyboardEvent.metaKey && !keyboardEvent.ctrlKey && !keyboardEvent.altKey;
|
|
120774
|
+
const activeMarks = editor.api.marks();
|
|
120775
|
+
const hasHighlight = Boolean(
|
|
120776
|
+
(activeMarks == null ? void 0 : activeMarks.highlight) || (activeMarks == null ? void 0 : activeMarks.highlightColor)
|
|
120777
|
+
);
|
|
120778
|
+
insertBreak2();
|
|
120779
|
+
if (!isPlainEnter || !hasHighlight) {
|
|
120780
|
+
return;
|
|
120781
|
+
}
|
|
120782
|
+
editor.tf.removeMark("highlight");
|
|
120783
|
+
editor.tf.removeMark("highlightColor");
|
|
120784
|
+
editor.tf.unsetNodes(["highlight", "highlightColor"], {
|
|
120785
|
+
at: editor.selection ?? void 0,
|
|
120786
|
+
match: (node3) => editor.api.isText(node3)
|
|
120787
|
+
});
|
|
120788
|
+
}
|
|
120789
|
+
}
|
|
120790
|
+
}));
|
|
119711
120791
|
const editorPlugins = [
|
|
119712
120792
|
createMdxBlockPlugin,
|
|
119713
120793
|
createMdxInlinePlugin,
|
|
@@ -119717,6 +120797,7 @@ const editorPlugins = [
|
|
|
119717
120797
|
createBlockquoteEnterBreakPlugin,
|
|
119718
120798
|
createInvalidMarkdownPlugin,
|
|
119719
120799
|
CorrectNodeBehaviorPlugin,
|
|
120800
|
+
ClearHighlightOnEnterPlugin,
|
|
119720
120801
|
LinkPlugin.configure({
|
|
119721
120802
|
options: {
|
|
119722
120803
|
// Custom validation function to allow relative links, e.g., /about
|