@atlaskit/editor-plugin-synced-block 8.2.15 → 8.2.17
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 +15 -0
- package/dist/cjs/pm-plugins/main.js +31 -12
- package/dist/cjs/ui/Flag.js +10 -2
- package/dist/cjs/ui/SyncedLocationDropdown.js +124 -7
- package/dist/es2019/pm-plugins/main.js +22 -2
- package/dist/es2019/ui/Flag.js +10 -2
- package/dist/es2019/ui/SyncedLocationDropdown.js +123 -8
- package/dist/esm/pm-plugins/main.js +22 -3
- package/dist/esm/ui/Flag.js +10 -2
- package/dist/esm/ui/SyncedLocationDropdown.js +125 -8
- package/dist/types/types/index.d.ts +7 -1
- package/dist/types-ts4.5/types/index.d.ts +7 -1
- package/package.json +7 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# @atlaskit/editor-plugin-synced-block
|
|
2
2
|
|
|
3
|
+
## 8.2.17
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`9784984097a8a`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/9784984097a8a) -
|
|
8
|
+
[ux] Improves synced block support for Jira work items, including product-specific copy,
|
|
9
|
+
issue-type icons, and enhanced analytics.
|
|
10
|
+
- Updated dependencies
|
|
11
|
+
|
|
12
|
+
## 8.2.16
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- Updated dependencies
|
|
17
|
+
|
|
3
18
|
## 8.2.15
|
|
4
19
|
|
|
5
20
|
### Patch Changes
|
|
@@ -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
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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;
|
package/dist/cjs/ui/Flag.js
CHANGED
|
@@ -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
|
-
|
|
82
|
-
|
|
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
|
-
//
|
|
137
|
-
//
|
|
138
|
-
//
|
|
139
|
-
//
|
|
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",
|
|
@@ -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
|
});
|
package/dist/es2019/ui/Flag.js
CHANGED
|
@@ -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 {
|
|
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
|
-
//
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
//
|
|
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",
|
|
@@ -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
|
});
|
package/dist/esm/ui/Flag.js
CHANGED
|
@@ -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
|
-
|
|
73
|
-
|
|
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 {
|
|
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
|
-
//
|
|
128
|
-
//
|
|
129
|
-
//
|
|
130
|
-
//
|
|
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",
|
|
@@ -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;
|
|
@@ -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.
|
|
3
|
+
"version": "8.2.17",
|
|
4
4
|
"description": "SyncedBlock plugin for @atlaskit/editor-core",
|
|
5
5
|
"author": "Atlassian Pty Ltd",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -44,7 +44,7 @@
|
|
|
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.
|
|
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": "^
|
|
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.
|
|
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
|
}
|