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.
Files changed (20) hide show
  1. package/dist/index.js +1092 -11
  2. package/dist/rich-text/index.d.ts +4 -0
  3. package/dist/rich-text/index.js +14 -0
  4. package/dist/toolkit/components/dashboard/dashboard-ui.d.ts +16 -0
  5. package/dist/toolkit/components/dashboard/media-usage-dashboard/index.d.ts +6 -0
  6. package/dist/toolkit/components/dashboard/media-usage-dashboard/media-lightbox.d.ts +6 -0
  7. package/dist/toolkit/components/dashboard/media-usage-dashboard/media-usage-scanner.d.ts +21 -0
  8. package/dist/toolkit/components/dashboard/media-usage-dashboard/media-usage-table.d.ts +7 -0
  9. package/dist/toolkit/components/dashboard/media-usage-dashboard/media-usage-thumbnails.d.ts +5 -0
  10. package/dist/toolkit/components/dashboard/media-usage-dashboard/useMediaUsageScanner.d.ts +8 -0
  11. package/dist/toolkit/components/ui/dialog.d.ts +16 -0
  12. package/dist/toolkit/fields/plugins/mdx-field-plugin/plate/components/plate-ui/icons.d.ts +1 -0
  13. package/dist/toolkit/fields/plugins/mdx-field-plugin/plate/components/plate-ui/mark-toolbar-button.d.ts +1 -0
  14. package/dist/toolkit/fields/plugins/mdx-field-plugin/plate/plugins/editor-plugins.d.ts +3 -3
  15. package/dist/toolkit/fields/plugins/mdx-field-plugin/plate/plugins/ui/components.d.ts +2 -0
  16. package/dist/toolkit/fields/plugins/mdx-field-plugin/plate/toolbar/toolbar-overrides.d.ts +1 -1
  17. package/dist/toolkit/form-builder/create-branch-modal.d.ts +3 -1
  18. package/dist/toolkit/plugin-screens/media-usage-dashboard-screen.d.ts +1 -0
  19. package/dist/toolkit/react-screens/screen-plugin.d.ts +2 -2
  20. 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.6.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
- const params = tinaApi.schema.transformPayload(collection.name, values);
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-[999999] whitespace-nowrap border bg-popover px-1 opacity-100 shadow-md print:hidden rounded-md"
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