@atlaskit/editor-plugin-synced-block 8.2.16 → 8.3.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/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # @atlaskit/editor-plugin-synced-block
2
2
 
3
+ ## 8.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`41168b2bd2790`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/41168b2bd2790) -
8
+ Autofix: add explicit package exports (barrel removal)
9
+
10
+ ### Patch Changes
11
+
12
+ - Updated dependencies
13
+
14
+ ## 8.2.17
15
+
16
+ ### Patch Changes
17
+
18
+ - [`9784984097a8a`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/9784984097a8a) -
19
+ [ux] Improves synced block support for Jira work items, including product-specific copy,
20
+ issue-type icons, and enhanced analytics.
21
+ - Updated dependencies
22
+
3
23
  ## 8.2.16
4
24
 
5
25
  ### Patch Changes
@@ -0,0 +1 @@
1
+ "use strict";
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ Object.defineProperty(exports, "syncedBlockPlugin", {
7
+ enumerable: true,
8
+ get: function get() {
9
+ return _syncedBlockPlugin.syncedBlockPlugin;
10
+ }
11
+ });
12
+ var _syncedBlockPlugin = require("../syncedBlockPlugin");
@@ -20,6 +20,7 @@ var _state = require("@atlaskit/editor-prosemirror/state");
20
20
  var _transform = require("@atlaskit/editor-prosemirror/transform");
21
21
  var _view = require("@atlaskit/editor-prosemirror/view");
22
22
  var _editorSyncedBlockProvider = require("@atlaskit/editor-synced-block-provider");
23
+ var _utils2 = require("@atlaskit/editor-synced-block-provider/utils");
23
24
  var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
24
25
  var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
25
26
  var _experiments = require("@atlaskit/tmp-editor-statsig/experiments");
@@ -33,7 +34,7 @@ var _ignoreDomEvent = require("./utils/ignore-dom-event");
33
34
  var _selectionDecorations = require("./utils/selection-decorations");
34
35
  var _trackSyncBlocks7 = require("./utils/track-sync-blocks");
35
36
  var _transactionInsertsSyncedBlock = require("./utils/transaction-inserts-synced-block");
36
- var _utils2 = require("./utils/utils");
37
+ var _utils3 = require("./utils/utils");
37
38
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
38
39
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
39
40
  function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
@@ -81,7 +82,7 @@ var mapRetryCreationPosMap = function mapRetryCreationPosMap(oldMap, newRetryCre
81
82
  return newMap;
82
83
  };
83
84
  var showCopiedFlag = function showCopiedFlag(api) {
84
- (0, _utils2.deferDispatch)(function () {
85
+ (0, _utils3.deferDispatch)(function () {
85
86
  api === null || api === void 0 || api.core.actions.execute(function (_ref) {
86
87
  var tr = _ref.tr;
87
88
  return tr.setMeta(syncedBlockPluginKey, {
@@ -97,11 +98,11 @@ var showExtensionInSyncBlockWarningIfNeeded = function showExtensionInSyncBlockW
97
98
  if (!tr.docChanged || tr.getMeta('isRemote') || Boolean(tr.getMeta(_utils.pmHistoryPluginKey)) || (0, _editorPluginConnectivity.isOfflineMode)(api === null || api === void 0 || (_api$connectivity = api.connectivity) === null || _api$connectivity === void 0 || (_api$connectivity = _api$connectivity.sharedState.currentState()) === null || _api$connectivity === void 0 ? void 0 : _api$connectivity.mode)) {
98
99
  return;
99
100
  }
100
- var resourceId = (0, _utils2.wasExtensionInsertedInBodiedSyncBlock)(tr, state);
101
+ var resourceId = (0, _utils3.wasExtensionInsertedInBodiedSyncBlock)(tr, state);
101
102
  // Only show the flag on the first instance per sync block (same as UNPUBLISHED_SYNC_BLOCK_PASTED)
102
103
  if (resourceId && !extensionFlagShown.has(resourceId)) {
103
104
  extensionFlagShown.add(resourceId);
104
- (0, _utils2.deferDispatch)(function () {
105
+ (0, _utils3.deferDispatch)(function () {
105
106
  api === null || api === void 0 || api.core.actions.execute(function (_ref2) {
106
107
  var tr = _ref2.tr;
107
108
  return tr.setMeta(syncedBlockPluginKey, {
@@ -121,6 +122,7 @@ var getDeleteReason = function getDeleteReason(tr) {
121
122
  return reason;
122
123
  };
123
124
  var filterTransactionOnline = function filterTransactionOnline(_ref3) {
125
+ var _tr$getMeta;
124
126
  var tr = _ref3.tr,
125
127
  state = _ref3.state,
126
128
  syncBlockStore = _ref3.syncBlockStore,
@@ -145,16 +147,26 @@ var filterTransactionOnline = function filterTransactionOnline(_ref3) {
145
147
  eventType: _analytics.EVENT_TYPE.OPERATIONAL
146
148
  });
147
149
  });
150
+
151
+ // Annotate every reference-block insertion with `sourceProduct`
152
+ // (derived from the resourceId) and `isPaste` (when the originating transaction was a
153
+ // paste). Together with the host product reported by the embedding product's pageview
154
+ // events this lets us triangulate cross-product paste behaviour without standing up a
155
+ // dedicated `cross_product_paste` event subject.
156
+ var isPaste = Boolean((_tr$getMeta = tr.getMeta('paste')) !== null && _tr$getMeta !== void 0 ? _tr$getMeta : tr.getMeta('uiEvent') === 'paste');
148
157
  syncBlockAdded.forEach(function (syncBlock) {
149
158
  var _api$analytics2;
150
159
  api === null || api === void 0 || (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 || (_api$analytics2 = _api$analytics2.actions) === null || _api$analytics2 === void 0 || _api$analytics2.fireAnalyticsEvent({
151
160
  action: _analytics.ACTION.INSERTED,
152
161
  actionSubject: _analytics.ACTION_SUBJECT.DOCUMENT,
153
162
  actionSubjectId: _analytics.ACTION_SUBJECT_ID.SYNCED_BLOCK,
154
- attributes: {
163
+ attributes: _objectSpread({
155
164
  resourceId: syncBlock.attrs.resourceId,
156
165
  blockInstanceId: syncBlock.attrs.localId
157
- },
166
+ }, (0, _platformFeatureFlags.fg)('platform_synced_block_patch_11') ? {
167
+ sourceProduct: (0, _utils2.getSourceProductFromResourceIdSafe)(syncBlock.attrs.resourceId),
168
+ isPaste: isPaste
169
+ } : {}),
158
170
  eventType: _analytics.EVENT_TYPE.TRACK
159
171
  });
160
172
  });
@@ -196,7 +208,7 @@ var filterTransactionOffline = function filterTransactionOffline(_ref4) {
196
208
  errorFlag = _types.FLAG_ID.CANNOT_EDIT_WHEN_OFFLINE;
197
209
  }
198
210
  if (errorFlag) {
199
- (0, _utils2.deferDispatch)(function () {
211
+ (0, _utils3.deferDispatch)(function () {
200
212
  api === null || api === void 0 || api.core.actions.execute(function (_ref5) {
201
213
  var tr = _ref5.tr;
202
214
  return tr.setMeta(syncedBlockPluginKey, {
@@ -303,7 +315,7 @@ var createPlugin = exports.createPlugin = function createPlugin(options, pmPlugi
303
315
  // It prevents false "Changes may not be saved" warnings when publishing
304
316
  // Classic pages with sync blocks.
305
317
  syncBlockStore.sourceManager.registerFlushCompletionCallback(function () {
306
- (0, _utils2.deferDispatch)(function () {
318
+ (0, _utils3.deferDispatch)(function () {
307
319
  api === null || api === void 0 || api.core.actions.execute(function (_ref7) {
308
320
  var tr = _ref7.tr;
309
321
  return tr;
@@ -316,12 +328,19 @@ var createPlugin = exports.createPlugin = function createPlugin(options, pmPlugi
316
328
  // Only show the flag once per sync block
317
329
  if (!unpublishedFlagShown.has(resourceId)) {
318
330
  unpublishedFlagShown.add(resourceId);
319
- (0, _utils2.deferDispatch)(function () {
331
+ // Surface the source product so the flag's copy can be tailored — Jira work
332
+ // items get "Pasted from unsaved item" / "...when the item's description is
333
+ // saved" rather than the Confluence page-flavoured default. Falls back to
334
+ // undefined when the resourceId can't be parsed (legacy shapes), in which
335
+ // case the Confluence default is used.
336
+ var sourceProduct = (0, _utils2.getSourceProductFromResourceIdSafe)(resourceId);
337
+ (0, _utils3.deferDispatch)(function () {
320
338
  api === null || api === void 0 || api.core.actions.execute(function (_ref8) {
321
339
  var tr = _ref8.tr;
322
340
  return tr.setMeta(syncedBlockPluginKey, {
323
341
  activeFlag: {
324
- id: _types.FLAG_ID.UNPUBLISHED_SYNC_BLOCK_PASTED
342
+ id: _types.FLAG_ID.UNPUBLISHED_SYNC_BLOCK_PASTED,
343
+ sourceProduct: sourceProduct
325
344
  }
326
345
  });
327
346
  });
@@ -634,7 +653,7 @@ var createPlugin = exports.createPlugin = function createPlugin(options, pmPlugi
634
653
  if (node.type.name === 'bodiedSyncBlock' && node.attrs.resourceId) {
635
654
  // if we only selected part of the bodied sync block content,
636
655
  // remove the sync block node and only keep the content
637
- if (!(0, _utils2.sliceFullyContainsNode)(slice, node)) {
656
+ if (!(0, _utils3.sliceFullyContainsNode)(slice, node)) {
638
657
  return node.content;
639
658
  }
640
659
  showCopiedFlag(api);
@@ -880,7 +899,7 @@ var createPlugin = exports.createPlugin = function createPlugin(options, pmPlugi
880
899
  tr.delete(dup.pos, dup.pos + dup.nodeSize);
881
900
  }
882
901
  tr.setMeta('addToHistory', false);
883
- (0, _utils2.deferDispatch)(function () {
902
+ (0, _utils3.deferDispatch)(function () {
884
903
  var _api$core;
885
904
  api === null || api === void 0 || (_api$core = api.core) === null || _api$core === void 0 || _api$core.actions.execute(function (_ref1) {
886
905
  var tr = _ref1.tr;
@@ -16,6 +16,7 @@ var _editorPluginConnectivity = require("@atlaskit/editor-plugin-connectivity");
16
16
  var _flag = _interopRequireWildcard(require("@atlaskit/flag"));
17
17
  var _statusSuccess = _interopRequireDefault(require("@atlaskit/icon/core/status-success"));
18
18
  var _statusWarning = _interopRequireDefault(require("@atlaskit/icon/core/status-warning"));
19
+ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
19
20
  var _main = require("../pm-plugins/main");
20
21
  var _types = require("../types");
21
22
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
@@ -78,13 +79,20 @@ var Flag = exports.Flag = function Flag(_ref) {
78
79
  return;
79
80
  }
80
81
  var _flagMap$activeFlag$i = flagMap[activeFlag.id],
81
- title = _flagMap$activeFlag$i.title,
82
- description = _flagMap$activeFlag$i.description,
82
+ defaultTitle = _flagMap$activeFlag$i.title,
83
+ defaultDescription = _flagMap$activeFlag$i.description,
83
84
  action = _flagMap$activeFlag$i.action,
84
85
  type = _flagMap$activeFlag$i.type;
85
86
  var onRetry = activeFlag.onRetry,
86
87
  onDismissedCallback = activeFlag.onDismissed;
87
88
 
89
+ // For the unpublished-paste flag, swap to the Jira-flavoured copy when the source
90
+ // is a Jira work item. Other flags don't currently vary by product. Gated by
91
+ // `platform_synced_block_patch_11` so the new copy can be dialled off independently.
92
+ var isJiraUnpublishedPaste = activeFlag.id === _types.FLAG_ID.UNPUBLISHED_SYNC_BLOCK_PASTED && activeFlag.sourceProduct === 'jira-work-item' && (0, _platformFeatureFlags.fg)('platform_synced_block_patch_11');
93
+ var title = isJiraUnpublishedPaste ? _messages.syncBlockMessages.unpublishedSyncBlockPastedTitleJiraWorkItem : defaultTitle;
94
+ var description = isJiraUnpublishedPaste ? _messages.syncBlockMessages.unpublishedSyncBlockPastedDescriptionJiraWorkItem : defaultDescription;
95
+
88
96
  // Retry button often involves network request, hence we dismiss the flag in offline mode to avoid retry
89
97
  if ((0, _editorPluginConnectivity.isOfflineMode)(mode) && !!onRetry) {
90
98
  api === null || api === void 0 || api.core.actions.execute(function (_ref2) {
@@ -25,10 +25,15 @@ var _ui = require("@atlaskit/editor-common/ui");
25
25
  var _editorSyncedBlockProvider = require("@atlaskit/editor-synced-block-provider");
26
26
  var _icon = require("@atlaskit/icon");
27
27
  var _pageLiveDoc = _interopRequireDefault(require("@atlaskit/icon-lab/core/page-live-doc"));
28
+ var _bug = _interopRequireDefault(require("@atlaskit/icon/core/bug"));
28
29
  var _chevronDown = _interopRequireDefault(require("@atlaskit/icon/core/chevron-down"));
30
+ var _epic = _interopRequireDefault(require("@atlaskit/icon/core/epic"));
29
31
  var _page = _interopRequireDefault(require("@atlaskit/icon/core/page"));
30
32
  var _quotationMark = _interopRequireDefault(require("@atlaskit/icon/core/quotation-mark"));
31
33
  var _statusError = _interopRequireDefault(require("@atlaskit/icon/core/status-error"));
34
+ var _story = _interopRequireDefault(require("@atlaskit/icon/core/story"));
35
+ var _subtasks = _interopRequireDefault(require("@atlaskit/icon/core/subtasks"));
36
+ var _task = _interopRequireDefault(require("@atlaskit/icon/core/task"));
32
37
  var _logo = require("@atlaskit/logo");
33
38
  var _lozenge = _interopRequireDefault(require("@atlaskit/lozenge"));
34
39
  var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
@@ -118,12 +123,113 @@ var ProductIcon = function ProductIcon(_ref2) {
118
123
  appearance: "neutral"
119
124
  }));
120
125
  };
126
+
127
+ // Map AGG issue-type names to ADS icons. The mapping is by the English `name` returned
128
+ // from AGG because Jira's REST/GraphQL API does not localise it at this layer. Custom
129
+ // (non-default) issue types fall through to the AGG `iconUrl`.
130
+ //
131
+ // Type the icons as the same shape as `TaskIcon` so we don't import `NewCoreIconProps`
132
+ // from a private icon entrypoint.
133
+
134
+ var jiraIssueTypeIconMap = {
135
+ Task: {
136
+ icon: _task.default,
137
+ messageKey: 'syncedLocationDropdownIssueTypeTask'
138
+ },
139
+ Bug: {
140
+ icon: _bug.default,
141
+ messageKey: 'syncedLocationDropdownIssueTypeBug'
142
+ },
143
+ Story: {
144
+ icon: _story.default,
145
+ messageKey: 'syncedLocationDropdownIssueTypeStory'
146
+ },
147
+ Epic: {
148
+ icon: _epic.default,
149
+ messageKey: 'syncedLocationDropdownIssueTypeEpic'
150
+ },
151
+ Subtask: {
152
+ icon: _subtasks.default,
153
+ messageKey: 'syncedLocationDropdownIssueTypeSubtask'
154
+ },
155
+ 'Sub-task': {
156
+ icon: _subtasks.default,
157
+ messageKey: 'syncedLocationDropdownIssueTypeSubtask'
158
+ }
159
+ };
160
+
161
+ /**
162
+ * Creates an icon component from a custom Jira issue-type `iconUrl` that conforms to the
163
+ * ADS icon component contract expected by `IconTile`. This lets us reuse `IconTile` for
164
+ * custom issue types — ensuring consistent sizing, background, and border-radius with the
165
+ * standard ADS icons used for known issue types (Bug, Story, etc.).
166
+ *
167
+ * The returned component ignores ADS icon props (color, spacing, etc.) because the image
168
+ * is an external raster/SVG asset that doesn't respond to design tokens.
169
+ */
170
+ var customIconCache = new Map();
171
+ var createCustomIssueTypeIcon = function createCustomIssueTypeIcon(iconUrl) {
172
+ var cached = customIconCache.get(iconUrl);
173
+ if (cached) {
174
+ return cached;
175
+ }
176
+ var CustomIssueTypeIcon = function CustomIssueTypeIcon() {
177
+ return /*#__PURE__*/React.createElement("img", {
178
+ src: iconUrl,
179
+ alt: "",
180
+ width: "12",
181
+ height: "12"
182
+ });
183
+ };
184
+ CustomIssueTypeIcon.displayName = 'CustomIssueTypeIcon';
185
+ customIconCache.set(iconUrl, CustomIssueTypeIcon);
186
+ return CustomIssueTypeIcon;
187
+ };
188
+
189
+ /**
190
+ * Returns the icon to render for a Jira issue type, or `null` when neither an ADS icon
191
+ * mapping nor an AGG-provided `iconUrl` is available so the caller can fall back to a
192
+ * generic product icon.
193
+ *
194
+ * Implemented as a plain function (not a React component) so the `null` check actually
195
+ * narrows — a JSX expression always evaluates to a truthy `ReactElement` object,
196
+ * meaning callers cannot distinguish a "would render nothing" component from one that
197
+ * renders an icon.
198
+ */
199
+ var renderJiraIssueTypeIcon = function renderJiraIssueTypeIcon(issueType, intl) {
200
+ var mapped = jiraIssueTypeIconMap[issueType.name];
201
+ if (mapped) {
202
+ var label = intl.formatMessage(_messages.syncBlockMessages[mapped.messageKey]);
203
+ return /*#__PURE__*/React.createElement(_icon.IconTile, {
204
+ icon: mapped.icon,
205
+ label: label,
206
+ appearance: 'gray',
207
+ size: "xsmall"
208
+ });
209
+ }
210
+
211
+ // Custom Jira issue types — render inside `IconTile` using a wrapper component so the
212
+ // icon gets the same tile background, border-radius, and sizing as known issue types.
213
+ if (issueType.iconUrl) {
214
+ var CustomIcon = createCustomIssueTypeIcon(issueType.iconUrl);
215
+ var _label = intl.formatMessage(_messages.syncBlockMessages.syncedLocationDropdownIssueTypeGeneric);
216
+ return /*#__PURE__*/React.createElement(_icon.IconTile, {
217
+ icon: CustomIcon,
218
+ label: _label,
219
+ appearance: 'gray',
220
+ size: "xsmall"
221
+ });
222
+ }
223
+ return null;
224
+ };
121
225
  var ItemIcon = function ItemIcon(_ref3) {
122
- var reference = _ref3.reference;
226
+ var reference = _ref3.reference,
227
+ intl = _ref3.intl;
123
228
  var hasAccess = reference.hasAccess,
124
229
  subType = reference.subType,
125
230
  productType = reference.productType,
126
- sourceAri = reference.sourceAri;
231
+ sourceAri = reference.sourceAri,
232
+ issueType = reference.issueType;
127
233
  if (productType === 'confluence-page' && hasAccess) {
128
234
  return /*#__PURE__*/React.createElement(_icon.IconTile, {
129
235
  icon: getConfluenceSubTypeIcon(sourceAri, subType),
@@ -133,10 +239,20 @@ var ItemIcon = function ItemIcon(_ref3) {
133
239
  });
134
240
  }
135
241
 
136
- // For `jira-work-item` (and any future product), we fall back to the generic product logo icon.
137
- // Jira issues don't have an equivalent page/blog subtype concept, so no rich IconTile is shown.
138
- // Future enhancement: if Jira issue type icons are needed, add an `issueType` field to
139
- // `SyncBlockSourceInfo` and fetch it in `fetchJiraWorkItemInfo` via the GraphQL query.
242
+ // Render a Jira issue-type icon when we have one, gated by the shared rollout flag.
243
+ // Falls through to the generic product icon when:
244
+ // - the gate is off,
245
+ // - we don't have access (issueType is not surfaced for no-access references),
246
+ // - AGG returned no `issueType` (partial index, deleted, etc.), or
247
+ // - the issue type is unrecognised AND has no `iconUrl`.
248
+ if (productType === 'jira-work-item' && hasAccess && issueType && (0, _platformFeatureFlags.fg)('platform_synced_block_patch_11')) {
249
+ var icon = renderJiraIssueTypeIcon(issueType, intl);
250
+ if (icon !== null) {
251
+ return icon;
252
+ }
253
+ }
254
+
255
+ // Generic product fallback for `jira-work-item` (and any future product).
140
256
  return /*#__PURE__*/React.createElement(ProductIcon, {
141
257
  product: productType
142
258
  });
@@ -320,7 +436,8 @@ var DropdownContent = function DropdownContent(_ref7) {
320
436
  content: title
321
437
  }, /*#__PURE__*/React.createElement(_dropdownMenu.DropdownItem, {
322
438
  elemBefore: /*#__PURE__*/React.createElement(ItemIcon, {
323
- reference: reference
439
+ reference: reference,
440
+ intl: intl
324
441
  }),
325
442
  href: reference.url,
326
443
  target: "_blank",
@@ -0,0 +1,2 @@
1
+ /* eslint-disable @atlaskit/editor/no-re-export */
2
+ export { syncedBlockPlugin } from '../syncedBlockPlugin';
@@ -10,6 +10,7 @@ import { PluginKey } from '@atlaskit/editor-prosemirror/state';
10
10
  import { ReplaceAroundStep, ReplaceStep } from '@atlaskit/editor-prosemirror/transform';
11
11
  import { DecorationSet, Decoration } from '@atlaskit/editor-prosemirror/view';
12
12
  import { convertPMNodesToSyncBlockNodes, rebaseTransaction } from '@atlaskit/editor-synced-block-provider';
13
+ import { getSourceProductFromResourceIdSafe } from '@atlaskit/editor-synced-block-provider/utils';
13
14
  import { fg } from '@atlaskit/platform-feature-flags';
14
15
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
15
16
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
@@ -104,6 +105,7 @@ const filterTransactionOnline = ({
104
105
  bodiedSyncBlockAdded,
105
106
  extensionFlagShown
106
107
  }) => {
108
+ var _tr$getMeta;
107
109
  const {
108
110
  removed: syncBlockRemoved,
109
111
  added: syncBlockAdded
@@ -121,6 +123,13 @@ const filterTransactionOnline = ({
121
123
  eventType: EVENT_TYPE.OPERATIONAL
122
124
  });
123
125
  });
126
+
127
+ // Annotate every reference-block insertion with `sourceProduct`
128
+ // (derived from the resourceId) and `isPaste` (when the originating transaction was a
129
+ // paste). Together with the host product reported by the embedding product's pageview
130
+ // events this lets us triangulate cross-product paste behaviour without standing up a
131
+ // dedicated `cross_product_paste` event subject.
132
+ const isPaste = Boolean((_tr$getMeta = tr.getMeta('paste')) !== null && _tr$getMeta !== void 0 ? _tr$getMeta : tr.getMeta('uiEvent') === 'paste');
124
133
  syncBlockAdded.forEach(syncBlock => {
125
134
  var _api$analytics2, _api$analytics2$actio;
126
135
  api === null || api === void 0 ? void 0 : (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 ? void 0 : (_api$analytics2$actio = _api$analytics2.actions) === null || _api$analytics2$actio === void 0 ? void 0 : _api$analytics2$actio.fireAnalyticsEvent({
@@ -129,7 +138,11 @@ const filterTransactionOnline = ({
129
138
  actionSubjectId: ACTION_SUBJECT_ID.SYNCED_BLOCK,
130
139
  attributes: {
131
140
  resourceId: syncBlock.attrs.resourceId,
132
- blockInstanceId: syncBlock.attrs.localId
141
+ blockInstanceId: syncBlock.attrs.localId,
142
+ ...(fg('platform_synced_block_patch_11') ? {
143
+ sourceProduct: getSourceProductFromResourceIdSafe(syncBlock.attrs.resourceId),
144
+ isPaste
145
+ } : {})
133
146
  },
134
147
  eventType: EVENT_TYPE.TRACK
135
148
  });
@@ -284,12 +297,19 @@ export const createPlugin = (options, pmPluginFactoryParams, syncBlockStore, api
284
297
  // Only show the flag once per sync block
285
298
  if (!unpublishedFlagShown.has(resourceId)) {
286
299
  unpublishedFlagShown.add(resourceId);
300
+ // Surface the source product so the flag's copy can be tailored — Jira work
301
+ // items get "Pasted from unsaved item" / "...when the item's description is
302
+ // saved" rather than the Confluence page-flavoured default. Falls back to
303
+ // undefined when the resourceId can't be parsed (legacy shapes), in which
304
+ // case the Confluence default is used.
305
+ const sourceProduct = getSourceProductFromResourceIdSafe(resourceId);
287
306
  deferDispatch(() => {
288
307
  api === null || api === void 0 ? void 0 : api.core.actions.execute(({
289
308
  tr
290
309
  }) => tr.setMeta(syncedBlockPluginKey, {
291
310
  activeFlag: {
292
- id: FLAG_ID.UNPUBLISHED_SYNC_BLOCK_PASTED
311
+ id: FLAG_ID.UNPUBLISHED_SYNC_BLOCK_PASTED,
312
+ sourceProduct
293
313
  }
294
314
  }));
295
315
  });
@@ -7,6 +7,7 @@ import { isOfflineMode } from '@atlaskit/editor-plugin-connectivity';
7
7
  import AkFlag, { AutoDismissFlag, FlagGroup } from '@atlaskit/flag';
8
8
  import StatusSuccessIcon from '@atlaskit/icon/core/status-success';
9
9
  import StatusWarningIcon from '@atlaskit/icon/core/status-warning';
10
+ import { fg } from '@atlaskit/platform-feature-flags';
10
11
  import { syncedBlockPluginKey } from '../pm-plugins/main';
11
12
  import { FLAG_ID } from '../types';
12
13
  const flagMap = {
@@ -80,8 +81,8 @@ export const Flag = ({
80
81
  return;
81
82
  }
82
83
  const {
83
- title,
84
- description,
84
+ title: defaultTitle,
85
+ description: defaultDescription,
85
86
  action,
86
87
  type
87
88
  } = flagMap[activeFlag.id];
@@ -90,6 +91,13 @@ export const Flag = ({
90
91
  onDismissed: onDismissedCallback
91
92
  } = activeFlag;
92
93
 
94
+ // For the unpublished-paste flag, swap to the Jira-flavoured copy when the source
95
+ // is a Jira work item. Other flags don't currently vary by product. Gated by
96
+ // `platform_synced_block_patch_11` so the new copy can be dialled off independently.
97
+ const isJiraUnpublishedPaste = activeFlag.id === FLAG_ID.UNPUBLISHED_SYNC_BLOCK_PASTED && activeFlag.sourceProduct === 'jira-work-item' && fg('platform_synced_block_patch_11');
98
+ const title = isJiraUnpublishedPaste ? messages.unpublishedSyncBlockPastedTitleJiraWorkItem : defaultTitle;
99
+ const description = isJiraUnpublishedPaste ? messages.unpublishedSyncBlockPastedDescriptionJiraWorkItem : defaultDescription;
100
+
93
101
  // Retry button often involves network request, hence we dismiss the flag in offline mode to avoid retry
94
102
  if (isOfflineMode(mode) && !!onRetry) {
95
103
  api === null || api === void 0 ? void 0 : api.core.actions.execute(({
@@ -3,7 +3,7 @@ import _extends from "@babel/runtime/helpers/extends";
3
3
  import "./SyncedLocationDropdown.compiled.css";
4
4
  import * as React from 'react';
5
5
  import { ax, ix } from "@compiled/react/runtime";
6
- import { useState, useEffect } from 'react';
6
+ import { useEffect, useState } from 'react';
7
7
  import { cx } from '@compiled/react';
8
8
  import DropdownMenu, { DropdownItem, DropdownItemGroup } from '@atlaskit/dropdown-menu';
9
9
  import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
@@ -13,10 +13,15 @@ import { FloatingToolbarButton as Button } from '@atlaskit/editor-common/ui';
13
13
  import { getPageIdAndTypeFromConfluencePageAri } from '@atlaskit/editor-synced-block-provider';
14
14
  import { IconTile } from '@atlaskit/icon';
15
15
  import PageLiveDocIcon from '@atlaskit/icon-lab/core/page-live-doc';
16
+ import BugIcon from '@atlaskit/icon/core/bug';
16
17
  import ChevronDownIcon from '@atlaskit/icon/core/chevron-down';
18
+ import EpicIcon from '@atlaskit/icon/core/epic';
17
19
  import PageIcon from '@atlaskit/icon/core/page';
18
20
  import QuotationMarkIcon from '@atlaskit/icon/core/quotation-mark';
19
21
  import StatusErrorIcon from '@atlaskit/icon/core/status-error';
22
+ import StoryIcon from '@atlaskit/icon/core/story';
23
+ import SubtaskIcon from '@atlaskit/icon/core/subtasks';
24
+ import TaskIcon from '@atlaskit/icon/core/task';
20
25
  import { ConfluenceIcon, JiraIcon, AtlassianIcon } from '@atlaskit/logo';
21
26
  import Lozenge from '@atlaskit/lozenge';
22
27
  import { fg } from '@atlaskit/platform-feature-flags';
@@ -104,14 +109,113 @@ const ProductIcon = ({
104
109
  appearance: "neutral"
105
110
  }));
106
111
  };
112
+
113
+ // Map AGG issue-type names to ADS icons. The mapping is by the English `name` returned
114
+ // from AGG because Jira's REST/GraphQL API does not localise it at this layer. Custom
115
+ // (non-default) issue types fall through to the AGG `iconUrl`.
116
+ //
117
+ // Type the icons as the same shape as `TaskIcon` so we don't import `NewCoreIconProps`
118
+ // from a private icon entrypoint.
119
+
120
+ const jiraIssueTypeIconMap = {
121
+ Task: {
122
+ icon: TaskIcon,
123
+ messageKey: 'syncedLocationDropdownIssueTypeTask'
124
+ },
125
+ Bug: {
126
+ icon: BugIcon,
127
+ messageKey: 'syncedLocationDropdownIssueTypeBug'
128
+ },
129
+ Story: {
130
+ icon: StoryIcon,
131
+ messageKey: 'syncedLocationDropdownIssueTypeStory'
132
+ },
133
+ Epic: {
134
+ icon: EpicIcon,
135
+ messageKey: 'syncedLocationDropdownIssueTypeEpic'
136
+ },
137
+ Subtask: {
138
+ icon: SubtaskIcon,
139
+ messageKey: 'syncedLocationDropdownIssueTypeSubtask'
140
+ },
141
+ 'Sub-task': {
142
+ icon: SubtaskIcon,
143
+ messageKey: 'syncedLocationDropdownIssueTypeSubtask'
144
+ }
145
+ };
146
+
147
+ /**
148
+ * Creates an icon component from a custom Jira issue-type `iconUrl` that conforms to the
149
+ * ADS icon component contract expected by `IconTile`. This lets us reuse `IconTile` for
150
+ * custom issue types — ensuring consistent sizing, background, and border-radius with the
151
+ * standard ADS icons used for known issue types (Bug, Story, etc.).
152
+ *
153
+ * The returned component ignores ADS icon props (color, spacing, etc.) because the image
154
+ * is an external raster/SVG asset that doesn't respond to design tokens.
155
+ */
156
+ const customIconCache = new Map();
157
+ const createCustomIssueTypeIcon = iconUrl => {
158
+ const cached = customIconCache.get(iconUrl);
159
+ if (cached) {
160
+ return cached;
161
+ }
162
+ const CustomIssueTypeIcon = () => /*#__PURE__*/React.createElement("img", {
163
+ src: iconUrl,
164
+ alt: "",
165
+ width: "12",
166
+ height: "12"
167
+ });
168
+ CustomIssueTypeIcon.displayName = 'CustomIssueTypeIcon';
169
+ customIconCache.set(iconUrl, CustomIssueTypeIcon);
170
+ return CustomIssueTypeIcon;
171
+ };
172
+
173
+ /**
174
+ * Returns the icon to render for a Jira issue type, or `null` when neither an ADS icon
175
+ * mapping nor an AGG-provided `iconUrl` is available so the caller can fall back to a
176
+ * generic product icon.
177
+ *
178
+ * Implemented as a plain function (not a React component) so the `null` check actually
179
+ * narrows — a JSX expression always evaluates to a truthy `ReactElement` object,
180
+ * meaning callers cannot distinguish a "would render nothing" component from one that
181
+ * renders an icon.
182
+ */
183
+ const renderJiraIssueTypeIcon = (issueType, intl) => {
184
+ const mapped = jiraIssueTypeIconMap[issueType.name];
185
+ if (mapped) {
186
+ const label = intl.formatMessage(messages[mapped.messageKey]);
187
+ return /*#__PURE__*/React.createElement(IconTile, {
188
+ icon: mapped.icon,
189
+ label: label,
190
+ appearance: 'gray',
191
+ size: "xsmall"
192
+ });
193
+ }
194
+
195
+ // Custom Jira issue types — render inside `IconTile` using a wrapper component so the
196
+ // icon gets the same tile background, border-radius, and sizing as known issue types.
197
+ if (issueType.iconUrl) {
198
+ const CustomIcon = createCustomIssueTypeIcon(issueType.iconUrl);
199
+ const label = intl.formatMessage(messages.syncedLocationDropdownIssueTypeGeneric);
200
+ return /*#__PURE__*/React.createElement(IconTile, {
201
+ icon: CustomIcon,
202
+ label: label,
203
+ appearance: 'gray',
204
+ size: "xsmall"
205
+ });
206
+ }
207
+ return null;
208
+ };
107
209
  const ItemIcon = ({
108
- reference
210
+ reference,
211
+ intl
109
212
  }) => {
110
213
  const {
111
214
  hasAccess,
112
215
  subType,
113
216
  productType,
114
- sourceAri
217
+ sourceAri,
218
+ issueType
115
219
  } = reference;
116
220
  if (productType === 'confluence-page' && hasAccess) {
117
221
  return /*#__PURE__*/React.createElement(IconTile, {
@@ -122,10 +226,20 @@ const ItemIcon = ({
122
226
  });
123
227
  }
124
228
 
125
- // For `jira-work-item` (and any future product), we fall back to the generic product logo icon.
126
- // Jira issues don't have an equivalent page/blog subtype concept, so no rich IconTile is shown.
127
- // Future enhancement: if Jira issue type icons are needed, add an `issueType` field to
128
- // `SyncBlockSourceInfo` and fetch it in `fetchJiraWorkItemInfo` via the GraphQL query.
229
+ // Render a Jira issue-type icon when we have one, gated by the shared rollout flag.
230
+ // Falls through to the generic product icon when:
231
+ // - the gate is off,
232
+ // - we don't have access (issueType is not surfaced for no-access references),
233
+ // - AGG returned no `issueType` (partial index, deleted, etc.), or
234
+ // - the issue type is unrecognised AND has no `iconUrl`.
235
+ if (productType === 'jira-work-item' && hasAccess && issueType && fg('platform_synced_block_patch_11')) {
236
+ const icon = renderJiraIssueTypeIcon(issueType, intl);
237
+ if (icon !== null) {
238
+ return icon;
239
+ }
240
+ }
241
+
242
+ // Generic product fallback for `jira-work-item` (and any future product).
129
243
  return /*#__PURE__*/React.createElement(ProductIcon, {
130
244
  product: productType
131
245
  });
@@ -275,7 +389,8 @@ const DropdownContent = ({
275
389
  content: title
276
390
  }, /*#__PURE__*/React.createElement(DropdownItem, {
277
391
  elemBefore: /*#__PURE__*/React.createElement(ItemIcon, {
278
- reference: reference
392
+ reference: reference,
393
+ intl: intl
279
394
  }),
280
395
  href: reference.url,
281
396
  target: "_blank",
@@ -0,0 +1,2 @@
1
+ /* eslint-disable @atlaskit/editor/no-re-export */
2
+ export { syncedBlockPlugin } from '../syncedBlockPlugin';
@@ -18,6 +18,7 @@ import { PluginKey } from '@atlaskit/editor-prosemirror/state';
18
18
  import { ReplaceAroundStep, ReplaceStep } from '@atlaskit/editor-prosemirror/transform';
19
19
  import { DecorationSet, Decoration } from '@atlaskit/editor-prosemirror/view';
20
20
  import { convertPMNodesToSyncBlockNodes, rebaseTransaction } from '@atlaskit/editor-synced-block-provider';
21
+ import { getSourceProductFromResourceIdSafe } from '@atlaskit/editor-synced-block-provider/utils';
21
22
  import { fg } from '@atlaskit/platform-feature-flags';
22
23
  import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
23
24
  import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
@@ -114,6 +115,7 @@ var getDeleteReason = function getDeleteReason(tr) {
114
115
  return reason;
115
116
  };
116
117
  var filterTransactionOnline = function filterTransactionOnline(_ref3) {
118
+ var _tr$getMeta;
117
119
  var tr = _ref3.tr,
118
120
  state = _ref3.state,
119
121
  syncBlockStore = _ref3.syncBlockStore,
@@ -138,16 +140,26 @@ var filterTransactionOnline = function filterTransactionOnline(_ref3) {
138
140
  eventType: EVENT_TYPE.OPERATIONAL
139
141
  });
140
142
  });
143
+
144
+ // Annotate every reference-block insertion with `sourceProduct`
145
+ // (derived from the resourceId) and `isPaste` (when the originating transaction was a
146
+ // paste). Together with the host product reported by the embedding product's pageview
147
+ // events this lets us triangulate cross-product paste behaviour without standing up a
148
+ // dedicated `cross_product_paste` event subject.
149
+ var isPaste = Boolean((_tr$getMeta = tr.getMeta('paste')) !== null && _tr$getMeta !== void 0 ? _tr$getMeta : tr.getMeta('uiEvent') === 'paste');
141
150
  syncBlockAdded.forEach(function (syncBlock) {
142
151
  var _api$analytics2;
143
152
  api === null || api === void 0 || (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 || (_api$analytics2 = _api$analytics2.actions) === null || _api$analytics2 === void 0 || _api$analytics2.fireAnalyticsEvent({
144
153
  action: ACTION.INSERTED,
145
154
  actionSubject: ACTION_SUBJECT.DOCUMENT,
146
155
  actionSubjectId: ACTION_SUBJECT_ID.SYNCED_BLOCK,
147
- attributes: {
156
+ attributes: _objectSpread({
148
157
  resourceId: syncBlock.attrs.resourceId,
149
158
  blockInstanceId: syncBlock.attrs.localId
150
- },
159
+ }, fg('platform_synced_block_patch_11') ? {
160
+ sourceProduct: getSourceProductFromResourceIdSafe(syncBlock.attrs.resourceId),
161
+ isPaste: isPaste
162
+ } : {}),
151
163
  eventType: EVENT_TYPE.TRACK
152
164
  });
153
165
  });
@@ -309,12 +321,19 @@ export var createPlugin = function createPlugin(options, pmPluginFactoryParams,
309
321
  // Only show the flag once per sync block
310
322
  if (!unpublishedFlagShown.has(resourceId)) {
311
323
  unpublishedFlagShown.add(resourceId);
324
+ // Surface the source product so the flag's copy can be tailored — Jira work
325
+ // items get "Pasted from unsaved item" / "...when the item's description is
326
+ // saved" rather than the Confluence page-flavoured default. Falls back to
327
+ // undefined when the resourceId can't be parsed (legacy shapes), in which
328
+ // case the Confluence default is used.
329
+ var sourceProduct = getSourceProductFromResourceIdSafe(resourceId);
312
330
  deferDispatch(function () {
313
331
  api === null || api === void 0 || api.core.actions.execute(function (_ref8) {
314
332
  var tr = _ref8.tr;
315
333
  return tr.setMeta(syncedBlockPluginKey, {
316
334
  activeFlag: {
317
- id: FLAG_ID.UNPUBLISHED_SYNC_BLOCK_PASTED
335
+ id: FLAG_ID.UNPUBLISHED_SYNC_BLOCK_PASTED,
336
+ sourceProduct: sourceProduct
318
337
  }
319
338
  });
320
339
  });
@@ -10,6 +10,7 @@ import { isOfflineMode } from '@atlaskit/editor-plugin-connectivity';
10
10
  import AkFlag, { AutoDismissFlag, FlagGroup } from '@atlaskit/flag';
11
11
  import StatusSuccessIcon from '@atlaskit/icon/core/status-success';
12
12
  import StatusWarningIcon from '@atlaskit/icon/core/status-warning';
13
+ import { fg } from '@atlaskit/platform-feature-flags';
13
14
  import { syncedBlockPluginKey } from '../pm-plugins/main';
14
15
  import { FLAG_ID } from '../types';
15
16
  var flagMap = _defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty({}, FLAG_ID.CANNOT_DELETE_WHEN_OFFLINE, {
@@ -69,13 +70,20 @@ export var Flag = function Flag(_ref) {
69
70
  return;
70
71
  }
71
72
  var _flagMap$activeFlag$i = flagMap[activeFlag.id],
72
- title = _flagMap$activeFlag$i.title,
73
- description = _flagMap$activeFlag$i.description,
73
+ defaultTitle = _flagMap$activeFlag$i.title,
74
+ defaultDescription = _flagMap$activeFlag$i.description,
74
75
  action = _flagMap$activeFlag$i.action,
75
76
  type = _flagMap$activeFlag$i.type;
76
77
  var onRetry = activeFlag.onRetry,
77
78
  onDismissedCallback = activeFlag.onDismissed;
78
79
 
80
+ // For the unpublished-paste flag, swap to the Jira-flavoured copy when the source
81
+ // is a Jira work item. Other flags don't currently vary by product. Gated by
82
+ // `platform_synced_block_patch_11` so the new copy can be dialled off independently.
83
+ var isJiraUnpublishedPaste = activeFlag.id === FLAG_ID.UNPUBLISHED_SYNC_BLOCK_PASTED && activeFlag.sourceProduct === 'jira-work-item' && fg('platform_synced_block_patch_11');
84
+ var title = isJiraUnpublishedPaste ? messages.unpublishedSyncBlockPastedTitleJiraWorkItem : defaultTitle;
85
+ var description = isJiraUnpublishedPaste ? messages.unpublishedSyncBlockPastedDescriptionJiraWorkItem : defaultDescription;
86
+
79
87
  // Retry button often involves network request, hence we dismiss the flag in offline mode to avoid retry
80
88
  if (isOfflineMode(mode) && !!onRetry) {
81
89
  api === null || api === void 0 || api.core.actions.execute(function (_ref2) {
@@ -11,7 +11,7 @@ import _regeneratorRuntime from "@babel/runtime/regenerator";
11
11
  function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
12
12
  function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
13
13
  function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
14
- import { useState, useEffect } from 'react';
14
+ import { useEffect, useState } from 'react';
15
15
  import { cx } from '@compiled/react';
16
16
  import DropdownMenu, { DropdownItem, DropdownItemGroup } from '@atlaskit/dropdown-menu';
17
17
  import { ACTION, ACTION_SUBJECT, ACTION_SUBJECT_ID, EVENT_TYPE } from '@atlaskit/editor-common/analytics';
@@ -21,10 +21,15 @@ import { FloatingToolbarButton as Button } from '@atlaskit/editor-common/ui';
21
21
  import { getPageIdAndTypeFromConfluencePageAri } from '@atlaskit/editor-synced-block-provider';
22
22
  import { IconTile } from '@atlaskit/icon';
23
23
  import PageLiveDocIcon from '@atlaskit/icon-lab/core/page-live-doc';
24
+ import BugIcon from '@atlaskit/icon/core/bug';
24
25
  import ChevronDownIcon from '@atlaskit/icon/core/chevron-down';
26
+ import EpicIcon from '@atlaskit/icon/core/epic';
25
27
  import PageIcon from '@atlaskit/icon/core/page';
26
28
  import QuotationMarkIcon from '@atlaskit/icon/core/quotation-mark';
27
29
  import StatusErrorIcon from '@atlaskit/icon/core/status-error';
30
+ import StoryIcon from '@atlaskit/icon/core/story';
31
+ import SubtaskIcon from '@atlaskit/icon/core/subtasks';
32
+ import TaskIcon from '@atlaskit/icon/core/task';
28
33
  import { ConfluenceIcon, JiraIcon, AtlassianIcon } from '@atlaskit/logo';
29
34
  import Lozenge from '@atlaskit/lozenge';
30
35
  import { fg } from '@atlaskit/platform-feature-flags';
@@ -109,12 +114,113 @@ var ProductIcon = function ProductIcon(_ref2) {
109
114
  appearance: "neutral"
110
115
  }));
111
116
  };
117
+
118
+ // Map AGG issue-type names to ADS icons. The mapping is by the English `name` returned
119
+ // from AGG because Jira's REST/GraphQL API does not localise it at this layer. Custom
120
+ // (non-default) issue types fall through to the AGG `iconUrl`.
121
+ //
122
+ // Type the icons as the same shape as `TaskIcon` so we don't import `NewCoreIconProps`
123
+ // from a private icon entrypoint.
124
+
125
+ var jiraIssueTypeIconMap = {
126
+ Task: {
127
+ icon: TaskIcon,
128
+ messageKey: 'syncedLocationDropdownIssueTypeTask'
129
+ },
130
+ Bug: {
131
+ icon: BugIcon,
132
+ messageKey: 'syncedLocationDropdownIssueTypeBug'
133
+ },
134
+ Story: {
135
+ icon: StoryIcon,
136
+ messageKey: 'syncedLocationDropdownIssueTypeStory'
137
+ },
138
+ Epic: {
139
+ icon: EpicIcon,
140
+ messageKey: 'syncedLocationDropdownIssueTypeEpic'
141
+ },
142
+ Subtask: {
143
+ icon: SubtaskIcon,
144
+ messageKey: 'syncedLocationDropdownIssueTypeSubtask'
145
+ },
146
+ 'Sub-task': {
147
+ icon: SubtaskIcon,
148
+ messageKey: 'syncedLocationDropdownIssueTypeSubtask'
149
+ }
150
+ };
151
+
152
+ /**
153
+ * Creates an icon component from a custom Jira issue-type `iconUrl` that conforms to the
154
+ * ADS icon component contract expected by `IconTile`. This lets us reuse `IconTile` for
155
+ * custom issue types — ensuring consistent sizing, background, and border-radius with the
156
+ * standard ADS icons used for known issue types (Bug, Story, etc.).
157
+ *
158
+ * The returned component ignores ADS icon props (color, spacing, etc.) because the image
159
+ * is an external raster/SVG asset that doesn't respond to design tokens.
160
+ */
161
+ var customIconCache = new Map();
162
+ var createCustomIssueTypeIcon = function createCustomIssueTypeIcon(iconUrl) {
163
+ var cached = customIconCache.get(iconUrl);
164
+ if (cached) {
165
+ return cached;
166
+ }
167
+ var CustomIssueTypeIcon = function CustomIssueTypeIcon() {
168
+ return /*#__PURE__*/React.createElement("img", {
169
+ src: iconUrl,
170
+ alt: "",
171
+ width: "12",
172
+ height: "12"
173
+ });
174
+ };
175
+ CustomIssueTypeIcon.displayName = 'CustomIssueTypeIcon';
176
+ customIconCache.set(iconUrl, CustomIssueTypeIcon);
177
+ return CustomIssueTypeIcon;
178
+ };
179
+
180
+ /**
181
+ * Returns the icon to render for a Jira issue type, or `null` when neither an ADS icon
182
+ * mapping nor an AGG-provided `iconUrl` is available so the caller can fall back to a
183
+ * generic product icon.
184
+ *
185
+ * Implemented as a plain function (not a React component) so the `null` check actually
186
+ * narrows — a JSX expression always evaluates to a truthy `ReactElement` object,
187
+ * meaning callers cannot distinguish a "would render nothing" component from one that
188
+ * renders an icon.
189
+ */
190
+ var renderJiraIssueTypeIcon = function renderJiraIssueTypeIcon(issueType, intl) {
191
+ var mapped = jiraIssueTypeIconMap[issueType.name];
192
+ if (mapped) {
193
+ var label = intl.formatMessage(messages[mapped.messageKey]);
194
+ return /*#__PURE__*/React.createElement(IconTile, {
195
+ icon: mapped.icon,
196
+ label: label,
197
+ appearance: 'gray',
198
+ size: "xsmall"
199
+ });
200
+ }
201
+
202
+ // Custom Jira issue types — render inside `IconTile` using a wrapper component so the
203
+ // icon gets the same tile background, border-radius, and sizing as known issue types.
204
+ if (issueType.iconUrl) {
205
+ var CustomIcon = createCustomIssueTypeIcon(issueType.iconUrl);
206
+ var _label = intl.formatMessage(messages.syncedLocationDropdownIssueTypeGeneric);
207
+ return /*#__PURE__*/React.createElement(IconTile, {
208
+ icon: CustomIcon,
209
+ label: _label,
210
+ appearance: 'gray',
211
+ size: "xsmall"
212
+ });
213
+ }
214
+ return null;
215
+ };
112
216
  var ItemIcon = function ItemIcon(_ref3) {
113
- var reference = _ref3.reference;
217
+ var reference = _ref3.reference,
218
+ intl = _ref3.intl;
114
219
  var hasAccess = reference.hasAccess,
115
220
  subType = reference.subType,
116
221
  productType = reference.productType,
117
- sourceAri = reference.sourceAri;
222
+ sourceAri = reference.sourceAri,
223
+ issueType = reference.issueType;
118
224
  if (productType === 'confluence-page' && hasAccess) {
119
225
  return /*#__PURE__*/React.createElement(IconTile, {
120
226
  icon: getConfluenceSubTypeIcon(sourceAri, subType),
@@ -124,10 +230,20 @@ var ItemIcon = function ItemIcon(_ref3) {
124
230
  });
125
231
  }
126
232
 
127
- // For `jira-work-item` (and any future product), we fall back to the generic product logo icon.
128
- // Jira issues don't have an equivalent page/blog subtype concept, so no rich IconTile is shown.
129
- // Future enhancement: if Jira issue type icons are needed, add an `issueType` field to
130
- // `SyncBlockSourceInfo` and fetch it in `fetchJiraWorkItemInfo` via the GraphQL query.
233
+ // Render a Jira issue-type icon when we have one, gated by the shared rollout flag.
234
+ // Falls through to the generic product icon when:
235
+ // - the gate is off,
236
+ // - we don't have access (issueType is not surfaced for no-access references),
237
+ // - AGG returned no `issueType` (partial index, deleted, etc.), or
238
+ // - the issue type is unrecognised AND has no `iconUrl`.
239
+ if (productType === 'jira-work-item' && hasAccess && issueType && fg('platform_synced_block_patch_11')) {
240
+ var icon = renderJiraIssueTypeIcon(issueType, intl);
241
+ if (icon !== null) {
242
+ return icon;
243
+ }
244
+ }
245
+
246
+ // Generic product fallback for `jira-work-item` (and any future product).
131
247
  return /*#__PURE__*/React.createElement(ProductIcon, {
132
248
  product: productType
133
249
  });
@@ -311,7 +427,8 @@ var DropdownContent = function DropdownContent(_ref7) {
311
427
  content: title
312
428
  }, /*#__PURE__*/React.createElement(DropdownItem, {
313
429
  elemBefore: /*#__PURE__*/React.createElement(ItemIcon, {
314
- reference: reference
430
+ reference: reference,
431
+ intl: intl
315
432
  }),
316
433
  href: reference.url,
317
434
  target: "_blank",
@@ -0,0 +1 @@
1
+ export type { SyncedBlockPlugin, SyncedBlockPluginOptions, SyncedBlockEditorProps, SyncedBlockRendererProps, } from '../syncedBlockPluginType';
@@ -0,0 +1 @@
1
+ export { syncedBlockPlugin } from '../syncedBlockPlugin';
@@ -1,6 +1,6 @@
1
1
  import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
2
2
  import type { Transaction } from '@atlaskit/editor-prosemirror/state';
3
- import type { SyncBlockStoreManager } from '@atlaskit/editor-synced-block-provider';
3
+ import type { SyncBlockProduct, SyncBlockStoreManager } from '@atlaskit/editor-synced-block-provider';
4
4
  export declare enum FLAG_ID {
5
5
  CANNOT_DELETE_WHEN_OFFLINE = "cannot-delete-when-offline",
6
6
  CANNOT_EDIT_WHEN_OFFLINE = "cannot-edit-when-offline",
@@ -17,6 +17,12 @@ type FlagConfig = {
17
17
  id: FLAG_ID;
18
18
  onDismissed?: (tr: Transaction) => Transaction | void;
19
19
  onRetry?: () => void;
20
+ /**
21
+ * Optional source product for the synced block triggering this flag. Currently used
22
+ * by the UNPUBLISHED_SYNC_BLOCK_PASTED flag so the displayed copy can be tailored
23
+ * for Jira work items vs Confluence pages.
24
+ */
25
+ sourceProduct?: SyncBlockProduct;
20
26
  };
21
27
  export type BodiedSyncBlockDeletionStatus = 'none' | 'processing' | 'completed';
22
28
  export type ActiveFlag = FlagConfig | false;
@@ -0,0 +1 @@
1
+ export type { SyncedBlockPlugin, SyncedBlockPluginOptions, SyncedBlockEditorProps, SyncedBlockRendererProps, } from '../syncedBlockPluginType';
@@ -0,0 +1 @@
1
+ export { syncedBlockPlugin } from '../syncedBlockPlugin';
@@ -1,6 +1,6 @@
1
1
  import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
2
2
  import type { Transaction } from '@atlaskit/editor-prosemirror/state';
3
- import type { SyncBlockStoreManager } from '@atlaskit/editor-synced-block-provider';
3
+ import type { SyncBlockProduct, SyncBlockStoreManager } from '@atlaskit/editor-synced-block-provider';
4
4
  export declare enum FLAG_ID {
5
5
  CANNOT_DELETE_WHEN_OFFLINE = "cannot-delete-when-offline",
6
6
  CANNOT_EDIT_WHEN_OFFLINE = "cannot-edit-when-offline",
@@ -17,6 +17,12 @@ type FlagConfig = {
17
17
  id: FLAG_ID;
18
18
  onDismissed?: (tr: Transaction) => Transaction | void;
19
19
  onRetry?: () => void;
20
+ /**
21
+ * Optional source product for the synced block triggering this flag. Currently used
22
+ * by the UNPUBLISHED_SYNC_BLOCK_PASTED flag so the displayed copy can be tailored
23
+ * for Jira work items vs Confluence pages.
24
+ */
25
+ sourceProduct?: SyncBlockProduct;
20
26
  };
21
27
  export type BodiedSyncBlockDeletionStatus = 'none' | 'processing' | 'completed';
22
28
  export type ActiveFlag = FlagConfig | false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/editor-plugin-synced-block",
3
- "version": "8.2.16",
3
+ "version": "8.3.0",
4
4
  "description": "SyncedBlock plugin for @atlaskit/editor-core",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -36,15 +36,15 @@
36
36
  "@atlaskit/editor-plugin-block-menu": "^9.1.0",
37
37
  "@atlaskit/editor-plugin-connectivity": "10.0.1",
38
38
  "@atlaskit/editor-plugin-content-format": "^4.0.0",
39
- "@atlaskit/editor-plugin-decorations": "^10.0.0",
40
- "@atlaskit/editor-plugin-floating-toolbar": "^12.0.0",
41
- "@atlaskit/editor-plugin-focus": "^9.0.0",
42
- "@atlaskit/editor-plugin-selection": "^10.0.0",
43
- "@atlaskit/editor-plugin-user-intent": "^8.1.0",
39
+ "@atlaskit/editor-plugin-decorations": "^10.1.0",
40
+ "@atlaskit/editor-plugin-floating-toolbar": "^12.1.0",
41
+ "@atlaskit/editor-plugin-focus": "^9.1.0",
42
+ "@atlaskit/editor-plugin-selection": "^10.1.0",
43
+ "@atlaskit/editor-plugin-user-intent": "^8.2.0",
44
44
  "@atlaskit/editor-prosemirror": "^7.3.0",
45
45
  "@atlaskit/editor-shared-styles": "^3.10.0",
46
46
  "@atlaskit/editor-synced-block-provider": "^6.5.0",
47
- "@atlaskit/editor-toolbar": "^1.1.0",
47
+ "@atlaskit/editor-toolbar": "^1.2.0",
48
48
  "@atlaskit/flag": "^17.11.0",
49
49
  "@atlaskit/icon": "34.5.0",
50
50
  "@atlaskit/icon-lab": "^6.9.0",
@@ -54,7 +54,7 @@
54
54
  "@atlaskit/platform-feature-flags": "^1.1.0",
55
55
  "@atlaskit/primitives": "^19.0.0",
56
56
  "@atlaskit/spinner": "19.1.2",
57
- "@atlaskit/tmp-editor-statsig": "^79.0.0",
57
+ "@atlaskit/tmp-editor-statsig": "^80.0.0",
58
58
  "@atlaskit/tokens": "13.0.4",
59
59
  "@atlaskit/tooltip": "^22.2.0",
60
60
  "@atlaskit/visually-hidden": "^3.1.0",
@@ -64,7 +64,7 @@
64
64
  "date-fns": "^2.17.0"
65
65
  },
66
66
  "peerDependencies": {
67
- "@atlaskit/editor-common": "^114.29.0",
67
+ "@atlaskit/editor-common": "^114.30.0",
68
68
  "react": "^18.2.0",
69
69
  "react-intl": "^5.25.1 || ^6.0.0 || ^7.0.0"
70
70
  },
@@ -126,6 +126,9 @@
126
126
  },
127
127
  "platform_synced_block_patch_10": {
128
128
  "type": "boolean"
129
+ },
130
+ "platform_synced_block_patch_11": {
131
+ "type": "boolean"
129
132
  }
130
133
  }
131
134
  }
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "@atlaskit/editor-plugin-synced-block/synced-block-plugin",
3
+ "main": "../dist/cjs/entry-points/synced-block-plugin.js",
4
+ "module": "../dist/esm/entry-points/synced-block-plugin.js",
5
+ "module:es2019": "../dist/es2019/entry-points/synced-block-plugin.js",
6
+ "sideEffects": false,
7
+ "types": "../dist/types/entry-points/synced-block-plugin.d.ts",
8
+ "typesVersions": {
9
+ ">=4.5 <5.9": {
10
+ "*": [
11
+ "../dist/types-ts4.5/entry-points/synced-block-plugin.d.ts"
12
+ ]
13
+ }
14
+ }
15
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "@atlaskit/editor-plugin-synced-block/synced-block-plugin-type",
3
+ "main": "../dist/cjs/entry-points/synced-block-plugin-type.js",
4
+ "module": "../dist/esm/entry-points/synced-block-plugin-type.js",
5
+ "module:es2019": "../dist/es2019/entry-points/synced-block-plugin-type.js",
6
+ "sideEffects": false,
7
+ "types": "../dist/types/entry-points/synced-block-plugin-type.d.ts",
8
+ "typesVersions": {
9
+ ">=4.5 <5.9": {
10
+ "*": [
11
+ "../dist/types-ts4.5/entry-points/synced-block-plugin-type.d.ts"
12
+ ]
13
+ }
14
+ }
15
+ }