@wordpress/editor 14.40.2-next.v.202602271551.0 → 14.41.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 (78) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/build/components/collaborators-presence/index.cjs +2 -2
  3. package/build/components/collaborators-presence/index.cjs.map +1 -1
  4. package/build/components/collaborators-presence/list.cjs +2 -2
  5. package/build/components/collaborators-presence/list.cjs.map +1 -1
  6. package/build/components/collaborators-presence/use-collaborator-notifications.cjs +174 -0
  7. package/build/components/collaborators-presence/use-collaborator-notifications.cjs.map +7 -0
  8. package/build/components/editor-interface/index.cjs +2 -0
  9. package/build/components/editor-interface/index.cjs.map +2 -2
  10. package/build/components/page-attributes/parent.cjs +0 -1
  11. package/build/components/page-attributes/parent.cjs.map +2 -2
  12. package/build/components/post-author/panel.cjs +0 -1
  13. package/build/components/post-author/panel.cjs.map +2 -2
  14. package/build/components/post-locked-modal/index.cjs +30 -14
  15. package/build/components/post-locked-modal/index.cjs.map +3 -3
  16. package/build/components/post-url/panel.cjs +0 -1
  17. package/build/components/post-url/panel.cjs.map +2 -2
  18. package/build/components/sync-connection-modal/index.cjs +78 -27
  19. package/build/components/sync-connection-modal/index.cjs.map +3 -3
  20. package/build/components/sync-connection-modal/use-retry-countdown.cjs +74 -0
  21. package/build/components/sync-connection-modal/use-retry-countdown.cjs.map +7 -0
  22. package/build/store/selectors.cjs +3 -0
  23. package/build/store/selectors.cjs.map +2 -2
  24. package/build/utils/sync-error-messages.cjs +16 -12
  25. package/build/utils/sync-error-messages.cjs.map +2 -2
  26. package/build-module/components/collaborators-presence/index.mjs +2 -2
  27. package/build-module/components/collaborators-presence/index.mjs.map +1 -1
  28. package/build-module/components/collaborators-presence/list.mjs +2 -2
  29. package/build-module/components/collaborators-presence/list.mjs.map +1 -1
  30. package/build-module/components/collaborators-presence/use-collaborator-notifications.mjs +151 -0
  31. package/build-module/components/collaborators-presence/use-collaborator-notifications.mjs.map +7 -0
  32. package/build-module/components/editor-interface/index.mjs +2 -0
  33. package/build-module/components/editor-interface/index.mjs.map +2 -2
  34. package/build-module/components/page-attributes/parent.mjs +0 -1
  35. package/build-module/components/page-attributes/parent.mjs.map +2 -2
  36. package/build-module/components/post-author/panel.mjs +0 -1
  37. package/build-module/components/post-author/panel.mjs.map +2 -2
  38. package/build-module/components/post-locked-modal/index.mjs +30 -14
  39. package/build-module/components/post-locked-modal/index.mjs.map +2 -2
  40. package/build-module/components/post-url/panel.mjs +0 -1
  41. package/build-module/components/post-url/panel.mjs.map +2 -2
  42. package/build-module/components/sync-connection-modal/index.mjs +83 -30
  43. package/build-module/components/sync-connection-modal/index.mjs.map +2 -2
  44. package/build-module/components/sync-connection-modal/use-retry-countdown.mjs +49 -0
  45. package/build-module/components/sync-connection-modal/use-retry-countdown.mjs.map +7 -0
  46. package/build-module/store/selectors.mjs +3 -0
  47. package/build-module/store/selectors.mjs.map +2 -2
  48. package/build-module/utils/sync-error-messages.mjs +16 -12
  49. package/build-module/utils/sync-error-messages.mjs.map +2 -2
  50. package/build-style/style-rtl.css +6 -22
  51. package/build-style/style.css +6 -22
  52. package/build-types/components/collaborators-presence/use-collaborator-notifications.d.ts +9 -0
  53. package/build-types/components/collaborators-presence/use-collaborator-notifications.d.ts.map +1 -0
  54. package/build-types/components/editor-interface/index.d.ts.map +1 -1
  55. package/build-types/components/page-attributes/parent.d.ts.map +1 -1
  56. package/build-types/components/post-author/panel.d.ts.map +1 -1
  57. package/build-types/components/post-locked-modal/index.d.ts.map +1 -1
  58. package/build-types/components/sync-connection-modal/index.d.ts.map +1 -1
  59. package/build-types/components/sync-connection-modal/use-retry-countdown.d.ts +9 -0
  60. package/build-types/components/sync-connection-modal/use-retry-countdown.d.ts.map +1 -0
  61. package/build-types/store/selectors.d.ts.map +1 -1
  62. package/build-types/utils/sync-error-messages.d.ts +1 -1
  63. package/build-types/utils/sync-error-messages.d.ts.map +1 -1
  64. package/package.json +44 -44
  65. package/src/components/collaborators-presence/test/use-collaborator-notifications.ts +454 -0
  66. package/src/components/collaborators-presence/use-collaborator-notifications.ts +258 -0
  67. package/src/components/editor-interface/index.js +5 -0
  68. package/src/components/page-attributes/parent.js +0 -1
  69. package/src/components/post-author/panel.js +0 -1
  70. package/src/components/post-locked-modal/index.js +46 -23
  71. package/src/components/post-url/panel.js +0 -1
  72. package/src/components/post-url/style.scss +0 -5
  73. package/src/components/sync-connection-modal/index.js +97 -37
  74. package/src/components/sync-connection-modal/style.scss +6 -8
  75. package/src/components/sync-connection-modal/use-retry-countdown.js +70 -0
  76. package/src/store/selectors.js +5 -0
  77. package/src/utils/sync-error-messages.js +17 -12
  78. package/src/utils/test/sync-error-messages.js +57 -0
@@ -2,7 +2,10 @@
2
2
  import { useSelect, select } from "@wordpress/data";
3
3
  import { useCopyToClipboard } from "@wordpress/compose";
4
4
  import { serialize } from "@wordpress/blocks";
5
- import { store as coreDataStore } from "@wordpress/core-data";
5
+ import {
6
+ store as coreDataStore,
7
+ privateApis as coreDataPrivateApis
8
+ } from "@wordpress/core-data";
6
9
  import {
7
10
  privateApis,
8
11
  store as blockEditorStore
@@ -10,22 +13,33 @@ import {
10
13
  import {
11
14
  Button,
12
15
  Modal,
13
- Icon,
14
16
  __experimentalHStack as HStack,
15
17
  __experimentalVStack as VStack
16
18
  } from "@wordpress/components";
17
19
  import { useState, useEffect, useRef } from "@wordpress/element";
18
- import { __ } from "@wordpress/i18n";
19
- import { error as errorIcon } from "@wordpress/icons";
20
+ import { __, sprintf, _n } from "@wordpress/i18n";
20
21
  import { getSyncErrorMessages } from "../../utils/sync-error-messages.mjs";
22
+ import { store as editorStore } from "../../store/index.mjs";
21
23
  import { unlock } from "../../lock-unlock.mjs";
24
+ import { useRetryCountdown } from "./use-retry-countdown.mjs";
22
25
  import { jsx, jsxs } from "react/jsx-runtime";
23
26
  var { BlockCanvasCover } = unlock(privateApis);
27
+ var { retrySyncConnection } = unlock(coreDataPrivateApis);
24
28
  var INITIAL_DISCONNECTED_DEBOUNCE_MS = 5e3;
29
+ var noop = () => {
30
+ };
25
31
  function SyncConnectionModal() {
26
- const connectionState = useSelect((selectFn) => {
27
- return selectFn(coreDataStore).getSyncConnectionStatus() || null;
32
+ const { connectionState, postType } = useSelect((selectFn) => {
33
+ const currentPostType = selectFn(editorStore).getCurrentPostType();
34
+ return {
35
+ connectionState: selectFn(coreDataStore).getSyncConnectionStatus() || null,
36
+ postType: currentPostType ? selectFn(coreDataStore).getPostType(currentPostType) : null
37
+ };
28
38
  }, []);
39
+ const { secondsRemaining, markRetrying } = useRetryCountdown(
40
+ connectionState?.retryInMs,
41
+ connectionState?.status
42
+ );
29
43
  const copyButtonRef = useCopyToClipboard(() => {
30
44
  const blocks = select(blockEditorStore).getBlocks();
31
45
  return serialize(blocks);
@@ -33,20 +47,21 @@ function SyncConnectionModal() {
33
47
  const [syncConnectionMessage, setSyncConnectionMessage] = useState(null);
34
48
  const debounceTimerRef = useRef(null);
35
49
  const hasInitializedRef = useRef(false);
50
+ const connectionStatus = connectionState?.status;
51
+ const connectionErrorCode = connectionState?.error?.code;
36
52
  useEffect(() => {
37
- const status = connectionState?.status;
38
53
  if (debounceTimerRef.current) {
39
54
  clearTimeout(debounceTimerRef.current);
40
55
  debounceTimerRef.current = null;
41
56
  }
42
- if (status === "connected") {
57
+ if (connectionStatus === "connected") {
43
58
  hasInitializedRef.current = true;
44
59
  setSyncConnectionMessage(null);
45
- } else if (status === "disconnected") {
60
+ } else if (connectionStatus === "disconnected") {
46
61
  const showModal = () => {
47
62
  hasInitializedRef.current = true;
48
63
  setSyncConnectionMessage(
49
- getSyncErrorMessages(connectionState.error ?? {})
64
+ getSyncErrorMessages({ code: connectionErrorCode })
50
65
  );
51
66
  };
52
67
  if (hasInitializedRef.current) {
@@ -63,48 +78,86 @@ function SyncConnectionModal() {
63
78
  clearTimeout(debounceTimerRef.current);
64
79
  }
65
80
  };
66
- }, [connectionState]);
81
+ }, [connectionStatus, connectionErrorCode]);
67
82
  if (!syncConnectionMessage) {
68
83
  return null;
69
84
  }
70
- const { title, description } = syncConnectionMessage;
85
+ const { title, description, canRetry } = syncConnectionMessage;
86
+ let retryCountdownText;
87
+ if (secondsRemaining > 0) {
88
+ retryCountdownText = sprintf(
89
+ /* translators: %d: number of seconds until retry */
90
+ _n(
91
+ "Retrying connection in %d second\u2026",
92
+ "Retrying connection in %d seconds\u2026",
93
+ secondsRemaining
94
+ ),
95
+ secondsRemaining
96
+ );
97
+ } else if (secondsRemaining === 0) {
98
+ retryCountdownText = __("Retrying\u2026");
99
+ }
100
+ let editPostHref = "edit.php";
101
+ if (postType?.slug) {
102
+ editPostHref = `edit.php?post_type=${postType.slug}`;
103
+ }
104
+ const isRetrying = secondsRemaining === 0;
71
105
  return /* @__PURE__ */ jsx(BlockCanvasCover.Fill, { children: /* @__PURE__ */ jsx(
72
106
  Modal,
73
107
  {
74
- __experimentalHideHeader: true,
75
- icon: errorIcon,
108
+ className: "editor-sync-connection-modal",
76
109
  isDismissible: false,
77
- isFullScreen: false,
78
- onRequestClose: () => {
79
- },
110
+ onRequestClose: noop,
80
111
  shouldCloseOnClickOutside: false,
81
112
  shouldCloseOnEsc: false,
82
- children: /* @__PURE__ */ jsx("div", { className: "editor-sync-connection-modal__container", children: /* @__PURE__ */ jsxs(VStack, { alignment: "center", justify: "center", spacing: 2, children: [
83
- /* @__PURE__ */ jsx(Icon, { fill: "#ccc", icon: errorIcon, size: 64 }),
84
- /* @__PURE__ */ jsx("h1", { children: title }),
85
- /* @__PURE__ */ jsx("p", { className: "editor-sync-connection-modal__description", children: description }),
86
- /* @__PURE__ */ jsxs(HStack, { spacing: 2, justify: "center", children: [
113
+ size: "medium",
114
+ title,
115
+ children: /* @__PURE__ */ jsxs(VStack, { spacing: 6, children: [
116
+ /* @__PURE__ */ jsx("p", { children: description }),
117
+ retryCountdownText && /* @__PURE__ */ jsx("p", { className: "editor-sync-connection-modal__retry-countdown", children: retryCountdownText }),
118
+ /* @__PURE__ */ jsxs(HStack, { justify: "right", children: [
87
119
  /* @__PURE__ */ jsx(
88
120
  Button,
89
121
  {
90
122
  __next40pxDefaultSize: true,
91
- ref: copyButtonRef,
92
- variant: "primary",
93
- children: __("Copy post content")
123
+ href: editPostHref,
124
+ isDestructive: true,
125
+ variant: "tertiary",
126
+ children: sprintf(
127
+ /* translators: %s: Post type name (e.g., "Posts", "Pages"). */
128
+ __("Back to %s"),
129
+ postType?.labels?.name ?? __("Posts")
130
+ )
94
131
  }
95
132
  ),
96
133
  /* @__PURE__ */ jsx(
97
134
  Button,
98
135
  {
99
136
  __next40pxDefaultSize: true,
100
- href: "edit.php",
101
- isDestructive: true,
102
- variant: "secondary",
103
- children: __("Edit another post")
137
+ ref: copyButtonRef,
138
+ variant: canRetry ? "secondary" : "primary",
139
+ children: __("Copy Post Content")
140
+ }
141
+ ),
142
+ canRetry && /* @__PURE__ */ jsx(
143
+ Button,
144
+ {
145
+ __next40pxDefaultSize: true,
146
+ "aria-disabled": isRetrying,
147
+ isBusy: isRetrying,
148
+ variant: "primary",
149
+ onClick: () => {
150
+ if (isRetrying) {
151
+ return;
152
+ }
153
+ markRetrying();
154
+ retrySyncConnection();
155
+ },
156
+ children: __("Retry")
104
157
  }
105
158
  )
106
159
  ] })
107
- ] }) })
160
+ ] })
108
161
  }
109
162
  ) });
110
163
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/components/sync-connection-modal/index.js"],
4
- "sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { useSelect, select } from '@wordpress/data';\nimport { useCopyToClipboard } from '@wordpress/compose';\nimport { serialize } from '@wordpress/blocks';\nimport { store as coreDataStore } from '@wordpress/core-data';\nimport {\n\tprivateApis,\n\tstore as blockEditorStore,\n} from '@wordpress/block-editor';\nimport {\n\tButton,\n\tModal,\n\tIcon,\n\t__experimentalHStack as HStack,\n\t__experimentalVStack as VStack,\n} from '@wordpress/components';\nimport { useState, useEffect, useRef } from '@wordpress/element';\nimport { __ } from '@wordpress/i18n';\nimport { error as errorIcon } from '@wordpress/icons';\n\n/**\n * Internal dependencies\n */\nimport { getSyncErrorMessages } from '../../utils/sync-error-messages';\nimport { unlock } from '../../lock-unlock';\n\nconst { BlockCanvasCover } = unlock( privateApis );\n\n// Debounce time for initial disconnected status to allow connection to establish.\nconst INITIAL_DISCONNECTED_DEBOUNCE_MS = 5000;\n\n/**\n * Sync connection modal that displays when any entity reports a disconnection.\n * Uses BlockCanvasCover.Fill to render in the block canvas.\n *\n * @return {Element|null} The modal component or null if not disconnected.\n */\nexport function SyncConnectionModal() {\n\tconst connectionState = useSelect( ( selectFn ) => {\n\t\treturn selectFn( coreDataStore ).getSyncConnectionStatus() || null;\n\t}, [] );\n\n\tconst copyButtonRef = useCopyToClipboard( () => {\n\t\tconst blocks = select( blockEditorStore ).getBlocks();\n\t\treturn serialize( blocks );\n\t} );\n\tconst [ syncConnectionMessage, setSyncConnectionMessage ] =\n\t\tuseState( null );\n\tconst debounceTimerRef = useRef( null );\n\t// Track whether we've passed the initial load phase.\n\t// Once true, disconnected status will show immediately without debounce.\n\tconst hasInitializedRef = useRef( false );\n\n\tuseEffect( () => {\n\t\tconst status = connectionState?.status;\n\n\t\t// Clear any pending debounce timer when status changes.\n\t\tif ( debounceTimerRef.current ) {\n\t\t\tclearTimeout( debounceTimerRef.current );\n\t\t\tdebounceTimerRef.current = null;\n\t\t}\n\n\t\tif ( status === 'connected' ) {\n\t\t\thasInitializedRef.current = true;\n\t\t\tsetSyncConnectionMessage( null );\n\t\t} else if ( status === 'disconnected' ) {\n\t\t\tconst showModal = () => {\n\t\t\t\thasInitializedRef.current = true;\n\t\t\t\tsetSyncConnectionMessage(\n\t\t\t\t\tgetSyncErrorMessages( connectionState.error ?? {} )\n\t\t\t\t);\n\t\t\t};\n\n\t\t\t// Debounce only on first load to allow connection to establish.\n\t\t\tif ( hasInitializedRef.current ) {\n\t\t\t\tshowModal();\n\t\t\t} else {\n\t\t\t\tdebounceTimerRef.current = setTimeout(\n\t\t\t\t\tshowModal,\n\t\t\t\t\tINITIAL_DISCONNECTED_DEBOUNCE_MS\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn () => {\n\t\t\tif ( debounceTimerRef.current ) {\n\t\t\t\tclearTimeout( debounceTimerRef.current );\n\t\t\t}\n\t\t};\n\t}, [ connectionState ] );\n\n\tif ( ! syncConnectionMessage ) {\n\t\treturn null;\n\t}\n\n\tconst { title, description } = syncConnectionMessage;\n\n\treturn (\n\t\t<BlockCanvasCover.Fill>\n\t\t\t<Modal\n\t\t\t\t__experimentalHideHeader\n\t\t\t\ticon={ errorIcon }\n\t\t\t\tisDismissible={ false }\n\t\t\t\tisFullScreen={ false }\n\t\t\t\tonRequestClose={ () => {} }\n\t\t\t\tshouldCloseOnClickOutside={ false }\n\t\t\t\tshouldCloseOnEsc={ false }\n\t\t\t>\n\t\t\t\t<div className=\"editor-sync-connection-modal__container\">\n\t\t\t\t\t<VStack alignment=\"center\" justify=\"center\" spacing={ 2 }>\n\t\t\t\t\t\t<Icon fill=\"#ccc\" icon={ errorIcon } size={ 64 } />\n\t\t\t\t\t\t<h1>{ title }</h1>\n\t\t\t\t\t\t<p className=\"editor-sync-connection-modal__description\">\n\t\t\t\t\t\t\t{ description }\n\t\t\t\t\t\t</p>\n\t\t\t\t\t\t<HStack spacing={ 2 } justify=\"center\">\n\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t__next40pxDefaultSize\n\t\t\t\t\t\t\t\tref={ copyButtonRef }\n\t\t\t\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{ __( 'Copy post content' ) }\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t__next40pxDefaultSize\n\t\t\t\t\t\t\t\thref=\"edit.php\"\n\t\t\t\t\t\t\t\tisDestructive\n\t\t\t\t\t\t\t\tvariant=\"secondary\"\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{ __( 'Edit another post' ) }\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t</HStack>\n\t\t\t\t\t</VStack>\n\t\t\t\t</div>\n\t\t\t</Modal>\n\t\t</BlockCanvasCover.Fill>\n\t);\n}\n"],
5
- "mappings": ";AAGA,SAAS,WAAW,cAAc;AAClC,SAAS,0BAA0B;AACnC,SAAS,iBAAiB;AAC1B,SAAS,SAAS,qBAAqB;AACvC;AAAA,EACC;AAAA,EACA,SAAS;AAAA,OACH;AACP;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACA,wBAAwB;AAAA,EACxB,wBAAwB;AAAA,OAClB;AACP,SAAS,UAAU,WAAW,cAAc;AAC5C,SAAS,UAAU;AACnB,SAAS,SAAS,iBAAiB;AAKnC,SAAS,4BAA4B;AACrC,SAAS,cAAc;AAsFjB,cAKA,YALA;AApFN,IAAM,EAAE,iBAAiB,IAAI,OAAQ,WAAY;AAGjD,IAAM,mCAAmC;AAQlC,SAAS,sBAAsB;AACrC,QAAM,kBAAkB,UAAW,CAAE,aAAc;AAClD,WAAO,SAAU,aAAc,EAAE,wBAAwB,KAAK;AAAA,EAC/D,GAAG,CAAC,CAAE;AAEN,QAAM,gBAAgB,mBAAoB,MAAM;AAC/C,UAAM,SAAS,OAAQ,gBAAiB,EAAE,UAAU;AACpD,WAAO,UAAW,MAAO;AAAA,EAC1B,CAAE;AACF,QAAM,CAAE,uBAAuB,wBAAyB,IACvD,SAAU,IAAK;AAChB,QAAM,mBAAmB,OAAQ,IAAK;AAGtC,QAAM,oBAAoB,OAAQ,KAAM;AAExC,YAAW,MAAM;AAChB,UAAM,SAAS,iBAAiB;AAGhC,QAAK,iBAAiB,SAAU;AAC/B,mBAAc,iBAAiB,OAAQ;AACvC,uBAAiB,UAAU;AAAA,IAC5B;AAEA,QAAK,WAAW,aAAc;AAC7B,wBAAkB,UAAU;AAC5B,+BAA0B,IAAK;AAAA,IAChC,WAAY,WAAW,gBAAiB;AACvC,YAAM,YAAY,MAAM;AACvB,0BAAkB,UAAU;AAC5B;AAAA,UACC,qBAAsB,gBAAgB,SAAS,CAAC,CAAE;AAAA,QACnD;AAAA,MACD;AAGA,UAAK,kBAAkB,SAAU;AAChC,kBAAU;AAAA,MACX,OAAO;AACN,yBAAiB,UAAU;AAAA,UAC1B;AAAA,UACA;AAAA,QACD;AAAA,MACD;AAAA,IACD;AAEA,WAAO,MAAM;AACZ,UAAK,iBAAiB,SAAU;AAC/B,qBAAc,iBAAiB,OAAQ;AAAA,MACxC;AAAA,IACD;AAAA,EACD,GAAG,CAAE,eAAgB,CAAE;AAEvB,MAAK,CAAE,uBAAwB;AAC9B,WAAO;AAAA,EACR;AAEA,QAAM,EAAE,OAAO,YAAY,IAAI;AAE/B,SACC,oBAAC,iBAAiB,MAAjB,EACA;AAAA,IAAC;AAAA;AAAA,MACA,0BAAwB;AAAA,MACxB,MAAO;AAAA,MACP,eAAgB;AAAA,MAChB,cAAe;AAAA,MACf,gBAAiB,MAAM;AAAA,MAAC;AAAA,MACxB,2BAA4B;AAAA,MAC5B,kBAAmB;AAAA,MAEnB,8BAAC,SAAI,WAAU,2CACd,+BAAC,UAAO,WAAU,UAAS,SAAQ,UAAS,SAAU,GACrD;AAAA,4BAAC,QAAK,MAAK,QAAO,MAAO,WAAY,MAAO,IAAK;AAAA,QACjD,oBAAC,QAAK,iBAAO;AAAA,QACb,oBAAC,OAAE,WAAU,6CACV,uBACH;AAAA,QACA,qBAAC,UAAO,SAAU,GAAI,SAAQ,UAC7B;AAAA;AAAA,YAAC;AAAA;AAAA,cACA,uBAAqB;AAAA,cACrB,KAAM;AAAA,cACN,SAAQ;AAAA,cAEN,aAAI,mBAAoB;AAAA;AAAA,UAC3B;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACA,uBAAqB;AAAA,cACrB,MAAK;AAAA,cACL,eAAa;AAAA,cACb,SAAQ;AAAA,cAEN,aAAI,mBAAoB;AAAA;AAAA,UAC3B;AAAA,WACD;AAAA,SACD,GACD;AAAA;AAAA,EACD,GACD;AAEF;",
4
+ "sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { useSelect, select } from '@wordpress/data';\nimport { useCopyToClipboard } from '@wordpress/compose';\nimport { serialize } from '@wordpress/blocks';\nimport {\n\tstore as coreDataStore,\n\tprivateApis as coreDataPrivateApis,\n} from '@wordpress/core-data';\nimport {\n\tprivateApis,\n\tstore as blockEditorStore,\n} from '@wordpress/block-editor';\nimport {\n\tButton,\n\tModal,\n\t__experimentalHStack as HStack,\n\t__experimentalVStack as VStack,\n} from '@wordpress/components';\nimport { useState, useEffect, useRef } from '@wordpress/element';\nimport { __, sprintf, _n } from '@wordpress/i18n';\n\n/**\n * Internal dependencies\n */\nimport { getSyncErrorMessages } from '../../utils/sync-error-messages';\nimport { store as editorStore } from '../../store';\nimport { unlock } from '../../lock-unlock';\nimport { useRetryCountdown } from './use-retry-countdown';\n\nconst { BlockCanvasCover } = unlock( privateApis );\nconst { retrySyncConnection } = unlock( coreDataPrivateApis );\n\n// Debounce time for initial disconnected status to allow connection to establish.\nconst INITIAL_DISCONNECTED_DEBOUNCE_MS = 5000;\nconst noop = () => {};\n\n/**\n * Sync connection modal that displays when any entity reports a disconnection.\n * Uses BlockCanvasCover.Fill to render in the block canvas.\n *\n * @return {Element|null} The modal component or null if not disconnected.\n */\nexport function SyncConnectionModal() {\n\tconst { connectionState, postType } = useSelect( ( selectFn ) => {\n\t\tconst currentPostType = selectFn( editorStore ).getCurrentPostType();\n\t\treturn {\n\t\t\tconnectionState:\n\t\t\t\tselectFn( coreDataStore ).getSyncConnectionStatus() || null,\n\t\t\tpostType: currentPostType\n\t\t\t\t? selectFn( coreDataStore ).getPostType( currentPostType )\n\t\t\t\t: null,\n\t\t};\n\t}, [] );\n\n\tconst { secondsRemaining, markRetrying } = useRetryCountdown(\n\t\tconnectionState?.retryInMs,\n\t\tconnectionState?.status\n\t);\n\n\tconst copyButtonRef = useCopyToClipboard( () => {\n\t\tconst blocks = select( blockEditorStore ).getBlocks();\n\t\treturn serialize( blocks );\n\t} );\n\tconst [ syncConnectionMessage, setSyncConnectionMessage ] =\n\t\tuseState( null );\n\tconst debounceTimerRef = useRef( null );\n\t// Track whether we've passed the initial load phase.\n\t// Once true, disconnected status will show immediately without debounce.\n\tconst hasInitializedRef = useRef( false );\n\n\tconst connectionStatus = connectionState?.status;\n\tconst connectionErrorCode = connectionState?.error?.code;\n\n\tuseEffect( () => {\n\t\t// Clear any pending debounce timer when status changes.\n\t\tif ( debounceTimerRef.current ) {\n\t\t\tclearTimeout( debounceTimerRef.current );\n\t\t\tdebounceTimerRef.current = null;\n\t\t}\n\n\t\tif ( connectionStatus === 'connected' ) {\n\t\t\thasInitializedRef.current = true;\n\t\t\tsetSyncConnectionMessage( null );\n\t\t} else if ( connectionStatus === 'disconnected' ) {\n\t\t\tconst showModal = () => {\n\t\t\t\thasInitializedRef.current = true;\n\t\t\t\tsetSyncConnectionMessage(\n\t\t\t\t\tgetSyncErrorMessages( { code: connectionErrorCode } )\n\t\t\t\t);\n\t\t\t};\n\n\t\t\t// Debounce only on first load to allow connection to establish.\n\t\t\tif ( hasInitializedRef.current ) {\n\t\t\t\tshowModal();\n\t\t\t} else {\n\t\t\t\tdebounceTimerRef.current = setTimeout(\n\t\t\t\t\tshowModal,\n\t\t\t\t\tINITIAL_DISCONNECTED_DEBOUNCE_MS\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\treturn () => {\n\t\t\tif ( debounceTimerRef.current ) {\n\t\t\t\tclearTimeout( debounceTimerRef.current );\n\t\t\t}\n\t\t};\n\t}, [ connectionStatus, connectionErrorCode ] );\n\n\tif ( ! syncConnectionMessage ) {\n\t\treturn null;\n\t}\n\n\tconst { title, description, canRetry } = syncConnectionMessage;\n\n\tlet retryCountdownText;\n\tif ( secondsRemaining > 0 ) {\n\t\tretryCountdownText = sprintf(\n\t\t\t/* translators: %d: number of seconds until retry */\n\t\t\t_n(\n\t\t\t\t'Retrying connection in %d second\\u2026',\n\t\t\t\t'Retrying connection in %d seconds\\u2026',\n\t\t\t\tsecondsRemaining\n\t\t\t),\n\t\t\tsecondsRemaining\n\t\t);\n\t} else if ( secondsRemaining === 0 ) {\n\t\tretryCountdownText = __( 'Retrying\\u2026' );\n\t}\n\n\tlet editPostHref = 'edit.php';\n\tif ( postType?.slug ) {\n\t\teditPostHref = `edit.php?post_type=${ postType.slug }`;\n\t}\n\n\tconst isRetrying = secondsRemaining === 0;\n\n\treturn (\n\t\t<BlockCanvasCover.Fill>\n\t\t\t<Modal\n\t\t\t\tclassName=\"editor-sync-connection-modal\"\n\t\t\t\tisDismissible={ false }\n\t\t\t\tonRequestClose={ noop }\n\t\t\t\tshouldCloseOnClickOutside={ false }\n\t\t\t\tshouldCloseOnEsc={ false }\n\t\t\t\tsize=\"medium\"\n\t\t\t\ttitle={ title }\n\t\t\t>\n\t\t\t\t<VStack spacing={ 6 }>\n\t\t\t\t\t<p>{ description }</p>\n\t\t\t\t\t{ retryCountdownText && (\n\t\t\t\t\t\t<p className=\"editor-sync-connection-modal__retry-countdown\">\n\t\t\t\t\t\t\t{ retryCountdownText }\n\t\t\t\t\t\t</p>\n\t\t\t\t\t) }\n\t\t\t\t\t<HStack justify=\"right\">\n\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t__next40pxDefaultSize\n\t\t\t\t\t\t\thref={ editPostHref }\n\t\t\t\t\t\t\tisDestructive\n\t\t\t\t\t\t\tvariant=\"tertiary\"\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{ sprintf(\n\t\t\t\t\t\t\t\t/* translators: %s: Post type name (e.g., \"Posts\", \"Pages\"). */\n\t\t\t\t\t\t\t\t__( 'Back to %s' ),\n\t\t\t\t\t\t\t\tpostType?.labels?.name ?? __( 'Posts' )\n\t\t\t\t\t\t\t) }\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t__next40pxDefaultSize\n\t\t\t\t\t\t\tref={ copyButtonRef }\n\t\t\t\t\t\t\tvariant={ canRetry ? 'secondary' : 'primary' }\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{ __( 'Copy Post Content' ) }\n\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t{ canRetry && (\n\t\t\t\t\t\t\t<Button\n\t\t\t\t\t\t\t\t__next40pxDefaultSize\n\t\t\t\t\t\t\t\taria-disabled={ isRetrying }\n\t\t\t\t\t\t\t\tisBusy={ isRetrying }\n\t\t\t\t\t\t\t\tvariant=\"primary\"\n\t\t\t\t\t\t\t\tonClick={ () => {\n\t\t\t\t\t\t\t\t\tif ( isRetrying ) {\n\t\t\t\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\tmarkRetrying();\n\t\t\t\t\t\t\t\t\tretrySyncConnection();\n\t\t\t\t\t\t\t\t} }\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{ __( 'Retry' ) }\n\t\t\t\t\t\t\t</Button>\n\t\t\t\t\t\t) }\n\t\t\t\t\t</HStack>\n\t\t\t\t</VStack>\n\t\t\t</Modal>\n\t\t</BlockCanvasCover.Fill>\n\t);\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,49 @@
1
+ // packages/editor/src/components/sync-connection-modal/use-retry-countdown.js
2
+ import { useState, useEffect, useRef } from "@wordpress/element";
3
+ var MIN_RETRYING_DISPLAY_MS = 600;
4
+ function useRetryCountdown(retryInMs, status) {
5
+ const [secondsRemaining, setSecondsRemaining] = useState(null);
6
+ const [isRetrying, setIsRetrying] = useState(false);
7
+ const retryAtRef = useRef(null);
8
+ const markRetrying = () => setIsRetrying(true);
9
+ useEffect(() => {
10
+ if (!isRetrying) {
11
+ return;
12
+ }
13
+ const id = setTimeout(
14
+ () => setIsRetrying(false),
15
+ MIN_RETRYING_DISPLAY_MS
16
+ );
17
+ return () => clearTimeout(id);
18
+ }, [isRetrying]);
19
+ useEffect(() => {
20
+ if (status === "connected") {
21
+ setSecondsRemaining(null);
22
+ retryAtRef.current = null;
23
+ return;
24
+ }
25
+ if (status !== "disconnected" || !retryInMs) {
26
+ return;
27
+ }
28
+ const retryAt = Date.now() + retryInMs;
29
+ retryAtRef.current = retryAt;
30
+ setSecondsRemaining(Math.ceil(retryInMs / 1e3));
31
+ const intervalId = setInterval(() => {
32
+ const remaining = Math.ceil(
33
+ (retryAtRef.current - Date.now()) / 1e3
34
+ );
35
+ setSecondsRemaining(Math.max(0, remaining));
36
+ if (remaining <= 0) {
37
+ clearInterval(intervalId);
38
+ setIsRetrying(true);
39
+ }
40
+ }, 1e3);
41
+ return () => clearInterval(intervalId);
42
+ }, [retryInMs, status]);
43
+ const displaySeconds = isRetrying ? 0 : secondsRemaining;
44
+ return { secondsRemaining: displaySeconds, markRetrying };
45
+ }
46
+ export {
47
+ useRetryCountdown
48
+ };
49
+ //# sourceMappingURL=use-retry-countdown.mjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/components/sync-connection-modal/use-retry-countdown.js"],
4
+ "sourcesContent": ["/**\n * WordPress dependencies\n */\nimport { useState, useEffect, useRef } from '@wordpress/element';\n\nconst MIN_RETRYING_DISPLAY_MS = 600;\n\n/**\n * Hook that computes a countdown in seconds from a retryInMs value.\n *\n * @param {number|undefined} retryInMs Milliseconds until next retry.\n * @param {string|undefined} status Current connection status.\n * @return {Object} Object with `secondsRemaining` (number|null) and `markRetrying` callback.\n */\nexport function useRetryCountdown( retryInMs, status ) {\n\tconst [ secondsRemaining, setSecondsRemaining ] = useState( null );\n\tconst [ isRetrying, setIsRetrying ] = useState( false );\n\tconst retryAtRef = useRef( null );\n\n\t// Show \"Retrying\u2026\" for a minimum duration when manually triggered.\n\tconst markRetrying = () => setIsRetrying( true );\n\n\tuseEffect( () => {\n\t\tif ( ! isRetrying ) {\n\t\t\treturn;\n\t\t}\n\t\tconst id = setTimeout(\n\t\t\t() => setIsRetrying( false ),\n\t\t\tMIN_RETRYING_DISPLAY_MS\n\t\t);\n\t\treturn () => clearTimeout( id );\n\t}, [ isRetrying ] );\n\n\tuseEffect( () => {\n\t\t// Only clear countdown when explicitly connected.\n\t\tif ( status === 'connected' ) {\n\t\t\tsetSecondsRemaining( null );\n\t\t\tretryAtRef.current = null;\n\t\t\treturn;\n\t\t}\n\n\t\t// For transient states (e.g. 'connecting' during a retry attempt)\n\t\t// or when retryInMs is not yet available, keep the previous\n\t\t// countdown value to avoid a brief flash.\n\t\tif ( status !== 'disconnected' || ! retryInMs ) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst retryAt = Date.now() + retryInMs;\n\t\tretryAtRef.current = retryAt;\n\t\tsetSecondsRemaining( Math.ceil( retryInMs / 1000 ) );\n\n\t\tconst intervalId = setInterval( () => {\n\t\t\tconst remaining = Math.ceil(\n\t\t\t\t( retryAtRef.current - Date.now() ) / 1000\n\t\t\t);\n\t\t\tsetSecondsRemaining( Math.max( 0, remaining ) );\n\t\t\tif ( remaining <= 0 ) {\n\t\t\t\tclearInterval( intervalId );\n\t\t\t\tsetIsRetrying( true );\n\t\t\t}\n\t\t}, 1000 );\n\n\t\treturn () => clearInterval( intervalId );\n\t}, [ retryInMs, status ] );\n\n\tconst displaySeconds = isRetrying ? 0 : secondsRemaining;\n\n\treturn { secondsRemaining: displaySeconds, markRetrying };\n}\n"],
5
+ "mappings": ";AAGA,SAAS,UAAU,WAAW,cAAc;AAE5C,IAAM,0BAA0B;AASzB,SAAS,kBAAmB,WAAW,QAAS;AACtD,QAAM,CAAE,kBAAkB,mBAAoB,IAAI,SAAU,IAAK;AACjE,QAAM,CAAE,YAAY,aAAc,IAAI,SAAU,KAAM;AACtD,QAAM,aAAa,OAAQ,IAAK;AAGhC,QAAM,eAAe,MAAM,cAAe,IAAK;AAE/C,YAAW,MAAM;AAChB,QAAK,CAAE,YAAa;AACnB;AAAA,IACD;AACA,UAAM,KAAK;AAAA,MACV,MAAM,cAAe,KAAM;AAAA,MAC3B;AAAA,IACD;AACA,WAAO,MAAM,aAAc,EAAG;AAAA,EAC/B,GAAG,CAAE,UAAW,CAAE;AAElB,YAAW,MAAM;AAEhB,QAAK,WAAW,aAAc;AAC7B,0BAAqB,IAAK;AAC1B,iBAAW,UAAU;AACrB;AAAA,IACD;AAKA,QAAK,WAAW,kBAAkB,CAAE,WAAY;AAC/C;AAAA,IACD;AAEA,UAAM,UAAU,KAAK,IAAI,IAAI;AAC7B,eAAW,UAAU;AACrB,wBAAqB,KAAK,KAAM,YAAY,GAAK,CAAE;AAEnD,UAAM,aAAa,YAAa,MAAM;AACrC,YAAM,YAAY,KAAK;AAAA,SACpB,WAAW,UAAU,KAAK,IAAI,KAAM;AAAA,MACvC;AACA,0BAAqB,KAAK,IAAK,GAAG,SAAU,CAAE;AAC9C,UAAK,aAAa,GAAI;AACrB,sBAAe,UAAW;AAC1B,sBAAe,IAAK;AAAA,MACrB;AAAA,IACD,GAAG,GAAK;AAER,WAAO,MAAM,cAAe,UAAW;AAAA,EACxC,GAAG,CAAE,WAAW,MAAO,CAAE;AAEzB,QAAM,iBAAiB,aAAa,IAAI;AAExC,SAAO,EAAE,kBAAkB,gBAAgB,aAAa;AACzD;",
6
+ "names": []
7
+ }
@@ -748,6 +748,9 @@ function isPublishSidebarOpened(state) {
748
748
  }
749
749
  var isCollaborationEnabledForCurrentPost = createRegistrySelector(
750
750
  (select) => (state) => {
751
+ if (!unlock(select(coreStore)).isCollaborationSupported()) {
752
+ return false;
753
+ }
751
754
  const currentPostType = getCurrentPostType(state);
752
755
  const entityConfig = select(coreStore).getEntityConfig(
753
756
  "postType",