tinacms 3.7.6 → 3.8.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 CHANGED
@@ -23,7 +23,7 @@ import { SlashInputPlugin, SlashPlugin } from "@udecode/plate-slash-command/reac
23
23
  import { TablePlugin, TableRowPlugin, useTableCellElement, TableProvider, useTableElement, useTableMergeState, useTableBordersDropdownMenuContentState, TableCellHeaderPlugin, TableCellPlugin } from "@udecode/plate-table/react";
24
24
  import { PlateElement, useReadOnly, useElement, PlateLeaf, useSelected, useFocused, useEditorRef, withRef as withRef$1, useComposedRef, useEditorPlugin, useElementSelector, withHOC, usePluginOption, useRemoveNodeButton, useEditorSelector, ParagraphPlugin, createPlatePlugin, PlateContent, PlateContainer, useEditorState, useMarkToolbarButtonState, useMarkToolbarButton, usePlateEditor, useFormInputProps, useEditorSelection, useEditorReadOnly, useOnClickOutside, useEventEditorValue, Plate } from "@udecode/plate/react";
25
25
  import { isLangSupported, formatCodeBlock, insertEmptyCodeBlock, unwrapCodeBlock, isCodeBlockEmpty, isSelectionAtCodeBlockStart } from "@udecode/plate-code-block";
26
- 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, 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";
26
+ 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 as Heading1$1, Heading2 as Heading2$1, Heading3 as Heading3$1, Heading4 as Heading4$1, Heading5 as Heading5$1, Heading6 as Heading6$1, Indent, Keyboard, WrapText, Minus, MoreHorizontal, Outdent, Pilcrow, RotateCcw, RectangleHorizontal, Settings, Highlighter, Strikethrough, Subscript, Superscript, Table as Table$1, Text, 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";
27
27
  import mermaid from "mermaid";
28
28
  import { Slot } from "@radix-ui/react-slot";
29
29
  import { cva } from "class-variance-authority";
@@ -1965,12 +1965,12 @@ const Icons = {
1965
1965
  editing: Edit2,
1966
1966
  emoji: Smile,
1967
1967
  externalLink: ExternalLink,
1968
- h1: Heading1,
1969
- h2: Heading2,
1970
- h3: Heading3,
1971
- h4: Heading4,
1972
- h5: Heading5,
1973
- h6: Heading6,
1968
+ h1: Heading1$1,
1969
+ h2: Heading2$1,
1970
+ h3: Heading3$1,
1971
+ h4: Heading4$1,
1972
+ h5: Heading5$1,
1973
+ h6: Heading6$1,
1974
1974
  // image: Image,
1975
1975
  indent: Indent,
1976
1976
  // italic: Italic,
@@ -3257,126 +3257,123 @@ const HighlightLeaf = ({
3257
3257
  }
3258
3258
  );
3259
3259
  };
3260
- const Components = () => {
3261
- return {
3262
- [SlashInputPlugin.key]: SlashInputElement,
3263
- [HEADING_KEYS.h1]: ({
3264
- attributes,
3265
- editor,
3266
- element,
3260
+ const Heading1 = ({
3261
+ attributes,
3262
+ children,
3263
+ className
3264
+ }) => /* @__PURE__ */ React__default.createElement(
3265
+ "h1",
3266
+ {
3267
+ ...attributes,
3268
+ className: classNames$1(
3269
+ headerClasses,
3270
+ blockClasses,
3267
3271
  className,
3268
- ...props
3269
- }) => /* @__PURE__ */ React__default.createElement(
3270
- "h1",
3271
- {
3272
- ...attributes,
3273
- ...props,
3274
- className: classNames$1(
3275
- headerClasses,
3276
- blockClasses,
3277
- className,
3278
- "text-4xl mb-4 last:mb-0 mt-6 first:mt-0 font-libre-baskerville"
3279
- )
3280
- }
3281
- ),
3282
- [HEADING_KEYS.h2]: ({
3283
- attributes,
3284
- editor,
3285
- element,
3272
+ "text-4xl mb-4 last:mb-0 mt-6 first:mt-0 font-libre-baskerville"
3273
+ )
3274
+ },
3275
+ children
3276
+ );
3277
+ const Heading2 = ({
3278
+ attributes,
3279
+ children,
3280
+ className
3281
+ }) => /* @__PURE__ */ React__default.createElement(
3282
+ "h2",
3283
+ {
3284
+ ...attributes,
3285
+ className: classNames$1(
3286
+ headerClasses,
3287
+ blockClasses,
3286
3288
  className,
3287
- ...props
3288
- }) => /* @__PURE__ */ React__default.createElement(
3289
- "h2",
3290
- {
3291
- ...attributes,
3292
- ...props,
3293
- className: classNames$1(
3294
- headerClasses,
3295
- blockClasses,
3296
- className,
3297
- "text-3xl mb-4 last:mb-0 mt-6 first:mt-0 font-libre-baskerville"
3298
- )
3299
- }
3300
- ),
3301
- [HEADING_KEYS.h3]: ({
3302
- attributes,
3303
- editor,
3304
- element,
3289
+ "text-3xl mb-4 last:mb-0 mt-6 first:mt-0 font-libre-baskerville"
3290
+ )
3291
+ },
3292
+ children
3293
+ );
3294
+ const Heading3 = ({
3295
+ attributes,
3296
+ children,
3297
+ className
3298
+ }) => /* @__PURE__ */ React__default.createElement(
3299
+ "h3",
3300
+ {
3301
+ ...attributes,
3302
+ className: classNames$1(
3303
+ headerClasses,
3304
+ blockClasses,
3305
3305
  className,
3306
- ...props
3307
- }) => /* @__PURE__ */ React__default.createElement(
3308
- "h3",
3309
- {
3310
- ...attributes,
3311
- ...props,
3312
- className: classNames$1(
3313
- headerClasses,
3314
- blockClasses,
3315
- className,
3316
- "text-2xl mb-4 last:mb-0 mt-6 first:mt-0 font-libre-baskerville"
3317
- )
3318
- }
3319
- ),
3320
- [HEADING_KEYS.h4]: ({
3321
- attributes,
3322
- editor,
3323
- element,
3306
+ "text-2xl mb-4 last:mb-0 mt-6 first:mt-0 font-libre-baskerville"
3307
+ )
3308
+ },
3309
+ children
3310
+ );
3311
+ const Heading4 = ({
3312
+ attributes,
3313
+ children,
3314
+ className
3315
+ }) => /* @__PURE__ */ React__default.createElement(
3316
+ "h4",
3317
+ {
3318
+ ...attributes,
3319
+ className: classNames$1(
3320
+ headerClasses,
3321
+ blockClasses,
3324
3322
  className,
3325
- ...props
3326
- }) => /* @__PURE__ */ React__default.createElement(
3327
- "h4",
3328
- {
3329
- ...attributes,
3330
- ...props,
3331
- className: classNames$1(
3332
- headerClasses,
3333
- blockClasses,
3334
- className,
3335
- "text-xl mb-4 last:mb-0 mt-6 first:mt-0 font-libre-baskerville"
3336
- )
3337
- }
3338
- ),
3339
- /** Tailwind prose doesn't style h5 and h6 elements */
3340
- [HEADING_KEYS.h5]: ({
3341
- attributes,
3342
- editor,
3343
- element,
3323
+ "text-xl mb-4 last:mb-0 mt-6 first:mt-0 font-libre-baskerville"
3324
+ )
3325
+ },
3326
+ children
3327
+ );
3328
+ const headerSerifStyle = {
3329
+ fontFamily: "'Libre Baskerville', serif",
3330
+ fontWeight: "400"
3331
+ };
3332
+ const Heading5 = ({
3333
+ attributes,
3334
+ children,
3335
+ className
3336
+ }) => /* @__PURE__ */ React__default.createElement(
3337
+ "h5",
3338
+ {
3339
+ ...attributes,
3340
+ className: classNames$1(
3341
+ headerClasses,
3342
+ blockClasses,
3344
3343
  className,
3345
- ...props
3346
- }) => /* @__PURE__ */ React__default.createElement(
3347
- "h5",
3348
- {
3349
- ...attributes,
3350
- ...props,
3351
- className: classNames$1(
3352
- headerClasses,
3353
- blockClasses,
3354
- className,
3355
- "text-lg mb-4 last:mb-0 mt-6 first:mt-0"
3356
- ),
3357
- style: { fontFamily: "'Libre Baskerville', serif", fontWeight: "400" }
3358
- }
3344
+ "text-lg mb-4 last:mb-0 mt-6 first:mt-0"
3359
3345
  ),
3360
- [HEADING_KEYS.h6]: ({
3361
- attributes,
3362
- editor,
3363
- element,
3346
+ style: headerSerifStyle
3347
+ },
3348
+ children
3349
+ );
3350
+ const Heading6 = ({
3351
+ attributes,
3352
+ children,
3353
+ className
3354
+ }) => /* @__PURE__ */ React__default.createElement(
3355
+ "h6",
3356
+ {
3357
+ ...attributes,
3358
+ className: classNames$1(
3359
+ headerClasses,
3360
+ blockClasses,
3364
3361
  className,
3365
- ...props
3366
- }) => /* @__PURE__ */ React__default.createElement(
3367
- "h6",
3368
- {
3369
- ...attributes,
3370
- ...props,
3371
- className: classNames$1(
3372
- headerClasses,
3373
- blockClasses,
3374
- className,
3375
- "text-base mb-4 last:mb-0 mt-6 first:mt-0"
3376
- ),
3377
- style: { fontFamily: "'Libre Baskerville', serif", fontWeight: "400" }
3378
- }
3362
+ "text-base mb-4 last:mb-0 mt-6 first:mt-0"
3379
3363
  ),
3364
+ style: headerSerifStyle
3365
+ },
3366
+ children
3367
+ );
3368
+ const Components = () => {
3369
+ return {
3370
+ [SlashInputPlugin.key]: SlashInputElement,
3371
+ [HEADING_KEYS.h1]: Heading1,
3372
+ [HEADING_KEYS.h2]: Heading2,
3373
+ [HEADING_KEYS.h3]: Heading3,
3374
+ [HEADING_KEYS.h4]: Heading4,
3375
+ [HEADING_KEYS.h5]: Heading5,
3376
+ [HEADING_KEYS.h6]: Heading6,
3380
3377
  [ParagraphPlugin.key]: ParagraphElement,
3381
3378
  [BlockquotePlugin.key]: BlockquoteElement,
3382
3379
  [CodeBlockPlugin.key]: CodeBlockElement,
@@ -3811,15 +3808,15 @@ class Form {
3811
3808
  })
3812
3809
  };
3813
3810
  } else {
3814
- const childrenIndex = namePath.findIndex(
3815
- (value2) => value2 === "children"
3816
- );
3817
- const propsIndex = namePath.slice(childrenIndex).findIndex((value2) => value2 === "props") + childrenIndex;
3811
+ const childrenIndex = namePath.indexOf("children", namePathIndex + 1);
3812
+ const propsIndex = namePath.indexOf("props", childrenIndex + 1);
3818
3813
  const itemName = namePath.slice(childrenIndex, propsIndex).join(".");
3819
3814
  const item = getIn(value, itemName);
3815
+ if (!item)
3816
+ return formOrObjectField;
3820
3817
  const props = item.props;
3821
3818
  const templateString = item.name;
3822
- const currentPathIndex = namePathIndex + Math.max(propsIndex, 3);
3819
+ const currentPathIndex = propsIndex;
3823
3820
  const isLastItem2 = currentPathIndex + 1 === namePath.length;
3824
3821
  const template = field.templates.find(
3825
3822
  (t) => t.name === templateString
@@ -3858,7 +3855,7 @@ class Form {
3858
3855
  formOrObjectField: template,
3859
3856
  values: props,
3860
3857
  namePath,
3861
- namePathIndex: namePathIndex + Math.max(4, childrenIndex + propsIndex)
3858
+ namePathIndex: propsIndex + 1
3862
3859
  });
3863
3860
  }
3864
3861
  if (!template) {
@@ -10039,76 +10036,141 @@ class TinaMediaStore {
10039
10036
  this.setup();
10040
10037
  return await this.api.authProvider.isAuthenticated();
10041
10038
  }
10039
+ /**
10040
+ * Returns the current branch as a single-encoded query-param value, or
10041
+ * an empty string when no branch is set.
10042
+ *
10043
+ * `this.api.branch` is already URL-encoded by `Client.setBranch()`, so we
10044
+ * decode then re-encode here to defend against double-encoding when this
10045
+ * value is concatenated into a URL.
10046
+ *
10047
+ * `Client.setBranch()` runs the constructor's `options.branch` through
10048
+ * `encodeURIComponent` without a guard, so an unset `options.branch`
10049
+ * lands here as the literal string `"undefined"`. We treat that and the
10050
+ * empty case as no-branch so we don't send `?branch=undefined` to the
10051
+ * assets-api (which would route the call to a non-existent staging path).
10052
+ */
10053
+ encodedBranchParam() {
10054
+ if (!this.api.branch)
10055
+ return "";
10056
+ const decoded = decodeURIComponent(this.api.branch);
10057
+ if (!decoded || decoded === "undefined")
10058
+ return "";
10059
+ return encodeURIComponent(decoded);
10060
+ }
10042
10061
  async persist_cloud(media) {
10043
- const newFiles = [];
10044
- if (await this.isAuthenticated()) {
10045
- for (const item of media) {
10046
- let directory = item.directory;
10047
- if (directory == null ? void 0 : directory.endsWith("/")) {
10048
- directory = directory.substr(0, directory.length - 1);
10049
- }
10050
- const path = `${directory && directory !== "/" ? `${directory}/${item.file.name}` : item.file.name}`;
10051
- const res = await this.api.authProvider.fetchWithToken(
10052
- `${this.url}/upload_url/${path}`,
10053
- { method: "GET" }
10054
- );
10055
- if (res.status === 412) {
10056
- const { message = "Unexpected error generating upload url" } = await res.json();
10057
- throw new Error(message);
10062
+ if (!await this.isAuthenticated()) {
10063
+ return [];
10064
+ }
10065
+ const encodedBranch = this.encodedBranchParam();
10066
+ const branchQuery = encodedBranch ? `?branch=${encodedBranch}` : "";
10067
+ for (const item of media) {
10068
+ let directory = item.directory;
10069
+ if (directory == null ? void 0 : directory.endsWith("/")) {
10070
+ directory = directory.substr(0, directory.length - 1);
10071
+ }
10072
+ const path = `${directory && directory !== "/" ? `${directory}/${item.file.name}` : item.file.name}`;
10073
+ const res = await this.api.authProvider.fetchWithToken(
10074
+ `${this.url}/upload_url/${path}${branchQuery}`,
10075
+ { method: "GET" }
10076
+ );
10077
+ if (res.status === 412) {
10078
+ const { message = "Unexpected error generating upload url" } = await res.json();
10079
+ throw new Error(message);
10080
+ }
10081
+ const { signedUrl, requestId } = await res.json();
10082
+ if (!signedUrl) {
10083
+ throw new Error("Unexpected error generating upload url");
10084
+ }
10085
+ const uploadRes = await this.fetchFunction(signedUrl, {
10086
+ method: "PUT",
10087
+ body: item.file,
10088
+ headers: {
10089
+ "Content-Type": item.file.type || "application/octet-stream",
10090
+ "Content-Length": String(item.file.size)
10058
10091
  }
10059
- const { signedUrl, requestId } = await res.json();
10060
- if (!signedUrl) {
10061
- throw new Error("Unexpected error generating upload url");
10092
+ });
10093
+ if (!uploadRes.ok) {
10094
+ const xmlRes = await uploadRes.text();
10095
+ const matches = s3ErrorRegex.exec(xmlRes);
10096
+ console.error(xmlRes);
10097
+ if (!matches) {
10098
+ throw new Error("Unexpected error uploading media asset");
10099
+ } else {
10100
+ throw new Error(`Upload error: '${matches[2]}'`);
10062
10101
  }
10063
- const uploadRes = await this.fetchFunction(signedUrl, {
10064
- method: "PUT",
10065
- body: item.file,
10066
- headers: {
10067
- "Content-Type": item.file.type || "application/octet-stream",
10068
- "Content-Length": String(item.file.size)
10069
- }
10070
- });
10071
- if (!uploadRes.ok) {
10072
- const xmlRes = await uploadRes.text();
10073
- const matches = s3ErrorRegex.exec(xmlRes);
10074
- console.error(xmlRes);
10075
- if (!matches) {
10076
- throw new Error("Unexpected error uploading media asset");
10102
+ }
10103
+ const updateStartTime = Date.now();
10104
+ while (true) {
10105
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
10106
+ const { error: error2, message } = await this.api.getRequestStatus(requestId);
10107
+ if (error2 !== void 0) {
10108
+ if (error2) {
10109
+ throw new Error(message);
10077
10110
  } else {
10078
- throw new Error(`Upload error: '${matches[2]}'`);
10111
+ break;
10079
10112
  }
10080
10113
  }
10081
- const updateStartTime = Date.now();
10082
- while (true) {
10083
- await new Promise((resolve) => setTimeout(resolve, 1e3));
10084
- const { error: error2, message } = await this.api.getRequestStatus(requestId);
10085
- if (error2 !== void 0) {
10086
- if (error2) {
10087
- throw new Error(message);
10088
- } else {
10089
- break;
10090
- }
10091
- }
10092
- if (Date.now() - updateStartTime > 3e4) {
10093
- throw new Error("Time out waiting for upload to complete");
10094
- }
10114
+ if (Date.now() - updateStartTime > 3e4) {
10115
+ throw new Error("Time out waiting for upload to complete");
10095
10116
  }
10096
- const src = `https://assets.tina.io/${this.api.clientId}/${path}`;
10097
- newFiles.push({
10098
- directory: item.directory,
10099
- filename: item.file.name,
10100
- id: item.file.name,
10101
- type: "file",
10102
- thumbnails: {
10103
- "75x75": src,
10104
- "400x400": src,
10105
- "1000x1000": src
10106
- },
10107
- src
10117
+ }
10118
+ }
10119
+ return this.fetchUploadedEntries(media);
10120
+ }
10121
+ /**
10122
+ * Resolves the just-uploaded items to canonical `Media` entries by hitting
10123
+ * the assets-api `list` endpoint, which is the source of truth for the
10124
+ * `src` URL — including the staging-branch path
10125
+ * (`__staging/<branch>/__file/...`) and the per-stage CDN host.
10126
+ * Constructing those URLs on the client would mirror server-side branch
10127
+ * routing and CDN-host logic, which is fragile to keep in sync.
10128
+ *
10129
+ * Best-effort: items not found within the first page of their directory
10130
+ * (e.g. very large directories) are omitted from the result rather than
10131
+ * throwing — the upload itself already succeeded.
10132
+ */
10133
+ async fetchUploadedEntries(media) {
10134
+ const byDirectory = /* @__PURE__ */ new Map();
10135
+ for (const item of media) {
10136
+ let dir = item.directory || "";
10137
+ while (dir.endsWith("/"))
10138
+ dir = dir.slice(0, -1);
10139
+ const bucket = byDirectory.get(dir) ?? [];
10140
+ bucket.push(item);
10141
+ byDirectory.set(dir, bucket);
10142
+ }
10143
+ const thumbnailSizes = [
10144
+ { w: 75, h: 75 },
10145
+ { w: 400, h: 400 },
10146
+ { w: 1e3, h: 1e3 }
10147
+ ];
10148
+ const results = [];
10149
+ for (const [directory, items2] of byDirectory) {
10150
+ let listed;
10151
+ try {
10152
+ listed = await this.list({
10153
+ directory,
10154
+ limit: Math.max(100, items2.length * 4),
10155
+ thumbnailSizes
10108
10156
  });
10157
+ } catch (err) {
10158
+ console.error("Failed to fetch canonical media entries:", err);
10159
+ continue;
10160
+ }
10161
+ const found = /* @__PURE__ */ new Map();
10162
+ for (const entry of listed.items) {
10163
+ if (entry.type === "file") {
10164
+ found.set(entry.filename, entry);
10165
+ }
10166
+ }
10167
+ for (const item of items2) {
10168
+ const entry = found.get(item.file.name);
10169
+ if (entry)
10170
+ results.push(entry);
10109
10171
  }
10110
10172
  }
10111
- return newFiles;
10173
+ return results;
10112
10174
  }
10113
10175
  async persist_local(media) {
10114
10176
  var _a, _b, _c, _d, _e, _f, _g, _h, _i;
@@ -10219,8 +10281,9 @@ class TinaMediaStore {
10219
10281
  }
10220
10282
  let res;
10221
10283
  if (!this.isLocal) {
10284
+ const encodedBranch = this.encodedBranchParam();
10222
10285
  res = await this.api.authProvider.fetchWithToken(
10223
- `${this.url}/list/${options.directory || ""}?limit=${options.limit || 20}${options.offset ? `&cursor=${options.offset}` : ""}`
10286
+ `${this.url}/list/${options.directory || ""}?limit=${options.limit || 20}${options.offset ? `&cursor=${options.offset}` : ""}${encodedBranch ? `&branch=${encodedBranch}` : ""}`
10224
10287
  );
10225
10288
  if (res.status == 401) {
10226
10289
  throw E_UNAUTHORIZED;
@@ -10274,8 +10337,10 @@ class TinaMediaStore {
10274
10337
  const path = `${media.directory ? `${media.directory}/${media.filename}` : media.filename}`;
10275
10338
  if (!this.isLocal) {
10276
10339
  if (await this.isAuthenticated()) {
10340
+ const encodedBranch = this.encodedBranchParam();
10341
+ const branchQuery = encodedBranch ? `?branch=${encodedBranch}` : "";
10277
10342
  const res = await this.api.authProvider.fetchWithToken(
10278
- `${this.url}/${path}`,
10343
+ `${this.url}/${path}${branchQuery}`,
10279
10344
  {
10280
10345
  method: "DELETE"
10281
10346
  }
@@ -12710,7 +12775,7 @@ const NavProvider = ({
12710
12775
  const name = "tinacms";
12711
12776
  const type = "module";
12712
12777
  const typings = "dist/index.d.ts";
12713
- const version$1 = "3.7.6";
12778
+ const version$1 = "3.8.0";
12714
12779
  const main = "dist/index.js";
12715
12780
  const module = "./dist/index.js";
12716
12781
  const exports = {
@@ -12718,6 +12783,8 @@ const exports = {
12718
12783
  "./dist/client": "./dist/client.js",
12719
12784
  "./react": "./dist/react.js",
12720
12785
  "./dist/react": "./dist/react.js",
12786
+ "./tina-field": "./dist/tina-field.js",
12787
+ "./dist/tina-field": "./dist/tina-field.js",
12721
12788
  "./dist/rich-text": "./dist/rich-text/index.js",
12722
12789
  "./dist/rich-text/static": "./dist/rich-text/static.js",
12723
12790
  "./dist/rich-text/prism": "./dist/rich-text/prism.js"
@@ -12732,6 +12799,7 @@ const buildConfig = {
12732
12799
  "src/rich-text/static.tsx",
12733
12800
  "src/rich-text/prism.tsx",
12734
12801
  "src/react.tsx",
12802
+ "src/tina-field.ts",
12735
12803
  {
12736
12804
  name: "src/client.ts",
12737
12805
  target: "node"
@@ -12767,6 +12835,7 @@ const dependencies = {
12767
12835
  "@radix-ui/react-tooltip": "catalog:",
12768
12836
  "@react-hook/window-size": "catalog:",
12769
12837
  "@tanstack/react-table": "^8.21.3",
12838
+ "@tinacms/bridge": "workspace:*",
12770
12839
  "@tinacms/mdx": "workspace:*",
12771
12840
  "@tinacms/schema-tools": "workspace:*",
12772
12841
  "@tinacms/search": "workspace:*",
package/dist/react.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { addMetadata, hashFromQuery } from '@tinacms/bridge/metadata';
2
+ export { addMetadata, hashFromQuery };
1
3
  export declare function useTina<T extends object>(props: {
2
4
  query: string;
3
5
  variables: object;
@@ -10,32 +12,4 @@ export declare function useTina<T extends object>(props: {
10
12
  export declare function useEditState(): {
11
13
  edit: boolean;
12
14
  };
13
- /**
14
- * Grab the field name for the given attribute
15
- * to signal to Tina which DOM element the field
16
- * is working with.
17
- */
18
- /**
19
- * Generate a field identifier for Tina to associate DOM elements with form fields.
20
- * Format: "queryId---path.to.field" or "queryId---path.to.array.index"
21
- */
22
- export declare const tinaField: <T extends {
23
- _content_source?: {
24
- queryId: string;
25
- path: (number | string)[];
26
- };
27
- } | Record<string, unknown> | null | undefined>(object: T, property?: keyof Omit<NonNullable<T>, "__typename" | "_sys">, index?: number) => string;
28
- /**
29
- * FIX: This function is updated to be more robust. It explicitly checks for
30
- * `null` and `String` objects to prevent them from being processed as
31
- * iterable objects, which is the root cause of the "Objects are not valid
32
- * as a React child" error.
33
- */
34
- export declare const addMetadata: <T extends object>(id: string, obj: T, path?: (string | number)[]) => T;
35
- /**
36
- * This is a pretty rudimentary approach to hashing the query and variables to
37
- * ensure we treat multiple queries on the page uniquely. It's possible
38
- * that we would have collisions, and I'm not sure of the likeliness but seems
39
- * like it'd be rare.
40
- */
41
- export declare const hashFromQuery: (input: string) => string;
15
+ export { tinaField } from './tina-field';
package/dist/react.js CHANGED
@@ -1,4 +1,8 @@
1
+ import { hashFromQuery, addMetadata } from "@tinacms/bridge/metadata";
2
+ import { addMetadata as addMetadata2, hashFromQuery as hashFromQuery2 } from "@tinacms/bridge/metadata";
3
+ import { QUICK_EDIT_CSS } from "@tinacms/bridge/quick-edit-css";
1
4
  import React from "react";
5
+ import { tinaField } from "@tinacms/bridge/tina-field";
2
6
  function useTina(props) {
3
7
  const stringifiedQuery = JSON.stringify({
4
8
  query: props.query,
@@ -53,48 +57,16 @@ function useTina(props) {
53
57
  }
54
58
  }
55
59
  }
56
- if (fieldName) {
57
- if (isInTinaIframe) {
58
- parent.postMessage(
59
- { type: "field:selected", fieldName },
60
- window.location.origin
61
- );
62
- }
60
+ if (fieldName && isInTinaIframe) {
61
+ parent.postMessage(
62
+ { type: "field:selected", fieldName },
63
+ window.location.origin
64
+ );
63
65
  }
64
66
  };
65
67
  const style = document.createElement("style");
66
68
  style.type = "text/css";
67
- style.textContent = `
68
- [data-tina-field] {
69
- outline: 2px dashed rgba(34,150,254,0.5);
70
- transition: box-shadow ease-out 150ms;
71
- }
72
- [data-tina-field]:hover {
73
- box-shadow: inset 100vi 100vh rgba(34,150,254,0.3);
74
- outline: 2px solid rgba(34,150,254,1);
75
- cursor: pointer;
76
- }
77
- [data-tina-field-overlay] {
78
- outline: 2px dashed rgba(34,150,254,0.5);
79
- position: relative;
80
- }
81
- [data-tina-field-overlay]:hover {
82
- cursor: pointer;
83
- outline: 2px solid rgba(34,150,254,1);
84
- }
85
- [data-tina-field-overlay]::after {
86
- content: '';
87
- position: absolute;
88
- inset: 0;
89
- z-index: 20;
90
- transition: opacity ease-out 150ms;
91
- background-color: rgba(34,150,254,0.3);
92
- opacity: 0;
93
- }
94
- [data-tina-field-overlay]:hover::after {
95
- opacity: 1;
96
- }
97
- `;
69
+ style.textContent = QUICK_EDIT_CSS;
98
70
  document.head.appendChild(style);
99
71
  document.body.classList.add("__tina-quick-editing-enabled");
100
72
  document.addEventListener("click", mouseDownHandler, true);
@@ -166,88 +138,9 @@ function useEditState() {
166
138
  }, []);
167
139
  return { edit };
168
140
  }
169
- const tinaField = (object, property, index) => {
170
- const contentSource = object == null ? void 0 : object._content_source;
171
- if (!contentSource) {
172
- return "";
173
- }
174
- const { queryId, path } = contentSource;
175
- if (!property) {
176
- return `${queryId}---${path.join(".")}`;
177
- }
178
- const fullPath = typeof index === "number" ? [...path, property, index] : [...path, property];
179
- return `${queryId}---${fullPath.join(".")}`;
180
- };
181
- const addMetadata = (id, obj, path = []) => {
182
- if (obj === null) {
183
- return obj;
184
- }
185
- if (isScalarOrUndefined(obj)) {
186
- return obj;
187
- }
188
- if (obj instanceof String) {
189
- return obj.valueOf();
190
- }
191
- if (Array.isArray(obj)) {
192
- return obj.map(
193
- (item, index) => addMetadata(id, item, [...path, index])
194
- );
195
- }
196
- const transformedObj = {};
197
- for (const [key, value] of Object.entries(obj)) {
198
- const currentPath = [...path, key];
199
- if ([
200
- "__typename",
201
- "_sys",
202
- "_internalSys",
203
- "_values",
204
- "_internalValues",
205
- "_content_source",
206
- "_tina_metadata"
207
- ].includes(key)) {
208
- transformedObj[key] = value;
209
- } else {
210
- transformedObj[key] = addMetadata(id, value, currentPath);
211
- }
212
- }
213
- if (transformedObj && typeof transformedObj === "object" && "type" in transformedObj && transformedObj.type === "root") {
214
- return transformedObj;
215
- }
216
- return { ...transformedObj, _content_source: { queryId: id, path } };
217
- };
218
- function isScalarOrUndefined(value) {
219
- const type = typeof value;
220
- if (type === "string")
221
- return true;
222
- if (type === "number")
223
- return true;
224
- if (type === "boolean")
225
- return true;
226
- if (type === "undefined")
227
- return true;
228
- if (value == null)
229
- return true;
230
- if (value instanceof String)
231
- return true;
232
- if (value instanceof Number)
233
- return true;
234
- if (value instanceof Boolean)
235
- return true;
236
- return false;
237
- }
238
- const hashFromQuery = (input) => {
239
- let hash = 0;
240
- for (let i = 0; i < input.length; i++) {
241
- const char = input.charCodeAt(i);
242
- hash = (hash << 5) - hash + char & 4294967295;
243
- }
244
- const nonNegativeHash = Math.abs(hash);
245
- const alphanumericHash = nonNegativeHash.toString(36);
246
- return alphanumericHash;
247
- };
248
141
  export {
249
- addMetadata,
250
- hashFromQuery,
142
+ addMetadata2 as addMetadata,
143
+ hashFromQuery2 as hashFromQuery,
251
144
  tinaField,
252
145
  useEditState,
253
146
  useTina
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Re-export of the canonical `tinaField` helper from `@tinacms/bridge`.
3
+ * Kept here so `tinacms/tina-field` consumers don't need to switch imports.
4
+ */
5
+ export { tinaField } from '@tinacms/bridge/tina-field';
@@ -0,0 +1,4 @@
1
+ import { tinaField } from "@tinacms/bridge/tina-field";
2
+ export {
3
+ tinaField
4
+ };
@@ -1,5 +1,5 @@
1
- import { Media, MediaStore, MediaUploadOptions, MediaList, MediaListOptions } from './media';
2
1
  import { CMS } from './cms';
2
+ import { Media, MediaList, MediaListOptions, MediaStore, MediaUploadOptions } from './media';
3
3
  export declare class DummyMediaStore implements MediaStore {
4
4
  accept: string;
5
5
  persist(files: MediaUploadOptions[]): Promise<Media[]>;
@@ -35,7 +35,35 @@ export declare class TinaMediaStore implements MediaStore {
35
35
  isAuthenticated(): Promise<boolean>;
36
36
  accept: string;
37
37
  maxSize: number;
38
+ /**
39
+ * Returns the current branch as a single-encoded query-param value, or
40
+ * an empty string when no branch is set.
41
+ *
42
+ * `this.api.branch` is already URL-encoded by `Client.setBranch()`, so we
43
+ * decode then re-encode here to defend against double-encoding when this
44
+ * value is concatenated into a URL.
45
+ *
46
+ * `Client.setBranch()` runs the constructor's `options.branch` through
47
+ * `encodeURIComponent` without a guard, so an unset `options.branch`
48
+ * lands here as the literal string `"undefined"`. We treat that and the
49
+ * empty case as no-branch so we don't send `?branch=undefined` to the
50
+ * assets-api (which would route the call to a non-existent staging path).
51
+ */
52
+ private encodedBranchParam;
38
53
  private persist_cloud;
54
+ /**
55
+ * Resolves the just-uploaded items to canonical `Media` entries by hitting
56
+ * the assets-api `list` endpoint, which is the source of truth for the
57
+ * `src` URL — including the staging-branch path
58
+ * (`__staging/<branch>/__file/...`) and the per-stage CDN host.
59
+ * Constructing those URLs on the client would mirror server-side branch
60
+ * routing and CDN-host logic, which is fragile to keep in sync.
61
+ *
62
+ * Best-effort: items not found within the first page of their directory
63
+ * (e.g. very large directories) are omitted from the result rather than
64
+ * throwing — the upload itself already succeeded.
65
+ */
66
+ private fetchUploadedEntries;
39
67
  private persist_local;
40
68
  persist(media: MediaUploadOptions[]): Promise<Media[]>;
41
69
  private genThumbnail;
@@ -65,6 +65,14 @@ export interface MediaStore {
65
65
  * Lists all media in a specific directory.
66
66
  */
67
67
  list(options?: MediaListOptions): Promise<MediaList>;
68
+ /**
69
+ * Reserved hook for renaming a media object in the store.
70
+ *
71
+ * Not yet implemented in `TinaMediaStore` or surfaced in `MediaManager` —
72
+ * declared here as an extension point so stores can begin to opt in once
73
+ * the corresponding assets-api endpoint is built.
74
+ */
75
+ rename?(from: string, to: string): Promise<Media>;
68
76
  /**
69
77
  * Indicates that uploads and deletions are not supported
70
78
  *
@@ -5,6 +5,11 @@ import { CodeLeaf } from '../../components/plate-ui/code-leaf';
5
5
  import { CodeSyntaxLeaf } from '../../components/plate-ui/code-syntax-leaf';
6
6
  import { HrElement } from '../../components/plate-ui/hr-element';
7
7
  import { LinkElement } from '../../components/plate-ui/link-element';
8
+ type HeadingComponentProps = {
9
+ attributes: React.HTMLAttributes<HTMLHeadingElement>;
10
+ children: React.ReactNode;
11
+ className?: string;
12
+ };
8
13
  export declare const Components: () => {
9
14
  [CodeLinePlugin.key]: React.ForwardRefExoticComponent<Omit<Omit<import("@udecode/plate/react").PlateElementProps<import("@udecode/plate").TElement, import("@udecode/plate").AnyPluginConfig>, keyof {
10
15
  className?: string;
@@ -34,49 +39,12 @@ export declare const Components: () => {
34
39
  className?: string;
35
40
  style?: React.CSSProperties;
36
41
  }, "ref"> & React.RefAttributes<never>>;
37
- h1: ({ attributes, editor, element, className, ...props }: {
38
- [x: string]: any;
39
- attributes: any;
40
- editor: any;
41
- element: any;
42
- className: any;
43
- }) => React.JSX.Element;
44
- h2: ({ attributes, editor, element, className, ...props }: {
45
- [x: string]: any;
46
- attributes: any;
47
- editor: any;
48
- element: any;
49
- className: any;
50
- }) => React.JSX.Element;
51
- h3: ({ attributes, editor, element, className, ...props }: {
52
- [x: string]: any;
53
- attributes: any;
54
- editor: any;
55
- element: any;
56
- className: any;
57
- }) => React.JSX.Element;
58
- h4: ({ attributes, editor, element, className, ...props }: {
59
- [x: string]: any;
60
- attributes: any;
61
- editor: any;
62
- element: any;
63
- className: any;
64
- }) => React.JSX.Element;
65
- /** Tailwind prose doesn't style h5 and h6 elements */
66
- h5: ({ attributes, editor, element, className, ...props }: {
67
- [x: string]: any;
68
- attributes: any;
69
- editor: any;
70
- element: any;
71
- className: any;
72
- }) => React.JSX.Element;
73
- h6: ({ attributes, editor, element, className, ...props }: {
74
- [x: string]: any;
75
- attributes: any;
76
- editor: any;
77
- element: any;
78
- className: any;
79
- }) => React.JSX.Element;
42
+ h1: ({ attributes, children, className, }: HeadingComponentProps) => React.JSX.Element;
43
+ h2: ({ attributes, children, className, }: HeadingComponentProps) => React.JSX.Element;
44
+ h3: ({ attributes, children, className, }: HeadingComponentProps) => React.JSX.Element;
45
+ h4: ({ attributes, children, className, }: HeadingComponentProps) => React.JSX.Element;
46
+ h5: ({ attributes, children, className, }: HeadingComponentProps) => React.JSX.Element;
47
+ h6: ({ attributes, children, className, }: HeadingComponentProps) => React.JSX.Element;
80
48
  p: React.ForwardRefExoticComponent<Omit<Omit<import("@udecode/plate/react").PlateElementProps<import("@udecode/plate").TElement, import("@udecode/plate").AnyPluginConfig>, keyof {
81
49
  className?: string;
82
50
  style?: React.CSSProperties;
@@ -327,3 +295,4 @@ export declare const Components: () => {
327
295
  style?: React.CSSProperties;
328
296
  }, "ref"> & React.RefAttributes<never>>;
329
297
  };
298
+ export {};
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "tinacms",
3
3
  "type": "module",
4
4
  "typings": "dist/index.d.ts",
5
- "version": "3.7.6",
5
+ "version": "3.8.0",
6
6
  "main": "dist/index.js",
7
7
  "module": "./dist/index.js",
8
8
  "exports": {
@@ -10,6 +10,8 @@
10
10
  "./dist/client": "./dist/client.js",
11
11
  "./react": "./dist/react.js",
12
12
  "./dist/react": "./dist/react.js",
13
+ "./tina-field": "./dist/tina-field.js",
14
+ "./dist/tina-field": "./dist/tina-field.js",
13
15
  "./dist/rich-text": "./dist/rich-text/index.js",
14
16
  "./dist/rich-text/static": "./dist/rich-text/static.js",
15
17
  "./dist/rich-text/prism": "./dist/rich-text/prism.js"
@@ -24,6 +26,7 @@
24
26
  "src/rich-text/static.tsx",
25
27
  "src/rich-text/prism.tsx",
26
28
  "src/react.tsx",
29
+ "src/tina-field.ts",
27
30
  {
28
31
  "name": "src/client.ts",
29
32
  "target": "node"
@@ -114,9 +117,10 @@
114
117
  "webfontloader": "1.6.28",
115
118
  "yup": "^1.6.1",
116
119
  "zod": "^3.24.2",
117
- "@tinacms/mdx": "2.1.4",
120
+ "@tinacms/bridge": "0.2.0",
118
121
  "@tinacms/schema-tools": "2.7.4",
119
- "@tinacms/search": "1.2.13"
122
+ "@tinacms/mdx": "2.1.4",
123
+ "@tinacms/search": "1.2.14"
120
124
  },
121
125
  "devDependencies": {
122
126
  "@graphql-tools/utils": "^10.8.1",